@porulle/core 0.7.0 → 0.9.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/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +26 -7
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +8 -5
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +7 -0
- package/dist/config/types.d.ts +26 -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-completion.d.ts.map +1 -1
- package/dist/hooks/checkout-completion.js +2 -2
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +17 -2
- 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 +206 -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 +9 -0
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +97 -21
- 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/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +13 -0
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +111 -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 +234 -1
- 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 +190 -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/database/scoped-db.d.ts +2 -1
- package/dist/kernel/database/scoped-db.d.ts.map +1 -1
- package/dist/kernel/database/scoped-db.js +62 -1
- 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 +26 -0
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +17 -2
- package/dist/modules/analytics/drizzle-adapter.d.ts.map +1 -1
- package/dist/modules/analytics/drizzle-adapter.js +16 -3
- 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 +61 -2
- 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 +166 -5
- 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/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/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 +391 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +56 -1
- package/dist/modules/orders/service.d.ts +102 -3
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +653 -29
- package/dist/modules/orders/stale-order-cleanup.d.ts.map +1 -1
- package/dist/modules/orders/stale-order-cleanup.js +13 -1
- 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 +43 -5
- 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/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +2 -0
- package/dist/test-utils/order-test-helpers.d.ts +12 -0
- package/dist/test-utils/order-test-helpers.d.ts.map +1 -0
- package/dist/test-utils/order-test-helpers.js +18 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +7 -0
- package/dist/test-utils/test-actors.js +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/package.json +3 -3
- package/src/auth/middleware.ts +26 -7
- package/src/auth/setup.ts +11 -4
- package/src/config/defaults.ts +7 -0
- package/src/config/types.ts +26 -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-completion.ts +22 -16
- package/src/hooks/checkout.ts +36 -11
- package/src/index.ts +1 -0
- package/src/interfaces/rest/index.ts +12 -0
- package/src/interfaces/rest/routes/admin/staff.ts +260 -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 +114 -27
- package/src/interfaces/rest/routes/documents.ts +61 -0
- package/src/interfaces/rest/routes/inventory.ts +14 -0
- package/src/interfaces/rest/routes/orders.ts +148 -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 +258 -1
- 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/database/scoped-db.ts +70 -1
- package/src/kernel/hooks/types.ts +1 -0
- package/src/kernel/plugin/manifest.ts +41 -2
- package/src/modules/analytics/drizzle-adapter.ts +19 -3
- 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 +104 -2
- package/src/modules/catalog/entity-service.ts +196 -4
- 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/customers/service.ts +6 -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 +58 -1
- package/src/modules/orders/service.ts +1041 -65
- package/src/modules/orders/stale-order-cleanup.ts +18 -4
- package/src/modules/pricing/repository/index.ts +34 -0
- package/src/modules/pricing/service.ts +76 -6
- 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/create-test-config.ts +2 -0
- package/src/test-utils/order-test-helpers.ts +26 -0
- package/src/test-utils/rest-api-test-utils.ts +7 -0
- package/src/test-utils/test-actors.ts +1 -1
- package/src/testing.ts +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
+
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
|
+
import { getReportRoute, listReportsRoute } from "../schemas/analytics.js";
|
|
4
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
|
|
5
|
+
|
|
6
|
+
export function analyticsRoutes(kernel: Kernel) {
|
|
7
|
+
const router = new OpenAPIHono<AppEnv>();
|
|
8
|
+
|
|
9
|
+
router.use("/reports", requirePerm("analytics:read"));
|
|
10
|
+
router.use("/reports/:name", requirePerm("analytics:read"));
|
|
11
|
+
|
|
12
|
+
router.openapi(listReportsRoute, (c) => {
|
|
13
|
+
return c.json({ data: kernel.services.analytics.listReports() });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// @ts-expect-error -- openapi handler union return type
|
|
17
|
+
router.openapi(getReportRoute, async (c) => {
|
|
18
|
+
const { date, from, to } = c.req.valid("query");
|
|
19
|
+
const result = await kernel.services.analytics.getReport(
|
|
20
|
+
c.req.param("name"),
|
|
21
|
+
{
|
|
22
|
+
...(date !== undefined ? { date } : {}),
|
|
23
|
+
...(from !== undefined ? { from } : {}),
|
|
24
|
+
...(to !== undefined ? { to } : {}),
|
|
25
|
+
},
|
|
26
|
+
c.get("actor"),
|
|
27
|
+
);
|
|
28
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
29
|
+
return c.json({ data: result.value });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return router;
|
|
33
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import type { CreateCartInput, AddCartItemInput } from "../../../modules/cart/schemas.js";
|
|
4
|
-
import { createCartRoute, addCartItemRoute, updateCartItemQuantityRoute, getCartRoute, removeCartItemRoute } from "../schemas/carts.js";
|
|
5
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
4
|
+
import { createCartRoute, addCartItemRoute, updateCartItemQuantityRoute, getCartRoute, removeCartItemRoute, listCartsRoute, recoverCartRoute } from "../schemas/carts.js";
|
|
5
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, parsePagination } from "../utils.js";
|
|
6
6
|
|
|
7
7
|
export function cartRoutes(kernel: Kernel) {
|
|
8
8
|
const router = new OpenAPIHono<AppEnv>();
|
|
@@ -21,6 +21,56 @@ export function cartRoutes(kernel: Kernel) {
|
|
|
21
21
|
return c.json({ data: result.value }, 201);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// @ts-expect-error -- openapi handler union return type
|
|
25
|
+
router.openapi(listCartsRoute, async (c) => {
|
|
26
|
+
const actor = c.get("actor");
|
|
27
|
+
const pagination = parsePagination(c.req.query());
|
|
28
|
+
const status = c.req.query("status");
|
|
29
|
+
const olderThanRaw = c.req.query("olderThan");
|
|
30
|
+
const hasCustomerRaw = c.req.query("hasCustomer");
|
|
31
|
+
|
|
32
|
+
let olderThan: Date | undefined;
|
|
33
|
+
if (olderThanRaw) {
|
|
34
|
+
const d = new Date(olderThanRaw);
|
|
35
|
+
if (Number.isNaN(d.getTime())) {
|
|
36
|
+
return c.json({ error: { code: "VALIDATION_FAILED", message: "olderThan must be an ISO timestamp." } }, 422);
|
|
37
|
+
}
|
|
38
|
+
olderThan = d;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = await kernel.services.cart.list(
|
|
42
|
+
{
|
|
43
|
+
page: pagination.page,
|
|
44
|
+
limit: pagination.limit,
|
|
45
|
+
...(status !== undefined ? { status } : {}),
|
|
46
|
+
...(olderThan !== undefined ? { olderThan } : {}),
|
|
47
|
+
...(hasCustomerRaw === "true" ? { hasCustomer: true } : {}),
|
|
48
|
+
...(hasCustomerRaw === "false" ? { hasCustomer: false } : {}),
|
|
49
|
+
},
|
|
50
|
+
actor,
|
|
51
|
+
);
|
|
52
|
+
if (!result.ok)
|
|
53
|
+
return c.json(
|
|
54
|
+
mapErrorToResponse(result.error),
|
|
55
|
+
mapErrorToStatus(result.error),
|
|
56
|
+
);
|
|
57
|
+
return c.json({
|
|
58
|
+
data: result.value.items,
|
|
59
|
+
meta: { pagination: result.value.pagination },
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// @ts-expect-error -- openapi handler union return type
|
|
64
|
+
router.openapi(recoverCartRoute, async (c) => {
|
|
65
|
+
const result = await kernel.services.cart.recover(c.req.param("id"), c.get("actor"));
|
|
66
|
+
if (!result.ok)
|
|
67
|
+
return c.json(
|
|
68
|
+
mapErrorToResponse(result.error),
|
|
69
|
+
mapErrorToStatus(result.error),
|
|
70
|
+
);
|
|
71
|
+
return c.json({ data: result.value });
|
|
72
|
+
});
|
|
73
|
+
|
|
24
74
|
// @ts-expect-error -- openapi handler union return type
|
|
25
75
|
router.openapi(getCartRoute, async (c) => {
|
|
26
76
|
const actor = c.get("actor");
|
|
@@ -44,6 +44,8 @@ import {
|
|
|
44
44
|
createOptionValueRoute,
|
|
45
45
|
createVariantRoute,
|
|
46
46
|
generateVariantsRoute,
|
|
47
|
+
quickVariantRoute,
|
|
48
|
+
bulkVariantsRoute,
|
|
47
49
|
} from "../schemas/catalog.js";
|
|
48
50
|
import {
|
|
49
51
|
type AppEnv,
|
|
@@ -508,6 +510,38 @@ export function catalogRoutes(kernel: Kernel) {
|
|
|
508
510
|
return c.json({ data: result.value }, 201);
|
|
509
511
|
});
|
|
510
512
|
|
|
513
|
+
// @ts-expect-error -- openapi handler union return type
|
|
514
|
+
router.openapi(quickVariantRoute, async (c) => {
|
|
515
|
+
const body = c.req.valid("json") as { options: Record<string, string>; sku?: string; barcode?: string };
|
|
516
|
+
const result = await kernel.services.catalog.quickCreateVariant(
|
|
517
|
+
c.req.param("id"),
|
|
518
|
+
body,
|
|
519
|
+
c.get("actor"),
|
|
520
|
+
);
|
|
521
|
+
if (!result.ok)
|
|
522
|
+
return c.json(
|
|
523
|
+
mapErrorToResponse(result.error),
|
|
524
|
+
mapErrorToStatus(result.error),
|
|
525
|
+
);
|
|
526
|
+
return c.json({ data: result.value }, result.value.created ? 201 : 200);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// @ts-expect-error -- openapi handler union return type
|
|
530
|
+
router.openapi(bulkVariantsRoute, async (c) => {
|
|
531
|
+
const body = c.req.valid("json") as { axes: Array<{ name: string; values: string[] }>; skuPrefix?: string };
|
|
532
|
+
const result = await kernel.services.catalog.bulkCreateVariants(
|
|
533
|
+
c.req.param("id"),
|
|
534
|
+
body,
|
|
535
|
+
c.get("actor"),
|
|
536
|
+
);
|
|
537
|
+
if (!result.ok)
|
|
538
|
+
return c.json(
|
|
539
|
+
mapErrorToResponse(result.error),
|
|
540
|
+
mapErrorToStatus(result.error),
|
|
541
|
+
);
|
|
542
|
+
return c.json({ data: result.value }, 201);
|
|
543
|
+
});
|
|
544
|
+
|
|
511
545
|
// @ts-expect-error -- openapi handler union return type
|
|
512
546
|
router.openapi(generateVariantsRoute, async (c) => {
|
|
513
547
|
const body = c.req.valid("json") as VariantGenerationStrategy;
|
|
@@ -21,8 +21,58 @@ import type { AfterHook, BeforeHook, ServiceContainer } from "../../../kernel/ho
|
|
|
21
21
|
import type { PluginDb } from "../../../kernel/database/plugin-types.js";
|
|
22
22
|
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
23
23
|
import { isCommerceError } from "../../../kernel/errors.js";
|
|
24
|
+
import { assertPermission } from "../../../auth/permissions.js";
|
|
24
25
|
import { makeId } from "../../../utils/id.js";
|
|
25
26
|
import type { ShippingAddress } from "../../../modules/shipping/calculator.js";
|
|
27
|
+
import type { Actor } from "../../../auth/types.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SEC-07 — resolve the customer profile a checkout order is attributed to.
|
|
31
|
+
* A self-service actor may ONLY attribute the order to its own customer
|
|
32
|
+
* profile; only actors with org-level `customers:read` (staff/clienteling) may
|
|
33
|
+
* name an arbitrary `customerId`. Returns undefined for guests / unresolved
|
|
34
|
+
* (guest checkout). Exported for unit testing.
|
|
35
|
+
*/
|
|
36
|
+
export async function resolveCheckoutCustomerUuid(
|
|
37
|
+
customers: Kernel["services"]["customers"],
|
|
38
|
+
actor: Actor | null,
|
|
39
|
+
customerId: string | undefined,
|
|
40
|
+
): Promise<string | undefined> {
|
|
41
|
+
const actorUserId = actor?.userId;
|
|
42
|
+
let canActForOthers = false;
|
|
43
|
+
if (actorUserId) {
|
|
44
|
+
try {
|
|
45
|
+
assertPermission(actor, "customers:read");
|
|
46
|
+
canActForOthers = true;
|
|
47
|
+
} catch {
|
|
48
|
+
canActForOthers = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (customerId) {
|
|
53
|
+
const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
54
|
+
if (uuidRe.test(customerId)) {
|
|
55
|
+
const byId = await customers.getById(customerId, actor);
|
|
56
|
+
// Honor a profile UUID only if it is the actor's own, or the actor may
|
|
57
|
+
// act for other customers.
|
|
58
|
+
if (byId.ok && (canActForOthers || byId.value.userId === actorUserId)) {
|
|
59
|
+
return byId.value.id;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (canActForOthers) {
|
|
63
|
+
// Staff may resolve/create a customer by a supplied user_id.
|
|
64
|
+
const byUser = await customers.getByUserId(customerId, actor);
|
|
65
|
+
if (byUser.ok) return byUser.value.id;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Self-service default: the authenticated actor's own customer profile.
|
|
70
|
+
if (actorUserId && !canActForOthers) {
|
|
71
|
+
const own = await customers.getByUserId(actorUserId, actor);
|
|
72
|
+
if (own.ok) return own.value.id;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
26
76
|
|
|
27
77
|
export function checkoutRoutes(kernel: Kernel) {
|
|
28
78
|
const router = new OpenAPIHono<AppEnv>();
|
|
@@ -34,6 +84,48 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
34
84
|
const body = c.req.valid("json");
|
|
35
85
|
|
|
36
86
|
const actor = c.get("actor");
|
|
87
|
+
|
|
88
|
+
// Idempotent replay: a re-submitted checkout (offline POS queue, network
|
|
89
|
+
// retry) returns the already-created order BEFORE the pipeline runs — no
|
|
90
|
+
// double payment authorization, no duplicate order.
|
|
91
|
+
if (body.idempotencyKey) {
|
|
92
|
+
const replay = await kernel.services.orders.getByIdempotencyKey(
|
|
93
|
+
body.idempotencyKey,
|
|
94
|
+
actor,
|
|
95
|
+
);
|
|
96
|
+
if (replay.ok && replay.value) {
|
|
97
|
+
// IDOR guard: an idempotency key must only replay the requester's OWN
|
|
98
|
+
// order. Without this, a same-org customer who supplies/guesses another
|
|
99
|
+
// customer's key would receive that customer's order (PII, totals, lines).
|
|
100
|
+
let canActForOthers = false;
|
|
101
|
+
try {
|
|
102
|
+
assertPermission(actor, "customers:read");
|
|
103
|
+
canActForOthers = true;
|
|
104
|
+
} catch {
|
|
105
|
+
canActForOthers = false;
|
|
106
|
+
}
|
|
107
|
+
const ownCustomer = actor
|
|
108
|
+
? await resolveCheckoutCustomerUuid(kernel.services.customers, actor, undefined)
|
|
109
|
+
: undefined;
|
|
110
|
+
const replayCustomer = replay.value.customerId ?? null;
|
|
111
|
+
const ownsOrder =
|
|
112
|
+
(replayCustomer !== null && replayCustomer === ownCustomer) ||
|
|
113
|
+
(replayCustomer === null && !ownCustomer);
|
|
114
|
+
if (canActForOthers || ownsOrder) {
|
|
115
|
+
return c.json({ data: replay.value }, 201);
|
|
116
|
+
}
|
|
117
|
+
return c.json(
|
|
118
|
+
{
|
|
119
|
+
error: {
|
|
120
|
+
code: "IDEMPOTENCY_CONFLICT",
|
|
121
|
+
message: "Idempotency key does not belong to this account.",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
409,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
37
129
|
const checkoutData: CheckoutData = {
|
|
38
130
|
checkoutId: makeId(),
|
|
39
131
|
cartId: body.cartId,
|
|
@@ -121,34 +213,19 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
121
213
|
context,
|
|
122
214
|
);
|
|
123
215
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
actor,
|
|
133
|
-
);
|
|
134
|
-
if (byIdResult.ok) {
|
|
135
|
-
customerUuid = byIdResult.value.id;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!customerUuid) {
|
|
139
|
-
// Fall back to user_id lookup (auto-creates customer profile if needed)
|
|
140
|
-
const byUserIdResult = await kernel.services.customers.getByUserId(
|
|
141
|
-
processed.customerId,
|
|
142
|
-
actor,
|
|
143
|
-
);
|
|
144
|
-
if (byUserIdResult.ok) {
|
|
145
|
-
customerUuid = byUserIdResult.value.id;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// If both lookups fail, we still allow guest checkout (customerUuid remains undefined)
|
|
149
|
-
}
|
|
216
|
+
// SEC-07: resolve the order's customer server-side. A self-service actor
|
|
217
|
+
// can only attribute the order to its own profile; a client-supplied
|
|
218
|
+
// foreign customerId is ignored unless the actor is staff.
|
|
219
|
+
const customerUuid = await resolveCheckoutCustomerUuid(
|
|
220
|
+
kernel.services.customers,
|
|
221
|
+
actor,
|
|
222
|
+
processed.customerId,
|
|
223
|
+
);
|
|
150
224
|
|
|
151
225
|
const orderPayload = {
|
|
226
|
+
...(body.idempotencyKey !== undefined
|
|
227
|
+
? { idempotencyKey: body.idempotencyKey }
|
|
228
|
+
: {}),
|
|
152
229
|
currency: processed.currency,
|
|
153
230
|
subtotal: processed.subtotal,
|
|
154
231
|
taxTotal: processed.taxTotal,
|
|
@@ -177,6 +254,11 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
177
254
|
quantity: lineItem.quantity,
|
|
178
255
|
unitPrice: lineItem.resolvedUnitPrice ?? 0,
|
|
179
256
|
totalPrice: lineItem.resolvedTotal ?? 0,
|
|
257
|
+
// Per-line tax from product tax classes (issue #57)
|
|
258
|
+
taxAmount: lineItem.taxAmount ?? 0,
|
|
259
|
+
...(lineItem.discountAmount !== undefined
|
|
260
|
+
? { discountAmount: lineItem.discountAmount }
|
|
261
|
+
: {}),
|
|
180
262
|
};
|
|
181
263
|
return lineItem.variantId !== undefined
|
|
182
264
|
? { ...payload, variantId: lineItem.variantId }
|
|
@@ -187,7 +269,12 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
187
269
|
: {}),
|
|
188
270
|
};
|
|
189
271
|
|
|
190
|
-
|
|
272
|
+
// Checkout is a trusted, already-server-priced pipeline (resolveCurrentPrices
|
|
273
|
+
// + promotions/tax) and reserves stock in its own after-hooks — so it hands
|
|
274
|
+
// the order primitive precomputed totals rather than re-deriving them.
|
|
275
|
+
const order = await kernel.services.orders.create(orderPayload, actor, undefined, {
|
|
276
|
+
trustedPricing: true,
|
|
277
|
+
});
|
|
191
278
|
|
|
192
279
|
if (!order.ok) {
|
|
193
280
|
return c.json(
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
+
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
|
+
import {
|
|
4
|
+
emailInvoiceRoute,
|
|
5
|
+
invoiceHtmlRoute,
|
|
6
|
+
invoicePdfRoute,
|
|
7
|
+
receiptHtmlRoute,
|
|
8
|
+
} from "../schemas/documents.js";
|
|
9
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Order document rendering (issue #47). Mounted under /orders. Access
|
|
13
|
+
* control rides on OrderService.getById (orders:read, or orders:read:own
|
|
14
|
+
* for the order's customer), so no extra scope is needed here.
|
|
15
|
+
*/
|
|
16
|
+
export function documentRoutes(kernel: Kernel) {
|
|
17
|
+
const router = new OpenAPIHono<AppEnv>();
|
|
18
|
+
|
|
19
|
+
router.openapi(invoicePdfRoute, async (c) => {
|
|
20
|
+
const result = await kernel.services.documents.renderInvoicePdf(
|
|
21
|
+
c.req.param("id"),
|
|
22
|
+
c.get("actor"),
|
|
23
|
+
);
|
|
24
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
25
|
+
return c.body(result.value.pdf.slice().buffer as ArrayBuffer, 200, {
|
|
26
|
+
"content-type": "application/pdf",
|
|
27
|
+
"content-disposition": `inline; filename="invoice-${result.value.invoiceNumber}.pdf"`,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
router.openapi(invoiceHtmlRoute, async (c) => {
|
|
32
|
+
const result = await kernel.services.documents.renderInvoiceHtml(
|
|
33
|
+
c.req.param("id"),
|
|
34
|
+
c.get("actor"),
|
|
35
|
+
);
|
|
36
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
37
|
+
return c.html(result.value.html);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
router.openapi(receiptHtmlRoute, async (c) => {
|
|
41
|
+
const result = await kernel.services.documents.renderReceiptHtml(
|
|
42
|
+
c.req.param("id"),
|
|
43
|
+
c.get("actor"),
|
|
44
|
+
);
|
|
45
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
46
|
+
return c.html(result.value.html);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// @ts-expect-error -- openapi handler union return type
|
|
50
|
+
router.openapi(emailInvoiceRoute, async (c) => {
|
|
51
|
+
const result = await kernel.services.documents.emailInvoice(
|
|
52
|
+
c.req.param("id"),
|
|
53
|
+
c.req.valid("json").to,
|
|
54
|
+
c.get("actor"),
|
|
55
|
+
);
|
|
56
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
57
|
+
return c.json({ data: result.value });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return router;
|
|
61
|
+
}
|
|
@@ -17,6 +17,14 @@ export function inventoryRoutes(kernel: Kernel) {
|
|
|
17
17
|
// Inventory levels listing requires inventory:read
|
|
18
18
|
router.use("/levels", requirePerm("inventory:read"));
|
|
19
19
|
|
|
20
|
+
// Warehouse config + reservation are staff operations, never public. Gate them
|
|
21
|
+
// at the route (reserve/release are also called internally by checkout with the
|
|
22
|
+
// customer's actor, so the service itself must stay callable — the boundary is
|
|
23
|
+
// the HTTP route). Blocks anonymous (401) and customers lacking the perm (403).
|
|
24
|
+
router.use("/warehouses", requirePerm("inventory:read"));
|
|
25
|
+
router.use("/reserve", requirePerm("inventory:adjust"));
|
|
26
|
+
router.use("/release", requirePerm("inventory:adjust"));
|
|
27
|
+
|
|
20
28
|
// @ts-expect-error -- openapi handler union return type
|
|
21
29
|
router.openapi(listInventoryLevelsRoute, async (c) => {
|
|
22
30
|
const actor = c.get("actor");
|
|
@@ -78,6 +86,12 @@ export function inventoryRoutes(kernel: Kernel) {
|
|
|
78
86
|
router.openapi(createWarehouseRoute, async (c) => {
|
|
79
87
|
const body = c.req.valid("json") as Parameters<typeof kernel.services.inventory.createWarehouse>[0];
|
|
80
88
|
const actor = c.get("actor");
|
|
89
|
+
// Creating a warehouse (write) needs the higher inventory:adjust perm; the
|
|
90
|
+
// router.use above already requires inventory:read for the /warehouses path.
|
|
91
|
+
const perms = (actor as { permissions?: string[] } | null)?.permissions ?? [];
|
|
92
|
+
if (!(perms.includes("inventory:adjust") || perms.includes("inventory:*") || perms.includes("*:*"))) {
|
|
93
|
+
return c.json({ error: { code: "FORBIDDEN", message: "Permission 'inventory:adjust' is required." } }, 403);
|
|
94
|
+
}
|
|
81
95
|
const result = await kernel.services.inventory.createWarehouse(body, actor);
|
|
82
96
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
83
97
|
return c.json({ data: result.value }, 201);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
|
-
import { changeOrderStatusRoute, listOrdersRoute, orderLookupRoute, getOrderRoute, getOrderFulfillmentsRoute, createOrderRoute, refundOrderRoute, captureOrderRoute } from "../schemas/orders.js";
|
|
3
|
+
import { changeOrderStatusRoute, listOrdersRoute, orderLookupRoute, getOrderRoute, getOrderFulfillmentsRoute, createOrderRoute, refundOrderRoute, captureOrderRoute, createOrderFulfillmentRoute, addOrderLineItemRoute, updateOrderLineItemRoute, removeOrderLineItemRoute, refundOrderLinesRoute, undoOrderRefundRoute, listOrderRefundsRoute, refundCapStatusRoute, createOrderNoteRoute, listOrderNotesRoute, deleteOrderNoteRoute, orderTimelineRoute } from "../schemas/orders.js";
|
|
4
4
|
import { type AppEnv, isUUID, mapErrorToResponse, mapErrorToStatus, parsePagination } from "../utils.js";
|
|
5
5
|
import type { CreateOrderInput } from "../../../modules/orders/service.js";
|
|
6
|
+
import { assertPermission } from "../../../auth/permissions.js";
|
|
6
7
|
|
|
7
8
|
export function orderRoutes(kernel: Kernel) {
|
|
8
9
|
const router = new OpenAPIHono<AppEnv>();
|
|
@@ -62,6 +63,11 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
62
63
|
|
|
63
64
|
// @ts-expect-error -- openapi handler union return type
|
|
64
65
|
router.openapi(createOrderRoute, async (c) => {
|
|
66
|
+
try {
|
|
67
|
+
assertPermission(c.get("actor"), "orders:manage");
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return c.json(mapErrorToResponse(error), mapErrorToStatus(error));
|
|
70
|
+
}
|
|
65
71
|
const body = c.req.valid("json") as CreateOrderInput;
|
|
66
72
|
const result = await kernel.services.orders.create(body, c.get("actor"));
|
|
67
73
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
@@ -82,6 +88,80 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
82
88
|
return c.json({ data: result.value });
|
|
83
89
|
});
|
|
84
90
|
|
|
91
|
+
// ── Notes + activity timeline (issue #56) ──────────────────────────
|
|
92
|
+
|
|
93
|
+
// @ts-expect-error -- openapi handler union return type
|
|
94
|
+
router.openapi(createOrderNoteRoute, async (c) => {
|
|
95
|
+
const body = c.req.valid("json") as { body: string; pinned?: boolean };
|
|
96
|
+
const result = await kernel.services.orders.addNote(c.req.param("id"), body, c.get("actor"));
|
|
97
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
98
|
+
return c.json({ data: result.value }, 201);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// @ts-expect-error -- openapi handler union return type
|
|
102
|
+
router.openapi(listOrderNotesRoute, async (c) => {
|
|
103
|
+
const result = await kernel.services.orders.listNotes(c.req.param("id"), c.get("actor"));
|
|
104
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
105
|
+
return c.json({ data: result.value });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// @ts-expect-error -- openapi handler union return type
|
|
109
|
+
router.openapi(deleteOrderNoteRoute, async (c) => {
|
|
110
|
+
const result = await kernel.services.orders.deleteNote(
|
|
111
|
+
c.req.param("id"),
|
|
112
|
+
c.req.param("noteId"),
|
|
113
|
+
c.get("actor"),
|
|
114
|
+
);
|
|
115
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
116
|
+
return c.json({ data: result.value });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// @ts-expect-error -- openapi handler union return type
|
|
120
|
+
router.openapi(orderTimelineRoute, async (c) => {
|
|
121
|
+
const result = await kernel.services.orders.timeline(c.req.param("id"), c.get("actor"));
|
|
122
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
123
|
+
return c.json({ data: result.value });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ── Line-level refunds (issue #52) ─────────────────────────────────
|
|
127
|
+
|
|
128
|
+
// @ts-expect-error -- openapi handler union return type
|
|
129
|
+
router.openapi(refundCapStatusRoute, async (c) => {
|
|
130
|
+
const result = await kernel.services.orders.refundCapStatus(c.get("actor"));
|
|
131
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
132
|
+
return c.json({ data: result.value });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// @ts-expect-error -- openapi handler union return type
|
|
136
|
+
router.openapi(refundOrderLinesRoute, async (c) => {
|
|
137
|
+
const body = c.req.valid("json") as { lines: Array<{ lineItemId: string; quantity: number }>; reason?: string };
|
|
138
|
+
const result = await kernel.services.orders.refundLines(
|
|
139
|
+
c.req.param("id"),
|
|
140
|
+
body,
|
|
141
|
+
c.get("actor"),
|
|
142
|
+
);
|
|
143
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
144
|
+
return c.json({ data: result.value }, 201);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// @ts-expect-error -- openapi handler union return type
|
|
148
|
+
router.openapi(undoOrderRefundRoute, async (c) => {
|
|
149
|
+
const result = await kernel.services.orders.undoRefund(
|
|
150
|
+
c.req.param("id"),
|
|
151
|
+
c.req.param("refundId"),
|
|
152
|
+
c.get("actor"),
|
|
153
|
+
);
|
|
154
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
155
|
+
return c.json({ data: result.value });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// @ts-expect-error -- openapi handler union return type
|
|
159
|
+
router.openapi(listOrderRefundsRoute, async (c) => {
|
|
160
|
+
const result = await kernel.services.orders.listRefunds(c.req.param("id"), c.get("actor"));
|
|
161
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
162
|
+
return c.json({ data: result.value });
|
|
163
|
+
});
|
|
164
|
+
|
|
85
165
|
// @ts-expect-error -- openapi handler union return type
|
|
86
166
|
router.openapi(captureOrderRoute, async (c) => {
|
|
87
167
|
const body = c.req.valid("json") as { amount?: number } | undefined;
|
|
@@ -94,6 +174,47 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
94
174
|
return c.json({ data: result.value });
|
|
95
175
|
});
|
|
96
176
|
|
|
177
|
+
// @ts-expect-error -- openapi handler union return type
|
|
178
|
+
router.openapi(addOrderLineItemRoute, async (c) => {
|
|
179
|
+
try {
|
|
180
|
+
assertPermission(c.get("actor"), "orders:manage");
|
|
181
|
+
} catch (error) {
|
|
182
|
+
return c.json(mapErrorToResponse(error), mapErrorToStatus(error));
|
|
183
|
+
}
|
|
184
|
+
const body = c.req.valid("json");
|
|
185
|
+
const result = await kernel.services.orders.addLineItem(
|
|
186
|
+
c.req.param("id"),
|
|
187
|
+
body,
|
|
188
|
+
c.get("actor"),
|
|
189
|
+
);
|
|
190
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
191
|
+
return c.json({ data: result.value }, 201);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// @ts-expect-error -- openapi handler union return type
|
|
195
|
+
router.openapi(updateOrderLineItemRoute, async (c) => {
|
|
196
|
+
const body = c.req.valid("json");
|
|
197
|
+
const result = await kernel.services.orders.updateOrderLineItem(
|
|
198
|
+
c.req.param("id"),
|
|
199
|
+
c.req.param("lineItemId"),
|
|
200
|
+
body,
|
|
201
|
+
c.get("actor"),
|
|
202
|
+
);
|
|
203
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
204
|
+
return c.json({ data: result.value });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// @ts-expect-error -- openapi handler union return type
|
|
208
|
+
router.openapi(removeOrderLineItemRoute, async (c) => {
|
|
209
|
+
const result = await kernel.services.orders.removeLineItem(
|
|
210
|
+
c.req.param("id"),
|
|
211
|
+
c.req.param("lineItemId"),
|
|
212
|
+
c.get("actor"),
|
|
213
|
+
);
|
|
214
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
215
|
+
return c.json({ data: result.value });
|
|
216
|
+
});
|
|
217
|
+
|
|
97
218
|
// @ts-expect-error -- openapi() enforces strict response typing but our handler
|
|
98
219
|
// returns union responses (200 | 400 | 404). The route definition documents the
|
|
99
220
|
// contract; the handler returns dynamic status.
|
|
@@ -112,6 +233,32 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
112
233
|
return c.json({ data: result.value });
|
|
113
234
|
});
|
|
114
235
|
|
|
236
|
+
// @ts-expect-error -- openapi handler union return type
|
|
237
|
+
router.openapi(createOrderFulfillmentRoute, async (c) => {
|
|
238
|
+
const actor = c.get("actor");
|
|
239
|
+
const orderId = c.req.param("id");
|
|
240
|
+
|
|
241
|
+
// Verify the order exists and the actor has access before recording
|
|
242
|
+
const orderResult = await kernel.services.orders.getById(orderId, actor);
|
|
243
|
+
if (!orderResult.ok) return c.json(mapErrorToResponse(orderResult.error), mapErrorToStatus(orderResult.error));
|
|
244
|
+
|
|
245
|
+
const body = c.req.valid("json") as {
|
|
246
|
+
lineItems: Array<{ orderLineItemId: string; quantity: number }>;
|
|
247
|
+
carrier?: string;
|
|
248
|
+
trackingNumber?: string;
|
|
249
|
+
trackingUrl?: string;
|
|
250
|
+
type?: string;
|
|
251
|
+
status?: string;
|
|
252
|
+
metadata?: Record<string, unknown>;
|
|
253
|
+
};
|
|
254
|
+
const result = await kernel.services.fulfillment.createFulfillment(
|
|
255
|
+
{ ...body, orderId: orderResult.value.id },
|
|
256
|
+
actor,
|
|
257
|
+
);
|
|
258
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
259
|
+
return c.json({ data: result.value }, 201);
|
|
260
|
+
});
|
|
261
|
+
|
|
115
262
|
// @ts-expect-error -- openapi handler union return type
|
|
116
263
|
router.openapi(getOrderFulfillmentsRoute, async (c) => {
|
|
117
264
|
const orderId = c.req.param("id");
|
|
@@ -4,7 +4,7 @@ import { CommerceValidationError } from "../../../kernel/errors.js";
|
|
|
4
4
|
import type { DrizzleDatabase } from "../../../kernel/database/drizzle-db.js";
|
|
5
5
|
import { CatalogRepository } from "../../../modules/catalog/repository/index.js";
|
|
6
6
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
7
|
-
import { setBasePriceRoute, createModifierRoute, listPricesRoute } from "../schemas/pricing.js";
|
|
7
|
+
import { setBasePriceRoute, createModifierRoute, listPricesRoute, listModifiersRoute, updateModifierRoute, deleteModifierRoute } from "../schemas/pricing.js";
|
|
8
8
|
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
|
|
9
9
|
|
|
10
10
|
export function pricingRoutes(kernel: Kernel) {
|
|
@@ -45,6 +45,49 @@ export function pricingRoutes(kernel: Kernel) {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
router.use("/modifiers", requirePerm("pricing:manage"));
|
|
48
|
+
router.use("/modifiers/:id", requirePerm("pricing:manage"));
|
|
49
|
+
|
|
50
|
+
// @ts-expect-error -- openapi handler union return type
|
|
51
|
+
router.openapi(listModifiersRoute, async (c) => {
|
|
52
|
+
const actor = c.get("actor");
|
|
53
|
+
const entityId = c.req.query("entityId");
|
|
54
|
+
const currency = c.req.query("currency");
|
|
55
|
+
const active = c.req.query("active");
|
|
56
|
+
|
|
57
|
+
const result = await kernel.services.pricing.listModifiers({
|
|
58
|
+
...(entityId !== undefined ? { entityId } : {}),
|
|
59
|
+
...(currency !== undefined ? { currency } : {}),
|
|
60
|
+
...(active === "true" ? { active: true } : {}),
|
|
61
|
+
}, actor);
|
|
62
|
+
if (!result.ok) {
|
|
63
|
+
return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
64
|
+
}
|
|
65
|
+
return c.json({ data: result.value });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// @ts-expect-error -- openapi handler union return type
|
|
69
|
+
router.openapi(updateModifierRoute, async (c) => {
|
|
70
|
+
const actor = c.get("actor");
|
|
71
|
+
const result = await kernel.services.pricing.updateModifier(
|
|
72
|
+
c.req.param("id"),
|
|
73
|
+
c.req.valid("json"),
|
|
74
|
+
actor,
|
|
75
|
+
);
|
|
76
|
+
if (!result.ok) {
|
|
77
|
+
return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
78
|
+
}
|
|
79
|
+
return c.json({ data: result.value });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// @ts-expect-error -- openapi handler union return type
|
|
83
|
+
router.openapi(deleteModifierRoute, async (c) => {
|
|
84
|
+
const actor = c.get("actor");
|
|
85
|
+
const result = await kernel.services.pricing.deleteModifier(c.req.param("id"), actor);
|
|
86
|
+
if (!result.ok) {
|
|
87
|
+
return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
88
|
+
}
|
|
89
|
+
return c.json({ data: result.value });
|
|
90
|
+
});
|
|
48
91
|
|
|
49
92
|
// @ts-expect-error -- openapi() enforces strict response typing but our handler
|
|
50
93
|
// returns union responses (201 | 400 | 422). The route definition documents the
|