@porulle/core 0.7.0 → 0.8.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/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +5 -0
- package/dist/config/types.d.ts +17 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/generated/plugin-manifest.d.ts +3 -0
- package/dist/generated/plugin-manifest.d.ts.map +1 -1
- package/dist/generated/plugin-manifest.js +1 -0
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +16 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +12 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts +12 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/staff.js +172 -0
- package/dist/interfaces/rest/routes/analytics.d.ts +5 -0
- package/dist/interfaces/rest/routes/analytics.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/analytics.js +24 -0
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +39 -2
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +17 -1
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +17 -0
- package/dist/interfaces/rest/routes/documents.d.ts +10 -0
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/documents.js +40 -0
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +98 -1
- package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/pricing.js +36 -1
- package/dist/interfaces/rest/routes/settings.d.ts +5 -0
- package/dist/interfaces/rest/routes/settings.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/settings.js +50 -0
- package/dist/interfaces/rest/routes/shipping.d.ts +5 -0
- package/dist/interfaces/rest/routes/shipping.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/shipping.js +68 -0
- package/dist/interfaces/rest/routes/tax.d.ts +5 -0
- package/dist/interfaces/rest/routes/tax.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/tax.js +68 -0
- package/dist/interfaces/rest/schemas/admin-staff.d.ts +899 -0
- package/dist/interfaces/rest/schemas/admin-staff.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-staff.js +98 -0
- package/dist/interfaces/rest/schemas/analytics.d.ts +252 -0
- package/dist/interfaces/rest/schemas/analytics.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/analytics.js +33 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +656 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/carts.js +38 -0
- package/dist/interfaces/rest/schemas/catalog.d.ts +1218 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +48 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts +2 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/checkout.js +4 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/documents.d.ts +509 -0
- package/dist/interfaces/rest/schemas/documents.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/documents.js +65 -0
- package/dist/interfaces/rest/schemas/orders.d.ts +3110 -43
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.js +233 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts +405 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.js +70 -0
- package/dist/interfaces/rest/schemas/responses.d.ts +156 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +17 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/settings.d.ts +397 -0
- package/dist/interfaces/rest/schemas/settings.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/settings.js +79 -0
- package/dist/interfaces/rest/schemas/shipping.d.ts +1093 -0
- package/dist/interfaces/rest/schemas/shipping.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/shipping.js +146 -0
- package/dist/interfaces/rest/schemas/tax.d.ts +1074 -0
- package/dist/interfaces/rest/schemas/tax.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/tax.js +132 -0
- package/dist/kernel/database/schema.d.ts +4 -0
- package/dist/kernel/database/schema.d.ts.map +1 -1
- package/dist/kernel/database/schema.js +8 -0
- package/dist/kernel/hooks/types.d.ts +1 -1
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts +23 -0
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +17 -2
- package/dist/modules/analytics/module.d.ts.map +1 -1
- package/dist/modules/analytics/module.js +6 -0
- package/dist/modules/analytics/reports.d.ts +58 -0
- package/dist/modules/analytics/reports.d.ts.map +1 -0
- package/dist/modules/analytics/reports.js +249 -0
- package/dist/modules/analytics/service.d.ts +10 -0
- package/dist/modules/analytics/service.d.ts.map +1 -1
- package/dist/modules/analytics/service.js +14 -0
- package/dist/modules/cart/repository/index.d.ts +7 -0
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +25 -1
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +2 -0
- package/dist/modules/cart/schemas.d.ts +1 -0
- package/dist/modules/cart/schemas.d.ts.map +1 -1
- package/dist/modules/cart/schemas.js +1 -0
- package/dist/modules/cart/service.d.ts +32 -0
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +58 -0
- package/dist/modules/catalog/entity-service.d.ts +21 -0
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +132 -3
- package/dist/modules/catalog/schema.d.ts +34 -0
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +5 -0
- package/dist/modules/catalog/schemas.d.ts +3 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +3 -0
- package/dist/modules/catalog/service.d.ts +36 -0
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +6 -0
- package/dist/modules/documents/module.d.ts +7 -0
- package/dist/modules/documents/module.d.ts.map +1 -0
- package/dist/modules/documents/module.js +12 -0
- package/dist/modules/documents/pdf.d.ts +25 -0
- package/dist/modules/documents/pdf.d.ts.map +1 -0
- package/dist/modules/documents/pdf.js +80 -0
- package/dist/modules/documents/render.d.ts +32 -0
- package/dist/modules/documents/render.d.ts.map +1 -0
- package/dist/modules/documents/render.js +190 -0
- package/dist/modules/documents/repository/index.d.ts +25 -0
- package/dist/modules/documents/repository/index.d.ts.map +1 -0
- package/dist/modules/documents/repository/index.js +47 -0
- package/dist/modules/documents/schema.d.ts +214 -0
- package/dist/modules/documents/schema.d.ts.map +1 -0
- package/dist/modules/documents/schema.js +42 -0
- package/dist/modules/documents/service.d.ts +51 -0
- package/dist/modules/documents/service.d.ts.map +1 -0
- package/dist/modules/documents/service.js +119 -0
- package/dist/modules/fulfillment/service.d.ts +18 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +85 -1
- package/dist/modules/orders/repository/index.d.ts +18 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +86 -1
- package/dist/modules/orders/schema.d.ts +374 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +52 -1
- package/dist/modules/orders/service.d.ts +86 -1
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +447 -20
- package/dist/modules/pricing/repository/index.d.ts +5 -0
- package/dist/modules/pricing/repository/index.d.ts.map +1 -1
- package/dist/modules/pricing/repository/index.js +19 -0
- package/dist/modules/pricing/service.d.ts +18 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +38 -0
- package/dist/modules/settings/module.d.ts +6 -0
- package/dist/modules/settings/module.d.ts.map +1 -0
- package/dist/modules/settings/module.js +11 -0
- package/dist/modules/settings/repository/index.d.ts +16 -0
- package/dist/modules/settings/repository/index.d.ts.map +1 -0
- package/dist/modules/settings/repository/index.js +41 -0
- package/dist/modules/settings/schema.d.ts +121 -0
- package/dist/modules/settings/schema.d.ts.map +1 -0
- package/dist/modules/settings/schema.js +24 -0
- package/dist/modules/settings/service.d.ts +26 -0
- package/dist/modules/settings/service.d.ts.map +1 -0
- package/dist/modules/settings/service.js +51 -0
- package/dist/modules/shipping/calculator.d.ts +5 -0
- package/dist/modules/shipping/calculator.d.ts.map +1 -1
- package/dist/modules/shipping/calculator.js +10 -0
- package/dist/modules/shipping/module.d.ts +5 -1
- package/dist/modules/shipping/module.d.ts.map +1 -1
- package/dist/modules/shipping/module.js +4 -1
- package/dist/modules/shipping/repository/index.d.ts +27 -2
- package/dist/modules/shipping/repository/index.d.ts.map +1 -1
- package/dist/modules/shipping/repository/index.js +105 -1
- package/dist/modules/shipping/schema.d.ts +419 -0
- package/dist/modules/shipping/schema.d.ts.map +1 -0
- package/dist/modules/shipping/schema.js +56 -0
- package/dist/modules/shipping/service.d.ts +54 -0
- package/dist/modules/shipping/service.d.ts.map +1 -1
- package/dist/modules/shipping/service.js +161 -2
- package/dist/modules/tax/adapter.d.ts +13 -0
- package/dist/modules/tax/adapter.d.ts.map +1 -1
- package/dist/modules/tax/module.d.ts +5 -1
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +4 -1
- package/dist/modules/tax/repository/index.d.ts +38 -2
- package/dist/modules/tax/repository/index.d.ts.map +1 -1
- package/dist/modules/tax/repository/index.js +138 -1
- package/dist/modules/tax/schema.d.ts +355 -0
- package/dist/modules/tax/schema.d.ts.map +1 -0
- package/dist/modules/tax/schema.js +55 -0
- package/dist/modules/tax/service.d.ts +60 -1
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +227 -2
- package/dist/runtime/kernel-modules.d.ts +15 -2
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/kernel-modules.js +4 -0
- package/dist/runtime/kernel-types.d.ts +5 -1
- package/dist/runtime/kernel-types.d.ts.map +1 -1
- package/dist/runtime/kernel-types.js +2 -0
- package/dist/runtime/server.js +1 -1
- package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
- package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
- package/dist/test-utils/create-plugin-test-app.js +15 -3
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +6 -0
- package/package.json +3 -3
- package/src/auth/setup.ts +1 -0
- package/src/config/defaults.ts +5 -0
- package/src/config/types.ts +17 -1
- package/src/generated/plugin-capabilities.d.ts +1 -0
- package/src/generated/plugin-manifest.ts +1 -0
- package/src/generated/plugin-repositories.d.ts +1 -0
- package/src/hooks/checkout.ts +25 -3
- package/src/index.ts +1 -0
- package/src/interfaces/rest/index.ts +12 -0
- package/src/interfaces/rest/routes/admin/staff.ts +218 -0
- package/src/interfaces/rest/routes/analytics.ts +33 -0
- package/src/interfaces/rest/routes/carts.ts +52 -2
- package/src/interfaces/rest/routes/catalog.ts +34 -0
- package/src/interfaces/rest/routes/checkout.ts +22 -0
- package/src/interfaces/rest/routes/documents.ts +61 -0
- package/src/interfaces/rest/routes/orders.ts +137 -1
- package/src/interfaces/rest/routes/pricing.ts +44 -1
- package/src/interfaces/rest/routes/settings.ts +61 -0
- package/src/interfaces/rest/routes/shipping.ts +92 -0
- package/src/interfaces/rest/routes/tax.ts +90 -0
- package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
- package/src/interfaces/rest/schemas/analytics.ts +39 -0
- package/src/interfaces/rest/schemas/carts.ts +40 -0
- package/src/interfaces/rest/schemas/catalog.ts +53 -0
- package/src/interfaces/rest/schemas/checkout.ts +4 -0
- package/src/interfaces/rest/schemas/documents.ts +72 -0
- package/src/interfaces/rest/schemas/orders.ts +257 -0
- package/src/interfaces/rest/schemas/pricing.ts +75 -0
- package/src/interfaces/rest/schemas/settings.ts +93 -0
- package/src/interfaces/rest/schemas/shipping.ts +164 -0
- package/src/interfaces/rest/schemas/tax.ts +148 -0
- package/src/kernel/database/schema.ts +12 -0
- package/src/kernel/hooks/types.ts +1 -0
- package/src/kernel/plugin/manifest.ts +38 -2
- package/src/modules/analytics/module.ts +10 -0
- package/src/modules/analytics/reports.ts +311 -0
- package/src/modules/analytics/service.ts +23 -0
- package/src/modules/cart/repository/index.ts +30 -1
- package/src/modules/cart/schema.ts +2 -0
- package/src/modules/cart/schemas.ts +1 -0
- package/src/modules/cart/service.ts +101 -0
- package/src/modules/catalog/entity-service.ts +168 -3
- package/src/modules/catalog/schema.ts +5 -0
- package/src/modules/catalog/schemas.ts +3 -0
- package/src/modules/catalog/service.ts +30 -0
- package/src/modules/documents/module.ts +19 -0
- package/src/modules/documents/pdf.ts +101 -0
- package/src/modules/documents/render.ts +231 -0
- package/src/modules/documents/repository/index.ts +68 -0
- package/src/modules/documents/schema.ts +44 -0
- package/src/modules/documents/service.ts +176 -0
- package/src/modules/fulfillment/service.ts +139 -1
- package/src/modules/orders/repository/index.ts +127 -1
- package/src/modules/orders/schema.ts +54 -1
- package/src/modules/orders/service.ts +654 -22
- package/src/modules/pricing/repository/index.ts +34 -0
- package/src/modules/pricing/service.ts +71 -0
- package/src/modules/settings/module.ts +18 -0
- package/src/modules/settings/repository/index.ts +59 -0
- package/src/modules/settings/schema.ts +25 -0
- package/src/modules/settings/service.ts +82 -0
- package/src/modules/shipping/calculator.ts +27 -0
- package/src/modules/shipping/module.ts +6 -2
- package/src/modules/shipping/repository/index.ts +136 -2
- package/src/modules/shipping/schema.ts +58 -0
- package/src/modules/shipping/service.ts +260 -1
- package/src/modules/tax/adapter.ts +9 -0
- package/src/modules/tax/module.ts +6 -2
- package/src/modules/tax/repository/index.ts +169 -2
- package/src/modules/tax/schema.ts +57 -0
- package/src/modules/tax/service.ts +313 -2
- package/src/runtime/kernel-modules.ts +4 -0
- package/src/runtime/kernel-types.ts +6 -0
- package/src/runtime/server.ts +1 -1
- package/src/test-utils/create-plugin-test-app.ts +17 -4
- package/src/test-utils/rest-api-test-utils.ts +6 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tax.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/rest/schemas/tax.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAe,MAAM,mBAAmB,CAAC;AAGnD,eAAO,MAAM,uBAAuB;;;;;;;;iBAQF,CAAC;AAEnC,eAAO,MAAM,uBAAuB;;;;;;;;iBAQF,CAAC;AAQnC,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAY7B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAS5B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAa7B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAU7B,CAAC;AAIH,eAAO,MAAM,wBAAwB;;;;;iBAKF,CAAC;AAEpC,eAAO,MAAM,wBAAwB;;;;;iBAKF,CAAC;AAIpC,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAU9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAS9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAa9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAU9B,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z, createRoute } from "@hono/zod-openapi";
|
|
2
|
+
import { errorResponses } from "./shared.js";
|
|
3
|
+
export const CreateTaxRateBodySchema = z.object({
|
|
4
|
+
name: z.string().min(1).openapi({ example: "NY sales tax" }),
|
|
5
|
+
country: z.string().min(1).openapi({ example: "US", description: "ISO 3166-1 alpha-2; \"*\" matches any country" }),
|
|
6
|
+
state: z.string().min(1).optional().openapi({ example: "NY" }),
|
|
7
|
+
rateBps: z.number().int().nonnegative().openapi({ example: 500, description: "Basis points: 500 = 5%" }),
|
|
8
|
+
appliesToShipping: z.boolean().optional(),
|
|
9
|
+
priority: z.number().int().optional(),
|
|
10
|
+
isActive: z.boolean().optional(),
|
|
11
|
+
}).openapi("CreateTaxRateRequest");
|
|
12
|
+
export const UpdateTaxRateBodySchema = z.object({
|
|
13
|
+
name: z.string().min(1).optional(),
|
|
14
|
+
country: z.string().min(1).optional(),
|
|
15
|
+
state: z.string().min(1).nullable().optional(),
|
|
16
|
+
rateBps: z.number().int().nonnegative().optional(),
|
|
17
|
+
appliesToShipping: z.boolean().optional(),
|
|
18
|
+
priority: z.number().int().optional(),
|
|
19
|
+
isActive: z.boolean().optional(),
|
|
20
|
+
}).openapi("UpdateTaxRateRequest");
|
|
21
|
+
const IdParam = z.object({
|
|
22
|
+
id: z.uuid().openapi({ example: "550e8400-e29b-41d4-a716-446655440000" }),
|
|
23
|
+
});
|
|
24
|
+
const DataResponse = z.object({ data: z.any() }).openapi("TaxConfigResponse");
|
|
25
|
+
export const createTaxRateRoute = createRoute({
|
|
26
|
+
method: "post",
|
|
27
|
+
path: "/rates",
|
|
28
|
+
tags: ["Tax"],
|
|
29
|
+
summary: "Create a tax rate",
|
|
30
|
+
request: {
|
|
31
|
+
body: { content: { "application/json": { schema: CreateTaxRateBodySchema } }, required: true },
|
|
32
|
+
},
|
|
33
|
+
responses: {
|
|
34
|
+
201: { content: { "application/json": { schema: DataResponse } }, description: "Tax rate created." },
|
|
35
|
+
...errorResponses,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
export const listTaxRatesRoute = createRoute({
|
|
39
|
+
method: "get",
|
|
40
|
+
path: "/rates",
|
|
41
|
+
tags: ["Tax"],
|
|
42
|
+
summary: "List tax rates",
|
|
43
|
+
responses: {
|
|
44
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Tax rates" },
|
|
45
|
+
...errorResponses,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
export const updateTaxRateRoute = createRoute({
|
|
49
|
+
method: "patch",
|
|
50
|
+
path: "/rates/{id}",
|
|
51
|
+
tags: ["Tax"],
|
|
52
|
+
summary: "Update a tax rate",
|
|
53
|
+
request: {
|
|
54
|
+
params: IdParam,
|
|
55
|
+
body: { content: { "application/json": { schema: UpdateTaxRateBodySchema } }, required: true },
|
|
56
|
+
},
|
|
57
|
+
responses: {
|
|
58
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Tax rate updated." },
|
|
59
|
+
...errorResponses,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
export const deleteTaxRateRoute = createRoute({
|
|
63
|
+
method: "delete",
|
|
64
|
+
path: "/rates/{id}",
|
|
65
|
+
tags: ["Tax"],
|
|
66
|
+
summary: "Delete a tax rate",
|
|
67
|
+
request: { params: IdParam },
|
|
68
|
+
responses: {
|
|
69
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Tax rate deleted." },
|
|
70
|
+
...errorResponses,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
// ── Product tax classes (issue #57) ─────────────────────────────────────────
|
|
74
|
+
export const CreateTaxClassBodySchema = z.object({
|
|
75
|
+
name: z.string().min(1).regex(/^[a-z][a-z0-9_-]*$/, "lowercase slug (e.g. standard, zero)"),
|
|
76
|
+
rateBps: z.number().int().nonnegative().openapi({ example: 1800, description: "Basis points: 1800 = 18%" }),
|
|
77
|
+
isDefault: z.boolean().optional().openapi({ description: "Lines with no taxClass use the default class." }),
|
|
78
|
+
isActive: z.boolean().optional(),
|
|
79
|
+
}).openapi("CreateTaxClassRequest");
|
|
80
|
+
export const UpdateTaxClassBodySchema = z.object({
|
|
81
|
+
name: z.string().min(1).regex(/^[a-z][a-z0-9_-]*$/).optional(),
|
|
82
|
+
rateBps: z.number().int().nonnegative().optional(),
|
|
83
|
+
isDefault: z.boolean().optional(),
|
|
84
|
+
isActive: z.boolean().optional(),
|
|
85
|
+
}).openapi("UpdateTaxClassRequest");
|
|
86
|
+
const ClassIdParam = z.object({ id: z.uuid() });
|
|
87
|
+
export const createTaxClassRoute = createRoute({
|
|
88
|
+
method: "post",
|
|
89
|
+
path: "/classes",
|
|
90
|
+
tags: ["Tax"],
|
|
91
|
+
summary: "Create a product tax class",
|
|
92
|
+
request: { body: { content: { "application/json": { schema: CreateTaxClassBodySchema } }, required: true } },
|
|
93
|
+
responses: {
|
|
94
|
+
201: { content: { "application/json": { schema: DataResponse } }, description: "Tax class created." },
|
|
95
|
+
...errorResponses,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
export const listTaxClassesRoute = createRoute({
|
|
99
|
+
method: "get",
|
|
100
|
+
path: "/classes",
|
|
101
|
+
tags: ["Tax"],
|
|
102
|
+
summary: "List product tax classes",
|
|
103
|
+
responses: {
|
|
104
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Tax classes." },
|
|
105
|
+
...errorResponses,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
export const updateTaxClassRoute = createRoute({
|
|
109
|
+
method: "patch",
|
|
110
|
+
path: "/classes/{id}",
|
|
111
|
+
tags: ["Tax"],
|
|
112
|
+
summary: "Update a product tax class",
|
|
113
|
+
request: {
|
|
114
|
+
params: ClassIdParam,
|
|
115
|
+
body: { content: { "application/json": { schema: UpdateTaxClassBodySchema } }, required: true },
|
|
116
|
+
},
|
|
117
|
+
responses: {
|
|
118
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Tax class updated." },
|
|
119
|
+
...errorResponses,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
export const deleteTaxClassRoute = createRoute({
|
|
123
|
+
method: "delete",
|
|
124
|
+
path: "/classes/{id}",
|
|
125
|
+
tags: ["Tax"],
|
|
126
|
+
summary: "Delete a product tax class",
|
|
127
|
+
request: { params: ClassIdParam },
|
|
128
|
+
responses: {
|
|
129
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Tax class deleted." },
|
|
130
|
+
...errorResponses,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
@@ -20,6 +20,10 @@ export * from "../../modules/promotions/schema.js";
|
|
|
20
20
|
export * from "../../modules/media/schema.js";
|
|
21
21
|
export * from "../../modules/webhooks/schema.js";
|
|
22
22
|
export * from "../../modules/fulfillment/schema.js";
|
|
23
|
+
export * from "../../modules/shipping/schema.js";
|
|
24
|
+
export * from "../../modules/tax/schema.js";
|
|
25
|
+
export * from "../../modules/settings/schema.js";
|
|
26
|
+
export * from "../../modules/documents/schema.js";
|
|
23
27
|
export * from "../jobs/schema.js";
|
|
24
28
|
export * from "../compensation/schema.js";
|
|
25
29
|
export * from "../../modules/audit/schema.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/kernel/database/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,2BAA2B,CAAC;AAG1C,cAAc,iCAAiC,CAAC;AAGhD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,8BAA8B,CAAC;AAG7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAGlD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,iCAAiC,CAAC;AAGhD,cAAc,oCAAoC,CAAC;AAGnD,cAAc,+BAA+B,CAAC;AAG9C,cAAc,kCAAkC,CAAC;AAGjD,cAAc,qCAAqC,CAAC;AAGpD,cAAc,mBAAmB,CAAC;AAGlC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,+BAA+B,CAAC"}
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/kernel/database/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,2BAA2B,CAAC;AAG1C,cAAc,iCAAiC,CAAC;AAGhD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,8BAA8B,CAAC;AAG7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAGlD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,iCAAiC,CAAC;AAGhD,cAAc,oCAAoC,CAAC;AAGnD,cAAc,+BAA+B,CAAC;AAG9C,cAAc,kCAAkC,CAAC;AAGjD,cAAc,qCAAqC,CAAC;AAGpD,cAAc,kCAAkC,CAAC;AAGjD,cAAc,6BAA6B,CAAC;AAG5C,cAAc,kCAAkC,CAAC;AAGjD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,mBAAmB,CAAC;AAGlC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,+BAA+B,CAAC"}
|
|
@@ -31,6 +31,14 @@ export * from "../../modules/media/schema.js";
|
|
|
31
31
|
export * from "../../modules/webhooks/schema.js";
|
|
32
32
|
// Fulfillment module
|
|
33
33
|
export * from "../../modules/fulfillment/schema.js";
|
|
34
|
+
// Shipping module (runtime zones & rates)
|
|
35
|
+
export * from "../../modules/shipping/schema.js";
|
|
36
|
+
// Tax module (runtime tax rates)
|
|
37
|
+
export * from "../../modules/tax/schema.js";
|
|
38
|
+
// Settings module (org-scoped runtime settings)
|
|
39
|
+
export * from "../../modules/settings/schema.js";
|
|
40
|
+
// Documents module (fiscal sequences + issued documents)
|
|
41
|
+
export * from "../../modules/documents/schema.js";
|
|
34
42
|
// Jobs (kernel)
|
|
35
43
|
export * from "../jobs/schema.js";
|
|
36
44
|
// Compensation failures (kernel)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Actor } from "../../auth/types.js";
|
|
2
2
|
import type { JobsAdapter } from "../jobs/adapter.js";
|
|
3
3
|
import type { PluginDb } from "../database/plugin-types.js";
|
|
4
|
-
export type HookOperation = "create" | "update" | "delete" | "read" | "list" | "statusChange" | "addItem" | "removeItem" | "custom";
|
|
4
|
+
export type HookOperation = "create" | "update" | "delete" | "read" | "list" | "statusChange" | "addItem" | "removeItem" | "recover" | "custom";
|
|
5
5
|
export interface Logger {
|
|
6
6
|
info(message: string, data?: unknown): void;
|
|
7
7
|
warn(message: string, data?: unknown): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/kernel/hooks/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,cAAc,GACd,SAAS,GACT,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB;;;OAGG;IACH,EAAE,EAAE,QAAQ,CAAC;CACd;AAED,MAAM,MAAM,UAAU,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,KAAK,CAAC;IACZ,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;AAE7B,MAAM,MAAM,SAAS,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE;IACpC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC;IACd,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/kernel/hooks/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,cAAc,GACd,SAAS,GACT,YAAY,GACZ,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB;;;OAGG;IACH,EAAE,EAAE,QAAQ,CAAC;CACd;AAED,MAAM,MAAM,UAAU,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,KAAK,CAAC;IACZ,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;AAE7B,MAAM,MAAM,SAAS,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE;IACpC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC;IACd,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC"}
|
|
@@ -41,6 +41,14 @@ export interface PluginContext {
|
|
|
41
41
|
transaction<T>(fn: (tx: PluginDb) => Promise<T>): Promise<T>;
|
|
42
42
|
};
|
|
43
43
|
logger: PluginLogger;
|
|
44
|
+
/**
|
|
45
|
+
* The Better Auth instance (issue #51) — lets plugins mint/revoke
|
|
46
|
+
* credentials (e.g. per-shift API keys from a POS PIN login). Present when
|
|
47
|
+
* routes are mounted by createServer; may be absent in bare-kernel setups,
|
|
48
|
+
* so plugins must degrade gracefully. Typed loosely to avoid coupling every
|
|
49
|
+
* plugin to Better Auth's generics — cast to the narrow surface you use.
|
|
50
|
+
*/
|
|
51
|
+
auth?: unknown;
|
|
44
52
|
}
|
|
45
53
|
export type { PluginPermission };
|
|
46
54
|
export interface CommercePluginManifest {
|
|
@@ -68,6 +76,21 @@ export interface CommercePluginManifest {
|
|
|
68
76
|
hooks?: () => PluginHookRegistration[];
|
|
69
77
|
routes?: (ctx: PluginContext) => PluginRouteRegistration[];
|
|
70
78
|
analyticsModels?: () => unknown[];
|
|
79
|
+
/**
|
|
80
|
+
* Named API-key scopes this plugin mints credentials under (issue #51),
|
|
81
|
+
* merged into `config.auth.apiKeyScopes`. User-defined scopes with the
|
|
82
|
+
* same name win.
|
|
83
|
+
*/
|
|
84
|
+
apiKeyScopes?: () => Record<string, {
|
|
85
|
+
prefix?: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
permissions?: Record<string, string[]>;
|
|
88
|
+
/** Expiry bounds in DAYS (fractions allowed — 1/24 = one hour). */
|
|
89
|
+
keyExpiration?: {
|
|
90
|
+
minExpiresIn?: number;
|
|
91
|
+
maxExpiresIn?: number;
|
|
92
|
+
};
|
|
93
|
+
}>;
|
|
71
94
|
}
|
|
72
95
|
export declare const _registeredPlugins: Set<string>;
|
|
73
96
|
/** @internal — called by defineConfig() before applying plugins */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/kernel/plugin/manifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAS5D,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAI/B,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAA;CAAE,GAC1E;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CAC1C;AAID,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE;QACR,0EAA0E;QAC1E,EAAE,EAAE,QAAQ,CAAC;QACb;;;WAGG;QACH,QAAQ,EAAE,QAAQ,CAAC;QACnB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KAC9D,CAAC;IACF,MAAM,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/kernel/plugin/manifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAS5D,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAI/B,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAA;CAAE,GAC1E;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CAC1C;AAID,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE;QACR,0EAA0E;QAC1E,EAAE,EAAE,QAAQ,CAAC;QACb;;;WAGG;QACH,QAAQ,EAAE,QAAQ,CAAC;QACnB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KAC9D,CAAC;IACF,MAAM,EAAE,YAAY,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAqDD,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,sBAAsB,EAAE,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,uBAAuB,EAAE,CAAC;IAC3D,eAAe,CAAC,EAAE,MAAM,OAAO,EAAE,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE;QAClC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACvC,mEAAmE;QACnE,aAAa,CAAC,EAAE;YAAE,YAAY,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAClE,CAAC,CAAC;CACJ;AAKD,eAAO,MAAM,kBAAkB,aAAoB,CAAC;AAEpD,mEAAmE;AACnE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,sBAAsB,GAC/B,cAAc,CAkKhB"}
|
|
@@ -83,6 +83,20 @@ export function defineCommercePlugin(manifest) {
|
|
|
83
83
|
pluginPermissions: [...(result.pluginPermissions ?? []), ...withPlugin],
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
+
// 0. API-key scopes — merge into auth.apiKeyScopes (user config wins)
|
|
87
|
+
if (manifest.apiKeyScopes) {
|
|
88
|
+
const scopes = manifest.apiKeyScopes();
|
|
89
|
+
result = {
|
|
90
|
+
...result,
|
|
91
|
+
auth: {
|
|
92
|
+
...(result.auth ?? {}),
|
|
93
|
+
apiKeyScopes: {
|
|
94
|
+
...scopes,
|
|
95
|
+
...(result.auth?.apiKeyScopes ?? {}),
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
86
100
|
// 1. Schema — push into customSchemas for kernel to register
|
|
87
101
|
if (manifest.schema) {
|
|
88
102
|
const schemas = manifest.schema();
|
|
@@ -108,8 +122,8 @@ export function defineCommercePlugin(manifest) {
|
|
|
108
122
|
const pluginRoutes = manifest.routes;
|
|
109
123
|
result = {
|
|
110
124
|
...result,
|
|
111
|
-
routes: (app, kernel) => {
|
|
112
|
-
existingRoutes?.(app, kernel);
|
|
125
|
+
routes: (app, kernel, auth) => {
|
|
126
|
+
existingRoutes?.(app, kernel, auth);
|
|
113
127
|
const k = kernel;
|
|
114
128
|
// Narrow DatabaseAdapter<unknown> → PluginContext.database once here.
|
|
115
129
|
// All plugin code receives typed PluginDb — no casts downstream.
|
|
@@ -118,6 +132,7 @@ export function defineCommercePlugin(manifest) {
|
|
|
118
132
|
services: k.services,
|
|
119
133
|
database: buildPluginContextDatabase(k.database.db, k.database.transaction, k.config, k.logger),
|
|
120
134
|
logger: k.logger,
|
|
135
|
+
...(auth !== undefined ? { auth } : {}),
|
|
121
136
|
});
|
|
122
137
|
for (const route of regs) {
|
|
123
138
|
// ── Error boundary: wrap handler with plugin context ──
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/module.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,eAAO,MAAM,eAAe,mHAyB1B,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineModule } from "../../kernel/module/index.js";
|
|
2
2
|
import { DrizzleAnalyticsAdapter } from "./drizzle-adapter.js";
|
|
3
3
|
import { BUILTIN_ANALYTICS_MODELS } from "./models.js";
|
|
4
|
+
import { RetailReportsEngine } from "./reports.js";
|
|
4
5
|
import { AnalyticsService } from "./service.js";
|
|
5
6
|
export const analyticsModule = defineModule({
|
|
6
7
|
id: "analytics",
|
|
@@ -10,9 +11,14 @@ export const analyticsModule = defineModule({
|
|
|
10
11
|
for (const model of BUILTIN_ANALYTICS_MODELS) {
|
|
11
12
|
adapter.registerModel(model);
|
|
12
13
|
}
|
|
14
|
+
// Settings is resolved lazily from the shared service container — the
|
|
15
|
+
// settings module may instantiate after analytics.
|
|
16
|
+
const services = deps.services;
|
|
17
|
+
const reports = new RetailReportsEngine(deps.db.db, (orgId, group) => services.settings.read(orgId, group));
|
|
13
18
|
return new AnalyticsService({
|
|
14
19
|
adapter,
|
|
15
20
|
config: deps.config,
|
|
21
|
+
reports,
|
|
16
22
|
});
|
|
17
23
|
},
|
|
18
24
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
|
|
2
|
+
import { type Result } from "../../kernel/result.js";
|
|
3
|
+
/**
|
|
4
|
+
* Canned retail reports (issue #48).
|
|
5
|
+
*
|
|
6
|
+
* Parameterized SQL over core tables, org-scoped, with all calendar math done
|
|
7
|
+
* by Postgres in the store's timezone (settings.general.timezone, default
|
|
8
|
+
* UTC). Range reports include a prior period of equal length with deltas.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReportParams {
|
|
11
|
+
/** Local calendar date (YYYY-MM-DD) for single-day reports. Default: today in store tz. */
|
|
12
|
+
date?: string;
|
|
13
|
+
/** Local range start (YYYY-MM-DD, inclusive). Default: first of current month in store tz. */
|
|
14
|
+
from?: string;
|
|
15
|
+
/** Local range end (YYYY-MM-DD, inclusive). Default: today in store tz. */
|
|
16
|
+
to?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const RETAIL_REPORTS: readonly [{
|
|
19
|
+
readonly name: "daily-journal";
|
|
20
|
+
readonly description: "Per-day sales journal: order rows + summary with prior-day deltas.";
|
|
21
|
+
}, {
|
|
22
|
+
readonly name: "tax-summary";
|
|
23
|
+
readonly description: "Tax collected over a range, bucketed per local day, with prior-period deltas.";
|
|
24
|
+
}, {
|
|
25
|
+
readonly name: "inventory-aging";
|
|
26
|
+
readonly description: "On-hand stock bucketed by days since last restock (0-30/31-60/61-90/90+).";
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "sell-through";
|
|
29
|
+
readonly description: "Units sold in the range vs current on-hand, per entity.";
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "reorder-needed";
|
|
32
|
+
readonly description: "Inventory rows at or below their reorder threshold.";
|
|
33
|
+
}, {
|
|
34
|
+
readonly name: "staff-sales";
|
|
35
|
+
readonly description: "Orders and revenue grouped by orders.metadata.staffId.";
|
|
36
|
+
}];
|
|
37
|
+
export type RetailReportName = (typeof RETAIL_REPORTS)[number]["name"];
|
|
38
|
+
type SettingsReader = (orgId: string, group: string) => Promise<Record<string, unknown>>;
|
|
39
|
+
export declare class RetailReportsEngine {
|
|
40
|
+
private readonly db;
|
|
41
|
+
private readonly readSettings;
|
|
42
|
+
constructor(db: DrizzleDatabase, readSettings: SettingsReader);
|
|
43
|
+
run(name: string, params: ReportParams, orgId: string): Promise<Result<Record<string, unknown>>>;
|
|
44
|
+
private timezoneFor;
|
|
45
|
+
private rangeOrDefault;
|
|
46
|
+
private rows;
|
|
47
|
+
private journalSummary;
|
|
48
|
+
private dailyJournal;
|
|
49
|
+
private taxSummary;
|
|
50
|
+
private inventoryAging;
|
|
51
|
+
private sellThrough;
|
|
52
|
+
private reorderNeeded;
|
|
53
|
+
private staffSales;
|
|
54
|
+
}
|
|
55
|
+
/** Today's local calendar date (YYYY-MM-DD) in an IANA timezone. */
|
|
56
|
+
export declare function localToday(tz: string): string;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=reports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/reports.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAE3E,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAOjB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAMvE,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAWzF,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,YAAY;gBADZ,EAAE,EAAE,eAAe,EACnB,YAAY,EAAE,cAAc;IAGzC,GAAG,CACP,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;YAoC7B,WAAW;IAKzB,OAAO,CAAC,cAAc;YAKR,IAAI;YAcJ,cAAc;YAqBd,YAAY;YAyBZ,UAAU;YA8BV,cAAc;YA4Bd,WAAW;YA4BX,aAAa;YAmBb,UAAU;CAezB;AAID,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { CommerceValidationError } from "../../kernel/errors.js";
|
|
3
|
+
import { Err, Ok } from "../../kernel/result.js";
|
|
4
|
+
export const RETAIL_REPORTS = [
|
|
5
|
+
{ name: "daily-journal", description: "Per-day sales journal: order rows + summary with prior-day deltas." },
|
|
6
|
+
{ name: "tax-summary", description: "Tax collected over a range, bucketed per local day, with prior-period deltas." },
|
|
7
|
+
{ name: "inventory-aging", description: "On-hand stock bucketed by days since last restock (0-30/31-60/61-90/90+)." },
|
|
8
|
+
{ name: "sell-through", description: "Units sold in the range vs current on-hand, per entity." },
|
|
9
|
+
{ name: "reorder-needed", description: "Inventory rows at or below their reorder threshold." },
|
|
10
|
+
{ name: "staff-sales", description: "Orders and revenue grouped by orders.metadata.staffId." },
|
|
11
|
+
];
|
|
12
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
13
|
+
// Orders excluded from financial reports.
|
|
14
|
+
const EXCLUDED_STATUSES = ["cancelled", "voided"];
|
|
15
|
+
export class RetailReportsEngine {
|
|
16
|
+
db;
|
|
17
|
+
readSettings;
|
|
18
|
+
constructor(db, readSettings) {
|
|
19
|
+
this.db = db;
|
|
20
|
+
this.readSettings = readSettings;
|
|
21
|
+
}
|
|
22
|
+
async run(name, params, orgId) {
|
|
23
|
+
for (const value of [params.date, params.from, params.to]) {
|
|
24
|
+
if (value !== undefined && !DATE_RE.test(value)) {
|
|
25
|
+
return Err(new CommerceValidationError("Dates must be YYYY-MM-DD."));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const tz = await this.timezoneFor(orgId);
|
|
29
|
+
switch (name) {
|
|
30
|
+
case "daily-journal":
|
|
31
|
+
return Ok(await this.dailyJournal(orgId, tz, params.date ?? localToday(tz)));
|
|
32
|
+
case "tax-summary": {
|
|
33
|
+
const [from, to] = this.rangeOrDefault(params, tz);
|
|
34
|
+
return Ok(await this.taxSummary(orgId, tz, from, to));
|
|
35
|
+
}
|
|
36
|
+
case "inventory-aging":
|
|
37
|
+
return Ok(await this.inventoryAging(orgId));
|
|
38
|
+
case "sell-through": {
|
|
39
|
+
const [from, to] = this.rangeOrDefault(params, tz);
|
|
40
|
+
return Ok(await this.sellThrough(orgId, tz, from, to));
|
|
41
|
+
}
|
|
42
|
+
case "reorder-needed":
|
|
43
|
+
return Ok(await this.reorderNeeded(orgId));
|
|
44
|
+
case "staff-sales": {
|
|
45
|
+
const [from, to] = this.rangeOrDefault(params, tz);
|
|
46
|
+
return Ok(await this.staffSales(orgId, tz, from, to));
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
return Err(new CommerceValidationError(`Unknown report "${name}". Available: ${RETAIL_REPORTS.map((r) => r.name).join(", ")}.`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async timezoneFor(orgId) {
|
|
53
|
+
const general = await this.readSettings(orgId, "general");
|
|
54
|
+
return typeof general.timezone === "string" ? general.timezone : "UTC";
|
|
55
|
+
}
|
|
56
|
+
rangeOrDefault(params, tz) {
|
|
57
|
+
const today = localToday(tz);
|
|
58
|
+
return [params.from ?? `${today.slice(0, 8)}01`, params.to ?? today];
|
|
59
|
+
}
|
|
60
|
+
async rows(query) {
|
|
61
|
+
const result = await this.db.execute(query);
|
|
62
|
+
const raw = Array.isArray(result)
|
|
63
|
+
? result
|
|
64
|
+
: (result.rows ?? []);
|
|
65
|
+
return raw.map((row) => {
|
|
66
|
+
const mapped = {};
|
|
67
|
+
for (const [key, value] of Object.entries(row)) {
|
|
68
|
+
mapped[key] = typeof value === "bigint" ? Number(value) : value;
|
|
69
|
+
}
|
|
70
|
+
return mapped;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async journalSummary(orgId, tz, from, to) {
|
|
74
|
+
const rows = await this.rows(sql `
|
|
75
|
+
SELECT count(*)::int AS "orderCount",
|
|
76
|
+
COALESCE(sum(grand_total), 0)::int AS "grossSales",
|
|
77
|
+
COALESCE(sum(subtotal), 0)::int AS "subtotal",
|
|
78
|
+
COALESCE(sum(discount_total), 0)::int AS "discounts",
|
|
79
|
+
COALESCE(sum(tax_total), 0)::int AS "tax",
|
|
80
|
+
COALESCE(sum(shipping_total), 0)::int AS "shipping"
|
|
81
|
+
FROM orders
|
|
82
|
+
WHERE organization_id = ${orgId}
|
|
83
|
+
AND status NOT IN (${sql.join(EXCLUDED_STATUSES.map((s) => sql `${s}`), sql `, `)})
|
|
84
|
+
AND (placed_at AT TIME ZONE ${tz})::date BETWEEN ${from}::date AND ${to}::date
|
|
85
|
+
`);
|
|
86
|
+
return rows[0];
|
|
87
|
+
}
|
|
88
|
+
async dailyJournal(orgId, tz, date) {
|
|
89
|
+
const summary = await this.journalSummary(orgId, tz, date, date);
|
|
90
|
+
const previousDate = shiftDate(date, -1);
|
|
91
|
+
const previous = await this.journalSummary(orgId, tz, previousDate, previousDate);
|
|
92
|
+
const orders = await this.rows(sql `
|
|
93
|
+
SELECT order_number AS "orderNumber", status, currency,
|
|
94
|
+
grand_total AS "grandTotal", tax_total AS "tax",
|
|
95
|
+
discount_total AS "discounts", placed_at AS "placedAt"
|
|
96
|
+
FROM orders
|
|
97
|
+
WHERE organization_id = ${orgId}
|
|
98
|
+
AND status NOT IN (${sql.join(EXCLUDED_STATUSES.map((s) => sql `${s}`), sql `, `)})
|
|
99
|
+
AND (placed_at AT TIME ZONE ${tz})::date = ${date}::date
|
|
100
|
+
ORDER BY placed_at ASC
|
|
101
|
+
`);
|
|
102
|
+
return {
|
|
103
|
+
report: "daily-journal",
|
|
104
|
+
timezone: tz,
|
|
105
|
+
date,
|
|
106
|
+
summary,
|
|
107
|
+
previous,
|
|
108
|
+
delta: deltas(summary, previous),
|
|
109
|
+
orders,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async taxSummary(orgId, tz, from, to) {
|
|
113
|
+
const days = await this.rows(sql `
|
|
114
|
+
SELECT to_char((placed_at AT TIME ZONE ${tz})::date, 'YYYY-MM-DD') AS "day",
|
|
115
|
+
count(*)::int AS "orderCount",
|
|
116
|
+
COALESCE(sum(tax_total), 0)::int AS "tax",
|
|
117
|
+
COALESCE(sum(grand_total), 0)::int AS "gross"
|
|
118
|
+
FROM orders
|
|
119
|
+
WHERE organization_id = ${orgId}
|
|
120
|
+
AND status NOT IN (${sql.join(EXCLUDED_STATUSES.map((s) => sql `${s}`), sql `, `)})
|
|
121
|
+
AND (placed_at AT TIME ZONE ${tz})::date BETWEEN ${from}::date AND ${to}::date
|
|
122
|
+
GROUP BY 1
|
|
123
|
+
ORDER BY 1
|
|
124
|
+
`);
|
|
125
|
+
const totals = await this.journalSummary(orgId, tz, from, to);
|
|
126
|
+
const lengthDays = daysBetween(from, to) + 1;
|
|
127
|
+
const prevFrom = shiftDate(from, -lengthDays);
|
|
128
|
+
const prevTo = shiftDate(to, -lengthDays);
|
|
129
|
+
const previous = await this.journalSummary(orgId, tz, prevFrom, prevTo);
|
|
130
|
+
return {
|
|
131
|
+
report: "tax-summary",
|
|
132
|
+
timezone: tz,
|
|
133
|
+
from,
|
|
134
|
+
to,
|
|
135
|
+
totals,
|
|
136
|
+
previous: { from: prevFrom, to: prevTo, ...previous },
|
|
137
|
+
delta: deltas(totals, previous),
|
|
138
|
+
days,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async inventoryAging(orgId) {
|
|
142
|
+
const rows = await this.rows(sql `
|
|
143
|
+
SELECT il.entity_id AS "entityId", il.variant_id AS "variantId",
|
|
144
|
+
e.slug AS "slug",
|
|
145
|
+
il.quantity_on_hand AS "quantityOnHand",
|
|
146
|
+
il.quantity_reserved AS "quantityReserved",
|
|
147
|
+
GREATEST(0, EXTRACT(day FROM now() - COALESCE(il.last_restocked_at, il.updated_at)))::int AS "ageDays"
|
|
148
|
+
FROM inventory_levels il
|
|
149
|
+
LEFT JOIN sellable_entities e ON e.id = il.entity_id
|
|
150
|
+
WHERE il.organization_id = ${orgId} AND il.quantity_on_hand > 0
|
|
151
|
+
ORDER BY "ageDays" DESC
|
|
152
|
+
`);
|
|
153
|
+
const buckets = [
|
|
154
|
+
{ bucket: "0-30", min: 0, max: 30 },
|
|
155
|
+
{ bucket: "31-60", min: 31, max: 60 },
|
|
156
|
+
{ bucket: "61-90", min: 61, max: 90 },
|
|
157
|
+
{ bucket: "90+", min: 91, max: Infinity },
|
|
158
|
+
].map(({ bucket, min, max }) => {
|
|
159
|
+
const inBucket = rows.filter((r) => r.ageDays >= min && r.ageDays <= max);
|
|
160
|
+
return {
|
|
161
|
+
bucket,
|
|
162
|
+
skuCount: inBucket.length,
|
|
163
|
+
quantityOnHand: inBucket.reduce((sum, r) => sum + r.quantityOnHand, 0),
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
return { report: "inventory-aging", buckets, rows };
|
|
167
|
+
}
|
|
168
|
+
async sellThrough(orgId, tz, from, to) {
|
|
169
|
+
const rows = await this.rows(sql `
|
|
170
|
+
SELECT li.entity_id AS "entityId",
|
|
171
|
+
min(li.title) AS "title",
|
|
172
|
+
sum(li.quantity)::int AS "unitsSold",
|
|
173
|
+
COALESCE((SELECT sum(quantity_on_hand)::int FROM inventory_levels il
|
|
174
|
+
WHERE il.entity_id = li.entity_id AND il.organization_id = ${orgId}), 0) AS "onHand"
|
|
175
|
+
FROM order_line_items li
|
|
176
|
+
JOIN orders o ON o.id = li.order_id
|
|
177
|
+
WHERE o.organization_id = ${orgId}
|
|
178
|
+
AND o.status NOT IN (${sql.join(EXCLUDED_STATUSES.map((s) => sql `${s}`), sql `, `)})
|
|
179
|
+
AND (o.placed_at AT TIME ZONE ${tz})::date BETWEEN ${from}::date AND ${to}::date
|
|
180
|
+
GROUP BY li.entity_id
|
|
181
|
+
ORDER BY "unitsSold" DESC
|
|
182
|
+
`);
|
|
183
|
+
return {
|
|
184
|
+
report: "sell-through",
|
|
185
|
+
timezone: tz,
|
|
186
|
+
from,
|
|
187
|
+
to,
|
|
188
|
+
rows: rows.map((r) => {
|
|
189
|
+
const sold = r.unitsSold;
|
|
190
|
+
const onHand = r.onHand;
|
|
191
|
+
return { ...r, sellThroughRate: sold + onHand > 0 ? sold / (sold + onHand) : 0 };
|
|
192
|
+
}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async reorderNeeded(orgId) {
|
|
196
|
+
const rows = await this.rows(sql `
|
|
197
|
+
SELECT il.entity_id AS "entityId", il.variant_id AS "variantId",
|
|
198
|
+
il.warehouse_id AS "warehouseId", e.slug AS "slug",
|
|
199
|
+
il.quantity_on_hand AS "quantityOnHand",
|
|
200
|
+
il.quantity_reserved AS "quantityReserved",
|
|
201
|
+
(il.quantity_on_hand - il.quantity_reserved) AS "available",
|
|
202
|
+
il.reorder_threshold AS "reorderThreshold",
|
|
203
|
+
il.reorder_quantity AS "reorderQuantity"
|
|
204
|
+
FROM inventory_levels il
|
|
205
|
+
LEFT JOIN sellable_entities e ON e.id = il.entity_id
|
|
206
|
+
WHERE il.organization_id = ${orgId}
|
|
207
|
+
AND il.reorder_threshold IS NOT NULL
|
|
208
|
+
AND (il.quantity_on_hand - il.quantity_reserved) <= il.reorder_threshold
|
|
209
|
+
ORDER BY "available" ASC
|
|
210
|
+
`);
|
|
211
|
+
return { report: "reorder-needed", rows };
|
|
212
|
+
}
|
|
213
|
+
async staffSales(orgId, tz, from, to) {
|
|
214
|
+
const rows = await this.rows(sql `
|
|
215
|
+
SELECT COALESCE(metadata->>'staffId', 'unattributed') AS "staffId",
|
|
216
|
+
count(*)::int AS "orderCount",
|
|
217
|
+
COALESCE(sum(grand_total), 0)::int AS "revenue",
|
|
218
|
+
COALESCE(sum(discount_total), 0)::int AS "discounts"
|
|
219
|
+
FROM orders
|
|
220
|
+
WHERE organization_id = ${orgId}
|
|
221
|
+
AND status NOT IN (${sql.join(EXCLUDED_STATUSES.map((s) => sql `${s}`), sql `, `)})
|
|
222
|
+
AND (placed_at AT TIME ZONE ${tz})::date BETWEEN ${from}::date AND ${to}::date
|
|
223
|
+
GROUP BY 1
|
|
224
|
+
ORDER BY "revenue" DESC
|
|
225
|
+
`);
|
|
226
|
+
return { report: "staff-sales", timezone: tz, from, to, rows };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// ── Pure date helpers (calendar dates, no time component) ──────────────────
|
|
230
|
+
/** Today's local calendar date (YYYY-MM-DD) in an IANA timezone. */
|
|
231
|
+
export function localToday(tz) {
|
|
232
|
+
return new Intl.DateTimeFormat("en-CA", { timeZone: tz }).format(new Date());
|
|
233
|
+
}
|
|
234
|
+
function shiftDate(date, days) {
|
|
235
|
+
const d = new Date(`${date}T00:00:00Z`);
|
|
236
|
+
d.setUTCDate(d.getUTCDate() + days);
|
|
237
|
+
return d.toISOString().slice(0, 10);
|
|
238
|
+
}
|
|
239
|
+
function daysBetween(from, to) {
|
|
240
|
+
const ms = new Date(`${to}T00:00:00Z`).getTime() - new Date(`${from}T00:00:00Z`).getTime();
|
|
241
|
+
return Math.round(ms / 86_400_000);
|
|
242
|
+
}
|
|
243
|
+
function deltas(current, previous) {
|
|
244
|
+
const out = {};
|
|
245
|
+
for (const key of Object.keys(current)) {
|
|
246
|
+
out[key] = (current[key] ?? 0) - (previous[key] ?? 0);
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { CommerceConfig } from "../../config/types.js";
|
|
9
9
|
import { type Result } from "../../kernel/result.js";
|
|
10
|
+
import type { Actor } from "../../auth/types.js";
|
|
11
|
+
import type { ReportParams, RetailReportsEngine } from "./reports.js";
|
|
10
12
|
import type { AnalyticsAdapter, AnalyticsMeta, AnalyticsQueryParams, AnalyticsQueryResult, AnalyticsScope } from "./types.js";
|
|
11
13
|
export type { AnalyticsTimeDimension, AnalyticsFilter, AnalyticsQueryParams, AnalyticsModelDefinition, AnalyticsMeta, } from "./types.js";
|
|
12
14
|
export interface AnalyticsServiceDeps {
|
|
13
15
|
adapter: AnalyticsAdapter;
|
|
14
16
|
config: CommerceConfig;
|
|
17
|
+
reports?: RetailReportsEngine;
|
|
15
18
|
}
|
|
16
19
|
export declare class AnalyticsService {
|
|
17
20
|
private deps;
|
|
@@ -33,6 +36,13 @@ export declare class AnalyticsService {
|
|
|
33
36
|
*/
|
|
34
37
|
query(params: AnalyticsQueryParams, scope: AnalyticsScope): Promise<Result<AnalyticsQueryResult>>;
|
|
35
38
|
getDashboard(name: string, scope: AnalyticsScope): Promise<Result<AnalyticsQueryResult>>;
|
|
39
|
+
/** The canned retail reports pack (issue #48). */
|
|
40
|
+
listReports(): Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** Runs a canned retail report (issue #48), org-scoped to the actor. */
|
|
45
|
+
getReport(name: string, params: ReportParams, actor?: Actor | null): Promise<Result<Record<string, unknown>>>;
|
|
36
46
|
getMeta(): Promise<Result<AnalyticsMeta>>;
|
|
37
47
|
meta(): Promise<Result<{
|
|
38
48
|
measures: string[];
|