@dainamite/billing 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +14 -0
  3. package/dist/modules/billing/api/export/account/[id]/route.js +5 -5
  4. package/dist/modules/billing/api/export/account/[id]/route.js.map +1 -1
  5. package/dist/modules/billing/api/invoices/add-line/route.js +1 -1
  6. package/dist/modules/billing/api/invoices/add-line/route.js.map +1 -1
  7. package/dist/modules/billing/api/invoices/edit-line/route.js +1 -1
  8. package/dist/modules/billing/api/invoices/edit-line/route.js.map +1 -1
  9. package/dist/modules/billing/api/invoices/remove-line/route.js +1 -1
  10. package/dist/modules/billing/api/invoices/remove-line/route.js.map +1 -1
  11. package/dist/modules/billing/api/run-outcomes/route.js +2 -2
  12. package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
  13. package/dist/modules/billing/api/runs/retry-failed/route.js +2 -2
  14. package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
  15. package/dist/modules/billing/api/runs/route.js +2 -2
  16. package/dist/modules/billing/api/runs/route.js.map +2 -2
  17. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +1 -1
  18. package/dist/modules/billing/backend/billing/accounts/page.js.map +1 -1
  19. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +1 -1
  20. package/dist/modules/billing/backend/billing/invoices/page.js.map +1 -1
  21. package/dist/modules/billing/backend/billing/items/page.js.map +1 -1
  22. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +1 -1
  23. package/dist/modules/billing/backend/billing/runs/page.js.map +1 -1
  24. package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +1 -1
  25. package/dist/modules/billing/backend/billing/statements/page.js.map +1 -1
  26. package/dist/modules/billing/backend/billing/topups/page.js.map +1 -1
  27. package/dist/modules/billing/backend/billing/transactions/page.js.map +1 -1
  28. package/dist/modules/billing/ce.js.map +2 -2
  29. package/dist/modules/billing/commands/invoices.js +4 -4
  30. package/dist/modules/billing/commands/invoices.js.map +2 -2
  31. package/dist/modules/billing/commands/runs.js +3 -3
  32. package/dist/modules/billing/commands/runs.js.map +2 -2
  33. package/dist/modules/billing/components/PrepaidPanel.js.map +1 -1
  34. package/dist/modules/billing/data/entities.js +56 -56
  35. package/dist/modules/billing/data/entities.js.map +2 -2
  36. package/dist/modules/billing/data/entityIds.js.map +2 -2
  37. package/dist/modules/billing/events.js +20 -0
  38. package/dist/modules/billing/events.js.map +2 -2
  39. package/dist/modules/billing/lib/billRunEngine.js +4 -4
  40. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  41. package/dist/modules/billing/lib/billRunLabel.js.map +1 -1
  42. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +1 -1
  43. package/dist/modules/billing/lib/tenantLock.js.map +1 -1
  44. package/dist/modules/billing/workers/reap-stale-bill-runs.js +2 -2
  45. package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +2 -2
  46. package/generated/entities/billing_account/index.ts +23 -0
  47. package/generated/entities/billing_account_balance/index.ts +13 -0
  48. package/generated/entities/billing_account_transaction/index.ts +21 -0
  49. package/generated/entities/billing_account_usage/index.ts +18 -0
  50. package/generated/entities/billing_draft_invoice_edit/index.ts +14 -0
  51. package/generated/entities/billing_item/index.ts +22 -0
  52. package/generated/entities/billing_run/index.ts +21 -0
  53. package/generated/entities/billing_run_outcome/index.ts +17 -0
  54. package/generated/entities/billing_statement/index.ts +21 -0
  55. package/generated/entities/billing_topup/index.ts +20 -0
  56. package/generated/entities.ids.generated.ts +28 -0
  57. package/package.json +11 -10
  58. package/src/modules/billing/__tests__/commands/invoices.test.ts +8 -8
  59. package/src/modules/billing/__tests__/commands/topups.test.ts +1 -1
  60. package/src/modules/billing/__tests__/commands/transactions.test.ts +25 -2
  61. package/src/modules/billing/__tests__/entityRegistry.test.ts +319 -0
  62. package/src/modules/billing/__tests__/events.test.ts +142 -0
  63. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +3 -3
  64. package/src/modules/billing/api/export/account/[id]/route.ts +7 -7
  65. package/src/modules/billing/api/invoices/add-line/route.ts +1 -1
  66. package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
  67. package/src/modules/billing/api/invoices/remove-line/route.ts +1 -1
  68. package/src/modules/billing/api/run-outcomes/route.ts +2 -2
  69. package/src/modules/billing/api/runs/retry-failed/route.ts +2 -2
  70. package/src/modules/billing/api/runs/route.ts +2 -2
  71. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +1 -1
  72. package/src/modules/billing/backend/billing/accounts/page.tsx +1 -1
  73. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +1 -1
  74. package/src/modules/billing/backend/billing/invoices/page.tsx +1 -1
  75. package/src/modules/billing/backend/billing/items/page.tsx +1 -1
  76. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +1 -1
  77. package/src/modules/billing/backend/billing/runs/page.tsx +1 -1
  78. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +1 -1
  79. package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
  80. package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
  81. package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
  82. package/src/modules/billing/ce.ts +8 -7
  83. package/src/modules/billing/commands/invoices.ts +9 -9
  84. package/src/modules/billing/commands/runs.ts +3 -3
  85. package/src/modules/billing/components/PrepaidPanel.tsx +1 -1
  86. package/src/modules/billing/data/entities.ts +31 -10
  87. package/src/modules/billing/data/entityIds.ts +16 -11
  88. package/src/modules/billing/events.ts +26 -0
  89. package/src/modules/billing/lib/billRunEngine.ts +12 -12
  90. package/src/modules/billing/lib/billRunLabel.ts +1 -1
  91. package/src/modules/billing/lib/prepaidPeriodClose.ts +3 -3
  92. package/src/modules/billing/lib/tenantLock.ts +1 -1
  93. package/src/modules/billing/workers/reap-stale-bill-runs.ts +4 -4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/commands/invoices.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands/types'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n} from '@open-mercato/shared/lib/commands/scope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { SalesInvoice, SalesInvoiceLine } from '@open-mercato/core/modules/sales/data/entities'\nimport { BillingAccount, DraftInvoiceEdit } from '../data/entities'\nimport { emitBillingEvent, type BillingEventId } from '../events'\nimport { buildPostpaidCreditCrossingEvents } from '../lib/accountStatus'\nimport {\n billingInvoiceAddLineSchema,\n billingInvoiceEditLineSchema,\n billingInvoicePostSchema,\n billingInvoiceRemoveLineSchema,\n billingWipeTestInvoicesSchema,\n type BillingInvoiceAddLineInput,\n type BillingInvoiceEditLineInput,\n type BillingInvoicePostInput,\n type BillingInvoiceRemoveLineInput,\n type BillingWipeTestInvoicesInput,\n} from '../data/validators'\nimport { resolveInvoiceStatusEntryId } from '../lib/invoiceStatus'\n\n/**\n * Posts a `core/sales` draft invoice that the Bill Run engine\n * created.\n *\n * Phase 2 deviation note: the invoice number is already set at\n * draft-create time (because `SalesInvoice.invoice_number` is NOT\n * NULL upstream). \"Post\" therefore only:\n * 1. Asserts the invoice is currently a draft (refuse otherwise).\n * 2. Refuses test-mode invoices (`metadata.test_run=true`) \u2014 they\n * should never reach production posting.\n * 3. Flips `status` (and `status_entry_id`) to `posted`.\n * 4. Fires `billing.invoice.posted` (one) + `billing.invoice.line_posted`\n * per line (for downstream consumers \u2014 accounting, analytics).\n *\n * Once posted, the invoice is frozen at the billing layer; corrections\n * go through `core/sales`'s manual edit flow.\n */\n\nfunction getEm(ctx: CommandRuntimeContext): EntityManager {\n return (ctx.container.resolve('em') as EntityManager).fork()\n}\n\ntype PostResult = {\n invoiceId: string\n invoiceNumber: string\n status: 'posted'\n lineCount: number\n}\n\nconst postInvoiceCommand: CommandHandler<BillingInvoicePostInput, PostResult> = {\n id: 'billing.invoices.post',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoicePostSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await em.findOne(SalesInvoice, {\n id: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!invoice) {\n throw new CrudHttpError(404, { error: 'Invoice not found' })\n }\n if (invoice.status !== 'draft') {\n throw new CrudHttpError(409, {\n error: 'Only draft invoices can be posted',\n code: 'billing.invoice.not_draft',\n currentStatus: invoice.status,\n })\n }\n const meta = (invoice.metadata as Record<string, unknown> | null) ?? {}\n if (meta.test_run === true) {\n // Test invoices live for inspection only \u2014 posting them would\n // leak test data into accounting. Force-delete the test invoice\n // instead (Phase 4b admin UI exposes a \"Wipe test drafts\"\n // button against `DELETE /api/billing/test-invoices`).\n throw new CrudHttpError(409, {\n error: 'Test-mode invoices cannot be posted',\n code: 'billing.invoice.test_run',\n })\n }\n\n const postedStatusEntryId = await resolveInvoiceStatusEntryId(\n em,\n parsed.tenantId,\n parsed.organizationId,\n 'posted',\n )\n\n await withAtomicFlush(em, [\n () => {\n invoice.status = 'posted'\n invoice.statusEntryId = postedStatusEntryId\n invoice.updatedAt = new Date()\n },\n ])\n\n const lines = await em.find(SalesInvoiceLine, {\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } as never)\n\n // Side effects fire AFTER the DB commit so downstream subscribers\n // (mailer, accounting bridge, analytics) never observe a \"posted\"\n // event for an invoice the DB doesn't reflect.\n await emitBillingEvent('billing.invoice.posted', {\n invoiceId: invoice.id,\n invoiceNumber: invoice.invoiceNumber,\n tenantId: invoice.tenantId,\n organizationId: invoice.organizationId,\n grandTotalNet: invoice.grandTotalNetAmount,\n grandTotalGross: invoice.grandTotalGrossAmount,\n currencyCode: invoice.currencyCode,\n billRunId: (meta as { bill_run_id?: string }).bill_run_id ?? null,\n billAccountId: (meta as { bill_account_id?: string }).bill_account_id ?? null,\n billPeriodStart: (meta as { bill_period_start?: string }).bill_period_start ?? null,\n billPeriodEnd: (meta as { bill_period_end?: string }).bill_period_end ?? null,\n lineCount: lines.length,\n })\n for (const line of lines) {\n const lineMeta = (line.metadata as Record<string, unknown> | null) ?? {}\n await emitBillingEvent('billing.invoice.line_posted', {\n invoiceId: invoice.id,\n invoiceLineId: line.id,\n billingItemId: lineMeta.billing_item_id ?? null,\n billingType: lineMeta.billing_type ?? null,\n description: line.description ?? null,\n unitPriceNet: line.unitPriceNet,\n quantity: line.quantity,\n totalNetAmount: line.totalNetAmount,\n tenantId: invoice.tenantId,\n organizationId: invoice.organizationId,\n })\n }\n\n // SPEC-002 P6: a newly-posted invoice raises the account's outstanding\n // exposure \u2014 emit billing.credit.over_limit on the upward crossing. Billing\n // never blocks; the consuming flow decides what to do.\n const billAccountId = (meta as { bill_account_id?: string }).bill_account_id\n if (billAccountId) {\n const account = await em.findOne(BillingAccount, {\n id: billAccountId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (account) {\n const creditEvents = await buildPostpaidCreditCrossingEvents(\n em,\n account,\n invoice.outstandingAmount,\n )\n for (const ev of creditEvents) {\n await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })\n }\n }\n }\n\n return {\n invoiceId: invoice.id,\n invoiceNumber: invoice.invoiceNumber,\n status: 'posted',\n lineCount: lines.length,\n }\n },\n}\n\n// \u2500\u2500\u2500 Draft edit / add / remove (Phase 4b) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Three commands sharing the same shape:\n// 1. Assert the invoice is still a draft (refuse otherwise).\n// 2. Mutate the line / collection.\n// 3. Write a `DraftInvoiceEdit` audit row carrying before+after\n// snapshots (`null` on the \"no prior state\" / \"no resulting state\"\n// side per the spec \u2014 see entity column docs).\n// 4. Recompute the invoice totals from the surviving lines.\n//\n// The audit row is append-only; once written, never updated. Soft-\n// deleting the invoice does NOT delete the audit rows (compliance).\n\nfunction roundHalfUp2dp(value: number): number {\n return Math.round((value + Number.EPSILON) * 100) / 100\n}\n\nfunction format4dp(value: number): string {\n return roundHalfUp2dp(value).toFixed(4)\n}\n\nasync function assertDraftInvoice(\n em: EntityManager,\n params: { invoiceId: string; tenantId: string; organizationId: string },\n): Promise<SalesInvoice> {\n const invoice = await em.findOne(SalesInvoice, {\n id: params.invoiceId,\n tenantId: params.tenantId,\n organizationId: params.organizationId,\n deletedAt: null,\n })\n if (!invoice) {\n throw new CrudHttpError(404, { error: 'Invoice not found' })\n }\n if (invoice.status !== 'draft') {\n throw new CrudHttpError(409, {\n error: 'Only draft invoices can be edited',\n code: 'billing.invoice.not_draft',\n currentStatus: invoice.status,\n })\n }\n return invoice\n}\n\ntype LineSnapshot = {\n id: string\n description: string | null\n quantity: string\n unitPriceNet: string\n totalNetAmount: string\n totalGrossAmount: string\n metadata: Record<string, unknown> | null\n}\n\nfunction snapshotLine(line: SalesInvoiceLine): LineSnapshot {\n return {\n id: line.id,\n description: line.description ?? null,\n quantity: line.quantity,\n unitPriceNet: line.unitPriceNet,\n totalNetAmount: line.totalNetAmount,\n totalGrossAmount: line.totalGrossAmount,\n metadata: (line.metadata as Record<string, unknown> | null) ?? null,\n }\n}\n\nasync function recomputeInvoiceTotals(\n em: EntityManager,\n invoice: SalesInvoice,\n): Promise<void> {\n const lines = await em.find(SalesInvoiceLine, {\n invoice,\n tenantId: invoice.tenantId,\n organizationId: invoice.organizationId,\n } as never)\n let totalNet = 0\n for (const line of lines) {\n totalNet += Number.parseFloat(line.totalNetAmount)\n }\n const formatted = format4dp(totalNet)\n invoice.subtotalNetAmount = formatted\n invoice.subtotalGrossAmount = formatted\n invoice.grandTotalNetAmount = formatted\n invoice.grandTotalGrossAmount = formatted\n invoice.outstandingAmount = formatted\n invoice.updatedAt = new Date()\n}\n\nfunction getUserIdFromCtx(ctx: CommandRuntimeContext): string {\n const userId = (ctx.auth?.sub as string | undefined) ?? null\n // The DraftInvoiceEdit.user_id column is NOT NULL. For system-driven\n // edits (e.g. catch-up flow that revises a value), we fall back to\n // a sentinel UUID. Operator-driven edits always have a real\n // ctx.auth.sub.\n return userId ?? '00000000-0000-0000-0000-000000000000'\n}\n\nconst editDraftLineCommand: CommandHandler<\n BillingInvoiceEditLineInput,\n { invoiceId: string; invoiceLineId: string; auditId: string }\n> = {\n id: 'billing.invoices.edit_draft_line',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoiceEditLineSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await assertDraftInvoice(em, {\n invoiceId: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n\n const line = await em.findOne(SalesInvoiceLine, {\n id: parsed.invoiceLineId,\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n if (!line) {\n throw new CrudHttpError(404, { error: 'Invoice line not found' })\n }\n\n const before = snapshotLine(line)\n let audit: DraftInvoiceEdit | null = null\n\n await withAtomicFlush(em, [\n () => {\n if (parsed.changes.description !== undefined) {\n line.description = parsed.changes.description\n }\n if (parsed.changes.quantity !== undefined) {\n line.quantity = format4dp(parsed.changes.quantity)\n line.normalizedQuantity = line.quantity\n }\n if (parsed.changes.unitPriceNet !== undefined) {\n line.unitPriceNet = format4dp(parsed.changes.unitPriceNet)\n line.unitPriceGross = line.unitPriceNet\n }\n\n // Recompute the line total. If the operator supplied an explicit\n // `totalNetAmount` override, honour it; otherwise compute from\n // unit_price \u00D7 quantity.\n if (parsed.changes.totalNetAmount !== undefined) {\n const total = format4dp(parsed.changes.totalNetAmount)\n line.totalNetAmount = total\n line.totalGrossAmount = total\n } else {\n const total = format4dp(\n Number.parseFloat(line.unitPriceNet) * Number.parseFloat(line.quantity),\n )\n line.totalNetAmount = total\n line.totalGrossAmount = total\n }\n },\n async () => {\n await recomputeInvoiceTotals(em, invoice)\n },\n () => {\n const after = snapshotLine(line)\n audit = em.create(DraftInvoiceEdit, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n invoiceId: invoice.id,\n invoiceLineId: line.id,\n userId: getUserIdFromCtx(ctx),\n action: 'line_edited',\n beforeJson: before as Record<string, unknown>,\n afterJson: after as Record<string, unknown>,\n createdAt: new Date(),\n })\n em.persist(audit)\n },\n ])\n\n return { invoiceId: invoice.id, invoiceLineId: line.id, auditId: audit!.id }\n },\n}\n\nconst addDraftLineCommand: CommandHandler<\n BillingInvoiceAddLineInput,\n { invoiceId: string; invoiceLineId: string; auditId: string }\n> = {\n id: 'billing.invoices.add_draft_line',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoiceAddLineSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await assertDraftInvoice(em, {\n invoiceId: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n\n const existing = await em.find(SalesInvoiceLine, {\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } as never)\n const nextLineNumber = existing.length + 1\n\n const total = format4dp(parsed.unitPriceNet * parsed.quantity)\n const line = em.create(SalesInvoiceLine, {\n invoice,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n lineNumber: nextLineNumber,\n kind: 'product',\n description: parsed.description,\n quantity: format4dp(parsed.quantity),\n normalizedQuantity: format4dp(parsed.quantity),\n currencyCode: invoice.currencyCode,\n unitPriceNet: format4dp(parsed.unitPriceNet),\n unitPriceGross: format4dp(parsed.unitPriceNet),\n discountAmount: '0',\n discountPercent: '0',\n taxRate: '0',\n taxAmount: '0',\n totalNetAmount: total,\n totalGrossAmount: total,\n metadata: {\n billing_item_id: parsed.billingItemId ?? null,\n billing_type: parsed.billingType ?? 'manual',\n operator_added: true,\n },\n })\n let audit: DraftInvoiceEdit | null = null\n\n await withAtomicFlush(em, [\n () => {\n em.persist(line)\n },\n async () => {\n await recomputeInvoiceTotals(em, invoice)\n },\n () => {\n const after = snapshotLine(line)\n audit = em.create(DraftInvoiceEdit, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n invoiceId: invoice.id,\n invoiceLineId: line.id,\n userId: getUserIdFromCtx(ctx),\n action: 'line_added',\n beforeJson: null,\n afterJson: after as Record<string, unknown>,\n createdAt: new Date(),\n })\n em.persist(audit)\n },\n ])\n\n return { invoiceId: invoice.id, invoiceLineId: line.id, auditId: audit!.id }\n },\n}\n\nconst removeDraftLineCommand: CommandHandler<\n BillingInvoiceRemoveLineInput,\n { invoiceId: string; invoiceLineId: string; auditId: string }\n> = {\n id: 'billing.invoices.remove_draft_line',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoiceRemoveLineSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await assertDraftInvoice(em, {\n invoiceId: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n const line = await em.findOne(SalesInvoiceLine, {\n id: parsed.invoiceLineId,\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n if (!line) {\n throw new CrudHttpError(404, { error: 'Invoice line not found' })\n }\n\n const before = snapshotLine(line)\n let audit: DraftInvoiceEdit | null = null\n\n await withAtomicFlush(em, [\n () => {\n em.remove(line)\n },\n async () => {\n await recomputeInvoiceTotals(em, invoice)\n },\n () => {\n audit = em.create(DraftInvoiceEdit, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n invoiceId: invoice.id,\n invoiceLineId: null,\n userId: getUserIdFromCtx(ctx),\n action: 'line_removed',\n beforeJson: before as Record<string, unknown>,\n afterJson: null,\n createdAt: new Date(),\n })\n em.persist(audit)\n },\n ])\n\n return { invoiceId: invoice.id, invoiceLineId: parsed.invoiceLineId, auditId: audit!.id }\n },\n}\n\n// \u2500\u2500\u2500 Wipe test invoices (Phase 4b) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Hard-deletes invoices flagged `metadata.test_run=true` (and their\n// lines via cascade \u2014 the invoice's lines collection points at it\n// with ON DELETE CASCADE). Optionally scoped to a single `bill_run_id`\n// via the param so operators can wipe a specific test run without\n// risking other test data.\n\nconst wipeTestInvoicesCommand: CommandHandler<\n BillingWipeTestInvoicesInput,\n { invoicesRemoved: number }\n> = {\n id: 'billing.invoices.wipe_test',\n\n async execute(rawInput, ctx) {\n const parsed = billingWipeTestInvoicesSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n // The metadata->>'test_run' = 'true' predicate uses Postgres'\n // JSONB ->> operator + the literal string 'true' (jsonb true is\n // serialized as the JSON literal `true`, which ->> returns as the\n // text \"true\").\n const params: unknown[] = [parsed.tenantId, parsed.organizationId]\n let billRunClause = ''\n if (parsed.billRunId) {\n billRunClause = ` AND metadata->>'bill_run_id' = ?`\n params.push(parsed.billRunId)\n }\n const rows = (await em.execute(\n `SELECT id FROM sales_invoices\n WHERE tenant_id = ?\n AND organization_id = ?\n AND deleted_at IS NULL\n AND metadata->>'test_run' = 'true'${billRunClause}`,\n params,\n )) as unknown as Array<{ id: string }> | undefined\n if (!rows || rows.length === 0) {\n return { invoicesRemoved: 0 }\n }\n const ids = rows.map((r) => r.id)\n // Hard-delete lines first then invoices to avoid relying on\n // cascade semantics that the migration may or may not encode.\n await em.execute(\n `DELETE FROM sales_invoice_lines WHERE invoice_id IN (${ids.map(() => '?').join(',')})`,\n ids,\n )\n await em.execute(\n `DELETE FROM sales_invoices WHERE id IN (${ids.map(() => '?').join(',')})`,\n ids,\n )\n return { invoicesRemoved: ids.length }\n },\n}\n\nregisterCommand(postInvoiceCommand)\nregisterCommand(editDraftLineCommand)\nregisterCommand(addDraftLineCommand)\nregisterCommand(removeDraftLineCommand)\nregisterCommand(wipeTestInvoicesCommand)\n\nexport {\n postInvoiceCommand,\n editDraftLineCommand,\n addDraftLineCommand,\n removeDraftLineCommand,\n wipeTestInvoicesCommand,\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands/types'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n} from '@open-mercato/shared/lib/commands/scope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { SalesInvoice, SalesInvoiceLine } from '@open-mercato/core/modules/sales/data/entities'\nimport { BillingAccount, BillingDraftInvoiceEdit } from '../data/entities'\nimport { emitBillingEvent, type BillingEventId } from '../events'\nimport { buildPostpaidCreditCrossingEvents } from '../lib/accountStatus'\nimport {\n billingInvoiceAddLineSchema,\n billingInvoiceEditLineSchema,\n billingInvoicePostSchema,\n billingInvoiceRemoveLineSchema,\n billingWipeTestInvoicesSchema,\n type BillingInvoiceAddLineInput,\n type BillingInvoiceEditLineInput,\n type BillingInvoicePostInput,\n type BillingInvoiceRemoveLineInput,\n type BillingWipeTestInvoicesInput,\n} from '../data/validators'\nimport { resolveInvoiceStatusEntryId } from '../lib/invoiceStatus'\n\n/**\n * Posts a `core/sales` draft invoice that the Bill Run engine\n * created.\n *\n * Phase 2 deviation note: the invoice number is already set at\n * draft-create time (because `SalesInvoice.invoice_number` is NOT\n * NULL upstream). \"Post\" therefore only:\n * 1. Asserts the invoice is currently a draft (refuse otherwise).\n * 2. Refuses test-mode invoices (`metadata.test_run=true`) \u2014 they\n * should never reach production posting.\n * 3. Flips `status` (and `status_entry_id`) to `posted`.\n * 4. Fires `billing.invoice.posted` (one) + `billing.invoice.line_posted`\n * per line (for downstream consumers \u2014 accounting, analytics).\n *\n * Once posted, the invoice is frozen at the billing layer; corrections\n * go through `core/sales`'s manual edit flow.\n */\n\nfunction getEm(ctx: CommandRuntimeContext): EntityManager {\n return (ctx.container.resolve('em') as EntityManager).fork()\n}\n\ntype PostResult = {\n invoiceId: string\n invoiceNumber: string\n status: 'posted'\n lineCount: number\n}\n\nconst postInvoiceCommand: CommandHandler<BillingInvoicePostInput, PostResult> = {\n id: 'billing.invoices.post',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoicePostSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await em.findOne(SalesInvoice, {\n id: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!invoice) {\n throw new CrudHttpError(404, { error: 'Invoice not found' })\n }\n if (invoice.status !== 'draft') {\n throw new CrudHttpError(409, {\n error: 'Only draft invoices can be posted',\n code: 'billing.invoice.not_draft',\n currentStatus: invoice.status,\n })\n }\n const meta = (invoice.metadata as Record<string, unknown> | null) ?? {}\n if (meta.test_run === true) {\n // Test invoices live for inspection only \u2014 posting them would\n // leak test data into accounting. Force-delete the test invoice\n // instead (Phase 4b admin UI exposes a \"Wipe test drafts\"\n // button against `DELETE /api/billing/test-invoices`).\n throw new CrudHttpError(409, {\n error: 'Test-mode invoices cannot be posted',\n code: 'billing.invoice.test_run',\n })\n }\n\n const postedStatusEntryId = await resolveInvoiceStatusEntryId(\n em,\n parsed.tenantId,\n parsed.organizationId,\n 'posted',\n )\n\n await withAtomicFlush(em, [\n () => {\n invoice.status = 'posted'\n invoice.statusEntryId = postedStatusEntryId\n invoice.updatedAt = new Date()\n },\n ])\n\n const lines = await em.find(SalesInvoiceLine, {\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } as never)\n\n // Side effects fire AFTER the DB commit so downstream subscribers\n // (mailer, accounting bridge, analytics) never observe a \"posted\"\n // event for an invoice the DB doesn't reflect.\n await emitBillingEvent('billing.invoice.posted', {\n invoiceId: invoice.id,\n invoiceNumber: invoice.invoiceNumber,\n tenantId: invoice.tenantId,\n organizationId: invoice.organizationId,\n grandTotalNet: invoice.grandTotalNetAmount,\n grandTotalGross: invoice.grandTotalGrossAmount,\n currencyCode: invoice.currencyCode,\n billRunId: (meta as { bill_run_id?: string }).bill_run_id ?? null,\n billAccountId: (meta as { bill_account_id?: string }).bill_account_id ?? null,\n billPeriodStart: (meta as { bill_period_start?: string }).bill_period_start ?? null,\n billPeriodEnd: (meta as { bill_period_end?: string }).bill_period_end ?? null,\n lineCount: lines.length,\n })\n for (const line of lines) {\n const lineMeta = (line.metadata as Record<string, unknown> | null) ?? {}\n await emitBillingEvent('billing.invoice.line_posted', {\n invoiceId: invoice.id,\n invoiceLineId: line.id,\n billingItemId: lineMeta.billing_item_id ?? null,\n billingType: lineMeta.billing_type ?? null,\n description: line.description ?? null,\n unitPriceNet: line.unitPriceNet,\n quantity: line.quantity,\n totalNetAmount: line.totalNetAmount,\n tenantId: invoice.tenantId,\n organizationId: invoice.organizationId,\n })\n }\n\n // SPEC-002 P6: a newly-posted invoice raises the account's outstanding\n // exposure \u2014 emit billing.credit.over_limit on the upward crossing. Billing\n // never blocks; the consuming flow decides what to do.\n const billAccountId = (meta as { bill_account_id?: string }).bill_account_id\n if (billAccountId) {\n const account = await em.findOne(BillingAccount, {\n id: billAccountId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (account) {\n const creditEvents = await buildPostpaidCreditCrossingEvents(\n em,\n account,\n invoice.outstandingAmount,\n )\n for (const ev of creditEvents) {\n await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })\n }\n }\n }\n\n return {\n invoiceId: invoice.id,\n invoiceNumber: invoice.invoiceNumber,\n status: 'posted',\n lineCount: lines.length,\n }\n },\n}\n\n// \u2500\u2500\u2500 Draft edit / add / remove (Phase 4b) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Three commands sharing the same shape:\n// 1. Assert the invoice is still a draft (refuse otherwise).\n// 2. Mutate the line / collection.\n// 3. Write a `BillingDraftInvoiceEdit` audit row carrying before+after\n// snapshots (`null` on the \"no prior state\" / \"no resulting state\"\n// side per the spec \u2014 see entity column docs).\n// 4. Recompute the invoice totals from the surviving lines.\n//\n// The audit row is append-only; once written, never updated. Soft-\n// deleting the invoice does NOT delete the audit rows (compliance).\n\nfunction roundHalfUp2dp(value: number): number {\n return Math.round((value + Number.EPSILON) * 100) / 100\n}\n\nfunction format4dp(value: number): string {\n return roundHalfUp2dp(value).toFixed(4)\n}\n\nasync function assertDraftInvoice(\n em: EntityManager,\n params: { invoiceId: string; tenantId: string; organizationId: string },\n): Promise<SalesInvoice> {\n const invoice = await em.findOne(SalesInvoice, {\n id: params.invoiceId,\n tenantId: params.tenantId,\n organizationId: params.organizationId,\n deletedAt: null,\n })\n if (!invoice) {\n throw new CrudHttpError(404, { error: 'Invoice not found' })\n }\n if (invoice.status !== 'draft') {\n throw new CrudHttpError(409, {\n error: 'Only draft invoices can be edited',\n code: 'billing.invoice.not_draft',\n currentStatus: invoice.status,\n })\n }\n return invoice\n}\n\ntype LineSnapshot = {\n id: string\n description: string | null\n quantity: string\n unitPriceNet: string\n totalNetAmount: string\n totalGrossAmount: string\n metadata: Record<string, unknown> | null\n}\n\nfunction snapshotLine(line: SalesInvoiceLine): LineSnapshot {\n return {\n id: line.id,\n description: line.description ?? null,\n quantity: line.quantity,\n unitPriceNet: line.unitPriceNet,\n totalNetAmount: line.totalNetAmount,\n totalGrossAmount: line.totalGrossAmount,\n metadata: (line.metadata as Record<string, unknown> | null) ?? null,\n }\n}\n\nasync function recomputeInvoiceTotals(\n em: EntityManager,\n invoice: SalesInvoice,\n): Promise<void> {\n const lines = await em.find(SalesInvoiceLine, {\n invoice,\n tenantId: invoice.tenantId,\n organizationId: invoice.organizationId,\n } as never)\n let totalNet = 0\n for (const line of lines) {\n totalNet += Number.parseFloat(line.totalNetAmount)\n }\n const formatted = format4dp(totalNet)\n invoice.subtotalNetAmount = formatted\n invoice.subtotalGrossAmount = formatted\n invoice.grandTotalNetAmount = formatted\n invoice.grandTotalGrossAmount = formatted\n invoice.outstandingAmount = formatted\n invoice.updatedAt = new Date()\n}\n\nfunction getUserIdFromCtx(ctx: CommandRuntimeContext): string {\n const userId = (ctx.auth?.sub as string | undefined) ?? null\n // The BillingDraftInvoiceEdit.user_id column is NOT NULL. For system-driven\n // edits (e.g. catch-up flow that revises a value), we fall back to\n // a sentinel UUID. Operator-driven edits always have a real\n // ctx.auth.sub.\n return userId ?? '00000000-0000-0000-0000-000000000000'\n}\n\nconst editDraftLineCommand: CommandHandler<\n BillingInvoiceEditLineInput,\n { invoiceId: string; invoiceLineId: string; auditId: string }\n> = {\n id: 'billing.invoices.edit_draft_line',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoiceEditLineSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await assertDraftInvoice(em, {\n invoiceId: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n\n const line = await em.findOne(SalesInvoiceLine, {\n id: parsed.invoiceLineId,\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n if (!line) {\n throw new CrudHttpError(404, { error: 'Invoice line not found' })\n }\n\n const before = snapshotLine(line)\n let audit: BillingDraftInvoiceEdit | null = null\n\n await withAtomicFlush(em, [\n () => {\n if (parsed.changes.description !== undefined) {\n line.description = parsed.changes.description\n }\n if (parsed.changes.quantity !== undefined) {\n line.quantity = format4dp(parsed.changes.quantity)\n line.normalizedQuantity = line.quantity\n }\n if (parsed.changes.unitPriceNet !== undefined) {\n line.unitPriceNet = format4dp(parsed.changes.unitPriceNet)\n line.unitPriceGross = line.unitPriceNet\n }\n\n // Recompute the line total. If the operator supplied an explicit\n // `totalNetAmount` override, honour it; otherwise compute from\n // unit_price \u00D7 quantity.\n if (parsed.changes.totalNetAmount !== undefined) {\n const total = format4dp(parsed.changes.totalNetAmount)\n line.totalNetAmount = total\n line.totalGrossAmount = total\n } else {\n const total = format4dp(\n Number.parseFloat(line.unitPriceNet) * Number.parseFloat(line.quantity),\n )\n line.totalNetAmount = total\n line.totalGrossAmount = total\n }\n },\n async () => {\n await recomputeInvoiceTotals(em, invoice)\n },\n () => {\n const after = snapshotLine(line)\n audit = em.create(BillingDraftInvoiceEdit, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n invoiceId: invoice.id,\n invoiceLineId: line.id,\n userId: getUserIdFromCtx(ctx),\n action: 'line_edited',\n beforeJson: before as Record<string, unknown>,\n afterJson: after as Record<string, unknown>,\n createdAt: new Date(),\n })\n em.persist(audit)\n },\n ])\n\n return { invoiceId: invoice.id, invoiceLineId: line.id, auditId: audit!.id }\n },\n}\n\nconst addDraftLineCommand: CommandHandler<\n BillingInvoiceAddLineInput,\n { invoiceId: string; invoiceLineId: string; auditId: string }\n> = {\n id: 'billing.invoices.add_draft_line',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoiceAddLineSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await assertDraftInvoice(em, {\n invoiceId: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n\n const existing = await em.find(SalesInvoiceLine, {\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } as never)\n const nextLineNumber = existing.length + 1\n\n const total = format4dp(parsed.unitPriceNet * parsed.quantity)\n const line = em.create(SalesInvoiceLine, {\n invoice,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n lineNumber: nextLineNumber,\n kind: 'product',\n description: parsed.description,\n quantity: format4dp(parsed.quantity),\n normalizedQuantity: format4dp(parsed.quantity),\n currencyCode: invoice.currencyCode,\n unitPriceNet: format4dp(parsed.unitPriceNet),\n unitPriceGross: format4dp(parsed.unitPriceNet),\n discountAmount: '0',\n discountPercent: '0',\n taxRate: '0',\n taxAmount: '0',\n totalNetAmount: total,\n totalGrossAmount: total,\n metadata: {\n billing_item_id: parsed.billingItemId ?? null,\n billing_type: parsed.billingType ?? 'manual',\n operator_added: true,\n },\n })\n let audit: BillingDraftInvoiceEdit | null = null\n\n await withAtomicFlush(em, [\n () => {\n em.persist(line)\n },\n async () => {\n await recomputeInvoiceTotals(em, invoice)\n },\n () => {\n const after = snapshotLine(line)\n audit = em.create(BillingDraftInvoiceEdit, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n invoiceId: invoice.id,\n invoiceLineId: line.id,\n userId: getUserIdFromCtx(ctx),\n action: 'line_added',\n beforeJson: null,\n afterJson: after as Record<string, unknown>,\n createdAt: new Date(),\n })\n em.persist(audit)\n },\n ])\n\n return { invoiceId: invoice.id, invoiceLineId: line.id, auditId: audit!.id }\n },\n}\n\nconst removeDraftLineCommand: CommandHandler<\n BillingInvoiceRemoveLineInput,\n { invoiceId: string; invoiceLineId: string; auditId: string }\n> = {\n id: 'billing.invoices.remove_draft_line',\n\n async execute(rawInput, ctx) {\n const parsed = billingInvoiceRemoveLineSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const invoice = await assertDraftInvoice(em, {\n invoiceId: parsed.invoiceId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n const line = await em.findOne(SalesInvoiceLine, {\n id: parsed.invoiceLineId,\n invoice,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n if (!line) {\n throw new CrudHttpError(404, { error: 'Invoice line not found' })\n }\n\n const before = snapshotLine(line)\n let audit: BillingDraftInvoiceEdit | null = null\n\n await withAtomicFlush(em, [\n () => {\n em.remove(line)\n },\n async () => {\n await recomputeInvoiceTotals(em, invoice)\n },\n () => {\n audit = em.create(BillingDraftInvoiceEdit, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n invoiceId: invoice.id,\n invoiceLineId: null,\n userId: getUserIdFromCtx(ctx),\n action: 'line_removed',\n beforeJson: before as Record<string, unknown>,\n afterJson: null,\n createdAt: new Date(),\n })\n em.persist(audit)\n },\n ])\n\n return { invoiceId: invoice.id, invoiceLineId: parsed.invoiceLineId, auditId: audit!.id }\n },\n}\n\n// \u2500\u2500\u2500 Wipe test invoices (Phase 4b) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Hard-deletes invoices flagged `metadata.test_run=true` (and their\n// lines via cascade \u2014 the invoice's lines collection points at it\n// with ON DELETE CASCADE). Optionally scoped to a single `bill_run_id`\n// via the param so operators can wipe a specific test run without\n// risking other test data.\n\nconst wipeTestInvoicesCommand: CommandHandler<\n BillingWipeTestInvoicesInput,\n { invoicesRemoved: number }\n> = {\n id: 'billing.invoices.wipe_test',\n\n async execute(rawInput, ctx) {\n const parsed = billingWipeTestInvoicesSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n // The metadata->>'test_run' = 'true' predicate uses Postgres'\n // JSONB ->> operator + the literal string 'true' (jsonb true is\n // serialized as the JSON literal `true`, which ->> returns as the\n // text \"true\").\n const params: unknown[] = [parsed.tenantId, parsed.organizationId]\n let billRunClause = ''\n if (parsed.billRunId) {\n billRunClause = ` AND metadata->>'bill_run_id' = ?`\n params.push(parsed.billRunId)\n }\n const rows = (await em.execute(\n `SELECT id FROM sales_invoices\n WHERE tenant_id = ?\n AND organization_id = ?\n AND deleted_at IS NULL\n AND metadata->>'test_run' = 'true'${billRunClause}`,\n params,\n )) as unknown as Array<{ id: string }> | undefined\n if (!rows || rows.length === 0) {\n return { invoicesRemoved: 0 }\n }\n const ids = rows.map((r) => r.id)\n // Hard-delete lines first then invoices to avoid relying on\n // cascade semantics that the migration may or may not encode.\n await em.execute(\n `DELETE FROM sales_invoice_lines WHERE invoice_id IN (${ids.map(() => '?').join(',')})`,\n ids,\n )\n await em.execute(\n `DELETE FROM sales_invoices WHERE id IN (${ids.map(() => '?').join(',')})`,\n ids,\n )\n return { invoicesRemoved: ids.length }\n },\n}\n\nregisterCommand(postInvoiceCommand)\nregisterCommand(editDraftLineCommand)\nregisterCommand(addDraftLineCommand)\nregisterCommand(removeDraftLineCommand)\nregisterCommand(wipeTestInvoicesCommand)\n\nexport {\n postInvoiceCommand,\n editDraftLineCommand,\n addDraftLineCommand,\n removeDraftLineCommand,\n wipeTestInvoicesCommand,\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,7 @@ import {
4
4
  ensureTenantScope
5
5
  } from "@open-mercato/shared/lib/commands/scope";
6
6
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
- import { BillRun, BillRunOutcome } from "../data/entities.js";
7
+ import { BillingRun, BillingRunOutcome } from "../data/entities.js";
8
8
  import {
9
9
  billingRunRetryFailedSchema,
10
10
  billingRunTriggerSchema
@@ -63,7 +63,7 @@ const retryFailedCommand = {
63
63
  ensureTenantScope(ctx, parsed.tenantId);
64
64
  ensureOrganizationScope(ctx, parsed.organizationId);
65
65
  const em = getEm(ctx);
66
- const parentRun = await em.findOne(BillRun, {
66
+ const parentRun = await em.findOne(BillingRun, {
67
67
  id: parsed.billRunId,
68
68
  tenantId: parsed.tenantId,
69
69
  organizationId: parsed.organizationId,
@@ -72,7 +72,7 @@ const retryFailedCommand = {
72
72
  if (!parentRun) {
73
73
  throw new CrudHttpError(404, { error: "Parent Bill Run not found" });
74
74
  }
75
- const failedOutcomes = await em.find(BillRunOutcome, {
75
+ const failedOutcomes = await em.find(BillingRunOutcome, {
76
76
  billRunId: parentRun.id,
77
77
  tenantId: parsed.tenantId,
78
78
  organizationId: parsed.organizationId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/commands/runs.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands/types'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n} from '@open-mercato/shared/lib/commands/scope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { BillRun, BillRunOutcome } from '../data/entities'\nimport {\n billingRunRetryFailedSchema,\n billingRunTriggerSchema,\n type BillingRunRetryFailedInput,\n type BillingRunTriggerInput,\n} from '../data/validators'\nimport { runBillRun } from '../lib/billRunEngine'\nimport { TenantLockBusyError } from '../lib/tenantLock'\n\n/**\n * Bill Run trigger commands.\n *\n * Both commands wrap `runBillRun(...)` from `lib/billRunEngine`. The\n * lock-busy condition (concurrent run on the same tenant) is mapped to\n * HTTP 409 here so the API surface returns the canonical Conflict\n * response without each route knowing the lock primitive's\n * implementation.\n *\n * `triggeredBy` is intentionally NOT exposed on the input schema \u2014\n * client-driven triggers are always `'manual'`. The scheduler worker\n * (Phase 4) injects `'schedule'` directly through a separate\n * orchestrator path, not through this command.\n */\n\nfunction getEm(ctx: CommandRuntimeContext): EntityManager {\n return (ctx.container.resolve('em') as EntityManager).fork()\n}\n\nfunction getContainer(ctx: CommandRuntimeContext): AwilixContainer {\n return ctx.container as AwilixContainer\n}\n\nfunction asLockBusyError(err: unknown): err is TenantLockBusyError {\n return err instanceof TenantLockBusyError\n}\n\n// \u2500\u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype TriggerResult = {\n billRunId: string\n status: string\n outcomesCount: number\n}\n\nconst triggerRunCommand: CommandHandler<BillingRunTriggerInput, TriggerResult> = {\n id: 'billing.runs.trigger',\n\n async execute(rawInput, ctx) {\n const parsed = billingRunTriggerSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const asOfDate = parsed.asOfDate ?? new Date()\n\n try {\n const { billRun, outcomes } = await runBillRun(em, getContainer(ctx), {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n asOfDate,\n triggeredBy: 'manual',\n mode: parsed.mode,\n scopedAccountIds: parsed.scopedAccountIds,\n catchUp: parsed.catchUp,\n })\n return {\n billRunId: billRun.id,\n status: billRun.status,\n outcomesCount: outcomes.length,\n }\n } catch (err) {\n if (asLockBusyError(err)) {\n // The route metadata maps `CrudHttpError(409)` to the canonical\n // 409 Conflict response. The error body carries the\n // `error_lock_busy` discriminator so clients can distinguish\n // \"lock busy\" from other 409 conditions.\n throw new CrudHttpError(409, {\n error: 'Another Bill Run is already in progress for this tenant',\n code: 'billing.run.lock_busy',\n tenantId: err.tenantId,\n lockName: err.lockName,\n })\n }\n throw err\n }\n },\n}\n\n// \u2500\u2500\u2500 Retry failed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst retryFailedCommand: CommandHandler<BillingRunRetryFailedInput, TriggerResult> = {\n id: 'billing.runs.retry_failed',\n\n async execute(rawInput, ctx) {\n const parsed = billingRunRetryFailedSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const parentRun = await em.findOne(BillRun, {\n id: parsed.billRunId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!parentRun) {\n throw new CrudHttpError(404, { error: 'Parent Bill Run not found' })\n }\n const failedOutcomes = await em.find(BillRunOutcome, {\n billRunId: parentRun.id,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n status: 'failed',\n })\n if (failedOutcomes.length === 0) {\n throw new CrudHttpError(409, {\n error: 'Parent run has no failed outcomes to retry',\n code: 'billing.run.nothing_to_retry',\n })\n }\n const scopedAccountIds = Array.from(new Set(failedOutcomes.map((o) => o.billAccountId)))\n\n try {\n const { billRun, outcomes } = await runBillRun(em, getContainer(ctx), {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n asOfDate: parentRun.asOfDate,\n triggeredBy: 'manual',\n mode: parsed.mode,\n scopedAccountIds,\n parentRunId: parentRun.id,\n catchUp: parentRun.catchUp,\n })\n return {\n billRunId: billRun.id,\n status: billRun.status,\n outcomesCount: outcomes.length,\n }\n } catch (err) {\n if (asLockBusyError(err)) {\n throw new CrudHttpError(409, {\n error: 'Another Bill Run is already in progress for this tenant',\n code: 'billing.run.lock_busy',\n tenantId: err.tenantId,\n lockName: err.lockName,\n })\n }\n throw err\n }\n },\n}\n\nregisterCommand(triggerRunCommand)\nregisterCommand(retryFailedCommand)\n\nexport { triggerRunCommand, retryFailedCommand }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands/types'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n} from '@open-mercato/shared/lib/commands/scope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { BillingRun, BillingRunOutcome } from '../data/entities'\nimport {\n billingRunRetryFailedSchema,\n billingRunTriggerSchema,\n type BillingRunRetryFailedInput,\n type BillingRunTriggerInput,\n} from '../data/validators'\nimport { runBillRun } from '../lib/billRunEngine'\nimport { TenantLockBusyError } from '../lib/tenantLock'\n\n/**\n * Bill Run trigger commands.\n *\n * Both commands wrap `runBillRun(...)` from `lib/billRunEngine`. The\n * lock-busy condition (concurrent run on the same tenant) is mapped to\n * HTTP 409 here so the API surface returns the canonical Conflict\n * response without each route knowing the lock primitive's\n * implementation.\n *\n * `triggeredBy` is intentionally NOT exposed on the input schema \u2014\n * client-driven triggers are always `'manual'`. The scheduler worker\n * (Phase 4) injects `'schedule'` directly through a separate\n * orchestrator path, not through this command.\n */\n\nfunction getEm(ctx: CommandRuntimeContext): EntityManager {\n return (ctx.container.resolve('em') as EntityManager).fork()\n}\n\nfunction getContainer(ctx: CommandRuntimeContext): AwilixContainer {\n return ctx.container as AwilixContainer\n}\n\nfunction asLockBusyError(err: unknown): err is TenantLockBusyError {\n return err instanceof TenantLockBusyError\n}\n\n// \u2500\u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype TriggerResult = {\n billRunId: string\n status: string\n outcomesCount: number\n}\n\nconst triggerRunCommand: CommandHandler<BillingRunTriggerInput, TriggerResult> = {\n id: 'billing.runs.trigger',\n\n async execute(rawInput, ctx) {\n const parsed = billingRunTriggerSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const asOfDate = parsed.asOfDate ?? new Date()\n\n try {\n const { billRun, outcomes } = await runBillRun(em, getContainer(ctx), {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n asOfDate,\n triggeredBy: 'manual',\n mode: parsed.mode,\n scopedAccountIds: parsed.scopedAccountIds,\n catchUp: parsed.catchUp,\n })\n return {\n billRunId: billRun.id,\n status: billRun.status,\n outcomesCount: outcomes.length,\n }\n } catch (err) {\n if (asLockBusyError(err)) {\n // The route metadata maps `CrudHttpError(409)` to the canonical\n // 409 Conflict response. The error body carries the\n // `error_lock_busy` discriminator so clients can distinguish\n // \"lock busy\" from other 409 conditions.\n throw new CrudHttpError(409, {\n error: 'Another Bill Run is already in progress for this tenant',\n code: 'billing.run.lock_busy',\n tenantId: err.tenantId,\n lockName: err.lockName,\n })\n }\n throw err\n }\n },\n}\n\n// \u2500\u2500\u2500 Retry failed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst retryFailedCommand: CommandHandler<BillingRunRetryFailedInput, TriggerResult> = {\n id: 'billing.runs.retry_failed',\n\n async execute(rawInput, ctx) {\n const parsed = billingRunRetryFailedSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = getEm(ctx)\n const parentRun = await em.findOne(BillingRun, {\n id: parsed.billRunId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!parentRun) {\n throw new CrudHttpError(404, { error: 'Parent Bill Run not found' })\n }\n const failedOutcomes = await em.find(BillingRunOutcome, {\n billRunId: parentRun.id,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n status: 'failed',\n })\n if (failedOutcomes.length === 0) {\n throw new CrudHttpError(409, {\n error: 'Parent run has no failed outcomes to retry',\n code: 'billing.run.nothing_to_retry',\n })\n }\n const scopedAccountIds = Array.from(new Set(failedOutcomes.map((o) => o.billAccountId)))\n\n try {\n const { billRun, outcomes } = await runBillRun(em, getContainer(ctx), {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n asOfDate: parentRun.asOfDate,\n triggeredBy: 'manual',\n mode: parsed.mode,\n scopedAccountIds,\n parentRunId: parentRun.id,\n catchUp: parentRun.catchUp,\n })\n return {\n billRunId: billRun.id,\n status: billRun.status,\n outcomesCount: outcomes.length,\n }\n } catch (err) {\n if (asLockBusyError(err)) {\n throw new CrudHttpError(409, {\n error: 'Another Bill Run is already in progress for this tenant',\n code: 'billing.run.lock_busy',\n tenantId: err.tenantId,\n lockName: err.lockName,\n })\n }\n throw err\n }\n },\n}\n\nregisterCommand(triggerRunCommand)\nregisterCommand(retryFailedCommand)\n\nexport { triggerRunCommand, retryFailedCommand }\n"],
5
+ "mappings": "AAGA,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAC9B,SAAS,YAAY,yBAAyB;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;AAiBpC,SAAS,MAAM,KAA2C;AACxD,SAAQ,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC7D;AAEA,SAAS,aAAa,KAA6C;AACjE,SAAO,IAAI;AACb;AAEA,SAAS,gBAAgB,KAA0C;AACjE,SAAO,eAAe;AACxB;AAUA,MAAM,oBAA2E;AAAA,EAC/E,IAAI;AAAA,EAEJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,wBAAwB,MAAM,QAAQ;AACrD,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAElD,UAAM,KAAK,MAAM,GAAG;AACpB,UAAM,WAAW,OAAO,YAAY,oBAAI,KAAK;AAE7C,QAAI;AACF,YAAM,EAAE,SAAS,SAAS,IAAI,MAAM,WAAW,IAAI,aAAa,GAAG,GAAG;AAAA,QACpE,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO;AAAA,QACvB;AAAA,QACA,aAAa;AAAA,QACb,MAAM,OAAO;AAAA,QACb,kBAAkB,OAAO;AAAA,QACzB,SAAS,OAAO;AAAA,MAClB,CAAC;AACD,aAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB,QAAQ,QAAQ;AAAA,QAChB,eAAe,SAAS;AAAA,MAC1B;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,GAAG;AAKxB,cAAM,IAAI,cAAc,KAAK;AAAA,UAC3B,OAAO;AAAA,UACP,MAAM;AAAA,UACN,UAAU,IAAI;AAAA,UACd,UAAU,IAAI;AAAA,QAChB,CAAC;AAAA,MACH;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAIA,MAAM,qBAAgF;AAAA,EACpF,IAAI;AAAA,EAEJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,4BAA4B,MAAM,QAAQ;AACzD,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAElD,UAAM,KAAK,MAAM,GAAG;AACpB,UAAM,YAAY,MAAM,GAAG,QAAQ,YAAY;AAAA,MAC7C,IAAI,OAAO;AAAA,MACX,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,4BAA4B,CAAC;AAAA,IACrE;AACA,UAAM,iBAAiB,MAAM,GAAG,KAAK,mBAAmB;AAAA,MACtD,WAAW,UAAU;AAAA,MACrB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,eAAe,WAAW,GAAG;AAC/B,YAAM,IAAI,cAAc,KAAK;AAAA,QAC3B,OAAO;AAAA,QACP,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,UAAM,mBAAmB,MAAM,KAAK,IAAI,IAAI,eAAe,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEvF,QAAI;AACF,YAAM,EAAE,SAAS,SAAS,IAAI,MAAM,WAAW,IAAI,aAAa,GAAG,GAAG;AAAA,QACpE,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO;AAAA,QACvB,UAAU,UAAU;AAAA,QACpB,aAAa;AAAA,QACb,MAAM,OAAO;AAAA,QACb;AAAA,QACA,aAAa,UAAU;AAAA,QACvB,SAAS,UAAU;AAAA,MACrB,CAAC;AACD,aAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB,QAAQ,QAAQ;AAAA,QAChB,eAAe,SAAS;AAAA,MAC1B;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,GAAG;AACxB,cAAM,IAAI,cAAc,KAAK;AAAA,UAC3B,OAAO;AAAA,UACP,MAAM;AAAA,UACN,UAAU,IAAI;AAAA,UACd,UAAU,IAAI;AAAA,QAChB,CAAC;AAAA,MACH;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,gBAAgB,iBAAiB;AACjC,gBAAgB,kBAAkB;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/components/PrepaidPanel.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Plus, SlidersHorizontal } from 'lucide-react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { StatCard, Stat } from './DetailFields'\n\n/**\n * Prepaid panel (SPEC-002 P4) injected into the Billing Account detail page\n * for `billing_mode='prepaid'` accounts: balance + status, credit status,\n * a top-up launcher, and the recent balance transactions.\n */\n\ntype BalanceSnapshot = {\n balance: string\n balanceStatus: 'ok' | 'low' | 'exhausted'\n lowBalanceThreshold: string\n lastMovementAt: string | null\n}\n\ntype CreditSnapshot = {\n mode: string\n creditLimit: string\n creditUsed: string\n availableCredit: string\n creditStatus: 'within_limit' | 'near_limit' | 'over_limit'\n}\n\ntype TransactionRow = {\n id: string\n type: string\n amount: string\n balance_after: string\n description: string\n created_at: string\n}\n\nfunction balanceVariant(status: string): 'success' | 'warning' | 'error' {\n if (status === 'low') return 'warning'\n if (status === 'exhausted') return 'error'\n return 'success'\n}\n\nfunction creditVariant(status: string): 'success' | 'warning' | 'error' {\n if (status === 'near_limit') return 'warning'\n if (status === 'over_limit') return 'error'\n return 'success'\n}\n\nfunction txnVariant(type: string): 'success' | 'info' | 'neutral' {\n if (type === 'topup' || type === 'adjustment') return 'success'\n if (type === 'usage') return 'info'\n return 'neutral'\n}\n\nfunction formatDateTime(value: string | null | undefined): string {\n if (!value) return '\u2014'\n const d = new Date(value)\n return Number.isNaN(d.getTime()) ? value : d.toLocaleString()\n}\n\nexport function PrepaidPanel({\n accountId,\n currencyCode,\n}: {\n accountId: string\n currencyCode: string\n}) {\n const t = useT()\n const router = useRouter()\n const [balance, setBalance] = React.useState<BalanceSnapshot | null>(null)\n const [credit, setCredit] = React.useState<CreditSnapshot | null>(null)\n const [rows, setRows] = React.useState<TransactionRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(true)\n const [topupOpen, setTopupOpen] = React.useState(false)\n const [amount, setAmount] = React.useState('')\n const [submitting, setSubmitting] = React.useState(false)\n const [adjustOpen, setAdjustOpen] = React.useState(false)\n const [adjAmount, setAdjAmount] = React.useState('')\n const [adjReason, setAdjReason] = React.useState('')\n const [adjSubmitting, setAdjSubmitting] = React.useState(false)\n const pageSize = 10\n\n const loadStatus = React.useCallback(async () => {\n const [bal, cred] = await Promise.all([\n readApiResultOrThrow<BalanceSnapshot>(`/api/billing/accounts/${accountId}/balance`),\n readApiResultOrThrow<CreditSnapshot>(`/api/billing/accounts/${accountId}/credit-status`),\n ])\n setBalance(bal)\n setCredit(cred)\n }, [accountId])\n\n const loadTransactions = React.useCallback(async () => {\n setLoading(true)\n try {\n const result = await readApiResultOrThrow<{\n items: TransactionRow[]\n total: number\n totalPages: number\n }>(\n `/api/billing/transactions?billAccountId=${accountId}&page=${page}` +\n `&pageSize=${pageSize}&sortField=createdAt&sortDir=desc`,\n )\n setRows(result.items ?? [])\n setTotal(result.total ?? 0)\n setTotalPages(result.totalPages ?? 1)\n } finally {\n setLoading(false)\n }\n }, [accountId, page])\n\n React.useEffect(() => {\n void loadStatus().catch(() => undefined)\n }, [loadStatus])\n React.useEffect(() => {\n void loadTransactions()\n }, [loadTransactions])\n\n const handleTopup = React.useCallback(async () => {\n const value = Number.parseFloat(amount)\n if (!Number.isFinite(value) || value <= 0) {\n flash(t('billing.prepaid.topup.invalid', 'Enter a positive amount'), 'error')\n return\n }\n setSubmitting(true)\n try {\n const res = await apiCallOrThrow<{ redirectUrl?: string | null }>(\n `/api/billing/accounts/${accountId}/topups`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n amount: value,\n successUrl: typeof window !== 'undefined' ? window.location.href : undefined,\n cancelUrl: typeof window !== 'undefined' ? window.location.href : undefined,\n }),\n },\n )\n const redirectUrl = res.result?.redirectUrl\n if (redirectUrl && typeof window !== 'undefined') {\n window.location.href = redirectUrl\n return\n }\n flash(t('billing.prepaid.topup.created', 'Top-up session created'), 'success')\n setTopupOpen(false)\n setAmount('')\n void loadStatus().catch(() => undefined)\n void loadTransactions()\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n flash(message || t('billing.prepaid.topup.error', 'Top-up failed'), 'error')\n } finally {\n setSubmitting(false)\n }\n }, [accountId, amount, loadStatus, loadTransactions, t])\n\n const handleAdjust = React.useCallback(async () => {\n const value = Number.parseFloat(adjAmount)\n if (!Number.isFinite(value) || value === 0) {\n flash(t('billing.prepaid.adjust.invalid_amount', 'Enter a non-zero amount'), 'error')\n return\n }\n if (!adjReason.trim()) {\n flash(t('billing.prepaid.adjust.invalid_description', 'Enter a reason'), 'error')\n return\n }\n setAdjSubmitting(true)\n try {\n await apiCallOrThrow('/api/billing/transactions/adjust', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n billAccountId: accountId,\n amount: value,\n description: adjReason.trim(),\n }),\n })\n flash(t('billing.prepaid.adjust.success', 'Balance adjusted'), 'success')\n setAdjustOpen(false)\n setAdjAmount('')\n setAdjReason('')\n void loadStatus().catch(() => undefined)\n void loadTransactions()\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n flash(message || t('billing.prepaid.adjust.error', 'Adjustment failed'), 'error')\n } finally {\n setAdjSubmitting(false)\n }\n }, [accountId, adjAmount, adjReason, loadStatus, loadTransactions, t])\n\n const columns = React.useMemo<ColumnDef<TransactionRow>[]>(\n () => [\n {\n accessorKey: 'created_at',\n header: t('billing.transactions.columns.when', 'When'),\n cell: ({ row }) => formatDateTime(row.original.created_at),\n },\n {\n accessorKey: 'type',\n header: t('billing.transactions.columns.type', 'Type'),\n cell: ({ row }) => (\n <Tag variant={txnVariant(row.original.type)}>\n {t(`billing.transactions.type.${row.original.type}`, row.original.type)}\n </Tag>\n ),\n },\n {\n accessorKey: 'description',\n header: t('billing.transactions.columns.description', 'Description'),\n cell: ({ row }) => <span className=\"text-sm\">{row.original.description}</span>,\n },\n {\n accessorKey: 'amount',\n header: t('billing.transactions.columns.amount', 'Amount'),\n cell: ({ row }) => (\n <span className=\"font-mono tabular-nums\">{row.original.amount}</span>\n ),\n },\n {\n accessorKey: 'balance_after',\n header: t('billing.transactions.columns.balance_after', 'Balance'),\n cell: ({ row }) => (\n <span className=\"font-mono tabular-nums\">{row.original.balance_after}</span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <div className=\"mt-6 space-y-4\">\n <StatCard title={t('billing.prepaid.panel.title', 'Prepaid balance')}>\n <Stat\n label={t('billing.prepaid.panel.balance', 'Balance')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"font-mono tabular-nums\">\n {balance?.balance ?? '\u2014'} {currencyCode}\n </span>\n {balance ? (\n <Tag variant={balanceVariant(balance.balanceStatus)}>\n {t(`billing.prepaid.status.${balance.balanceStatus}`, balance.balanceStatus)}\n </Tag>\n ) : null}\n </span>\n }\n />\n <Stat\n label={t('billing.prepaid.panel.threshold', 'Low threshold')}\n value={\n <span className=\"font-mono tabular-nums\">{balance?.lowBalanceThreshold ?? '\u2014'}</span>\n }\n />\n {credit ? (\n <Stat\n label={t('billing.prepaid.panel.credit', 'Credit')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"font-mono tabular-nums\">\n {credit.creditUsed} / {credit.creditLimit}\n </span>\n <Tag variant={creditVariant(credit.creditStatus)}>\n {t(`billing.prepaid.credit.${credit.creditStatus}`, credit.creditStatus)}\n </Tag>\n </span>\n }\n />\n ) : null}\n <Stat\n label={t('billing.prepaid.panel.last_movement', 'Last movement')}\n value={formatDateTime(balance?.lastMovementAt)}\n />\n </StatCard>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {!topupOpen && !adjustOpen ? (\n <>\n <Button type=\"button\" onClick={() => setTopupOpen(true)}>\n <Plus size={16} />\n {t('billing.prepaid.topup.action', 'Top up')}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setAdjustOpen(true)}>\n <SlidersHorizontal size={16} />\n {t('billing.prepaid.adjust.action', 'Adjust')}\n </Button>\n </>\n ) : null}\n\n {topupOpen ? (\n <div className=\"flex items-center gap-2\">\n <input\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n autoFocus\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void handleTopup()\n if (e.key === 'Escape') setTopupOpen(false)\n }}\n placeholder={t('billing.prepaid.topup.amount', 'Amount')}\n className=\"h-9 w-32 rounded-md border border-border bg-background px-3 text-sm\"\n />\n <Button type=\"button\" onClick={handleTopup} disabled={submitting}>\n {submitting\n ? t('billing.prepaid.topup.submitting', 'Starting\u2026')\n : t('billing.prepaid.topup.confirm', 'Start checkout')}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTopupOpen(false)}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n </div>\n ) : null}\n\n {adjustOpen ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <input\n type=\"number\"\n step=\"0.01\"\n autoFocus\n value={adjAmount}\n onChange={(e) => setAdjAmount(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void handleAdjust()\n if (e.key === 'Escape') setAdjustOpen(false)\n }}\n placeholder={t('billing.prepaid.adjust.amount', 'Amount (+credit / \u2212debit)')}\n className=\"h-9 w-48 rounded-md border border-border bg-background px-3 text-sm\"\n />\n <input\n type=\"text\"\n value={adjReason}\n onChange={(e) => setAdjReason(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void handleAdjust()\n if (e.key === 'Escape') setAdjustOpen(false)\n }}\n placeholder={t('billing.prepaid.adjust.description', 'Reason')}\n className=\"h-9 w-64 rounded-md border border-border bg-background px-3 text-sm\"\n />\n <Button type=\"button\" onClick={handleAdjust} disabled={adjSubmitting}>\n {adjSubmitting\n ? t('billing.prepaid.adjust.submitting', 'Applying\u2026')\n : t('billing.prepaid.adjust.confirm', 'Apply adjustment')}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setAdjustOpen(false)}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n </div>\n ) : null}\n </div>\n\n <DataTable\n title={t('billing.prepaid.panel.transactions', 'Balance transactions')}\n columns={columns}\n data={rows}\n isLoading={loading}\n onRowClick={(row) => router.push(`/backend/billing/transactions/${row.id}`)}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('billing.prepaid.panel.no_transactions', 'No balance movements yet.')}\n </div>\n }\n />\n </div>\n )\n}\n\nexport default PrepaidPanel\n"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Plus, SlidersHorizontal } from 'lucide-react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { StatCard, Stat } from './DetailFields'\n\n/**\n * Prepaid panel (SPEC-002 P4) injected into the Billing Account detail page\n * for `billing_mode='prepaid'` accounts: balance + status, credit status,\n * a top-up launcher, and the recent balance transactions.\n */\n\ntype BalanceSnapshot = {\n balance: string\n balanceStatus: 'ok' | 'low' | 'exhausted'\n lowBalanceThreshold: string\n lastMovementAt: string | null\n}\n\ntype CreditSnapshot = {\n mode: string\n creditLimit: string\n creditUsed: string\n availableCredit: string\n creditStatus: 'within_limit' | 'near_limit' | 'over_limit'\n}\n\ntype TransactionRow = {\n id: string\n type: string\n amount: string\n balance_after: string\n description: string\n created_at: string\n}\n\nfunction balanceVariant(status: string): 'success' | 'warning' | 'error' {\n if (status === 'low') return 'warning'\n if (status === 'exhausted') return 'error'\n return 'success'\n}\n\nfunction creditVariant(status: string): 'success' | 'warning' | 'error' {\n if (status === 'near_limit') return 'warning'\n if (status === 'over_limit') return 'error'\n return 'success'\n}\n\nfunction txnVariant(type: string): 'success' | 'info' | 'neutral' {\n if (type === 'topup' || type === 'adjustment') return 'success'\n if (type === 'usage') return 'info'\n return 'neutral'\n}\n\nfunction formatDateTime(value: string | null | undefined): string {\n if (!value) return '\u2014'\n const d = new Date(value)\n return Number.isNaN(d.getTime()) ? value : d.toLocaleString()\n}\n\nexport function PrepaidPanel({\n accountId,\n currencyCode,\n}: {\n accountId: string\n currencyCode: string\n}) {\n const t = useT()\n const router = useRouter()\n const [balance, setBalance] = React.useState<BalanceSnapshot | null>(null)\n const [credit, setCredit] = React.useState<CreditSnapshot | null>(null)\n const [rows, setRows] = React.useState<TransactionRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(true)\n const [topupOpen, setTopupOpen] = React.useState(false)\n const [amount, setAmount] = React.useState('')\n const [submitting, setSubmitting] = React.useState(false)\n const [adjustOpen, setAdjustOpen] = React.useState(false)\n const [adjAmount, setAdjAmount] = React.useState('')\n const [adjReason, setAdjReason] = React.useState('')\n const [adjSubmitting, setAdjSubmitting] = React.useState(false)\n const pageSize = 10\n\n const loadStatus = React.useCallback(async () => {\n const [bal, cred] = await Promise.all([\n readApiResultOrThrow<BalanceSnapshot>(`/api/billing/accounts/${accountId}/balance`),\n readApiResultOrThrow<CreditSnapshot>(`/api/billing/accounts/${accountId}/credit-status`),\n ])\n setBalance(bal)\n setCredit(cred)\n }, [accountId])\n\n const loadTransactions = React.useCallback(async () => {\n setLoading(true)\n try {\n const result = await readApiResultOrThrow<{\n items: TransactionRow[]\n total: number\n totalPages: number\n }>(\n `/api/billing/transactions?billAccountId=${accountId}&page=${page}` +\n `&pageSize=${pageSize}&sortField=createdAt&sortDir=desc`,\n )\n setRows(result.items ?? [])\n setTotal(result.total ?? 0)\n setTotalPages(result.totalPages ?? 1)\n } finally {\n setLoading(false)\n }\n }, [accountId, page])\n\n React.useEffect(() => {\n void loadStatus().catch(() => undefined)\n }, [loadStatus])\n React.useEffect(() => {\n void loadTransactions()\n }, [loadTransactions])\n\n const handleTopup = React.useCallback(async () => {\n const value = Number.parseFloat(amount)\n if (!Number.isFinite(value) || value <= 0) {\n flash(t('billing.prepaid.topup.invalid', 'Enter a positive amount'), 'error')\n return\n }\n setSubmitting(true)\n try {\n const res = await apiCallOrThrow<{ redirectUrl?: string | null }>(\n `/api/billing/accounts/${accountId}/topups`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n amount: value,\n successUrl: typeof window !== 'undefined' ? window.location.href : undefined,\n cancelUrl: typeof window !== 'undefined' ? window.location.href : undefined,\n }),\n },\n )\n const redirectUrl = res.result?.redirectUrl\n if (redirectUrl && typeof window !== 'undefined') {\n window.location.href = redirectUrl\n return\n }\n flash(t('billing.prepaid.topup.created', 'Top-up session created'), 'success')\n setTopupOpen(false)\n setAmount('')\n void loadStatus().catch(() => undefined)\n void loadTransactions()\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n flash(message || t('billing.prepaid.topup.error', 'Top-up failed'), 'error')\n } finally {\n setSubmitting(false)\n }\n }, [accountId, amount, loadStatus, loadTransactions, t])\n\n const handleAdjust = React.useCallback(async () => {\n const value = Number.parseFloat(adjAmount)\n if (!Number.isFinite(value) || value === 0) {\n flash(t('billing.prepaid.adjust.invalid_amount', 'Enter a non-zero amount'), 'error')\n return\n }\n if (!adjReason.trim()) {\n flash(t('billing.prepaid.adjust.invalid_description', 'Enter a reason'), 'error')\n return\n }\n setAdjSubmitting(true)\n try {\n await apiCallOrThrow('/api/billing/transactions/adjust', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n billAccountId: accountId,\n amount: value,\n description: adjReason.trim(),\n }),\n })\n flash(t('billing.prepaid.adjust.success', 'Balance adjusted'), 'success')\n setAdjustOpen(false)\n setAdjAmount('')\n setAdjReason('')\n void loadStatus().catch(() => undefined)\n void loadTransactions()\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n flash(message || t('billing.prepaid.adjust.error', 'Adjustment failed'), 'error')\n } finally {\n setAdjSubmitting(false)\n }\n }, [accountId, adjAmount, adjReason, loadStatus, loadTransactions, t])\n\n const columns = React.useMemo<ColumnDef<TransactionRow>[]>(\n () => [\n {\n accessorKey: 'created_at',\n header: t('billing.transactions.columns.when', 'When'),\n cell: ({ row }) => formatDateTime(row.original.created_at),\n },\n {\n accessorKey: 'type',\n header: t('billing.transactions.columns.type', 'Type'),\n cell: ({ row }) => (\n <Tag variant={txnVariant(row.original.type)}>\n {t(`billing.transactions.type.${row.original.type}`, row.original.type)}\n </Tag>\n ),\n },\n {\n accessorKey: 'description',\n header: t('billing.transactions.columns.description', 'Description'),\n cell: ({ row }) => <span className=\"text-sm\">{row.original.description}</span>,\n },\n {\n accessorKey: 'amount',\n header: t('billing.transactions.columns.amount', 'Amount'),\n cell: ({ row }) => (\n <span className=\"font-mono tabular-nums\">{row.original.amount}</span>\n ),\n },\n {\n accessorKey: 'balance_after',\n header: t('billing.transactions.columns.balance_after', 'Balance'),\n cell: ({ row }) => (\n <span className=\"font-mono tabular-nums\">{row.original.balance_after}</span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <div className=\"mt-6 space-y-4\">\n <StatCard title={t('billing.prepaid.panel.title', 'Prepaid balance')}>\n <Stat\n label={t('billing.prepaid.panel.balance', 'Balance')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"font-mono tabular-nums\">\n {balance?.balance ?? '\u2014'} {currencyCode}\n </span>\n {balance ? (\n <Tag variant={balanceVariant(balance.balanceStatus)}>\n {t(`billing.prepaid.status.${balance.balanceStatus}`, balance.balanceStatus)}\n </Tag>\n ) : null}\n </span>\n }\n />\n <Stat\n label={t('billing.prepaid.panel.threshold', 'Low threshold')}\n value={\n <span className=\"font-mono tabular-nums\">{balance?.lowBalanceThreshold ?? '\u2014'}</span>\n }\n />\n {credit ? (\n <Stat\n label={t('billing.prepaid.panel.credit', 'Credit')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"font-mono tabular-nums\">\n {credit.creditUsed} / {credit.creditLimit}\n </span>\n <Tag variant={creditVariant(credit.creditStatus)}>\n {t(`billing.prepaid.credit.${credit.creditStatus}`, credit.creditStatus)}\n </Tag>\n </span>\n }\n />\n ) : null}\n <Stat\n label={t('billing.prepaid.panel.last_movement', 'Last movement')}\n value={formatDateTime(balance?.lastMovementAt)}\n />\n </StatCard>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {!topupOpen && !adjustOpen ? (\n <>\n <Button type=\"button\" onClick={() => setTopupOpen(true)}>\n <Plus size={16} />\n {t('billing.prepaid.topup.action', 'Top up')}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setAdjustOpen(true)}>\n <SlidersHorizontal size={16} />\n {t('billing.prepaid.adjust.action', 'Adjust')}\n </Button>\n </>\n ) : null}\n\n {topupOpen ? (\n <div className=\"flex items-center gap-2\">\n <input\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n autoFocus\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void handleTopup()\n if (e.key === 'Escape') setTopupOpen(false)\n }}\n placeholder={t('billing.prepaid.topup.amount', 'Amount')}\n className=\"h-9 w-32 rounded-md border border-border bg-background px-3 text-sm\"\n />\n <Button type=\"button\" onClick={handleTopup} disabled={submitting}>\n {submitting\n ? t('billing.prepaid.topup.submitting', 'Starting\u2026')\n : t('billing.prepaid.topup.confirm', 'Start checkout')}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTopupOpen(false)}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n </div>\n ) : null}\n\n {adjustOpen ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <input\n type=\"number\"\n step=\"0.01\"\n autoFocus\n value={adjAmount}\n onChange={(e) => setAdjAmount(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void handleAdjust()\n if (e.key === 'Escape') setAdjustOpen(false)\n }}\n placeholder={t('billing.prepaid.adjust.amount', 'Amount (+credit / \u2212debit)')}\n className=\"h-9 w-48 rounded-md border border-border bg-background px-3 text-sm\"\n />\n <input\n type=\"text\"\n value={adjReason}\n onChange={(e) => setAdjReason(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void handleAdjust()\n if (e.key === 'Escape') setAdjustOpen(false)\n }}\n placeholder={t('billing.prepaid.adjust.description', 'Reason')}\n className=\"h-9 w-64 rounded-md border border-border bg-background px-3 text-sm\"\n />\n <Button type=\"button\" onClick={handleAdjust} disabled={adjSubmitting}>\n {adjSubmitting\n ? t('billing.prepaid.adjust.submitting', 'Applying\u2026')\n : t('billing.prepaid.adjust.confirm', 'Apply adjustment')}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setAdjustOpen(false)}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n </div>\n ) : null}\n </div>\n\n <DataTable\n title={t('billing.prepaid.panel.transactions', 'Balance transactions')}\n columns={columns}\n data={rows}\n isLoading={loading}\n onRowClick={(row) => router.push(`/backend/billing/transactions/${row.id}`)}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('billing.prepaid.panel.no_transactions', 'No balance movements yet.')}\n </div>\n }\n />\n </div>\n )\n}\n\nexport default PrepaidPanel\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -247,7 +247,7 @@ BillingAccountUsage = __decorateClass([
247
247
  })
248
248
  ], BillingAccountUsage);
249
249
  OptionalProps;
250
- let BillRun = class {
250
+ let BillingRun = class {
251
251
  constructor() {
252
252
  this.dryRun = false;
253
253
  this.testMode = false;
@@ -259,59 +259,59 @@ let BillRun = class {
259
259
  };
260
260
  __decorateClass([
261
261
  PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
262
- ], BillRun.prototype, "id", 2);
262
+ ], BillingRun.prototype, "id", 2);
263
263
  __decorateClass([
264
264
  Property({ name: "organization_id", type: "uuid" })
265
- ], BillRun.prototype, "organizationId", 2);
265
+ ], BillingRun.prototype, "organizationId", 2);
266
266
  __decorateClass([
267
267
  Property({ name: "tenant_id", type: "uuid" })
268
- ], BillRun.prototype, "tenantId", 2);
268
+ ], BillingRun.prototype, "tenantId", 2);
269
269
  __decorateClass([
270
270
  Property({ name: "triggered_by", type: "text" })
271
- ], BillRun.prototype, "triggeredBy", 2);
271
+ ], BillingRun.prototype, "triggeredBy", 2);
272
272
  __decorateClass([
273
273
  Property({ name: "parent_run_id", type: "uuid", nullable: true })
274
- ], BillRun.prototype, "parentRunId", 2);
274
+ ], BillingRun.prototype, "parentRunId", 2);
275
275
  __decorateClass([
276
276
  Property({ name: "dry_run", type: "boolean", default: false })
277
- ], BillRun.prototype, "dryRun", 2);
277
+ ], BillingRun.prototype, "dryRun", 2);
278
278
  __decorateClass([
279
279
  Property({ name: "test_mode", type: "boolean", default: false })
280
- ], BillRun.prototype, "testMode", 2);
280
+ ], BillingRun.prototype, "testMode", 2);
281
281
  __decorateClass([
282
282
  Property({ name: "catch_up", type: "boolean", default: false })
283
- ], BillRun.prototype, "catchUp", 2);
283
+ ], BillingRun.prototype, "catchUp", 2);
284
284
  __decorateClass([
285
285
  Property({ name: "scoped_account_ids", type: "jsonb", nullable: true })
286
- ], BillRun.prototype, "scopedAccountIds", 2);
286
+ ], BillingRun.prototype, "scopedAccountIds", 2);
287
287
  __decorateClass([
288
288
  Property({ name: "as_of_date", type: "date", columnType: "date" })
289
- ], BillRun.prototype, "asOfDate", 2);
289
+ ], BillingRun.prototype, "asOfDate", 2);
290
290
  __decorateClass([
291
291
  Property({ name: "started_at", type: Date, nullable: true })
292
- ], BillRun.prototype, "startedAt", 2);
292
+ ], BillingRun.prototype, "startedAt", 2);
293
293
  __decorateClass([
294
294
  Property({ name: "finished_at", type: Date, nullable: true })
295
- ], BillRun.prototype, "finishedAt", 2);
295
+ ], BillingRun.prototype, "finishedAt", 2);
296
296
  __decorateClass([
297
297
  Property({ type: "text" })
298
- ], BillRun.prototype, "status", 2);
298
+ ], BillingRun.prototype, "status", 2);
299
299
  __decorateClass([
300
300
  Property({ type: "jsonb", nullable: true })
301
- ], BillRun.prototype, "summary", 2);
301
+ ], BillingRun.prototype, "summary", 2);
302
302
  __decorateClass([
303
303
  Property({ name: "is_active", type: "boolean", default: true })
304
- ], BillRun.prototype, "isActive", 2);
304
+ ], BillingRun.prototype, "isActive", 2);
305
305
  __decorateClass([
306
306
  Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
307
- ], BillRun.prototype, "createdAt", 2);
307
+ ], BillingRun.prototype, "createdAt", 2);
308
308
  __decorateClass([
309
309
  Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
310
- ], BillRun.prototype, "updatedAt", 2);
310
+ ], BillingRun.prototype, "updatedAt", 2);
311
311
  __decorateClass([
312
312
  Property({ name: "deleted_at", type: Date, nullable: true })
313
- ], BillRun.prototype, "deletedAt", 2);
314
- BillRun = __decorateClass([
313
+ ], BillingRun.prototype, "deletedAt", 2);
314
+ BillingRun = __decorateClass([
315
315
  Entity({ tableName: "billing_runs" }),
316
316
  Index({
317
317
  name: "billing_runs_tenant_status_idx",
@@ -321,9 +321,9 @@ BillRun = __decorateClass([
321
321
  name: "billing_runs_parent_idx",
322
322
  properties: ["parentRunId"]
323
323
  })
324
- ], BillRun);
324
+ ], BillingRun);
325
325
  OptionalProps;
326
- let BillRunOutcome = class {
326
+ let BillingRunOutcome = class {
327
327
  constructor() {
328
328
  this.isActive = true;
329
329
  this.createdAt = /* @__PURE__ */ new Date();
@@ -332,47 +332,47 @@ let BillRunOutcome = class {
332
332
  };
333
333
  __decorateClass([
334
334
  PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
335
- ], BillRunOutcome.prototype, "id", 2);
335
+ ], BillingRunOutcome.prototype, "id", 2);
336
336
  __decorateClass([
337
337
  Property({ name: "organization_id", type: "uuid" })
338
- ], BillRunOutcome.prototype, "organizationId", 2);
338
+ ], BillingRunOutcome.prototype, "organizationId", 2);
339
339
  __decorateClass([
340
340
  Property({ name: "tenant_id", type: "uuid" })
341
- ], BillRunOutcome.prototype, "tenantId", 2);
341
+ ], BillingRunOutcome.prototype, "tenantId", 2);
342
342
  __decorateClass([
343
343
  Property({ name: "bill_run_id", type: "uuid" })
344
- ], BillRunOutcome.prototype, "billRunId", 2);
344
+ ], BillingRunOutcome.prototype, "billRunId", 2);
345
345
  __decorateClass([
346
346
  Property({ name: "bill_account_id", type: "uuid" })
347
- ], BillRunOutcome.prototype, "billAccountId", 2);
347
+ ], BillingRunOutcome.prototype, "billAccountId", 2);
348
348
  __decorateClass([
349
349
  Property({ type: "text" })
350
- ], BillRunOutcome.prototype, "status", 2);
350
+ ], BillingRunOutcome.prototype, "status", 2);
351
351
  __decorateClass([
352
352
  Property({ name: "error_message", type: "text", nullable: true })
353
- ], BillRunOutcome.prototype, "errorMessage", 2);
353
+ ], BillingRunOutcome.prototype, "errorMessage", 2);
354
354
  __decorateClass([
355
355
  Property({ type: "jsonb", nullable: true })
356
- ], BillRunOutcome.prototype, "warnings", 2);
356
+ ], BillingRunOutcome.prototype, "warnings", 2);
357
357
  __decorateClass([
358
358
  Property({ name: "draft_invoice_id", type: "uuid", nullable: true })
359
- ], BillRunOutcome.prototype, "draftInvoiceId", 2);
359
+ ], BillingRunOutcome.prototype, "draftInvoiceId", 2);
360
360
  __decorateClass([
361
361
  Property({ name: "statement_id", type: "uuid", nullable: true })
362
- ], BillRunOutcome.prototype, "statementId", 2);
362
+ ], BillingRunOutcome.prototype, "statementId", 2);
363
363
  __decorateClass([
364
364
  Property({ name: "is_active", type: "boolean", default: true })
365
- ], BillRunOutcome.prototype, "isActive", 2);
365
+ ], BillingRunOutcome.prototype, "isActive", 2);
366
366
  __decorateClass([
367
367
  Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
368
- ], BillRunOutcome.prototype, "createdAt", 2);
368
+ ], BillingRunOutcome.prototype, "createdAt", 2);
369
369
  __decorateClass([
370
370
  Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
371
- ], BillRunOutcome.prototype, "updatedAt", 2);
371
+ ], BillingRunOutcome.prototype, "updatedAt", 2);
372
372
  __decorateClass([
373
373
  Property({ name: "deleted_at", type: Date, nullable: true })
374
- ], BillRunOutcome.prototype, "deletedAt", 2);
375
- BillRunOutcome = __decorateClass([
374
+ ], BillingRunOutcome.prototype, "deletedAt", 2);
375
+ BillingRunOutcome = __decorateClass([
376
376
  Entity({ tableName: "billing_run_outcomes" }),
377
377
  Index({
378
378
  name: "billing_run_outcomes_run_status_idx",
@@ -382,47 +382,47 @@ BillRunOutcome = __decorateClass([
382
382
  name: "billing_run_outcomes_account_history_idx",
383
383
  properties: ["organizationId", "tenantId", "billAccountId", "createdAt"]
384
384
  })
385
- ], BillRunOutcome);
385
+ ], BillingRunOutcome);
386
386
  OptionalProps;
387
- let DraftInvoiceEdit = class {
387
+ let BillingDraftInvoiceEdit = class {
388
388
  constructor() {
389
389
  this.createdAt = /* @__PURE__ */ new Date();
390
390
  }
391
391
  };
392
392
  __decorateClass([
393
393
  PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
394
- ], DraftInvoiceEdit.prototype, "id", 2);
394
+ ], BillingDraftInvoiceEdit.prototype, "id", 2);
395
395
  __decorateClass([
396
396
  Property({ name: "organization_id", type: "uuid" })
397
- ], DraftInvoiceEdit.prototype, "organizationId", 2);
397
+ ], BillingDraftInvoiceEdit.prototype, "organizationId", 2);
398
398
  __decorateClass([
399
399
  Property({ name: "tenant_id", type: "uuid" })
400
- ], DraftInvoiceEdit.prototype, "tenantId", 2);
400
+ ], BillingDraftInvoiceEdit.prototype, "tenantId", 2);
401
401
  __decorateClass([
402
402
  Property({ name: "invoice_id", type: "uuid" })
403
- ], DraftInvoiceEdit.prototype, "invoiceId", 2);
403
+ ], BillingDraftInvoiceEdit.prototype, "invoiceId", 2);
404
404
  __decorateClass([
405
405
  Property({ name: "invoice_line_id", type: "uuid", nullable: true })
406
- ], DraftInvoiceEdit.prototype, "invoiceLineId", 2);
406
+ ], BillingDraftInvoiceEdit.prototype, "invoiceLineId", 2);
407
407
  __decorateClass([
408
408
  Property({ name: "user_id", type: "uuid" })
409
- ], DraftInvoiceEdit.prototype, "userId", 2);
409
+ ], BillingDraftInvoiceEdit.prototype, "userId", 2);
410
410
  __decorateClass([
411
411
  Property({ type: "text" })
412
- ], DraftInvoiceEdit.prototype, "action", 2);
412
+ ], BillingDraftInvoiceEdit.prototype, "action", 2);
413
413
  __decorateClass([
414
414
  Property({ name: "before_json", type: "jsonb", nullable: true })
415
- ], DraftInvoiceEdit.prototype, "beforeJson", 2);
415
+ ], BillingDraftInvoiceEdit.prototype, "beforeJson", 2);
416
416
  __decorateClass([
417
417
  Property({ name: "after_json", type: "jsonb", nullable: true })
418
- ], DraftInvoiceEdit.prototype, "afterJson", 2);
418
+ ], BillingDraftInvoiceEdit.prototype, "afterJson", 2);
419
419
  __decorateClass([
420
420
  Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
421
- ], DraftInvoiceEdit.prototype, "createdAt", 2);
421
+ ], BillingDraftInvoiceEdit.prototype, "createdAt", 2);
422
422
  __decorateClass([
423
423
  Property({ name: "deleted_at", type: Date, nullable: true })
424
- ], DraftInvoiceEdit.prototype, "deletedAt", 2);
425
- DraftInvoiceEdit = __decorateClass([
424
+ ], BillingDraftInvoiceEdit.prototype, "deletedAt", 2);
425
+ BillingDraftInvoiceEdit = __decorateClass([
426
426
  Entity({ tableName: "billing_draft_invoice_edits" }),
427
427
  Index({
428
428
  name: "billing_draft_invoice_edits_invoice_idx",
@@ -432,7 +432,7 @@ DraftInvoiceEdit = __decorateClass([
432
432
  name: "billing_draft_invoice_edits_user_idx",
433
433
  properties: ["organizationId", "tenantId", "userId", "createdAt"]
434
434
  })
435
- ], DraftInvoiceEdit);
435
+ ], BillingDraftInvoiceEdit);
436
436
  OptionalProps;
437
437
  let BillingAccountBalance = class {
438
438
  constructor() {
@@ -709,15 +709,15 @@ BillingStatement = __decorateClass([
709
709
  })
710
710
  ], BillingStatement);
711
711
  export {
712
- BillRun,
713
- BillRunOutcome,
714
712
  BillingAccount,
715
713
  BillingAccountBalance,
716
714
  BillingAccountTransaction,
717
715
  BillingAccountUsage,
716
+ BillingDraftInvoiceEdit,
718
717
  BillingItem,
718
+ BillingRun,
719
+ BillingRunOutcome,
719
720
  BillingStatement,
720
- BillingTopup,
721
- DraftInvoiceEdit
721
+ BillingTopup
722
722
  };
723
723
  //# sourceMappingURL=entities.js.map