@porulle/core 0.10.8 → 0.13.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/auth-schema-guard.d.ts +2444 -0
- package/dist/auth/auth-schema-guard.d.ts.map +1 -0
- package/dist/auth/auth-schema-guard.js +113 -0
- package/dist/auth/auth-schema.d.ts +228 -0
- package/dist/auth/auth-schema.d.ts.map +1 -1
- package/dist/auth/auth-schema.js +24 -2
- package/dist/auth/middleware.d.ts +1 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +13 -5
- package/dist/auth/org.d.ts +8 -2
- package/dist/auth/org.d.ts.map +1 -1
- package/dist/auth/org.js +12 -4
- package/dist/auth/organization-guard.d.ts +5 -0
- package/dist/auth/organization-guard.d.ts.map +1 -0
- package/dist/auth/organization-guard.js +124 -0
- package/dist/auth/permissions.d.ts +10 -0
- package/dist/auth/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +31 -7
- package/dist/auth/role-authority.d.ts +44 -0
- package/dist/auth/role-authority.d.ts.map +1 -0
- package/dist/auth/role-authority.js +85 -0
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +17 -0
- package/dist/auth/strict-org-resolution.d.ts.map +1 -1
- package/dist/auth/strict-org-resolution.js +9 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +33 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +10 -9
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
- package/dist/interfaces/rest/audit-middleware.js +2 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/customer-portal.js +9 -8
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +4 -0
- package/dist/interfaces/rest/route-coverage.d.ts +14 -0
- package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
- package/dist/interfaces/rest/route-coverage.js +81 -0
- package/dist/interfaces/rest/router.d.ts +7 -2
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/router.js +8 -4
- package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
- package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/staff.js +126 -66
- package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
- package/dist/interfaces/rest/routes/audit.js +3 -3
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +13 -10
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +47 -4
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +30 -35
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/documents.js +10 -5
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +2 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +7 -3
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +26 -8
- package/dist/interfaces/rest/routes/pricing.js +2 -2
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +13 -10
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +14 -1
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +86 -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/orders.d.ts +204 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +1 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.js +1 -0
- package/dist/interfaces/rest/utils.d.ts +15 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.js +45 -2
- package/dist/kernel/compensation/executor.js +2 -2
- package/dist/kernel/database/tx-context.d.ts +8 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -1
- package/dist/kernel/database/tx-context.js +13 -0
- package/dist/kernel/hooks/create-context.d.ts +2 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -1
- package/dist/kernel/hooks/create-context.js +1 -0
- package/dist/kernel/hooks/types.d.ts +2 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +9 -5
- package/dist/modules/analytics/service.js +2 -2
- package/dist/modules/audit/service.js +3 -3
- package/dist/modules/cart/repository/index.d.ts +13 -7
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +16 -10
- 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 +4 -0
- package/dist/modules/cart/service.d.ts +22 -9
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +57 -33
- package/dist/modules/catalog/brand-service.js +7 -7
- package/dist/modules/catalog/category-service.js +7 -7
- package/dist/modules/catalog/entity-service.d.ts +19 -10
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +180 -51
- package/dist/modules/catalog/module.d.ts +6 -1
- package/dist/modules/catalog/module.d.ts.map +1 -1
- package/dist/modules/catalog/module.js +6 -1
- package/dist/modules/catalog/ownership.d.ts +5 -0
- package/dist/modules/catalog/ownership.d.ts.map +1 -0
- package/dist/modules/catalog/ownership.js +11 -0
- package/dist/modules/catalog/read-policy.d.ts +9 -0
- package/dist/modules/catalog/read-policy.d.ts.map +1 -0
- package/dist/modules/catalog/read-policy.js +8 -0
- package/dist/modules/catalog/repository/index.d.ts +55 -3
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +335 -6
- package/dist/modules/catalog/revision.d.ts +4 -0
- package/dist/modules/catalog/revision.d.ts.map +1 -0
- package/dist/modules/catalog/revision.js +33 -0
- package/dist/modules/catalog/schema.d.ts +979 -110
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +112 -2
- package/dist/modules/catalog/schemas.d.ts +2 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +2 -0
- package/dist/modules/catalog/service.d.ts +80 -15
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +522 -14
- package/dist/modules/channels/adapter.d.ts +85 -0
- package/dist/modules/channels/adapter.d.ts.map +1 -1
- package/dist/modules/channels/adapter.js +9 -1
- package/dist/modules/customers/module.d.ts.map +1 -1
- package/dist/modules/customers/module.js +1 -0
- package/dist/modules/customers/service.d.ts +2 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +21 -20
- package/dist/modules/documents/module.d.ts.map +1 -1
- package/dist/modules/documents/module.js +1 -0
- package/dist/modules/documents/service.d.ts +18 -4
- package/dist/modules/documents/service.d.ts.map +1 -1
- package/dist/modules/documents/service.js +54 -16
- package/dist/modules/fulfillment/module.d.ts.map +1 -1
- package/dist/modules/fulfillment/module.js +1 -0
- package/dist/modules/fulfillment/service.d.ts +2 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +15 -6
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +15 -14
- package/dist/modules/media/module.d.ts +68 -0
- package/dist/modules/media/module.d.ts.map +1 -1
- package/dist/modules/media/module.js +2 -0
- package/dist/modules/media/schema.d.ts +70 -0
- package/dist/modules/media/schema.d.ts.map +1 -1
- package/dist/modules/media/schema.js +20 -2
- package/dist/modules/media/service.d.ts +8 -1
- package/dist/modules/media/service.d.ts.map +1 -1
- package/dist/modules/media/service.js +71 -23
- package/dist/modules/orders/guest-access.d.ts +27 -0
- package/dist/modules/orders/guest-access.d.ts.map +1 -0
- package/dist/modules/orders/guest-access.js +46 -0
- package/dist/modules/orders/module.d.ts.map +1 -1
- package/dist/modules/orders/module.js +1 -0
- package/dist/modules/orders/quote.d.ts.map +1 -1
- package/dist/modules/orders/quote.js +1 -0
- package/dist/modules/orders/repository/index.d.ts +1 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +2 -2
- package/dist/modules/orders/schema.d.ts +17 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +5 -3
- package/dist/modules/orders/service.d.ts +16 -7
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +184 -55
- package/dist/modules/pricing/module.d.ts.map +1 -1
- package/dist/modules/pricing/module.js +1 -0
- package/dist/modules/pricing/schema.d.ts +17 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -1
- package/dist/modules/pricing/schema.js +1 -0
- package/dist/modules/pricing/schemas.d.ts +1 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -1
- package/dist/modules/pricing/schemas.js +1 -0
- package/dist/modules/pricing/service.d.ts +2 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +18 -12
- package/dist/modules/promotions/module.d.ts.map +1 -1
- package/dist/modules/promotions/module.js +1 -0
- package/dist/modules/promotions/service.d.ts +3 -0
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +19 -11
- package/dist/modules/search/adapter.d.ts +8 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +3 -0
- package/dist/modules/search/service.d.ts +5 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +131 -11
- package/dist/modules/settings/module.d.ts.map +1 -1
- package/dist/modules/settings/module.js +1 -0
- package/dist/modules/settings/service.d.ts +3 -0
- package/dist/modules/settings/service.d.ts.map +1 -1
- package/dist/modules/settings/service.js +6 -4
- package/dist/modules/shipping/service.js +9 -9
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +1 -0
- package/dist/modules/tax/service.d.ts +3 -0
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +11 -9
- package/dist/modules/webhooks/hook.js +2 -2
- package/dist/modules/webhooks/module.d.ts.map +1 -1
- package/dist/modules/webhooks/module.js +1 -0
- package/dist/modules/webhooks/service.d.ts +2 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -1
- package/dist/modules/webhooks/service.js +4 -4
- package/dist/runtime/kernel-modules.d.ts +73 -0
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +10 -2
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +35 -13
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +4 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +2 -0
- package/dist/test-utils/test-actors.js +2 -2
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +11 -0
- package/package.json +6 -5
- package/src/auth/auth-schema-guard.ts +143 -0
- package/src/auth/auth-schema.ts +50 -19
- package/src/auth/middleware.ts +17 -5
- package/src/auth/org.ts +15 -3
- package/src/auth/organization-guard.ts +158 -0
- package/src/auth/permissions.ts +31 -8
- package/src/auth/role-authority.ts +107 -0
- package/src/auth/setup.ts +17 -0
- package/src/auth/strict-org-resolution.ts +7 -1
- package/src/auth/types.ts +1 -1
- package/src/config/defaults.ts +2 -0
- package/src/config/types.ts +32 -4
- package/src/hooks/checkout.ts +19 -12
- package/src/index.ts +30 -6
- package/src/interfaces/rest/audit-middleware.ts +2 -0
- package/src/interfaces/rest/customer-portal.ts +31 -24
- package/src/interfaces/rest/index.ts +4 -0
- package/src/interfaces/rest/route-coverage.ts +103 -0
- package/src/interfaces/rest/router.ts +16 -10
- package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
- package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
- package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
- package/src/interfaces/rest/routes/admin/staff.ts +161 -72
- package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
- package/src/interfaces/rest/routes/audit.ts +3 -3
- package/src/interfaces/rest/routes/carts.ts +17 -7
- package/src/interfaces/rest/routes/catalog.ts +96 -0
- package/src/interfaces/rest/routes/checkout.ts +41 -38
- package/src/interfaces/rest/routes/documents.ts +15 -1
- package/src/interfaces/rest/routes/inventory.ts +2 -0
- package/src/interfaces/rest/routes/media.ts +8 -3
- package/src/interfaces/rest/routes/orders.ts +27 -8
- package/src/interfaces/rest/routes/pricing.ts +2 -2
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/routes/search.ts +19 -1
- package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
- package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
- package/src/interfaces/rest/schemas/catalog.ts +92 -0
- package/src/interfaces/rest/schemas/search.ts +2 -0
- package/src/interfaces/rest/utils.ts +61 -2
- package/src/kernel/compensation/executor.ts +2 -2
- package/src/kernel/database/tx-context.ts +25 -0
- package/src/kernel/hooks/create-context.ts +3 -0
- package/src/kernel/hooks/types.ts +2 -0
- package/src/kernel/plugin/manifest.ts +9 -5
- package/src/modules/analytics/service.ts +2 -2
- package/src/modules/audit/service.ts +3 -3
- package/src/modules/cart/repository/index.ts +22 -8
- package/src/modules/cart/schema.ts +4 -0
- package/src/modules/cart/service.ts +62 -27
- package/src/modules/catalog/brand-service.ts +7 -7
- package/src/modules/catalog/category-service.ts +7 -7
- package/src/modules/catalog/entity-service.ts +188 -53
- package/src/modules/catalog/module.ts +15 -0
- package/src/modules/catalog/ownership.ts +17 -0
- package/src/modules/catalog/read-policy.ts +16 -0
- package/src/modules/catalog/repository/index.ts +566 -4
- package/src/modules/catalog/revision.ts +35 -0
- package/src/modules/catalog/schema.ts +169 -1
- package/src/modules/catalog/schemas.ts +2 -0
- package/src/modules/catalog/service.ts +766 -26
- package/src/modules/channels/adapter.ts +107 -1
- package/src/modules/customers/module.ts +2 -0
- package/src/modules/customers/service.ts +23 -22
- package/src/modules/documents/module.ts +2 -0
- package/src/modules/documents/service.ts +74 -11
- package/src/modules/fulfillment/module.ts +2 -0
- package/src/modules/fulfillment/service.ts +17 -6
- package/src/modules/inventory/service.ts +15 -24
- package/src/modules/media/module.ts +2 -0
- package/src/modules/media/schema.ts +23 -2
- package/src/modules/media/service.ts +89 -28
- package/src/modules/orders/guest-access.ts +55 -0
- package/src/modules/orders/module.ts +1 -0
- package/src/modules/orders/quote.ts +1 -0
- package/src/modules/orders/repository/index.ts +2 -0
- package/src/modules/orders/schema.ts +5 -3
- package/src/modules/orders/service.ts +302 -52
- package/src/modules/pricing/module.ts +2 -0
- package/src/modules/pricing/schema.ts +1 -0
- package/src/modules/pricing/schemas.ts +1 -0
- package/src/modules/pricing/service.ts +21 -18
- package/src/modules/promotions/module.ts +2 -0
- package/src/modules/promotions/service.ts +25 -16
- package/src/modules/search/adapter.ts +8 -0
- package/src/modules/search/module.ts +3 -0
- package/src/modules/search/service.ts +142 -12
- package/src/modules/settings/module.ts +2 -0
- package/src/modules/settings/service.ts +8 -4
- package/src/modules/shipping/service.ts +9 -9
- package/src/modules/tax/module.ts +1 -0
- package/src/modules/tax/service.ts +13 -9
- package/src/modules/webhooks/hook.ts +2 -2
- package/src/modules/webhooks/module.ts +2 -0
- package/src/modules/webhooks/service.ts +6 -4
- package/src/runtime/server.ts +16 -2
- package/src/test-utils/create-pglite-adapter.ts +34 -12
- package/src/test-utils/create-test-config.ts +10 -6
- package/src/test-utils/rest-api-test-utils.ts +2 -0
- package/src/test-utils/test-actors.ts +2 -2
- package/src/utils/id.ts +20 -0
|
@@ -10,6 +10,7 @@ export interface ChannelConnectorCapabilities {
|
|
|
10
10
|
readonly importCatalog: boolean;
|
|
11
11
|
readonly importInventory: boolean;
|
|
12
12
|
readonly pushOrder: boolean;
|
|
13
|
+
readonly pushCatalog?: boolean;
|
|
13
14
|
readonly receiveWebhooks: boolean;
|
|
14
15
|
readonly reserve?: boolean;
|
|
15
16
|
}
|
|
@@ -24,11 +25,49 @@ export interface ChannelStore {
|
|
|
24
25
|
webhookSecret: string | null;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
export interface ChannelCatalogLocalizedAttributes {
|
|
29
|
+
locale: string;
|
|
30
|
+
title: string;
|
|
31
|
+
subtitle?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
richDescription?: unknown;
|
|
34
|
+
seoTitle?: string;
|
|
35
|
+
seoDescription?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ChannelCatalogImage {
|
|
39
|
+
externalId?: string;
|
|
40
|
+
url: string;
|
|
41
|
+
alt?: string;
|
|
42
|
+
role: "primary" | "gallery" | "thumbnail" | "video" | "document";
|
|
43
|
+
sortOrder?: number;
|
|
44
|
+
variantExternalIds?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ChannelCatalogOptionType {
|
|
48
|
+
name: string;
|
|
49
|
+
displayName: string;
|
|
50
|
+
sortOrder?: number;
|
|
51
|
+
values: Array<{
|
|
52
|
+
value: string;
|
|
53
|
+
displayValue: string;
|
|
54
|
+
sortOrder?: number;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ChannelCatalogPrice {
|
|
59
|
+
currency: string;
|
|
60
|
+
amount: number;
|
|
61
|
+
compareAtAmount?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
27
64
|
export interface ChannelCatalogVariant {
|
|
28
65
|
externalId: string;
|
|
29
66
|
sku?: string;
|
|
30
67
|
barcode?: string;
|
|
31
68
|
metadata?: Record<string, unknown>;
|
|
69
|
+
optionValues?: Record<string, string>;
|
|
70
|
+
prices?: ChannelCatalogPrice[];
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
export interface ChannelCatalogItem {
|
|
@@ -38,6 +77,13 @@ export interface ChannelCatalogItem {
|
|
|
38
77
|
description?: string;
|
|
39
78
|
variants: ChannelCatalogVariant[];
|
|
40
79
|
metadata?: Record<string, unknown>;
|
|
80
|
+
attributes?: ChannelCatalogLocalizedAttributes[];
|
|
81
|
+
images?: ChannelCatalogImage[];
|
|
82
|
+
options?: ChannelCatalogOptionType[];
|
|
83
|
+
tags?: string[];
|
|
84
|
+
brand?: string;
|
|
85
|
+
categories?: string[];
|
|
86
|
+
status?: "draft" | "active" | "archived" | "discontinued";
|
|
41
87
|
}
|
|
42
88
|
|
|
43
89
|
export interface ChannelCatalogPage {
|
|
@@ -76,6 +122,54 @@ export interface ChannelPushOrderResult {
|
|
|
76
122
|
remoteUrl?: string;
|
|
77
123
|
}
|
|
78
124
|
|
|
125
|
+
export type ChannelPushCatalogIntent = "filterable" | "display" | "tag";
|
|
126
|
+
|
|
127
|
+
export interface ChannelPushCatalogField {
|
|
128
|
+
fieldPath: string;
|
|
129
|
+
intent: ChannelPushCatalogIntent;
|
|
130
|
+
value: unknown;
|
|
131
|
+
locale?: string;
|
|
132
|
+
remoteKey?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ChannelPushCatalogImage {
|
|
136
|
+
externalId?: string;
|
|
137
|
+
url: string;
|
|
138
|
+
alt?: string;
|
|
139
|
+
role: "primary" | "gallery" | "thumbnail" | "video" | "document";
|
|
140
|
+
sortOrder?: number;
|
|
141
|
+
variantExternalIds?: string[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ChannelPushCatalogVariant {
|
|
145
|
+
externalId: string;
|
|
146
|
+
fields: ChannelPushCatalogField[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ChannelPushCatalogItem {
|
|
150
|
+
externalId: string;
|
|
151
|
+
variants?: ChannelPushCatalogVariant[];
|
|
152
|
+
fields: ChannelPushCatalogField[];
|
|
153
|
+
images?: ChannelPushCatalogImage[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ChannelPushCatalogPreviousField {
|
|
157
|
+
fieldPath: string;
|
|
158
|
+
value: unknown;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ChannelPushCatalogItemOutcome {
|
|
162
|
+
externalId: string;
|
|
163
|
+
ok: boolean;
|
|
164
|
+
error?: ChannelConnectorError;
|
|
165
|
+
remoteUpdatedAt?: string;
|
|
166
|
+
previousFields?: ChannelPushCatalogPreviousField[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ChannelPushCatalogResult {
|
|
170
|
+
outcomes: ChannelPushCatalogItemOutcome[];
|
|
171
|
+
}
|
|
172
|
+
|
|
79
173
|
export interface ChannelOrderStatus {
|
|
80
174
|
status: "pending" | "confirmed" | "failed" | "cancelled" | "fulfilled";
|
|
81
175
|
}
|
|
@@ -113,6 +207,11 @@ export interface ChannelConnector {
|
|
|
113
207
|
importCatalog(store: ChannelStore, cursor?: string): Promise<Result<ChannelCatalogPage>>;
|
|
114
208
|
fetchInventory(store: ChannelStore, ids?: string[]): Promise<Result<ChannelInventoryLevel[]>>;
|
|
115
209
|
pushOrder(store: ChannelStore, slice: ChannelOrderSlice): Promise<Result<ChannelPushOrderResult, ChannelConnectorError>>;
|
|
210
|
+
pushCatalog?(
|
|
211
|
+
store: ChannelStore,
|
|
212
|
+
items: ChannelPushCatalogItem[],
|
|
213
|
+
opts?: { dryRun?: boolean },
|
|
214
|
+
): Promise<Result<ChannelPushCatalogResult, ChannelConnectorError>>;
|
|
116
215
|
fetchOrderStatus(store: ChannelStore, remoteId: string): Promise<Result<ChannelOrderStatus, ChannelConnectorError>>;
|
|
117
216
|
verifyWebhook(store: ChannelStore, request: Request): Promise<Result<ChannelWebhookEvent>>;
|
|
118
217
|
verifyAppWebhook?(
|
|
@@ -135,5 +234,12 @@ export interface ChannelConnector {
|
|
|
135
234
|
}
|
|
136
235
|
|
|
137
236
|
export function defineChannelConnector<T extends ChannelConnector>(connector: T): T {
|
|
138
|
-
return connector;
|
|
237
|
+
if (connector.capabilities.pushCatalog !== undefined) return connector;
|
|
238
|
+
return {
|
|
239
|
+
...connector,
|
|
240
|
+
capabilities: {
|
|
241
|
+
...connector.capabilities,
|
|
242
|
+
pushCatalog: false,
|
|
243
|
+
},
|
|
244
|
+
} as T;
|
|
139
245
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
1
2
|
import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
|
|
2
3
|
import { defineModule } from "../../kernel/module/index.js";
|
|
3
4
|
import { CustomersRepository } from "./repository/index.js";
|
|
@@ -21,6 +22,7 @@ export const customersModule = defineModule({
|
|
|
21
22
|
new CustomerService({
|
|
22
23
|
repository: new CustomersRepository(deps.db.db as DrizzleDatabase),
|
|
23
24
|
hooks: deps.hooks,
|
|
25
|
+
config: deps.config as CommerceConfig,
|
|
24
26
|
services: deps.services,
|
|
25
27
|
database: deps.db,
|
|
26
28
|
}),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import { assertPermission } from "../../auth/permissions.js";
|
|
3
3
|
import type { Actor } from "../../auth/types.js";
|
|
4
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
4
5
|
import { CommerceNotFoundError, toCommerceError } from "../../kernel/errors.js";
|
|
5
6
|
import { runAfterHooks } from "../../kernel/hooks/executor.js";
|
|
6
7
|
import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
interface CustomerServiceDeps {
|
|
23
24
|
repository: CustomersRepository;
|
|
24
25
|
hooks: HookRegistry;
|
|
26
|
+
config: CommerceConfig;
|
|
25
27
|
services: Record<string, unknown>;
|
|
26
28
|
database: DatabaseAdapter;
|
|
27
29
|
}
|
|
@@ -30,6 +32,7 @@ function hookContext(
|
|
|
30
32
|
actor: Actor | null,
|
|
31
33
|
services: Record<string, unknown>,
|
|
32
34
|
database: DatabaseAdapter,
|
|
35
|
+
config: CommerceConfig,
|
|
33
36
|
tx: unknown,
|
|
34
37
|
): HookContext {
|
|
35
38
|
return createHookContext({
|
|
@@ -39,6 +42,7 @@ function hookContext(
|
|
|
39
42
|
services,
|
|
40
43
|
context: { moduleName: "customers" },
|
|
41
44
|
database: { db: database.db as PluginDb },
|
|
45
|
+
commerceConfig: config,
|
|
42
46
|
});
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -73,7 +77,7 @@ export class CustomerService {
|
|
|
73
77
|
return Err(toCommerceError(error));
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
const orgId =
|
|
80
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
77
81
|
const isWalkIn = input.userId === undefined;
|
|
78
82
|
const userId = input.userId ?? `anonymous_${crypto.randomUUID()}`;
|
|
79
83
|
const metadata = {
|
|
@@ -97,7 +101,7 @@ export class CustomerService {
|
|
|
97
101
|
const afterHooks = this.deps.hooks.resolve(
|
|
98
102
|
"customers.afterCreate",
|
|
99
103
|
) as AfterHook<Customer>[];
|
|
100
|
-
const hctx = hookContext(actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
104
|
+
const hctx = hookContext(actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
101
105
|
await runAfterHooks(afterHooks, null, customer, "create", hctx);
|
|
102
106
|
|
|
103
107
|
return Ok(customer);
|
|
@@ -107,7 +111,7 @@ export class CustomerService {
|
|
|
107
111
|
|
|
108
112
|
async listInteractions(customerId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<CustomerInteraction[]>> {
|
|
109
113
|
try { assertPermission(actor ?? null, "customers:read"); } catch (error) { return Err(toCommerceError(error)); }
|
|
110
|
-
const orgId =
|
|
114
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
111
115
|
return Ok(await this.repo.listInteractions(orgId, customerId, ctx));
|
|
112
116
|
}
|
|
113
117
|
|
|
@@ -118,7 +122,7 @@ export class CustomerService {
|
|
|
118
122
|
ctx?: TxContext,
|
|
119
123
|
): Promise<Result<CustomerInteraction>> {
|
|
120
124
|
try { assertPermission(actor ?? null, "customers:update"); } catch (error) { return Err(toCommerceError(error)); }
|
|
121
|
-
const orgId =
|
|
125
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
122
126
|
const customer = await this.repo.findById(orgId, customerId, ctx);
|
|
123
127
|
if (!customer) return Err(new CommerceNotFoundError("Customer not found."));
|
|
124
128
|
const interaction = await this.repo.createInteraction(
|
|
@@ -144,7 +148,7 @@ export class CustomerService {
|
|
|
144
148
|
ctx?: TxContext,
|
|
145
149
|
): Promise<Result<CustomerInteraction>> {
|
|
146
150
|
try { assertPermission(actor ?? null, "customers:update"); } catch (error) { return Err(toCommerceError(error)); }
|
|
147
|
-
const orgId =
|
|
151
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
148
152
|
const existing = await this.repo.findInteractionById(orgId, interactionId, ctx);
|
|
149
153
|
if (!existing || existing.customerId !== customerId) return Err(new CommerceNotFoundError("Interaction not found."));
|
|
150
154
|
const patch = {
|
|
@@ -160,7 +164,7 @@ export class CustomerService {
|
|
|
160
164
|
|
|
161
165
|
async deleteInteraction(customerId: string, interactionId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>> {
|
|
162
166
|
try { assertPermission(actor ?? null, "customers:update"); } catch (error) { return Err(toCommerceError(error)); }
|
|
163
|
-
const orgId =
|
|
167
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
164
168
|
const existing = await this.repo.findInteractionById(orgId, interactionId, ctx);
|
|
165
169
|
if (!existing || existing.customerId !== customerId) return Err(new CommerceNotFoundError("Interaction not found."));
|
|
166
170
|
await this.repo.deleteInteraction(orgId, interactionId, ctx);
|
|
@@ -190,7 +194,7 @@ export class CustomerService {
|
|
|
190
194
|
const afterHooks = this.deps.hooks.resolve(
|
|
191
195
|
"customers.afterCreate",
|
|
192
196
|
) as AfterHook<Customer>[];
|
|
193
|
-
const hctx = hookContext(actor, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
197
|
+
const hctx = hookContext(actor, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
194
198
|
await runAfterHooks(afterHooks, null, customer, "create", hctx);
|
|
195
199
|
|
|
196
200
|
return customer;
|
|
@@ -200,7 +204,7 @@ export class CustomerService {
|
|
|
200
204
|
actor?: Actor | null,
|
|
201
205
|
ctx?: TxContext,
|
|
202
206
|
): Promise<Result<Customer[]>> {
|
|
203
|
-
const orgId =
|
|
207
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
204
208
|
const customers = await this.repo.findAll(orgId, ctx);
|
|
205
209
|
return Ok(customers);
|
|
206
210
|
}
|
|
@@ -210,7 +214,7 @@ export class CustomerService {
|
|
|
210
214
|
actor?: Actor | null,
|
|
211
215
|
ctx?: TxContext,
|
|
212
216
|
): Promise<Result<Customer>> {
|
|
213
|
-
const orgId =
|
|
217
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
214
218
|
const customer = await this.repo.findById(orgId, id, ctx);
|
|
215
219
|
if (!customer) return Err(new CommerceNotFoundError("Customer not found."));
|
|
216
220
|
return Ok(customer);
|
|
@@ -221,7 +225,7 @@ export class CustomerService {
|
|
|
221
225
|
actor?: Actor | null,
|
|
222
226
|
ctx?: TxContext,
|
|
223
227
|
): Promise<Result<Customer>> {
|
|
224
|
-
const orgId =
|
|
228
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
225
229
|
const resolvedActor = actor ?? ctx?.actor ?? null;
|
|
226
230
|
const customer = await this.getOrCreateByUserId(
|
|
227
231
|
orgId,
|
|
@@ -247,7 +251,7 @@ export class CustomerService {
|
|
|
247
251
|
return Err(toCommerceError(error));
|
|
248
252
|
}
|
|
249
253
|
|
|
250
|
-
const orgId =
|
|
254
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
251
255
|
const existing = await this.repo.findById(orgId, id, ctx);
|
|
252
256
|
if (!existing) return Err(new CommerceNotFoundError("Customer not found."));
|
|
253
257
|
|
|
@@ -271,10 +275,7 @@ export class CustomerService {
|
|
|
271
275
|
"customers.afterUpdate",
|
|
272
276
|
) as AfterHook<Customer>[];
|
|
273
277
|
const hctx = hookContext(
|
|
274
|
-
actor ?? ctx?.actor ?? null,
|
|
275
|
-
this.deps.services,
|
|
276
|
-
this.deps.database,
|
|
277
|
-
ctx?.tx ?? null,
|
|
278
|
+
actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
|
|
278
279
|
);
|
|
279
280
|
await runAfterHooks(afterHooks, existing, updated, "update", hctx);
|
|
280
281
|
|
|
@@ -289,7 +290,7 @@ export class CustomerService {
|
|
|
289
290
|
actor?: Actor | null,
|
|
290
291
|
ctx?: TxContext,
|
|
291
292
|
): Promise<Result<Customer>> {
|
|
292
|
-
const orgId =
|
|
293
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
293
294
|
const resolvedActor = actor ?? ctx?.actor ?? null;
|
|
294
295
|
const customer = await this.getOrCreateByUserId(
|
|
295
296
|
orgId,
|
|
@@ -306,7 +307,7 @@ export class CustomerService {
|
|
|
306
307
|
const afterHooks = this.deps.hooks.resolve(
|
|
307
308
|
"customers.afterUpdate",
|
|
308
309
|
) as AfterHook<Customer>[];
|
|
309
|
-
const hctx = hookContext(resolvedActor, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
310
|
+
const hctx = hookContext(resolvedActor, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
310
311
|
await runAfterHooks(afterHooks, customer, updated, "update", hctx);
|
|
311
312
|
|
|
312
313
|
return Ok(updated);
|
|
@@ -317,7 +318,7 @@ export class CustomerService {
|
|
|
317
318
|
actor?: Actor | null,
|
|
318
319
|
ctx?: TxContext,
|
|
319
320
|
): Promise<Result<CustomerAddress[]>> {
|
|
320
|
-
const orgId =
|
|
321
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
321
322
|
const resolvedActor = actor ?? ctx?.actor ?? null;
|
|
322
323
|
const customer = await this.getOrCreateByUserId(
|
|
323
324
|
orgId,
|
|
@@ -338,7 +339,7 @@ export class CustomerService {
|
|
|
338
339
|
actor?: Actor | null,
|
|
339
340
|
ctx?: TxContext,
|
|
340
341
|
): Promise<Result<CustomerAddress>> {
|
|
341
|
-
const orgId =
|
|
342
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
342
343
|
const resolvedActor = actor ?? ctx?.actor ?? null;
|
|
343
344
|
const customer = await this.getOrCreateByUserId(
|
|
344
345
|
orgId,
|
|
@@ -376,7 +377,7 @@ export class CustomerService {
|
|
|
376
377
|
actor?: Actor | null,
|
|
377
378
|
ctx?: TxContext,
|
|
378
379
|
): Promise<Result<void>> {
|
|
379
|
-
const orgId =
|
|
380
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
380
381
|
const resolvedActor = actor ?? ctx?.actor ?? null;
|
|
381
382
|
const customer = await this.getOrCreateByUserId(
|
|
382
383
|
orgId,
|
|
@@ -403,7 +404,7 @@ export class CustomerService {
|
|
|
403
404
|
actor?: Actor | null,
|
|
404
405
|
ctx?: TxContext,
|
|
405
406
|
): Promise<{ id: string; name: string } | null> {
|
|
406
|
-
const orgId =
|
|
407
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
407
408
|
const user = await this.repo.findByPosPin(orgId, hashedPin, ctx);
|
|
408
409
|
if (!user) return null;
|
|
409
410
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
1
2
|
import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
|
|
2
3
|
import { defineModule } from "../../kernel/module/index.js";
|
|
3
4
|
import { DocumentsRepository } from "./repository/index.js";
|
|
@@ -15,5 +16,6 @@ export const documentsModule = defineModule<
|
|
|
15
16
|
new DocumentsService({
|
|
16
17
|
repository: new DocumentsRepository(deps.db.db as DrizzleDatabase),
|
|
17
18
|
services: deps.services as Record<string, unknown>,
|
|
19
|
+
config: deps.config as CommerceConfig,
|
|
18
20
|
}),
|
|
19
21
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import type { Actor } from "../../auth/types.js";
|
|
3
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
3
4
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
4
|
-
import {
|
|
5
|
+
import { hasPermission } from "../../auth/permissions.js";
|
|
6
|
+
import { CommerceForbiddenError, CommerceValidationError } from "../../kernel/errors.js";
|
|
5
7
|
import { Err, Ok, type Result } from "../../kernel/result.js";
|
|
6
8
|
import type { HydratedOrder, OrderService } from "../orders/service.js";
|
|
7
9
|
import type { SettingsService } from "../settings/service.js";
|
|
@@ -19,6 +21,15 @@ interface DocumentsServiceDeps {
|
|
|
19
21
|
// The shared kernel service container — orders/settings/email are resolved
|
|
20
22
|
// at call time (they may instantiate after this module).
|
|
21
23
|
services: Record<string, unknown>;
|
|
24
|
+
config: CommerceConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CustomerLookup {
|
|
28
|
+
getById(
|
|
29
|
+
id: string,
|
|
30
|
+
actor?: Actor | null,
|
|
31
|
+
ctx?: TxContext,
|
|
32
|
+
): Promise<{ ok: true; value: { email: string | null } } | { ok: false }>;
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
interface EmailAdapter {
|
|
@@ -33,10 +44,12 @@ const DEFAULT_INVOICE_PREFIX = "INV-";
|
|
|
33
44
|
export class DocumentsService {
|
|
34
45
|
private repository: DocumentsRepository;
|
|
35
46
|
private services: Record<string, unknown>;
|
|
47
|
+
private config: CommerceConfig;
|
|
36
48
|
|
|
37
49
|
constructor(deps: DocumentsServiceDeps) {
|
|
38
50
|
this.repository = deps.repository;
|
|
39
51
|
this.services = deps.services;
|
|
52
|
+
this.config = deps.config;
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
private get orders(): OrderService {
|
|
@@ -90,16 +103,18 @@ export class DocumentsService {
|
|
|
90
103
|
orderId: string,
|
|
91
104
|
actor: Actor | null,
|
|
92
105
|
ctx?: TxContext,
|
|
106
|
+
guestCredential?: string,
|
|
93
107
|
): Promise<Result<HydratedOrder>> {
|
|
94
|
-
return this.orders.getById(orderId, actor, ctx);
|
|
108
|
+
return this.orders.getById(orderId, actor, ctx, guestCredential);
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
async renderInvoiceHtml(
|
|
98
112
|
orderId: string,
|
|
99
113
|
actor: Actor | null,
|
|
100
114
|
ctx?: TxContext,
|
|
115
|
+
guestCredential?: string,
|
|
101
116
|
): Promise<Result<{ html: string; invoiceNumber: string }>> {
|
|
102
|
-
const order = await this.loadOrder(orderId, actor, ctx);
|
|
117
|
+
const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
|
|
103
118
|
if (!order.ok) return order;
|
|
104
119
|
const branding = await this.brandingFor(order.value.organizationId, ctx);
|
|
105
120
|
const doc = await this.issueInvoiceNumber(order.value, ctx);
|
|
@@ -113,8 +128,9 @@ export class DocumentsService {
|
|
|
113
128
|
orderId: string,
|
|
114
129
|
actor: Actor | null,
|
|
115
130
|
ctx?: TxContext,
|
|
131
|
+
guestCredential?: string,
|
|
116
132
|
): Promise<Result<{ pdf: Uint8Array; invoiceNumber: string }>> {
|
|
117
|
-
const order = await this.loadOrder(orderId, actor, ctx);
|
|
133
|
+
const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
|
|
118
134
|
if (!order.ok) return order;
|
|
119
135
|
const branding = await this.brandingFor(order.value.organizationId, ctx);
|
|
120
136
|
const doc = await this.issueInvoiceNumber(order.value, ctx);
|
|
@@ -130,39 +146,86 @@ export class DocumentsService {
|
|
|
130
146
|
orderId: string,
|
|
131
147
|
actor: Actor | null,
|
|
132
148
|
ctx?: TxContext,
|
|
149
|
+
guestCredential?: string,
|
|
133
150
|
): Promise<Result<{ html: string }>> {
|
|
134
|
-
const order = await this.loadOrder(orderId, actor, ctx);
|
|
151
|
+
const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
|
|
135
152
|
if (!order.ok) return order;
|
|
136
153
|
const branding = await this.brandingFor(order.value.organizationId, ctx);
|
|
137
154
|
return Ok({ html: receiptHtml({ order: order.value, branding }) });
|
|
138
155
|
}
|
|
139
156
|
|
|
157
|
+
/**
|
|
158
|
+
* The address that belongs to this order: its customer profile's email.
|
|
159
|
+
* A guest order has no customer profile and therefore no address of its own.
|
|
160
|
+
*/
|
|
161
|
+
private async orderEmail(
|
|
162
|
+
order: HydratedOrder,
|
|
163
|
+
actor: Actor | null,
|
|
164
|
+
ctx?: TxContext,
|
|
165
|
+
): Promise<string | null> {
|
|
166
|
+
if (!order.customerId) return null;
|
|
167
|
+
const customers = this.services.customers as CustomerLookup | undefined;
|
|
168
|
+
if (!customers) return null;
|
|
169
|
+
const customer = await customers.getById(order.customerId, actor, ctx);
|
|
170
|
+
return customer.ok ? (customer.value.email ?? null) : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
140
173
|
/**
|
|
141
174
|
* Emails the invoice via the configured email adapter. The adapter's
|
|
142
175
|
* template contract is (template, to, data) — the rendered HTML and the
|
|
143
176
|
* fiscal number ride in `data` so template-based adapters can use either.
|
|
177
|
+
*
|
|
178
|
+
* The destination is the order's own address, not the caller's choice. A
|
|
179
|
+
* cart secret authorizes reading an order, and a caller-supplied `to` turned
|
|
180
|
+
* that read into delivery: items, totals and shipping address mailed to any
|
|
181
|
+
* address, with nothing left in the shopper's inbox. Only staff holding
|
|
182
|
+
* org-wide `orders:read` may name a different recipient.
|
|
144
183
|
*/
|
|
145
184
|
async emailInvoice(
|
|
146
185
|
orderId: string,
|
|
147
186
|
to: string,
|
|
148
187
|
actor: Actor | null,
|
|
149
188
|
ctx?: TxContext,
|
|
189
|
+
guestCredential?: string,
|
|
150
190
|
): Promise<Result<{ sent: true; invoiceNumber: string; to: string }>> {
|
|
151
191
|
const email = this.services.email as EmailAdapter | undefined;
|
|
152
192
|
if (!email) {
|
|
153
193
|
return Err(new CommerceValidationError("No email adapter is configured."));
|
|
154
194
|
}
|
|
155
|
-
const rendered = await this.renderInvoiceHtml(orderId, actor, ctx);
|
|
195
|
+
const rendered = await this.renderInvoiceHtml(orderId, actor, ctx, guestCredential);
|
|
156
196
|
if (!rendered.ok) return rendered;
|
|
157
197
|
|
|
158
|
-
const order = await this.loadOrder(orderId, actor, ctx);
|
|
198
|
+
const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
|
|
159
199
|
if (!order.ok) return order;
|
|
160
200
|
|
|
201
|
+
const ownAddress = await this.orderEmail(order.value, actor, ctx);
|
|
202
|
+
const mayDirect = hasPermission(actor, "orders:read");
|
|
203
|
+
const requested = to.trim();
|
|
204
|
+
|
|
205
|
+
let destination: string;
|
|
206
|
+
if (mayDirect && requested.length > 0) {
|
|
207
|
+
destination = requested;
|
|
208
|
+
} else if (ownAddress === null) {
|
|
209
|
+
return Err(
|
|
210
|
+
new CommerceValidationError(
|
|
211
|
+
"This order has no email address of its own. Render the invoice instead, or send it as staff.",
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
} else if (requested.length > 0 && requested.toLowerCase() !== ownAddress.toLowerCase()) {
|
|
215
|
+
return Err(
|
|
216
|
+
new CommerceForbiddenError(
|
|
217
|
+
"An invoice can only be emailed to the address on the order.",
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
destination = ownAddress;
|
|
222
|
+
}
|
|
223
|
+
|
|
161
224
|
await email.send({
|
|
162
225
|
template: "order-invoice",
|
|
163
|
-
to,
|
|
226
|
+
to: destination,
|
|
164
227
|
data: {
|
|
165
|
-
orgId:
|
|
228
|
+
orgId: resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config),
|
|
166
229
|
orderId,
|
|
167
230
|
orderNumber: order.value.orderNumber,
|
|
168
231
|
invoiceNumber: rendered.value.invoiceNumber,
|
|
@@ -171,6 +234,6 @@ export class DocumentsService {
|
|
|
171
234
|
html: rendered.value.html,
|
|
172
235
|
},
|
|
173
236
|
});
|
|
174
|
-
return Ok({ sent: true, invoiceNumber: rendered.value.invoiceNumber, to });
|
|
237
|
+
return Ok({ sent: true, invoiceNumber: rendered.value.invoiceNumber, to: destination });
|
|
175
238
|
}
|
|
176
239
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
1
2
|
import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
|
|
2
3
|
import { defineModule } from "../../kernel/module/index.js";
|
|
3
4
|
import type { InventoryService } from "../inventory/service.js";
|
|
@@ -36,6 +37,7 @@ export const fulfillmentModule = defineModule<
|
|
|
36
37
|
ordersRepository: new OrdersRepository(deps.db.db as DrizzleDatabase),
|
|
37
38
|
inventoryService: deps.services.inventory,
|
|
38
39
|
hooks: deps.hooks,
|
|
40
|
+
config: deps.config as CommerceConfig,
|
|
39
41
|
services: deps.services,
|
|
40
42
|
database: deps.db,
|
|
41
43
|
}),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
3
|
+
import { assertPermission } from "../../auth/permissions.js";
|
|
3
4
|
import type { Actor } from "../../auth/types.js";
|
|
5
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
4
6
|
import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
|
|
5
7
|
import type { PluginDb } from "../../kernel/database/plugin-types.js";
|
|
6
8
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
7
|
-
import { CommerceNotFoundError, CommerceValidationError } from "../../kernel/errors.js";
|
|
9
|
+
import { CommerceNotFoundError, CommerceValidationError, toCommerceError } from "../../kernel/errors.js";
|
|
8
10
|
import { runAfterHooks } from "../../kernel/hooks/executor.js";
|
|
9
11
|
import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
10
12
|
import type { HookRegistry } from "../../kernel/hooks/registry.js";
|
|
@@ -48,6 +50,7 @@ interface FulfillmentServiceDeps {
|
|
|
48
50
|
ordersRepository: OrdersRepository;
|
|
49
51
|
inventoryService?: InventoryServiceLike;
|
|
50
52
|
hooks: HookRegistry;
|
|
53
|
+
config: CommerceConfig;
|
|
51
54
|
services: Record<string, unknown>;
|
|
52
55
|
database: DatabaseAdapter;
|
|
53
56
|
}
|
|
@@ -259,7 +262,7 @@ export class FulfillmentService {
|
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
async fulfillOrder(orderId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>> {
|
|
262
|
-
const orgId =
|
|
265
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
263
266
|
const order = await this.deps.ordersRepository.findById(orgId, orderId, ctx);
|
|
264
267
|
if (!order) return Err(new CommerceNotFoundError("Order not found."));
|
|
265
268
|
|
|
@@ -326,6 +329,7 @@ export class FulfillmentService {
|
|
|
326
329
|
services: this.deps.services,
|
|
327
330
|
context: { moduleName: "fulfillment" },
|
|
328
331
|
database: { db: this.deps.database.db as PluginDb },
|
|
332
|
+
commerceConfig: this.deps.config,
|
|
329
333
|
});
|
|
330
334
|
await runAfterHooks(afterHooks, null, record, "create", hookCtx);
|
|
331
335
|
|
|
@@ -365,7 +369,13 @@ export class FulfillmentService {
|
|
|
365
369
|
actor?: Actor | null,
|
|
366
370
|
ctx?: TxContext,
|
|
367
371
|
): Promise<Result<FulfillmentRecord>> {
|
|
368
|
-
|
|
372
|
+
try {
|
|
373
|
+
assertPermission(actor ?? null, "orders:update");
|
|
374
|
+
} catch (error) {
|
|
375
|
+
return Err(toCommerceError(error));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
369
379
|
const order = await this.deps.ordersRepository.findById(orgId, input.orderId, ctx);
|
|
370
380
|
if (!order) return Err(new CommerceNotFoundError("Order not found."));
|
|
371
381
|
|
|
@@ -432,7 +442,7 @@ export class FulfillmentService {
|
|
|
432
442
|
fulfillmentId: created.id,
|
|
433
443
|
eventType: "created",
|
|
434
444
|
toStatus: status,
|
|
435
|
-
...(actor?.userId
|
|
445
|
+
...(actor?.userId != null ? { actorId: actor.userId } : {}),
|
|
436
446
|
description: input.trackingNumber
|
|
437
447
|
? `Shipment recorded (${input.trackingNumber})`
|
|
438
448
|
: "Fulfillment recorded",
|
|
@@ -478,6 +488,7 @@ export class FulfillmentService {
|
|
|
478
488
|
services: this.deps.services,
|
|
479
489
|
context: { moduleName: "fulfillment" },
|
|
480
490
|
database: { db: this.deps.database.db as PluginDb },
|
|
491
|
+
commerceConfig: this.deps.config,
|
|
481
492
|
});
|
|
482
493
|
await runAfterHooks(afterHooks, null, record, "create", hookCtx);
|
|
483
494
|
|
|
@@ -563,7 +574,7 @@ export class FulfillmentService {
|
|
|
563
574
|
return Err(new CommerceNotFoundError("Digital download not found."));
|
|
564
575
|
}
|
|
565
576
|
|
|
566
|
-
const orgId =
|
|
577
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
567
578
|
const order = await this.deps.ordersRepository.findById(orgId, orderId, ctx);
|
|
568
579
|
if (!order || order.customerId !== userId) {
|
|
569
580
|
return Err(new CommerceNotFoundError("Order not found."));
|