Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/web/src/lib/organizations/sales-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
organization_memberships,
organization_user_usage,
organizations,
sales_demo_spend_ledger,
type Organization,
type User,
} from '@kilocode/db/schema';
Expand Down Expand Up @@ -222,6 +223,19 @@ export async function restoreSalesDemoOrganization(args: {

const ownerId = org.created_by_kilo_user_id;

// The deletes below destroy the org's usage and credit rows, so record the
// discarded spend first. `microdollars_used` is the org counter that the LLM,
// Exa, and compute charge paths all increment.
const spentMicrodollars = Number(org.microdollars_used);
if (spentMicrodollars > 0) {
await txn.insert(sales_demo_spend_ledger).values({
organization_id: organizationId,
owner_kilo_user_id: ownerId,
period_start: org.settings.sales_demo_last_reset_at ?? org.created_at,
microdollars_used: spentMicrodollars,
});
}

await txn.delete(microdollar_usage).where(eq(microdollar_usage.organization_id, organizationId));
await txn.delete(exa_usage_log).where(eq(exa_usage_log.organization_id, organizationId));
await txn
Expand Down
32 changes: 32 additions & 0 deletions apps/web/src/routers/admin/sales-demo-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
organization_memberships,
organization_user_usage,
organizations,
sales_demo_spend_ledger,
} from '@kilocode/db/schema';
import { and, eq, inArray, sql } from 'drizzle-orm';
import { insertTestUser } from '@/tests/helpers/user.helper';
Expand Down Expand Up @@ -81,6 +82,9 @@ describe('sales demo router', () => {
await db
.delete(organization_memberships)
.where(inArray(organization_memberships.organization_id, orgIds));
await db
.delete(sales_demo_spend_ledger)
.where(inArray(sales_demo_spend_ledger.organization_id, orgIds));
await db.delete(organizations).where(inArray(organizations.id, orgIds));
}

Expand Down Expand Up @@ -273,6 +277,11 @@ describe('sales demo router', () => {
organization_id: firstOrgId,
});

await db
.update(organizations)
.set({ microdollars_used: sql`${organizations.microdollars_used} + 1000000` })
.where(eq(organizations.id, firstOrgId));

const extraGrant = await grantEntityCreditForCategory(
{ user: admin, organization: before },
{ credit_category: 'sales-demo', counts_as_selfservice: false, amount_usd: 10 }
Expand Down Expand Up @@ -392,6 +401,29 @@ describe('sales demo router', () => {
.where(eq(credit_transactions.organization_id, firstOrgId));
const total = txns.reduce((acc, tx) => acc + Number(tx.amount_microdollars), 0);
expect(total).toBe(50_000_000);

const ledger = await db
.select()
.from(sales_demo_spend_ledger)
.where(eq(sales_demo_spend_ledger.organization_id, firstOrgId));
expect(ledger).toHaveLength(1);
expect(Number(ledger[0].microdollars_used)).toBe(1_000_000);
expect(ledger[0].owner_kilo_user_id).toBe(target.id);
expect(new Date(ledger[0].period_start).getTime()).toBe(
new Date(before.settings.sales_demo_last_reset_at ?? before.created_at).getTime()
);
});

it('writes no ledger row when a demo org reset discards no spend', async () => {
const caller = await createCallerForUser(admin.id);

await caller.admin.salesDemo.reset({ organizationId: secondOrgId });

const ledger = await db
.select({ id: sales_demo_spend_ledger.id })
.from(sales_demo_spend_ledger)
.where(eq(sales_demo_spend_ledger.organization_id, secondOrgId));
expect(ledger).toHaveLength(0);
});

it('rejects reset for a normal org with NOT_FOUND', async () => {
Expand Down
11 changes: 11 additions & 0 deletions packages/db/src/migrations/0227_exotic_scarecrow.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE "sales_demo_spend_ledger" (
"id" uuid PRIMARY KEY DEFAULT pg_catalog.gen_random_uuid() NOT NULL,
"organization_id" uuid NOT NULL,
"owner_kilo_user_id" text,
"period_start" timestamp with time zone NOT NULL,
"period_end" timestamp with time zone DEFAULT now() NOT NULL,
"microdollars_used" bigint NOT NULL,
CONSTRAINT "sales_demo_spend_ledger_spend_positive" CHECK ("sales_demo_spend_ledger"."microdollars_used" > 0)
);
--> statement-breakpoint
ALTER TABLE "sales_demo_spend_ledger" ADD CONSTRAINT "sales_demo_spend_ledger_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE restrict ON UPDATE no action;
Loading