@dainamite/billing 0.17.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.
- package/CHANGELOG.md +658 -0
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/modules/billing/acl.js +20 -0
- package/dist/modules/billing/acl.js.map +7 -0
- package/dist/modules/billing/api/accounts/route.js +182 -0
- package/dist/modules/billing/api/accounts/route.js.map +7 -0
- package/dist/modules/billing/api/export/account/[id]/route.js +170 -0
- package/dist/modules/billing/api/export/account/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/[id]/route.js +85 -0
- package/dist/modules/billing/api/invoices/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/add-line/route.js +69 -0
- package/dist/modules/billing/api/invoices/add-line/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/edit-line/route.js +78 -0
- package/dist/modules/billing/api/invoices/edit-line/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/post/route.js +74 -0
- package/dist/modules/billing/api/invoices/post/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/remove-line/route.js +69 -0
- package/dist/modules/billing/api/invoices/remove-line/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/route.js +109 -0
- package/dist/modules/billing/api/invoices/route.js.map +7 -0
- package/dist/modules/billing/api/items/bulk/route.js +74 -0
- package/dist/modules/billing/api/items/bulk/route.js.map +7 -0
- package/dist/modules/billing/api/items/route.js +224 -0
- package/dist/modules/billing/api/items/route.js.map +7 -0
- package/dist/modules/billing/api/openapi.js +25 -0
- package/dist/modules/billing/api/openapi.js.map +7 -0
- package/dist/modules/billing/api/run-outcomes/route.js +122 -0
- package/dist/modules/billing/api/run-outcomes/route.js.map +7 -0
- package/dist/modules/billing/api/runs/retry-failed/route.js +70 -0
- package/dist/modules/billing/api/runs/retry-failed/route.js.map +7 -0
- package/dist/modules/billing/api/runs/route.js +143 -0
- package/dist/modules/billing/api/runs/route.js.map +7 -0
- package/dist/modules/billing/api/test-invoices/wipe/route.js +65 -0
- package/dist/modules/billing/api/test-invoices/wipe/route.js.map +7 -0
- package/dist/modules/billing/api/usage/route.js +138 -0
- package/dist/modules/billing/api/usage/route.js.map +7 -0
- package/dist/modules/billing/backend/billing/accounts/[id]/page.js +402 -0
- package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/accounts/[id]/page.meta.js +25 -0
- package/dist/modules/billing/backend/billing/accounts/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/accounts/create/page.js +82 -0
- package/dist/modules/billing/backend/billing/accounts/create/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/accounts/create/page.meta.js +25 -0
- package/dist/modules/billing/backend/billing/accounts/create/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/accounts/page.js +173 -0
- package/dist/modules/billing/backend/billing/accounts/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/accounts/page.meta.js +18 -0
- package/dist/modules/billing/backend/billing/accounts/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/invoices/[id]/page.js +500 -0
- package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/invoices/[id]/page.meta.js +25 -0
- package/dist/modules/billing/backend/billing/invoices/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/invoices/page.js +187 -0
- package/dist/modules/billing/backend/billing/invoices/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/invoices/page.meta.js +18 -0
- package/dist/modules/billing/backend/billing/invoices/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/items/[id]/page.js +265 -0
- package/dist/modules/billing/backend/billing/items/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/items/[id]/page.meta.js +25 -0
- package/dist/modules/billing/backend/billing/items/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/items/create/page.js +90 -0
- package/dist/modules/billing/backend/billing/items/create/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/items/create/page.meta.js +25 -0
- package/dist/modules/billing/backend/billing/items/create/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/items/page.js +208 -0
- package/dist/modules/billing/backend/billing/items/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/items/page.meta.js +18 -0
- package/dist/modules/billing/backend/billing/items/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/runs/[id]/page.js +293 -0
- package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/runs/[id]/page.meta.js +21 -0
- package/dist/modules/billing/backend/billing/runs/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/runs/page.js +341 -0
- package/dist/modules/billing/backend/billing/runs/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/runs/page.meta.js +18 -0
- package/dist/modules/billing/backend/billing/runs/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/usage/create/page.js +100 -0
- package/dist/modules/billing/backend/billing/usage/create/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/usage/create/page.meta.js +25 -0
- package/dist/modules/billing/backend/billing/usage/create/page.meta.js.map +7 -0
- package/dist/modules/billing/ce.js +56 -0
- package/dist/modules/billing/ce.js.map +7 -0
- package/dist/modules/billing/cli.js +67 -0
- package/dist/modules/billing/cli.js.map +7 -0
- package/dist/modules/billing/commands/accounts.js +157 -0
- package/dist/modules/billing/commands/accounts.js.map +7 -0
- package/dist/modules/billing/commands/index.js +6 -0
- package/dist/modules/billing/commands/index.js.map +7 -0
- package/dist/modules/billing/commands/invoices.js +401 -0
- package/dist/modules/billing/commands/invoices.js.map +7 -0
- package/dist/modules/billing/commands/items.js +317 -0
- package/dist/modules/billing/commands/items.js.map +7 -0
- package/dist/modules/billing/commands/runs.js +123 -0
- package/dist/modules/billing/commands/runs.js.map +7 -0
- package/dist/modules/billing/commands/usage.js +88 -0
- package/dist/modules/billing/commands/usage.js.map +7 -0
- package/dist/modules/billing/components/AccountForm.js +420 -0
- package/dist/modules/billing/components/AccountForm.js.map +7 -0
- package/dist/modules/billing/components/DetailFields.js +37 -0
- package/dist/modules/billing/components/DetailFields.js.map +7 -0
- package/dist/modules/billing/components/ItemForm.js +440 -0
- package/dist/modules/billing/components/ItemForm.js.map +7 -0
- package/dist/modules/billing/components/LineFormDialog.js +195 -0
- package/dist/modules/billing/components/LineFormDialog.js.map +7 -0
- package/dist/modules/billing/components/UsageForm.js +262 -0
- package/dist/modules/billing/components/UsageForm.js.map +7 -0
- package/dist/modules/billing/data/entities.js +433 -0
- package/dist/modules/billing/data/entities.js.map +7 -0
- package/dist/modules/billing/data/entityIds.js +12 -0
- package/dist/modules/billing/data/entityIds.js.map +7 -0
- package/dist/modules/billing/data/validators.js +396 -0
- package/dist/modules/billing/data/validators.js.map +7 -0
- package/dist/modules/billing/di.js +6 -0
- package/dist/modules/billing/di.js.map +7 -0
- package/dist/modules/billing/events.js +35 -0
- package/dist/modules/billing/events.js.map +7 -0
- package/dist/modules/billing/i18n/en.json +336 -0
- package/dist/modules/billing/i18n/pl.json +336 -0
- package/dist/modules/billing/index.js +17 -0
- package/dist/modules/billing/index.js.map +7 -0
- package/dist/modules/billing/lib/billPeriod.js +101 -0
- package/dist/modules/billing/lib/billPeriod.js.map +7 -0
- package/dist/modules/billing/lib/billRunEngine.js +256 -0
- package/dist/modules/billing/lib/billRunEngine.js.map +7 -0
- package/dist/modules/billing/lib/billRunLabel.js +8 -0
- package/dist/modules/billing/lib/billRunLabel.js.map +7 -0
- package/dist/modules/billing/lib/idempotency.js +13 -0
- package/dist/modules/billing/lib/idempotency.js.map +7 -0
- package/dist/modules/billing/lib/invoiceStatus.js +38 -0
- package/dist/modules/billing/lib/invoiceStatus.js.map +7 -0
- package/dist/modules/billing/lib/invoiceWriter.js +153 -0
- package/dist/modules/billing/lib/invoiceWriter.js.map +7 -0
- package/dist/modules/billing/lib/itemSelector.js +77 -0
- package/dist/modules/billing/lib/itemSelector.js.map +7 -0
- package/dist/modules/billing/lib/seeds/demo.js +130 -0
- package/dist/modules/billing/lib/seeds/demo.js.map +7 -0
- package/dist/modules/billing/lib/tenantLock.js +32 -0
- package/dist/modules/billing/lib/tenantLock.js.map +7 -0
- package/dist/modules/billing/lib/usageRater.js +150 -0
- package/dist/modules/billing/lib/usageRater.js.map +7 -0
- package/dist/modules/billing/lib/usageRunner.js +82 -0
- package/dist/modules/billing/lib/usageRunner.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20260519000000_billing.js +144 -0
- package/dist/modules/billing/migrations/Migration20260519000000_billing.js.map +7 -0
- package/dist/modules/billing/setup.js +190 -0
- package/dist/modules/billing/setup.js.map +7 -0
- package/dist/modules/billing/subscribers/payment-captured-to-paid.js +47 -0
- package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +7 -0
- package/dist/modules/billing/translations.js +5 -0
- package/dist/modules/billing/translations.js.map +7 -0
- package/dist/modules/billing/workers/reap-stale-bill-runs.js +31 -0
- package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +7 -0
- package/package.json +148 -0
- package/src/modules/billing/__integration__/TC-BILL-001.spec.ts +116 -0
- package/src/modules/billing/__integration__/TC-BILL-002.spec.ts +169 -0
- package/src/modules/billing/__integration__/TC-BILL-003.spec.ts +28 -0
- package/src/modules/billing/__integration__/meta.ts +4 -0
- package/src/modules/billing/__tests__/commands/invoices.test.ts +464 -0
- package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +284 -0
- package/src/modules/billing/__tests__/lib/billPeriod.test.ts +131 -0
- package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +561 -0
- package/src/modules/billing/__tests__/lib/idempotency.test.ts +81 -0
- package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +97 -0
- package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +119 -0
- package/src/modules/billing/__tests__/lib/itemSelector.test.ts +192 -0
- package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +86 -0
- package/src/modules/billing/__tests__/lib/tenantLock.test.ts +126 -0
- package/src/modules/billing/__tests__/lib/usageRater.test.ts +190 -0
- package/src/modules/billing/__tests__/package-isolation.test.ts +223 -0
- package/src/modules/billing/__tests__/setup.test.ts +107 -0
- package/src/modules/billing/__tests__/validators.test.ts +594 -0
- package/src/modules/billing/acl.ts +16 -0
- package/src/modules/billing/api/accounts/route.ts +198 -0
- package/src/modules/billing/api/export/account/[id]/route.ts +233 -0
- package/src/modules/billing/api/invoices/[id]/route.ts +143 -0
- package/src/modules/billing/api/invoices/add-line/route.ts +74 -0
- package/src/modules/billing/api/invoices/edit-line/route.ts +97 -0
- package/src/modules/billing/api/invoices/post/route.ts +97 -0
- package/src/modules/billing/api/invoices/remove-line/route.ts +75 -0
- package/src/modules/billing/api/invoices/route.ts +167 -0
- package/src/modules/billing/api/items/bulk/route.ts +92 -0
- package/src/modules/billing/api/items/route.ts +255 -0
- package/src/modules/billing/api/openapi.ts +33 -0
- package/src/modules/billing/api/run-outcomes/route.ts +149 -0
- package/src/modules/billing/api/runs/retry-failed/route.ts +88 -0
- package/src/modules/billing/api/runs/route.ts +170 -0
- package/src/modules/billing/api/test-invoices/wipe/route.ts +87 -0
- package/src/modules/billing/api/usage/route.ts +148 -0
- package/src/modules/billing/backend/billing/accounts/[id]/page.meta.ts +23 -0
- package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +574 -0
- package/src/modules/billing/backend/billing/accounts/create/page.meta.ts +23 -0
- package/src/modules/billing/backend/billing/accounts/create/page.tsx +101 -0
- package/src/modules/billing/backend/billing/accounts/page.meta.ts +16 -0
- package/src/modules/billing/backend/billing/accounts/page.tsx +222 -0
- package/src/modules/billing/backend/billing/invoices/[id]/page.meta.ts +23 -0
- package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +613 -0
- package/src/modules/billing/backend/billing/invoices/page.meta.ts +16 -0
- package/src/modules/billing/backend/billing/invoices/page.tsx +245 -0
- package/src/modules/billing/backend/billing/items/[id]/page.meta.ts +23 -0
- package/src/modules/billing/backend/billing/items/[id]/page.tsx +366 -0
- package/src/modules/billing/backend/billing/items/create/page.meta.ts +23 -0
- package/src/modules/billing/backend/billing/items/create/page.tsx +110 -0
- package/src/modules/billing/backend/billing/items/page.meta.ts +16 -0
- package/src/modules/billing/backend/billing/items/page.tsx +272 -0
- package/src/modules/billing/backend/billing/runs/[id]/page.meta.ts +19 -0
- package/src/modules/billing/backend/billing/runs/[id]/page.tsx +404 -0
- package/src/modules/billing/backend/billing/runs/page.meta.ts +16 -0
- package/src/modules/billing/backend/billing/runs/page.tsx +426 -0
- package/src/modules/billing/backend/billing/usage/create/page.meta.ts +23 -0
- package/src/modules/billing/backend/billing/usage/create/page.tsx +120 -0
- package/src/modules/billing/ce.ts +74 -0
- package/src/modules/billing/cli.ts +89 -0
- package/src/modules/billing/commands/accounts.ts +205 -0
- package/src/modules/billing/commands/index.ts +13 -0
- package/src/modules/billing/commands/invoices.ts +542 -0
- package/src/modules/billing/commands/items.ts +438 -0
- package/src/modules/billing/commands/runs.ts +166 -0
- package/src/modules/billing/commands/usage.ts +118 -0
- package/src/modules/billing/components/AccountForm.tsx +517 -0
- package/src/modules/billing/components/DetailFields.tsx +60 -0
- package/src/modules/billing/components/ItemForm.tsx +558 -0
- package/src/modules/billing/components/LineFormDialog.tsx +253 -0
- package/src/modules/billing/components/UsageForm.tsx +326 -0
- package/src/modules/billing/data/entities.ts +533 -0
- package/src/modules/billing/data/entityIds.ts +25 -0
- package/src/modules/billing/data/validators.ts +562 -0
- package/src/modules/billing/di.ts +14 -0
- package/src/modules/billing/events.ts +47 -0
- package/src/modules/billing/i18n/en.json +336 -0
- package/src/modules/billing/i18n/pl.json +336 -0
- package/src/modules/billing/index.ts +22 -0
- package/src/modules/billing/lib/billPeriod.ts +181 -0
- package/src/modules/billing/lib/billRunEngine.ts +416 -0
- package/src/modules/billing/lib/billRunLabel.ts +14 -0
- package/src/modules/billing/lib/idempotency.ts +46 -0
- package/src/modules/billing/lib/invoiceStatus.ts +61 -0
- package/src/modules/billing/lib/invoiceWriter.ts +260 -0
- package/src/modules/billing/lib/itemSelector.ts +154 -0
- package/src/modules/billing/lib/seeds/demo.ts +199 -0
- package/src/modules/billing/lib/tenantLock.ts +87 -0
- package/src/modules/billing/lib/usageRater.ts +250 -0
- package/src/modules/billing/lib/usageRunner.ts +189 -0
- package/src/modules/billing/migrations/Migration20260519000000_billing.ts +175 -0
- package/src/modules/billing/setup.ts +252 -0
- package/src/modules/billing/subscribers/payment-captured-to-paid.ts +89 -0
- package/src/modules/billing/translations.ts +9 -0
- package/src/modules/billing/workers/reap-stale-bill-runs.ts +57 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { SalesInvoice } from "@open-mercato/core/modules/sales/data/entities";
|
|
6
|
+
import { billingEntityIds } from "../../../data/entityIds.js";
|
|
7
|
+
import { billingInvoiceAddLineSchema } from "../../../data/validators.js";
|
|
8
|
+
import { createBillingCrudOpenApi } from "../../openapi.js";
|
|
9
|
+
const routeMetadata = {
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
|
|
11
|
+
};
|
|
12
|
+
const metadata = routeMetadata;
|
|
13
|
+
const addLineResponseSchema = z.object({
|
|
14
|
+
invoiceId: z.string().uuid(),
|
|
15
|
+
invoiceLineId: z.string().uuid(),
|
|
16
|
+
auditId: z.string().uuid()
|
|
17
|
+
});
|
|
18
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
19
|
+
const crud = makeCrudRoute({
|
|
20
|
+
metadata: routeMetadata,
|
|
21
|
+
orm: {
|
|
22
|
+
entity: SalesInvoice,
|
|
23
|
+
idField: "id",
|
|
24
|
+
orgField: "organizationId",
|
|
25
|
+
tenantField: "tenantId",
|
|
26
|
+
softDeleteField: "deletedAt"
|
|
27
|
+
},
|
|
28
|
+
list: {
|
|
29
|
+
schema: z.object({}).passthrough(),
|
|
30
|
+
entityId: billingEntityIds.draftInvoiceEdit,
|
|
31
|
+
fields: ["id"],
|
|
32
|
+
buildFilters: async () => ({})
|
|
33
|
+
},
|
|
34
|
+
actions: {
|
|
35
|
+
create: {
|
|
36
|
+
commandId: "billing.invoices.add_draft_line",
|
|
37
|
+
schema: rawBodySchema,
|
|
38
|
+
mapInput: async ({ raw, ctx }) => {
|
|
39
|
+
const { translate } = await resolveTranslations();
|
|
40
|
+
const scoped = withScopedPayload(raw ?? {}, ctx, translate);
|
|
41
|
+
return billingInvoiceAddLineSchema.parse(scoped);
|
|
42
|
+
},
|
|
43
|
+
response: ({ result }) => ({
|
|
44
|
+
invoiceId: result?.invoiceId ?? null,
|
|
45
|
+
invoiceLineId: result?.invoiceLineId ?? null,
|
|
46
|
+
auditId: result?.auditId ?? null
|
|
47
|
+
}),
|
|
48
|
+
status: 201
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const POST = crud.POST;
|
|
53
|
+
const openApi = createBillingCrudOpenApi({
|
|
54
|
+
resourceName: "BillingInvoiceLineAdd",
|
|
55
|
+
pluralName: "BillingInvoiceLineAdds",
|
|
56
|
+
querySchema: z.object({}).passthrough(),
|
|
57
|
+
listResponseSchema: z.object({ items: z.array(z.unknown()) }),
|
|
58
|
+
create: {
|
|
59
|
+
schema: billingInvoiceAddLineSchema,
|
|
60
|
+
responseSchema: addLineResponseSchema,
|
|
61
|
+
description: "Appends a new line to a draft invoice (e.g. operator adds a one-off adjustment). Writes a `DraftInvoiceEdit` audit row (action=`line_added`, `before_json=null`). Recomputes invoice totals. Refuses non-draft invoices."
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
export {
|
|
65
|
+
POST,
|
|
66
|
+
metadata,
|
|
67
|
+
openApi
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/invoices/add-line/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoiceAddLineSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst addLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.add_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceAddLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 201,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineAdd',\n pluralName: 'BillingInvoiceLineAdds',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceAddLineSchema,\n responseSchema: addLineResponseSchema,\n description:\n 'Appends a new line to a draft invoice (e.g. operator adds a ' +\n 'one-off adjustment). Writes a `DraftInvoiceEdit` audit row ' +\n '(action=`line_added`, `before_json=null`). Recomputes invoice ' +\n 'totals. Refuses non-draft invoices.',\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AAEzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,4BAA4B,MAAM,MAAM;AAAA,MACjD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAIJ;AACF,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { SalesInvoice } from "@open-mercato/core/modules/sales/data/entities";
|
|
6
|
+
import { billingEntityIds } from "../../../data/entityIds.js";
|
|
7
|
+
import {
|
|
8
|
+
billingInvoiceAddLineSchema,
|
|
9
|
+
billingInvoiceEditLineSchema,
|
|
10
|
+
billingInvoiceRemoveLineSchema
|
|
11
|
+
} from "../../../data/validators.js";
|
|
12
|
+
import { createBillingCrudOpenApi } from "../../openapi.js";
|
|
13
|
+
const routeMetadata = {
|
|
14
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
|
|
15
|
+
};
|
|
16
|
+
const metadata = routeMetadata;
|
|
17
|
+
const editLineResponseSchema = z.object({
|
|
18
|
+
invoiceId: z.string().uuid(),
|
|
19
|
+
invoiceLineId: z.string().uuid(),
|
|
20
|
+
auditId: z.string().uuid()
|
|
21
|
+
});
|
|
22
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
23
|
+
const crud = makeCrudRoute({
|
|
24
|
+
metadata: routeMetadata,
|
|
25
|
+
orm: {
|
|
26
|
+
entity: SalesInvoice,
|
|
27
|
+
idField: "id",
|
|
28
|
+
orgField: "organizationId",
|
|
29
|
+
tenantField: "tenantId",
|
|
30
|
+
softDeleteField: "deletedAt"
|
|
31
|
+
},
|
|
32
|
+
list: {
|
|
33
|
+
schema: z.object({}).passthrough(),
|
|
34
|
+
entityId: billingEntityIds.draftInvoiceEdit,
|
|
35
|
+
fields: ["id"],
|
|
36
|
+
buildFilters: async () => ({})
|
|
37
|
+
},
|
|
38
|
+
actions: {
|
|
39
|
+
create: {
|
|
40
|
+
commandId: "billing.invoices.edit_draft_line",
|
|
41
|
+
schema: rawBodySchema,
|
|
42
|
+
mapInput: async ({ raw, ctx }) => {
|
|
43
|
+
const { translate } = await resolveTranslations();
|
|
44
|
+
const scoped = withScopedPayload(raw ?? {}, ctx, translate);
|
|
45
|
+
return billingInvoiceEditLineSchema.parse(scoped);
|
|
46
|
+
},
|
|
47
|
+
response: ({ result }) => ({
|
|
48
|
+
invoiceId: result?.invoiceId ?? null,
|
|
49
|
+
invoiceLineId: result?.invoiceLineId ?? null,
|
|
50
|
+
auditId: result?.auditId ?? null
|
|
51
|
+
}),
|
|
52
|
+
status: 200
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const POST = crud.POST;
|
|
57
|
+
const openApi = createBillingCrudOpenApi({
|
|
58
|
+
resourceName: "BillingInvoiceLineEdit",
|
|
59
|
+
pluralName: "BillingInvoiceLineEdits",
|
|
60
|
+
querySchema: z.object({}).passthrough(),
|
|
61
|
+
listResponseSchema: z.object({ items: z.array(z.unknown()) }),
|
|
62
|
+
create: {
|
|
63
|
+
schema: billingInvoiceEditLineSchema,
|
|
64
|
+
responseSchema: editLineResponseSchema,
|
|
65
|
+
description: "Edits one line of a draft invoice (description, quantity, unit_price, or explicit `totalNetAmount` override). Writes a `DraftInvoiceEdit` audit row with before+after snapshots and recomputes invoice totals. Refuses non-draft invoices with HTTP 409 + `billing.invoice.not_draft`."
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const _phaseSchemaRefs = {
|
|
69
|
+
add: billingInvoiceAddLineSchema,
|
|
70
|
+
remove: billingInvoiceRemoveLineSchema
|
|
71
|
+
};
|
|
72
|
+
export {
|
|
73
|
+
POST,
|
|
74
|
+
_phaseSchemaRefs,
|
|
75
|
+
metadata,
|
|
76
|
+
openApi
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/invoices/edit-line/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport {\n billingInvoiceAddLineSchema,\n billingInvoiceEditLineSchema,\n billingInvoiceRemoveLineSchema,\n} from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/invoices/edit-line` \u2014 edits one line of a draft\n * invoice. Refuses non-drafts (409 / `billing.invoice.not_draft`).\n * Writes a `DraftInvoiceEdit` audit row per spec. Recomputes invoice\n * totals from the surviving lines after the edit.\n *\n * Operator surface: route is gated by `billing.invoice.edit_draft`.\n * Future Phase 4c admin UI calls into the same command via this route.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst editLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.edit_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceEditLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineEdit',\n pluralName: 'BillingInvoiceLineEdits',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceEditLineSchema,\n responseSchema: editLineResponseSchema,\n description:\n 'Edits one line of a draft invoice (description, quantity, ' +\n \"unit_price, or explicit `totalNetAmount` override). Writes a \" +\n '`DraftInvoiceEdit` audit row with before+after snapshots and ' +\n 'recomputes invoice totals. Refuses non-draft invoices with ' +\n 'HTTP 409 + `billing.invoice.not_draft`.',\n },\n})\n\n// Re-export the add/remove route schemas so the OpenAPI bundle keeps\n// them discoverable from a single import surface \u2014 the dedicated\n// routes (sibling folders) own the actual handlers.\nexport const _phaseSchemaRefs = {\n add: billingInvoiceAddLineSchema,\n remove: billingInvoiceRemoveLineSchema,\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gCAAgC;AAYzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,6BAA6B,MAAM,MAAM;AAAA,MAClD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAKJ;AACF,CAAC;AAKM,MAAM,mBAAmB;AAAA,EAC9B,KAAK;AAAA,EACL,QAAQ;AACV;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { SalesInvoice } from "@open-mercato/core/modules/sales/data/entities";
|
|
6
|
+
import { billingEntityIds } from "../../../data/entityIds.js";
|
|
7
|
+
import { billingInvoicePostSchema } from "../../../data/validators.js";
|
|
8
|
+
import { createBillingCrudOpenApi } from "../../openapi.js";
|
|
9
|
+
const routeMetadata = {
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.post"] }
|
|
11
|
+
};
|
|
12
|
+
const metadata = routeMetadata;
|
|
13
|
+
const postResponseSchema = z.object({
|
|
14
|
+
invoiceId: z.string().uuid(),
|
|
15
|
+
invoiceNumber: z.string(),
|
|
16
|
+
status: z.literal("posted"),
|
|
17
|
+
lineCount: z.number().int().nonnegative()
|
|
18
|
+
});
|
|
19
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
20
|
+
const crud = makeCrudRoute({
|
|
21
|
+
metadata: routeMetadata,
|
|
22
|
+
orm: {
|
|
23
|
+
entity: SalesInvoice,
|
|
24
|
+
idField: "id",
|
|
25
|
+
orgField: "organizationId",
|
|
26
|
+
tenantField: "tenantId",
|
|
27
|
+
softDeleteField: "deletedAt"
|
|
28
|
+
},
|
|
29
|
+
list: {
|
|
30
|
+
schema: z.object({}).passthrough(),
|
|
31
|
+
// `entityId` is technically a core/sales concept, but the factory
|
|
32
|
+
// only needs it as an identifier — using a billing-namespaced one
|
|
33
|
+
// keeps the registry tidy (no surprise list endpoints).
|
|
34
|
+
entityId: billingEntityIds.draftInvoiceEdit,
|
|
35
|
+
fields: ["id"],
|
|
36
|
+
buildFilters: async () => ({})
|
|
37
|
+
},
|
|
38
|
+
actions: {
|
|
39
|
+
create: {
|
|
40
|
+
commandId: "billing.invoices.post",
|
|
41
|
+
schema: rawBodySchema,
|
|
42
|
+
mapInput: async ({ raw, ctx }) => {
|
|
43
|
+
const { translate } = await resolveTranslations();
|
|
44
|
+
const scoped = withScopedPayload(raw ?? {}, ctx, translate);
|
|
45
|
+
return billingInvoicePostSchema.parse(scoped);
|
|
46
|
+
},
|
|
47
|
+
response: ({ result }) => ({
|
|
48
|
+
invoiceId: result?.invoiceId ?? null,
|
|
49
|
+
invoiceNumber: result?.invoiceNumber ?? null,
|
|
50
|
+
status: result?.status ?? null,
|
|
51
|
+
lineCount: result?.lineCount ?? 0
|
|
52
|
+
}),
|
|
53
|
+
status: 200
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const POST = crud.POST;
|
|
58
|
+
const openApi = createBillingCrudOpenApi({
|
|
59
|
+
resourceName: "BillingInvoicePost",
|
|
60
|
+
pluralName: "BillingInvoicePosts",
|
|
61
|
+
querySchema: z.object({}).passthrough(),
|
|
62
|
+
listResponseSchema: z.object({ items: z.array(z.unknown()) }),
|
|
63
|
+
create: {
|
|
64
|
+
schema: billingInvoicePostSchema,
|
|
65
|
+
responseSchema: postResponseSchema,
|
|
66
|
+
description: "Flips a billing-drafted `core/sales` invoice from `draft` to `posted`. Refuses non-draft invoices (409 / `billing.invoice.not_draft`) and test-mode invoices (409 / `billing.invoice.test_run`). Fires `billing.invoice.posted` + one `billing.invoice.line_posted` per line. The invoice number is NOT re-assigned (set at draft create \u2014 see Phase 2 deviation)."
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
export {
|
|
70
|
+
POST,
|
|
71
|
+
metadata,
|
|
72
|
+
openApi
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/invoices/post/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoicePostSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/invoices/post`\n *\n * Operator action: flip a billing-drafted invoice from `draft` \u2192\n * `posted`. The command refuses non-draft inputs (409 with\n * `billing.invoice.not_draft`) and test-mode invoices (409 with\n * `billing.invoice.test_run`). The invoice number is NOT re-assigned\n * (it was set at draft create \u2014 see Phase 2 deviation note in the\n * spec).\n *\n * Implemented as a create-only `makeCrudRoute` so it inherits the same\n * RBAC + OpenAPI plumbing as the rest of the billing surface. The\n * `entity` is `SalesInvoice` only because the factory requires one;\n * the GET half is never exposed on this URL.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.post'] },\n}\n\nexport const metadata = routeMetadata\n\nconst postResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceNumber: z.string(),\n status: z.literal('posted'),\n lineCount: z.number().int().nonnegative(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n // `entityId` is technically a core/sales concept, but the factory\n // only needs it as an identifier \u2014 using a billing-namespaced one\n // keeps the registry tidy (no surprise list endpoints).\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.post',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoicePostSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceNumber: result?.invoiceNumber ?? null,\n status: result?.status ?? null,\n lineCount: result?.lineCount ?? 0,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoicePost',\n pluralName: 'BillingInvoicePosts',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoicePostSchema,\n responseSchema: postResponseSchema,\n description:\n 'Flips a billing-drafted `core/sales` invoice from `draft` to ' +\n '`posted`. Refuses non-draft invoices (409 / ' +\n '`billing.invoice.not_draft`) and test-mode invoices (409 / ' +\n '`billing.invoice.test_run`). Fires `billing.invoice.posted` + ' +\n 'one `billing.invoice.line_posted` per line. The invoice number ' +\n 'is NOT re-assigned (set at draft create \u2014 see Phase 2 deviation).',\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,gCAAgC;AACzC,SAAS,gCAAgC;AAkBzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACvE;AAEO,MAAM,WAAW;AAExB,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO;AAAA,EACxB,QAAQ,EAAE,QAAQ,QAAQ;AAAA,EAC1B,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAC1C,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA;AAAA;AAAA;AAAA,IAIjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,yBAAyB,MAAM,MAAM;AAAA,MAC9C;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,QAAQ,QAAQ,UAAU;AAAA,QAC1B,WAAW,QAAQ,aAAa;AAAA,MAClC;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAMJ;AACF,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { SalesInvoice } from "@open-mercato/core/modules/sales/data/entities";
|
|
6
|
+
import { billingEntityIds } from "../../../data/entityIds.js";
|
|
7
|
+
import { billingInvoiceRemoveLineSchema } from "../../../data/validators.js";
|
|
8
|
+
import { createBillingCrudOpenApi } from "../../openapi.js";
|
|
9
|
+
const routeMetadata = {
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
|
|
11
|
+
};
|
|
12
|
+
const metadata = routeMetadata;
|
|
13
|
+
const removeLineResponseSchema = z.object({
|
|
14
|
+
invoiceId: z.string().uuid(),
|
|
15
|
+
invoiceLineId: z.string().uuid(),
|
|
16
|
+
auditId: z.string().uuid()
|
|
17
|
+
});
|
|
18
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
19
|
+
const crud = makeCrudRoute({
|
|
20
|
+
metadata: routeMetadata,
|
|
21
|
+
orm: {
|
|
22
|
+
entity: SalesInvoice,
|
|
23
|
+
idField: "id",
|
|
24
|
+
orgField: "organizationId",
|
|
25
|
+
tenantField: "tenantId",
|
|
26
|
+
softDeleteField: "deletedAt"
|
|
27
|
+
},
|
|
28
|
+
list: {
|
|
29
|
+
schema: z.object({}).passthrough(),
|
|
30
|
+
entityId: billingEntityIds.draftInvoiceEdit,
|
|
31
|
+
fields: ["id"],
|
|
32
|
+
buildFilters: async () => ({})
|
|
33
|
+
},
|
|
34
|
+
actions: {
|
|
35
|
+
create: {
|
|
36
|
+
commandId: "billing.invoices.remove_draft_line",
|
|
37
|
+
schema: rawBodySchema,
|
|
38
|
+
mapInput: async ({ raw, ctx }) => {
|
|
39
|
+
const { translate } = await resolveTranslations();
|
|
40
|
+
const scoped = withScopedPayload(raw ?? {}, ctx, translate);
|
|
41
|
+
return billingInvoiceRemoveLineSchema.parse(scoped);
|
|
42
|
+
},
|
|
43
|
+
response: ({ result }) => ({
|
|
44
|
+
invoiceId: result?.invoiceId ?? null,
|
|
45
|
+
invoiceLineId: result?.invoiceLineId ?? null,
|
|
46
|
+
auditId: result?.auditId ?? null
|
|
47
|
+
}),
|
|
48
|
+
status: 200
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const POST = crud.POST;
|
|
53
|
+
const openApi = createBillingCrudOpenApi({
|
|
54
|
+
resourceName: "BillingInvoiceLineRemove",
|
|
55
|
+
pluralName: "BillingInvoiceLineRemoves",
|
|
56
|
+
querySchema: z.object({}).passthrough(),
|
|
57
|
+
listResponseSchema: z.object({ items: z.array(z.unknown()) }),
|
|
58
|
+
create: {
|
|
59
|
+
schema: billingInvoiceRemoveLineSchema,
|
|
60
|
+
responseSchema: removeLineResponseSchema,
|
|
61
|
+
description: "Removes one line from a draft invoice (hard delete \u2014 the line never existed as far as future readers are concerned). Writes a `DraftInvoiceEdit` audit row (action=`line_removed`, `after_json=null`) carrying the before-snapshot for forensics. Recomputes invoice totals. Refuses non-draft invoices."
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
export {
|
|
65
|
+
POST,
|
|
66
|
+
metadata,
|
|
67
|
+
openApi
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/invoices/remove-line/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoiceRemoveLineSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst removeLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.remove_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceRemoveLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineRemove',\n pluralName: 'BillingInvoiceLineRemoves',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceRemoveLineSchema,\n responseSchema: removeLineResponseSchema,\n description:\n 'Removes one line from a draft invoice (hard delete \u2014 the line ' +\n 'never existed as far as future readers are concerned). Writes ' +\n 'a `DraftInvoiceEdit` audit row (action=`line_removed`, ' +\n '`after_json=null`) carrying the before-snapshot for forensics. ' +\n 'Recomputes invoice totals. Refuses non-draft invoices.',\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,sCAAsC;AAC/C,SAAS,gCAAgC;AAEzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,+BAA+B,MAAM,MAAM;AAAA,MACpD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAKJ;AACF,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
const metadata = {
|
|
6
|
+
GET: { requireAuth: true, requireFeatures: ["billing.invoice.view"] }
|
|
7
|
+
};
|
|
8
|
+
const querySchema = z.object({
|
|
9
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
10
|
+
pageSize: z.coerce.number().int().min(1).max(100).default(50),
|
|
11
|
+
status: z.enum(["draft", "posted", "paid", "void"]).optional(),
|
|
12
|
+
billRunId: z.string().uuid().optional(),
|
|
13
|
+
billAccountId: z.string().uuid().optional(),
|
|
14
|
+
testRun: z.coerce.boolean().optional(),
|
|
15
|
+
search: z.string().optional()
|
|
16
|
+
}).passthrough();
|
|
17
|
+
async function GET(req) {
|
|
18
|
+
try {
|
|
19
|
+
const container = await createRequestContainer();
|
|
20
|
+
const auth = await getAuthFromRequest(req);
|
|
21
|
+
if (!auth || !auth.tenantId) {
|
|
22
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
23
|
+
}
|
|
24
|
+
const organizationId = auth.orgId ?? null;
|
|
25
|
+
if (!organizationId) {
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: "Organization scope required" },
|
|
28
|
+
{ status: 403 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const url = new URL(req.url);
|
|
32
|
+
const query = querySchema.parse(
|
|
33
|
+
Object.fromEntries(url.searchParams.entries())
|
|
34
|
+
);
|
|
35
|
+
const em = container.resolve("em").fork();
|
|
36
|
+
const whereClauses = [
|
|
37
|
+
"tenant_id = ?",
|
|
38
|
+
"organization_id = ?",
|
|
39
|
+
"deleted_at IS NULL",
|
|
40
|
+
"jsonb_exists(metadata, 'bill_run_id')"
|
|
41
|
+
];
|
|
42
|
+
const params = [auth.tenantId, organizationId];
|
|
43
|
+
if (query.status) {
|
|
44
|
+
whereClauses.push("status = ?");
|
|
45
|
+
params.push(query.status);
|
|
46
|
+
}
|
|
47
|
+
if (query.billRunId) {
|
|
48
|
+
whereClauses.push("metadata->>'bill_run_id' = ?");
|
|
49
|
+
params.push(query.billRunId);
|
|
50
|
+
}
|
|
51
|
+
if (query.billAccountId) {
|
|
52
|
+
whereClauses.push("metadata->>'bill_account_id' = ?");
|
|
53
|
+
params.push(query.billAccountId);
|
|
54
|
+
}
|
|
55
|
+
if (typeof query.testRun === "boolean") {
|
|
56
|
+
if (query.testRun) {
|
|
57
|
+
whereClauses.push("metadata->>'test_run' = 'true'");
|
|
58
|
+
} else {
|
|
59
|
+
whereClauses.push("(metadata->>'test_run' IS NULL OR metadata->>'test_run' != 'true')");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (typeof query.search === "string" && query.search.trim()) {
|
|
63
|
+
whereClauses.push("invoice_number ILIKE ?");
|
|
64
|
+
params.push(`%${query.search.trim().replace(/[%_]/g, (m) => `\\${m}`)}%`);
|
|
65
|
+
}
|
|
66
|
+
const whereSql = whereClauses.join(" AND ");
|
|
67
|
+
const countRows = await em.execute(
|
|
68
|
+
`SELECT COUNT(*)::int AS total FROM sales_invoices WHERE ${whereSql}`,
|
|
69
|
+
params
|
|
70
|
+
);
|
|
71
|
+
const total = countRows?.[0]?.total ?? 0;
|
|
72
|
+
const offset = (query.page - 1) * query.pageSize;
|
|
73
|
+
const itemRows = await em.execute(
|
|
74
|
+
`SELECT id, invoice_number, status, currency_code, issue_date, due_date,
|
|
75
|
+
subtotal_net_amount, grand_total_net_amount, grand_total_gross_amount,
|
|
76
|
+
paid_total_amount, outstanding_amount, metadata, created_at, updated_at
|
|
77
|
+
FROM sales_invoices
|
|
78
|
+
WHERE ${whereSql}
|
|
79
|
+
ORDER BY created_at DESC
|
|
80
|
+
LIMIT ? OFFSET ?`,
|
|
81
|
+
[...params, query.pageSize, offset]
|
|
82
|
+
);
|
|
83
|
+
return NextResponse.json({
|
|
84
|
+
items: itemRows,
|
|
85
|
+
total,
|
|
86
|
+
page: query.page,
|
|
87
|
+
pageSize: query.pageSize,
|
|
88
|
+
totalPages: total === 0 ? 1 : Math.ceil(total / query.pageSize)
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error("[billing/invoices.GET]", err);
|
|
92
|
+
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const openApi = {
|
|
96
|
+
methods: {
|
|
97
|
+
GET: {
|
|
98
|
+
tags: ["Billing"],
|
|
99
|
+
summary: "List billing-managed invoices",
|
|
100
|
+
description: "Returns a paginated list of `core/sales` invoices that the billing engine emitted (matched via `metadata->>'bill_run_id'` presence). Supports status / billRunId / billAccountId / testRun / invoice_number search filters."
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export {
|
|
105
|
+
GET,
|
|
106
|
+
metadata,
|
|
107
|
+
openApi
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/billing/api/invoices/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\n\n/**\n * `GET /api/billing/invoices`\n *\n * Lists `core/sales` invoices that the billing engine emitted (i.e.\n * carry `metadata.bill_run_id`). Hand-rolled rather than wrapped via\n * `makeCrudRoute` because the filter predicate is a JSONB\n * `metadata->>'bill_run_id' IS NOT NULL` check that doesn't map\n * cleanly onto the factory's `where`-shaped filter API, and the\n * entity itself is owned by `core/sales` \u2014 keeping the surface\n * read-only and hand-rolled avoids surprising the cross-module\n * reviewer.\n *\n * Filters:\n * - `status` \u2014 draft / posted / paid / void\n * - `billRunId` \u2014 exact match on `metadata->>'bill_run_id'`\n * - `billAccountId` \u2014 exact match on `metadata->>'bill_account_id'`\n * - `testRun=true|false` \u2014 flips the `metadata->>'test_run'` filter\n * - `search` \u2014 substring on invoice_number\n *\n * Sort: started_at DESC (most recent first). Pagination: standard\n * page / pageSize (capped at 100).\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.invoice.view'] },\n}\n\nconst querySchema = z\n .object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(50),\n status: z.enum(['draft', 'posted', 'paid', 'void']).optional(),\n billRunId: z.string().uuid().optional(),\n billAccountId: z.string().uuid().optional(),\n testRun: z.coerce.boolean().optional(),\n search: z.string().optional(),\n })\n .passthrough()\n\ntype BillingInvoiceRow = {\n id: string\n invoice_number: string\n status: string | null\n currency_code: string\n issue_date: string | null\n due_date: string | null\n subtotal_net_amount: string\n grand_total_net_amount: string\n grand_total_gross_amount: string\n paid_total_amount: string\n outstanding_amount: string\n metadata: Record<string, unknown> | null\n created_at: string\n updated_at: string\n}\n\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = (auth as { orgId?: string }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json(\n { error: 'Organization scope required' },\n { status: 403 },\n )\n }\n\n const url = new URL(req.url)\n const query = querySchema.parse(\n Object.fromEntries(url.searchParams.entries()),\n )\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n // Build the predicate. `jsonb_exists(metadata, 'bill_run_id')` is\n // the function form of the JSONB `?` \"key exists\" operator \u2014 the\n // operator form cannot be used here because `em.execute` treats a\n // literal `?` as a positional parameter placeholder. Keeps us\n // narrow to billing-managed invoices, never bleeding into invoices\n // written by other modules.\n const whereClauses: string[] = [\n 'tenant_id = ?',\n 'organization_id = ?',\n 'deleted_at IS NULL',\n \"jsonb_exists(metadata, 'bill_run_id')\",\n ]\n const params: unknown[] = [auth.tenantId, organizationId]\n\n if (query.status) {\n whereClauses.push('status = ?')\n params.push(query.status)\n }\n if (query.billRunId) {\n whereClauses.push(\"metadata->>'bill_run_id' = ?\")\n params.push(query.billRunId)\n }\n if (query.billAccountId) {\n whereClauses.push(\"metadata->>'bill_account_id' = ?\")\n params.push(query.billAccountId)\n }\n if (typeof query.testRun === 'boolean') {\n if (query.testRun) {\n whereClauses.push(\"metadata->>'test_run' = 'true'\")\n } else {\n whereClauses.push(\"(metadata->>'test_run' IS NULL OR metadata->>'test_run' != 'true')\")\n }\n }\n if (typeof query.search === 'string' && query.search.trim()) {\n whereClauses.push('invoice_number ILIKE ?')\n params.push(`%${query.search.trim().replace(/[%_]/g, (m) => `\\\\${m}`)}%`)\n }\n\n const whereSql = whereClauses.join(' AND ')\n const countRows = (await em.execute(\n `SELECT COUNT(*)::int AS total FROM sales_invoices WHERE ${whereSql}`,\n params,\n )) as unknown as Array<{ total: number }>\n const total = countRows?.[0]?.total ?? 0\n\n const offset = (query.page - 1) * query.pageSize\n const itemRows = (await em.execute(\n `SELECT id, invoice_number, status, currency_code, issue_date, due_date,\n subtotal_net_amount, grand_total_net_amount, grand_total_gross_amount,\n paid_total_amount, outstanding_amount, metadata, created_at, updated_at\n FROM sales_invoices\n WHERE ${whereSql}\n ORDER BY created_at DESC\n LIMIT ? OFFSET ?`,\n [...params, query.pageSize, offset],\n )) as unknown as BillingInvoiceRow[]\n\n return NextResponse.json({\n items: itemRows,\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: total === 0 ? 1 : Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n console.error('[billing/invoices.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n GET: {\n tags: ['Billing'],\n summary: 'List billing-managed invoices',\n description:\n 'Returns a paginated list of `core/sales` invoices that the ' +\n 'billing engine emitted (matched via `metadata->>\\'bill_run_id\\'` ' +\n 'presence). Supports status / billRunId / billAccountId / testRun ' +\n '/ invoice_number search filters.',\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AAyB5B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAEA,MAAM,cAAc,EACjB,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EAC5D,QAAQ,EAAE,KAAK,CAAC,SAAS,UAAU,QAAQ,MAAM,CAAC,EAAE,SAAS;AAAA,EAC7D,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACtC,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC1C,SAAS,EAAE,OAAO,QAAQ,EAAE,SAAS;AAAA,EACrC,QAAQ,EAAE,OAAO,EAAE,SAAS;AAC9B,CAAC,EACA,YAAY;AAmBf,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,UAAM,iBAAkB,KAA4B,SAAS;AAC7D,QAAI,CAAC,gBAAgB;AACnB,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,8BAA8B;AAAA,QACvC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,YAAY;AAAA,MACxB,OAAO,YAAY,IAAI,aAAa,QAAQ,CAAC;AAAA,IAC/C;AAEA,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAQ3D,UAAM,eAAyB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,SAAoB,CAAC,KAAK,UAAU,cAAc;AAExD,QAAI,MAAM,QAAQ;AAChB,mBAAa,KAAK,YAAY;AAC9B,aAAO,KAAK,MAAM,MAAM;AAAA,IAC1B;AACA,QAAI,MAAM,WAAW;AACnB,mBAAa,KAAK,8BAA8B;AAChD,aAAO,KAAK,MAAM,SAAS;AAAA,IAC7B;AACA,QAAI,MAAM,eAAe;AACvB,mBAAa,KAAK,kCAAkC;AACpD,aAAO,KAAK,MAAM,aAAa;AAAA,IACjC;AACA,QAAI,OAAO,MAAM,YAAY,WAAW;AACtC,UAAI,MAAM,SAAS;AACjB,qBAAa,KAAK,gCAAgC;AAAA,MACpD,OAAO;AACL,qBAAa,KAAK,oEAAoE;AAAA,MACxF;AAAA,IACF;AACA,QAAI,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,KAAK,GAAG;AAC3D,mBAAa,KAAK,wBAAwB;AAC1C,aAAO,KAAK,IAAI,MAAM,OAAO,KAAK,EAAE,QAAQ,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC,GAAG;AAAA,IAC1E;AAEA,UAAM,WAAW,aAAa,KAAK,OAAO;AAC1C,UAAM,YAAa,MAAM,GAAG;AAAA,MAC1B,2DAA2D,QAAQ;AAAA,MACnE;AAAA,IACF;AACA,UAAM,QAAQ,YAAY,CAAC,GAAG,SAAS;AAEvC,UAAM,UAAU,MAAM,OAAO,KAAK,MAAM;AACxC,UAAM,WAAY,MAAM,GAAG;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA,eAIS,QAAQ;AAAA;AAAA;AAAA,MAGjB,CAAC,GAAG,QAAQ,MAAM,UAAU,MAAM;AAAA,IACpC;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,OAAO;AAAA,MACP;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,YAAY,UAAU,IAAI,IAAI,KAAK,KAAK,QAAQ,MAAM,QAAQ;AAAA,IAChE,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,0BAA0B,GAAG;AAC3C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,KAAK;AAAA,MACH,MAAM,CAAC,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,aACE;AAAA,IAIJ;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { BillingItem } from "../../../data/entities.js";
|
|
6
|
+
import { billingEntityIds } from "../../../data/entityIds.js";
|
|
7
|
+
import { billingItemBulkCreateSchema } from "../../../data/validators.js";
|
|
8
|
+
import { createBillingCrudOpenApi } from "../../openapi.js";
|
|
9
|
+
const routeMetadata = {
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ["billing.item.manage"] }
|
|
11
|
+
};
|
|
12
|
+
const metadata = routeMetadata;
|
|
13
|
+
const bulkResultEntrySchema = z.object({
|
|
14
|
+
sourceRef: z.string().nullable(),
|
|
15
|
+
id: z.string().uuid(),
|
|
16
|
+
deduplicated: z.boolean()
|
|
17
|
+
});
|
|
18
|
+
const bulkResponseSchema = z.object({
|
|
19
|
+
created: z.number().int().nonnegative(),
|
|
20
|
+
deduplicated: z.number().int().nonnegative(),
|
|
21
|
+
items: z.array(bulkResultEntrySchema)
|
|
22
|
+
});
|
|
23
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
24
|
+
const crud = makeCrudRoute({
|
|
25
|
+
metadata: routeMetadata,
|
|
26
|
+
orm: {
|
|
27
|
+
entity: BillingItem,
|
|
28
|
+
idField: "id",
|
|
29
|
+
orgField: "organizationId",
|
|
30
|
+
tenantField: "tenantId",
|
|
31
|
+
softDeleteField: "deletedAt"
|
|
32
|
+
},
|
|
33
|
+
list: {
|
|
34
|
+
schema: z.object({}).passthrough(),
|
|
35
|
+
entityId: billingEntityIds.item,
|
|
36
|
+
fields: ["id"],
|
|
37
|
+
buildFilters: async () => ({})
|
|
38
|
+
},
|
|
39
|
+
actions: {
|
|
40
|
+
create: {
|
|
41
|
+
commandId: "billing.items.bulk_create",
|
|
42
|
+
schema: rawBodySchema,
|
|
43
|
+
mapInput: async ({ raw, ctx }) => {
|
|
44
|
+
const { translate } = await resolveTranslations();
|
|
45
|
+
const scoped = withScopedPayload(raw ?? {}, ctx, translate);
|
|
46
|
+
return billingItemBulkCreateSchema.parse(scoped);
|
|
47
|
+
},
|
|
48
|
+
response: ({ result }) => ({
|
|
49
|
+
created: result?.created ?? 0,
|
|
50
|
+
deduplicated: result?.deduplicated ?? 0,
|
|
51
|
+
items: result?.items ?? []
|
|
52
|
+
}),
|
|
53
|
+
status: 200
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const POST = crud.POST;
|
|
58
|
+
const openApi = createBillingCrudOpenApi({
|
|
59
|
+
resourceName: "BillingItemBulkCreate",
|
|
60
|
+
pluralName: "BillingItemBulkCreates",
|
|
61
|
+
querySchema: z.object({}).passthrough(),
|
|
62
|
+
listResponseSchema: z.object({ items: z.array(z.unknown()) }),
|
|
63
|
+
create: {
|
|
64
|
+
schema: billingItemBulkCreateSchema,
|
|
65
|
+
responseSchema: bulkResponseSchema,
|
|
66
|
+
description: "Batch-creates up to 500 Billing Items under one scope envelope. Idempotent per `sourceRef` (one batched existence check). Returns `{ created, deduplicated, items: [{ sourceRef, id, deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported in the body, never an error."
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
export {
|
|
70
|
+
POST,
|
|
71
|
+
metadata,
|
|
72
|
+
openApi
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/items/bulk/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { BillingItem } from '../../../data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingItemBulkCreateSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/items/bulk` \u2014 batch-create Billing Items.\n *\n * One scope envelope, up to 500 item payloads. Idempotent per\n * `source_ref` with a *single* batched existence check, then a\n * single flush. The CPQ connector's subscription-activated /\n * -amended / -renewed subscribers are the primary callers \u2014 bulk\n * collapses what used to be one `billing.items.create` round-trip\n * per charge.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst bulkResultEntrySchema = z.object({\n sourceRef: z.string().nullable(),\n id: z.string().uuid(),\n deduplicated: z.boolean(),\n})\n\nconst bulkResponseSchema = z.object({\n created: z.number().int().nonnegative(),\n deduplicated: z.number().int().nonnegative(),\n items: z.array(bulkResultEntrySchema),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingItem,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.item,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.items.bulk_create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemBulkCreateSchema.parse(scoped)\n },\n response: ({ result }) => ({\n created: result?.created ?? 0,\n deduplicated: result?.deduplicated ?? 0,\n items: result?.items ?? [],\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingItemBulkCreate',\n pluralName: 'BillingItemBulkCreates',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingItemBulkCreateSchema,\n responseSchema: bulkResponseSchema,\n description:\n 'Batch-creates up to 500 Billing Items under one scope envelope. ' +\n 'Idempotent per `sourceRef` (one batched existence check). ' +\n 'Returns `{ created, deduplicated, items: [{ sourceRef, id, ' +\n 'deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported ' +\n 'in the body, never an error.',\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AAazC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACtE;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,cAAc,EAAE,QAAQ;AAC1B,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,OAAO,EAAE,MAAM,qBAAqB;AACtC,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,4BAA4B,MAAM,MAAM;AAAA,MACjD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,SAAS,QAAQ,WAAW;AAAA,QAC5B,cAAc,QAAQ,gBAAgB;AAAA,QACtC,OAAO,QAAQ,SAAS,CAAC;AAAA,MAC3B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAKJ;AACF,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|