@endora-commerce/mod-quick-order 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/admin/api/quick-order-client.d.ts +76 -0
  4. package/dist/admin/api/quick-order-client.d.ts.map +1 -0
  5. package/dist/admin/api/quick-order-client.js +18 -0
  6. package/dist/admin/api/quick-order-client.js.map +1 -0
  7. package/dist/admin/components/DefaultPreferencesPanel.d.ts +44 -0
  8. package/dist/admin/components/DefaultPreferencesPanel.d.ts.map +1 -0
  9. package/dist/admin/components/DefaultPreferencesPanel.js +107 -0
  10. package/dist/admin/components/DefaultPreferencesPanel.js.map +1 -0
  11. package/dist/admin/index.d.ts +48 -0
  12. package/dist/admin/index.d.ts.map +1 -0
  13. package/dist/admin/index.js +120 -0
  14. package/dist/admin/index.js.map +1 -0
  15. package/dist/admin/pages/QuickOrderOnBehalfPage.d.ts +16 -0
  16. package/dist/admin/pages/QuickOrderOnBehalfPage.d.ts.map +1 -0
  17. package/dist/admin/pages/QuickOrderOnBehalfPage.js +85 -0
  18. package/dist/admin/pages/QuickOrderOnBehalfPage.js.map +1 -0
  19. package/dist/admin/zones/CustomerDefaults.d.ts +27 -0
  20. package/dist/admin/zones/CustomerDefaults.d.ts.map +1 -0
  21. package/dist/admin/zones/CustomerDefaults.js +11 -0
  22. package/dist/admin/zones/CustomerDefaults.js.map +1 -0
  23. package/dist/admin/zones/OrderEntryQuickTab.d.ts +21 -0
  24. package/dist/admin/zones/OrderEntryQuickTab.d.ts.map +1 -0
  25. package/dist/admin/zones/OrderEntryQuickTab.js +26 -0
  26. package/dist/admin/zones/OrderEntryQuickTab.js.map +1 -0
  27. package/dist/admin/zones/OrganizationDefaults.d.ts +30 -0
  28. package/dist/admin/zones/OrganizationDefaults.d.ts.map +1 -0
  29. package/dist/admin/zones/OrganizationDefaults.js +11 -0
  30. package/dist/admin/zones/OrganizationDefaults.js.map +1 -0
  31. package/dist/backend/entities/quick-order-default-preference.entity.d.ts +22 -0
  32. package/dist/backend/entities/quick-order-default-preference.entity.d.ts.map +1 -0
  33. package/dist/backend/entities/quick-order-default-preference.entity.js +76 -0
  34. package/dist/backend/entities/quick-order-default-preference.entity.js.map +1 -0
  35. package/dist/backend/index.d.ts +88 -0
  36. package/dist/backend/index.d.ts.map +1 -0
  37. package/dist/backend/index.js +235 -0
  38. package/dist/backend/index.js.map +1 -0
  39. package/dist/backend/routes.admin.d.ts +35 -0
  40. package/dist/backend/routes.admin.d.ts.map +1 -0
  41. package/dist/backend/routes.admin.js +64 -0
  42. package/dist/backend/routes.admin.js.map +1 -0
  43. package/dist/backend/routes.d.ts +35 -0
  44. package/dist/backend/routes.d.ts.map +1 -0
  45. package/dist/backend/routes.js +77 -0
  46. package/dist/backend/routes.js.map +1 -0
  47. package/dist/backend/routes.one-click.d.ts +22 -0
  48. package/dist/backend/routes.one-click.d.ts.map +1 -0
  49. package/dist/backend/routes.one-click.js +35 -0
  50. package/dist/backend/routes.one-click.js.map +1 -0
  51. package/dist/backend/routes.preferences.admin.d.ts +23 -0
  52. package/dist/backend/routes.preferences.admin.d.ts.map +1 -0
  53. package/dist/backend/routes.preferences.admin.js +40 -0
  54. package/dist/backend/routes.preferences.admin.js.map +1 -0
  55. package/dist/backend/routes.preferences.d.ts +21 -0
  56. package/dist/backend/routes.preferences.d.ts.map +1 -0
  57. package/dist/backend/routes.preferences.js +42 -0
  58. package/dist/backend/routes.preferences.js.map +1 -0
  59. package/dist/backend/services/catalog-lookup.d.ts +39 -0
  60. package/dist/backend/services/catalog-lookup.d.ts.map +1 -0
  61. package/dist/backend/services/catalog-lookup.js +104 -0
  62. package/dist/backend/services/catalog-lookup.js.map +1 -0
  63. package/dist/backend/services/default-preference-authz.d.ts +31 -0
  64. package/dist/backend/services/default-preference-authz.d.ts.map +1 -0
  65. package/dist/backend/services/default-preference-authz.js +26 -0
  66. package/dist/backend/services/default-preference-authz.js.map +1 -0
  67. package/dist/backend/services/default-preference-resolver.d.ts +28 -0
  68. package/dist/backend/services/default-preference-resolver.d.ts.map +1 -0
  69. package/dist/backend/services/default-preference-resolver.js +27 -0
  70. package/dist/backend/services/default-preference-resolver.js.map +1 -0
  71. package/dist/backend/services/default-preference-service.d.ts +63 -0
  72. package/dist/backend/services/default-preference-service.d.ts.map +1 -0
  73. package/dist/backend/services/default-preference-service.js +176 -0
  74. package/dist/backend/services/default-preference-service.js.map +1 -0
  75. package/dist/backend/services/excel-importer.d.ts +11 -0
  76. package/dist/backend/services/excel-importer.d.ts.map +1 -0
  77. package/dist/backend/services/excel-importer.js +47 -0
  78. package/dist/backend/services/excel-importer.js.map +1 -0
  79. package/dist/backend/services/import-from-request.d.ts +9 -0
  80. package/dist/backend/services/import-from-request.d.ts.map +1 -0
  81. package/dist/backend/services/import-from-request.js +17 -0
  82. package/dist/backend/services/import-from-request.js.map +1 -0
  83. package/dist/backend/services/import-pipeline.d.ts +89 -0
  84. package/dist/backend/services/import-pipeline.d.ts.map +1 -0
  85. package/dist/backend/services/import-pipeline.js +188 -0
  86. package/dist/backend/services/import-pipeline.js.map +1 -0
  87. package/dist/backend/services/import-rows.d.ts +43 -0
  88. package/dist/backend/services/import-rows.d.ts.map +1 -0
  89. package/dist/backend/services/import-rows.js +102 -0
  90. package/dist/backend/services/import-rows.js.map +1 -0
  91. package/dist/backend/services/one-click-service.d.ts +50 -0
  92. package/dist/backend/services/one-click-service.d.ts.map +1 -0
  93. package/dist/backend/services/one-click-service.js +83 -0
  94. package/dist/backend/services/one-click-service.js.map +1 -0
  95. package/dist/backend/services/quick-order-build-service.d.ts +35 -0
  96. package/dist/backend/services/quick-order-build-service.d.ts.map +1 -0
  97. package/dist/backend/services/quick-order-build-service.js +61 -0
  98. package/dist/backend/services/quick-order-build-service.js.map +1 -0
  99. package/dist/backend/services/variant-resolver.d.ts +40 -0
  100. package/dist/backend/services/variant-resolver.d.ts.map +1 -0
  101. package/dist/backend/services/variant-resolver.js +47 -0
  102. package/dist/backend/services/variant-resolver.js.map +1 -0
  103. package/dist/manifest.d.ts +190 -0
  104. package/dist/manifest.d.ts.map +1 -0
  105. package/dist/manifest.js +129 -0
  106. package/dist/manifest.js.map +1 -0
  107. package/dist/migrations/20260611T140359_quick_order_default_preferences.d.ts +15 -0
  108. package/dist/migrations/20260611T140359_quick_order_default_preferences.d.ts.map +1 -0
  109. package/dist/migrations/20260611T140359_quick_order_default_preferences.js +53 -0
  110. package/dist/migrations/20260611T140359_quick_order_default_preferences.js.map +1 -0
  111. package/dist/migrations/index.d.ts +27 -0
  112. package/dist/migrations/index.d.ts.map +1 -0
  113. package/dist/migrations/index.js +29 -0
  114. package/dist/migrations/index.js.map +1 -0
  115. package/docs/quick_order.md +98 -0
  116. package/i18n/en.json +47 -0
  117. package/i18n/pl.json +47 -0
  118. package/package.json +91 -0
  119. package/tailwind.css +14 -0
@@ -0,0 +1,21 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import { type CustomerAccountReadPort } from '@endora-commerce/contracts';
3
+ import type { DefaultPreferenceService } from './services/default-preference-service.js';
4
+ /**
5
+ * Storefront default-preferences routes (feature 039, US2). The caller manages
6
+ * their own customer scope, and — when they hold the organization-admin role —
7
+ * their organization scope and its customers. Checkout reads the resolved
8
+ * defaults via `/resolved`.
9
+ */
10
+ export interface QuickOrderPreferenceRoutesDeps {
11
+ service: DefaultPreferenceService;
12
+ /** `customer_accounts`' read model — the caller's organisation and role. */
13
+ customerAccounts: CustomerAccountReadPort;
14
+ requireCustomer: (req: FastifyRequest, reply: unknown) => Promise<void>;
15
+ resolveCustomerContext: (req: FastifyRequest) => {
16
+ customerAccountId: string;
17
+ organizationId: string;
18
+ };
19
+ }
20
+ export declare function registerQuickOrderPreferenceRoutes(app: FastifyInstance, deps: QuickOrderPreferenceRoutesDeps): Promise<void>;
21
+ //# sourceMappingURL=routes.preferences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.preferences.d.ts","sourceRoot":"","sources":["../../src/backend/routes.preferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAGzF;;;;;GAKG;AACH,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,wBAAwB,CAAC;IAClC,4EAA4E;IAC5E,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,eAAe,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,sBAAsB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAC/C,iBAAiB,EAAE,MAAM,CAAC;QAC1B,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAcD,wBAAsB,kCAAkC,CACtD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,IAAI,CAAC,CA0Cf"}
@@ -0,0 +1,42 @@
1
+ import { ERROR_CODES, quickOrderPreferenceScopeSchema, quickOrderPreferenceUpsertSchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { canManagePreference } from './services/default-preference-authz.js';
4
+ async function resolveActor(customerAccounts, customerAccountId) {
5
+ const account = await customerAccounts.findById(customerAccountId);
6
+ const organizationId = account?.organizationId ?? null;
7
+ if (account?.role === 'organization_admin' && organizationId) {
8
+ return { actor: { kind: 'org_admin', organizationId }, organizationId };
9
+ }
10
+ return { actor: { kind: 'customer', customerAccountId }, organizationId };
11
+ }
12
+ export async function registerQuickOrderPreferenceRoutes(app, deps) {
13
+ const { service, customerAccounts, requireCustomer, resolveCustomerContext } = deps;
14
+ app.get('/api/v1/quick-order/preferences/resolved', { preHandler: requireCustomer }, async (request) => {
15
+ const ctx = resolveCustomerContext(request);
16
+ return { data: await service.resolveForCustomer(ctx.customerAccountId) };
17
+ });
18
+ app.get('/api/v1/quick-order/preferences', { preHandler: requireCustomer }, async (request) => {
19
+ const query = request.query;
20
+ const scope = quickOrderPreferenceScopeSchema.parse(query.scope);
21
+ const scopeId = query.scopeId ?? '';
22
+ const ctx = resolveCustomerContext(request);
23
+ const { actor } = await resolveActor(customerAccounts, ctx.customerAccountId);
24
+ const targetCustomerOrgId = scope === 'customer'
25
+ ? ((await customerAccounts.findById(scopeId))?.organizationId ?? null)
26
+ : null;
27
+ if (!canManagePreference(actor, { scope, scopeId }, targetCustomerOrgId)) {
28
+ throw new HttpError(403, ERROR_CODES.FORBIDDEN, 'Not allowed to read these defaults.');
29
+ }
30
+ return { data: await service.readRaw(scope, scopeId) };
31
+ });
32
+ app.put('/api/v1/quick-order/preferences', { preHandler: requireCustomer, schema: { body: quickOrderPreferenceUpsertSchema } }, async (request) => {
33
+ const body = quickOrderPreferenceUpsertSchema.parse(request.body);
34
+ const ctx = resolveCustomerContext(request);
35
+ const { actor } = await resolveActor(customerAccounts, ctx.customerAccountId);
36
+ const result = await service.upsert(actor, body, {
37
+ customerAccountId: ctx.customerAccountId,
38
+ });
39
+ return { data: result };
40
+ });
41
+ }
42
+ //# sourceMappingURL=routes.preferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.preferences.js","sourceRoot":"","sources":["../../src/backend/routes.preferences.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { type CatalogProductReadPort } from '@endora-commerce/contracts';
2
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
3
+ import type { ProductLike, QuickOrderCatalogLookup, QuickOrderImportAudience, VariantBySku } from './import-pipeline.js';
4
+ import type { VariantLike } from './variant-resolver.js';
5
+ /**
6
+ * {@link QuickOrderCatalogLookup} over `catalog`'s published read port
7
+ * (feature 075, Phase C).
8
+ *
9
+ * It used to run three `em.find` calls against `Product` and `ProductVariant`
10
+ * — two tables `catalog` owns — so a switched-off `catalog` still resolved
11
+ * SKUs out of rows deactivation leaves in place, and a CSV import went on
12
+ * recognising products the platform had stopped serving. Each call is now the
13
+ * port's own batched equivalent, and an absent owner refuses at the seam.
14
+ *
15
+ * The three mappings stay here: the pipeline owns the resolution logic and
16
+ * wants only the four or five fields below, so what crosses is narrower than
17
+ * the record.
18
+ */
19
+ export declare class CatalogPortLookup implements QuickOrderCatalogLookup {
20
+ #private;
21
+ private readonly catalogProducts;
22
+ /**
23
+ * The sanctioned bridge accessor (Constitution XII), for the channel half
24
+ * of the same answer (issue #259).
25
+ */
26
+ private readonly channelMembership;
27
+ constructor(catalogProducts: CatalogProductReadPort,
28
+ /**
29
+ * The sanctioned bridge accessor (Constitution XII), for the channel half
30
+ * of the same answer (issue #259).
31
+ */
32
+ channelMembership: SalesChannelMembershipPort);
33
+ findProductsBySku(skus: string[], audience: QuickOrderImportAudience): Promise<ProductLike[]>;
34
+ findVariantsBySku(skus: string[], audience: QuickOrderImportAudience): Promise<VariantBySku[]>;
35
+ findVariantsByParent(productIds: string[]): Promise<Array<VariantLike & {
36
+ parentProductId: string;
37
+ }>>;
38
+ }
39
+ //# sourceMappingURL=catalog-lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-lookup.d.ts","sourceRoot":"","sources":["../../../src/backend/services/catalog-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAEnF,OAAO,KAAK,EACV,WAAW,EACX,uBAAuB,EACvB,wBAAwB,EACxB,YAAY,EACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,YAAW,uBAAuB;;IAE7D,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBALjB,eAAe,EAAE,sBAAsB;IACxD;;;OAGG;IACc,iBAAiB,EAAE,0BAA0B;IA0B1D,iBAAiB,CACrB,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,wBAAwB,GACjC,OAAO,CAAC,WAAW,EAAE,CAAC;IAmBnB,iBAAiB,CACrB,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,wBAAwB,GACjC,OAAO,CAAC,YAAY,EAAE,CAAC;IAoCpB,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;QAAE,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAU7D"}
@@ -0,0 +1,104 @@
1
+ import { isProductVisibleTo } from '@endora-commerce/contracts';
2
+ import { productIdsInRequestChannel } from '@endora-commerce/platform/kernel';
3
+ /**
4
+ * {@link QuickOrderCatalogLookup} over `catalog`'s published read port
5
+ * (feature 075, Phase C).
6
+ *
7
+ * It used to run three `em.find` calls against `Product` and `ProductVariant`
8
+ * — two tables `catalog` owns — so a switched-off `catalog` still resolved
9
+ * SKUs out of rows deactivation leaves in place, and a CSV import went on
10
+ * recognising products the platform had stopped serving. Each call is now the
11
+ * port's own batched equivalent, and an absent owner refuses at the seam.
12
+ *
13
+ * The three mappings stay here: the pipeline owns the resolution logic and
14
+ * wants only the four or five fields below, so what crosses is narrower than
15
+ * the record.
16
+ */
17
+ export class CatalogPortLookup {
18
+ catalogProducts;
19
+ channelMembership;
20
+ constructor(catalogProducts,
21
+ /**
22
+ * The sanctioned bridge accessor (Constitution XII), for the channel half
23
+ * of the same answer (issue #259).
24
+ */
25
+ channelMembership) {
26
+ this.catalogProducts = catalogProducts;
27
+ this.channelMembership = channelMembership;
28
+ }
29
+ /**
30
+ * Narrow to what the request's channel publishes — the second filter
31
+ * `isProductVisibleTo` says it is not (issue #259).
32
+ *
33
+ * A pasted SKU list is the cheapest enumeration oracle on the platform, which
34
+ * is why issue #227 gave this file an audience. The channel is the other axis
35
+ * of the same oracle: without it a buyer shopping one storefront could paste
36
+ * another's private assortment and have every line come back recognised.
37
+ *
38
+ * `'unrestricted'` skips it for the same reason it skips the audience filter:
39
+ * `POST /api/v1/admin/quick-order/import` is gated by `orders:write` and the
40
+ * operator names the organisation — and therefore the buyer, and therefore
41
+ * the channel — one step later on the *build* call. At import time there is
42
+ * nobody to answer for on either axis, and the permission is the enforcement.
43
+ */
44
+ async #publishedHere(audience, productIds) {
45
+ if (audience === 'unrestricted')
46
+ return null;
47
+ return productIdsInRequestChannel(this.channelMembership, productIds);
48
+ }
49
+ async findProductsBySku(skus, audience) {
50
+ if (skus.length === 0)
51
+ return [];
52
+ const products = (await this.catalogProducts.findBySkus(skus)).filter((p) => audience === 'unrestricted' || isProductVisibleTo(p, audience));
53
+ const publishedHere = await this.#publishedHere(audience, products.map((p) => p.id));
54
+ return products
55
+ .filter((p) => publishedHere === null || publishedHere.has(p.id))
56
+ .map((p) => ({
57
+ id: p.id,
58
+ sku: p.sku,
59
+ status: p.status,
60
+ deletedAt: p.deletedAt,
61
+ }));
62
+ }
63
+ async findVariantsBySku(skus, audience) {
64
+ if (skus.length === 0)
65
+ return [];
66
+ const variants = await this.catalogProducts.findVariantsBySkus(skus);
67
+ if (variants.length === 0 || audience === 'unrestricted') {
68
+ return variants.map((v) => ({
69
+ id: v.id,
70
+ sku: v.sku,
71
+ parentProductId: v.parentProductId,
72
+ }));
73
+ }
74
+ // A variant carries no visibility of its own — its parent's answer is the
75
+ // whole answer (issue #227). Without this pass a pasted *variant* SKU
76
+ // resolved a restricted parent's id, which is the same oracle by one more
77
+ // hop; the parents are fetched here rather than left to the pipeline
78
+ // because only the record carries the two columns.
79
+ const parents = await this.catalogProducts.findByIds(Array.from(new Set(variants.map((v) => v.parentProductId))));
80
+ const visibleParents = parents.filter((p) => isProductVisibleTo(p, audience));
81
+ // Issue #259 — and the channel answer is the parent's too. A variant SKU
82
+ // that resolved a parent this channel does not publish is the same oracle
83
+ // by one more hop, which is the reason the parents are fetched at all.
84
+ const publishedHere = await this.#publishedHere(audience, visibleParents.map((p) => p.id));
85
+ const acquirableParents = new Set(visibleParents
86
+ .filter((p) => publishedHere === null || publishedHere.has(p.id))
87
+ .map((p) => p.id));
88
+ return variants
89
+ .filter((v) => acquirableParents.has(v.parentProductId))
90
+ .map((v) => ({ id: v.id, sku: v.sku, parentProductId: v.parentProductId }));
91
+ }
92
+ async findVariantsByParent(productIds) {
93
+ if (productIds.length === 0)
94
+ return [];
95
+ const variants = await this.catalogProducts.listVariantsByProductIds(productIds);
96
+ return variants.map((v) => ({
97
+ id: v.id,
98
+ sku: v.sku,
99
+ parentProductId: v.parentProductId,
100
+ variantAttributeValues: v.variantAttributeValues,
101
+ }));
102
+ }
103
+ }
104
+ //# sourceMappingURL=catalog-lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-lookup.js","sourceRoot":"","sources":["../../../src/backend/services/catalog-lookup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { PreferenceActor } from '@endora-commerce/contracts';
2
+ import type { QuickOrderPreferenceScope } from '@endora-commerce/contracts';
3
+ /**
4
+ * Pure authorization for managing default ordering preferences (feature 039,
5
+ * FR-018). The actor's identity + (for a customer-scoped target) the target
6
+ * customer's organization are resolved by the service from the DB; this
7
+ * function only applies the rule, so it is unit-testable.
8
+ *
9
+ * - Customer → only their own customer scope.
10
+ * - Org admin → own organization scope + customers within that org.
11
+ * - Salesperson → assigned organizations + customers within them.
12
+ * - Platform admin → any scope.
13
+ */
14
+ /**
15
+ * The union moved to `@endora-commerce/contracts` in feature 075's Phase P — `customers`
16
+ * writes a buyer's defaults through `defaultPreferencePort` and has to name
17
+ * the actor. Re-exported here for the length of Phase P, which cuts no
18
+ * consumer.
19
+ */
20
+ export type { PreferenceActor };
21
+ export interface PreferenceTarget {
22
+ scope: QuickOrderPreferenceScope;
23
+ scopeId: string;
24
+ }
25
+ /**
26
+ * @param targetCustomerOrganizationId for a customer-scoped target, the
27
+ * organization that customer belongs to (null if none). Ignored for an
28
+ * organization-scoped target.
29
+ */
30
+ export declare function canManagePreference(actor: PreferenceActor, target: PreferenceTarget, targetCustomerOrganizationId: string | null): boolean;
31
+ //# sourceMappingURL=default-preference-authz.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-preference-authz.d.ts","sourceRoot":"","sources":["../../../src/backend/services/default-preference-authz.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E;;;;;;;;;;GAUG;AACH;;;;;GAKG;AACH,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,yBAAyB,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,gBAAgB,EACxB,4BAA4B,EAAE,MAAM,GAAG,IAAI,GAC1C,OAAO,CAwBT"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @param targetCustomerOrganizationId for a customer-scoped target, the
3
+ * organization that customer belongs to (null if none). Ignored for an
4
+ * organization-scoped target.
5
+ */
6
+ export function canManagePreference(actor, target, targetCustomerOrganizationId) {
7
+ switch (actor.kind) {
8
+ case 'platform_admin':
9
+ return true;
10
+ case 'customer':
11
+ return target.scope === 'customer' && target.scopeId === actor.customerAccountId;
12
+ case 'org_admin':
13
+ if (target.scope === 'organization')
14
+ return target.scopeId === actor.organizationId;
15
+ return targetCustomerOrganizationId === actor.organizationId;
16
+ case 'salesperson':
17
+ if (target.scope === 'organization') {
18
+ return actor.assignedOrganizationIds.includes(target.scopeId);
19
+ }
20
+ return (targetCustomerOrganizationId !== null &&
21
+ actor.assignedOrganizationIds.includes(targetCustomerOrganizationId));
22
+ default:
23
+ return false;
24
+ }
25
+ }
26
+ //# sourceMappingURL=default-preference-authz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-preference-authz.js","sourceRoot":"","sources":["../../../src/backend/services/default-preference-authz.ts"],"names":[],"mappings":"AA2BA;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAsB,EACtB,MAAwB,EACxB,4BAA2C;IAE3C,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,gBAAgB;YACnB,OAAO,IAAI,CAAC;QAEd,KAAK,UAAU;YACb,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,iBAAiB,CAAC;QAEnF,KAAK,WAAW;YACd,IAAI,MAAM,CAAC,KAAK,KAAK,cAAc;gBAAE,OAAO,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,cAAc,CAAC;YACpF,OAAO,4BAA4B,KAAK,KAAK,CAAC,cAAc,CAAC;QAE/D,KAAK,aAAa;YAChB,IAAI,MAAM,CAAC,KAAK,KAAK,cAAc,EAAE,CAAC;gBACpC,OAAO,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAChE,CAAC;YACD,OAAO,CACL,4BAA4B,KAAK,IAAI;gBACrC,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CACrE,CAAC;QAEJ;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pure resolution of default ordering preferences (feature 039, US2 / R4).
3
+ *
4
+ * Most-specific-wins per field: a customer-level value overrides the
5
+ * organization-level value; otherwise the organization value is the fallback.
6
+ * Eligibility re-checking (active / org-allowed / address-exists) is applied
7
+ * by the service on top of this — kept separate so the inheritance rule is
8
+ * unit-testable without a database.
9
+ */
10
+ export interface PreferenceRow {
11
+ defaultPaymentMethodId: string | null;
12
+ defaultDeliveryMethodId: string | null;
13
+ defaultBillingAddressId: string | null;
14
+ defaultShippingAddressId: string | null;
15
+ }
16
+ export type PreferenceFieldSource = 'customer' | 'organization' | null;
17
+ export interface ResolvedField {
18
+ id: string | null;
19
+ source: PreferenceFieldSource;
20
+ }
21
+ export interface ResolvedPreferenceFields {
22
+ payment: ResolvedField;
23
+ delivery: ResolvedField;
24
+ billing: ResolvedField;
25
+ shipping: ResolvedField;
26
+ }
27
+ export declare function resolvePreferenceFields(customerRow: PreferenceRow | null, orgRow: PreferenceRow | null): ResolvedPreferenceFields;
28
+ //# sourceMappingURL=default-preference-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-preference-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/default-preference-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,aAAa;IAC5B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,cAAc,GAAG,IAAI,CAAC;AAEvE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAgBD,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,aAAa,GAAG,IAAI,EACjC,MAAM,EAAE,aAAa,GAAG,IAAI,GAC3B,wBAAwB,CAO1B"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Pure resolution of default ordering preferences (feature 039, US2 / R4).
3
+ *
4
+ * Most-specific-wins per field: a customer-level value overrides the
5
+ * organization-level value; otherwise the organization value is the fallback.
6
+ * Eligibility re-checking (active / org-allowed / address-exists) is applied
7
+ * by the service on top of this — kept separate so the inheritance rule is
8
+ * unit-testable without a database.
9
+ */
10
+ function pick(field, customerRow, orgRow) {
11
+ const fromCustomer = customerRow?.[field] ?? null;
12
+ if (fromCustomer)
13
+ return { id: fromCustomer, source: 'customer' };
14
+ const fromOrg = orgRow?.[field] ?? null;
15
+ if (fromOrg)
16
+ return { id: fromOrg, source: 'organization' };
17
+ return { id: null, source: null };
18
+ }
19
+ export function resolvePreferenceFields(customerRow, orgRow) {
20
+ return {
21
+ payment: pick('defaultPaymentMethodId', customerRow, orgRow),
22
+ delivery: pick('defaultDeliveryMethodId', customerRow, orgRow),
23
+ billing: pick('defaultBillingAddressId', customerRow, orgRow),
24
+ shipping: pick('defaultShippingAddressId', customerRow, orgRow),
25
+ };
26
+ }
27
+ //# sourceMappingURL=default-preference-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-preference-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/default-preference-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyBH,SAAS,IAAI,CACX,KAAsB,EACtB,WAAiC,EACjC,MAA4B;IAE5B,MAAM,YAAY,GAAG,WAAW,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IAClD,IAAI,YAAY;QAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAClE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IACxC,IAAI,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAC5D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,WAAiC,EACjC,MAA4B;IAE5B,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,EAAE,MAAM,CAAC;QAC5D,QAAQ,EAAE,IAAI,CAAC,yBAAyB,EAAE,WAAW,EAAE,MAAM,CAAC;QAC9D,OAAO,EAAE,IAAI,CAAC,yBAAyB,EAAE,WAAW,EAAE,MAAM,CAAC;QAC7D,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,WAAW,EAAE,MAAM,CAAC;KAChE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,63 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type AddressReadPort, type CustomerAccountReadPort, type CustomerAddressReadPort, type DeliveryMethodReadPort, type OrganizationRestrictionPort, type PaymentMethodReadPort, type QuickOrderPreference, type QuickOrderPreferenceScope, type QuickOrderPreferenceUpsert, type QuickOrderResolvedDefaults } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import type { PreferenceAuditContext } from '@endora-commerce/contracts';
5
+ import { type PreferenceActor } from './default-preference-authz.js';
6
+ /**
7
+ * Moved to `@endora-commerce/contracts` in feature 075's Phase P — `customers` passes it
8
+ * when it writes a buyer's defaults. Re-exported here for the length of
9
+ * Phase P, which cuts no consumer.
10
+ */
11
+ export type { PreferenceAuditContext };
12
+ /**
13
+ * DefaultPreferenceService (feature 039, US2). Role-scoped CRUD over
14
+ * `quick_order_default_preferences`, plus the effective-defaults resolver
15
+ * (customer-over-org with use-time eligibility re-check). Every write is
16
+ * audited (FR-021).
17
+ */
18
+ export interface DefaultPreferenceCollaborators {
19
+ /** `customer_accounts` — whose defaults these are. */
20
+ readonly customerAccounts: CustomerAccountReadPort;
21
+ /** `payment_methods` — is the stored default still a live method? */
22
+ readonly paymentMethods: PaymentMethodReadPort;
23
+ /** `delivery_methods` — the same question, delivery side. */
24
+ readonly deliveryMethods: DeliveryMethodReadPort;
25
+ /** `addresses` — the organisation's shared addresses. */
26
+ readonly addresses: AddressReadPort;
27
+ /**
28
+ * `customers` — the buyer's personal addresses (feature 040).
29
+ *
30
+ * The one collaborator whose owner may be absent without this service
31
+ * failing: see the null object in `backend.ts` and the module's
32
+ * `nonBindingDependencies` entry.
33
+ */
34
+ readonly customerAddresses: CustomerAddressReadPort;
35
+ /** `organizations` — the buyer organisation's method allow-lists. */
36
+ readonly restriction: OrganizationRestrictionPort;
37
+ }
38
+ export declare class DefaultPreferenceService {
39
+ private readonly emFactory;
40
+ private readonly auditLog;
41
+ private readonly ports;
42
+ constructor(emFactory: () => EntityManager, auditLog: AuditPort, ports: DefaultPreferenceCollaborators);
43
+ /** Raw stored row for a scope (null if unset). */
44
+ readRaw(scope: QuickOrderPreferenceScope, scopeId: string): Promise<QuickOrderPreference | null>;
45
+ /**
46
+ * Upsert a scope's defaults. Each field: `undefined` = leave unchanged;
47
+ * explicit `null` = clear. Authorization per FR-018.
48
+ */
49
+ upsert(actor: PreferenceActor, input: QuickOrderPreferenceUpsert, audit: PreferenceAuditContext): Promise<QuickOrderPreference>;
50
+ /**
51
+ * Effective defaults for a customer: customer-over-org per field, then each
52
+ * value is dropped to null if it is no longer eligible (FR-020).
53
+ */
54
+ resolveForCustomer(customerAccountId: string): Promise<QuickOrderResolvedDefaults>;
55
+ private keepIf;
56
+ private isPaymentEligible;
57
+ private isDeliveryEligible;
58
+ private isAddressEligible;
59
+ private customerOrganizationId;
60
+ private rowFields;
61
+ private toContract;
62
+ }
63
+ //# sourceMappingURL=default-preference-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-preference-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/default-preference-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAO1F;;;;GAIG;AACH,YAAY,EAAE,sBAAsB,EAAE,CAAC;AAcvC;;;;;GAKG;AACH,MAAM,WAAW,8BAA8B;IAC7C,sDAAsD;IACtD,QAAQ,CAAC,gBAAgB,EAAE,uBAAuB,CAAC;IACnD,qEAAqE;IACrE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,eAAe,EAAE,sBAAsB,CAAC;IACjD,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,EAAE,uBAAuB,CAAC;IACpD,qEAAqE;IACrE,QAAQ,CAAC,WAAW,EAAE,2BAA2B,CAAC;CACnD;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAFL,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,EAAE,SAAS,EACnB,KAAK,EAAE,8BAA8B;IAGxD,kDAAkD;IAC5C,OAAO,CACX,KAAK,EAAE,yBAAyB,EAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAMvC;;;OAGG;IACG,MAAM,CACV,KAAK,EAAE,eAAe,EACtB,KAAK,EAAE,0BAA0B,EACjC,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,oBAAoB,CAAC;IAyChC;;;OAGG;IACG,kBAAkB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC;YA0D1E,MAAM;YAQN,iBAAiB;YAMjB,kBAAkB;YAMlB,iBAAiB;YAwBjB,sBAAsB;IAKpC,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;CAUnB"}
@@ -0,0 +1,176 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { QuickOrderDefaultPreference } from '../entities/quick-order-default-preference.entity.js';
4
+ import { canManagePreference } from './default-preference-authz.js';
5
+ import { resolvePreferenceFields, } from './default-preference-resolver.js';
6
+ /** No allow-list at all, or an empty one: both mean every method is allowed. */
7
+ function unrestricted(allowed) {
8
+ return allowed === null || allowed.length === 0;
9
+ }
10
+ const FIELDS = [
11
+ 'defaultPaymentMethodId',
12
+ 'defaultDeliveryMethodId',
13
+ 'defaultBillingAddressId',
14
+ 'defaultShippingAddressId',
15
+ ];
16
+ export class DefaultPreferenceService {
17
+ emFactory;
18
+ auditLog;
19
+ ports;
20
+ constructor(emFactory, auditLog, ports) {
21
+ this.emFactory = emFactory;
22
+ this.auditLog = auditLog;
23
+ this.ports = ports;
24
+ }
25
+ /** Raw stored row for a scope (null if unset). */
26
+ async readRaw(scope, scopeId) {
27
+ const em = this.emFactory();
28
+ const row = await em.findOne(QuickOrderDefaultPreference, { scope, scopeId });
29
+ return row ? this.toContract(row) : null;
30
+ }
31
+ /**
32
+ * Upsert a scope's defaults. Each field: `undefined` = leave unchanged;
33
+ * explicit `null` = clear. Authorization per FR-018.
34
+ */
35
+ async upsert(actor, input, audit) {
36
+ const em = this.emFactory();
37
+ const targetCustomerOrgId = input.scope === 'customer' ? await this.customerOrganizationId(input.scopeId) : null;
38
+ if (!canManagePreference(actor, { scope: input.scope, scopeId: input.scopeId }, targetCustomerOrgId)) {
39
+ throw new HttpError(403, ERROR_CODES.FORBIDDEN, 'Not allowed to manage these defaults.');
40
+ }
41
+ let row = await em.findOne(QuickOrderDefaultPreference, {
42
+ scope: input.scope,
43
+ scopeId: input.scopeId,
44
+ });
45
+ const before = row ? this.toContract(row) : null;
46
+ if (!row) {
47
+ row = em.create(QuickOrderDefaultPreference, { scope: input.scope, scopeId: input.scopeId });
48
+ }
49
+ for (const field of FIELDS) {
50
+ if (field in input && input[field] !== undefined) {
51
+ row[field] = input[field] ?? null;
52
+ }
53
+ }
54
+ await em.persistAndFlush(row);
55
+ const after = this.toContract(row);
56
+ await this.auditLog.record({
57
+ actorAdminUserId: audit.actorAdminUserId ?? null,
58
+ impersonatedCustomerAccountId: audit.customerAccountId ?? null,
59
+ action: 'quick_order.default_preference.update',
60
+ objectType: 'QuickOrderDefaultPreference',
61
+ objectId: row.id,
62
+ stateBefore: before,
63
+ stateAfter: after,
64
+ ipAddress: audit.ipAddress ?? null,
65
+ userAgent: audit.userAgent ?? null,
66
+ requestId: audit.requestId ?? null,
67
+ });
68
+ return after;
69
+ }
70
+ /**
71
+ * Effective defaults for a customer: customer-over-org per field, then each
72
+ * value is dropped to null if it is no longer eligible (FR-020).
73
+ */
74
+ async resolveForCustomer(customerAccountId) {
75
+ const em = this.emFactory();
76
+ const organizationId = await this.customerOrganizationId(customerAccountId);
77
+ const customerRow = this.rowFields(await em.findOne(QuickOrderDefaultPreference, { scope: 'customer', scopeId: customerAccountId }));
78
+ const orgRow = organizationId
79
+ ? this.rowFields(await em.findOne(QuickOrderDefaultPreference, {
80
+ scope: 'organization',
81
+ scopeId: organizationId,
82
+ }))
83
+ : null;
84
+ const resolved = resolvePreferenceFields(customerRow, orgRow);
85
+ // Two spellings of "unrestricted", and both mean allow-everything. `null`
86
+ // is the degrade the owner put inside its own return type (composition
87
+ // checklist item 7) — an organisation the restriction module cannot find —
88
+ // and an **empty array** is an organisation with no link rows, which is the
89
+ // ordinary case. Reading either as "nothing is allowed" drops every stored
90
+ // default silently; see `isPaymentEligible`.
91
+ const allowedPayment = organizationId
92
+ ? await this.ports.restriction.allowedIdsFor(organizationId, 'paymentMethodIds')
93
+ : null;
94
+ const allowedDelivery = organizationId
95
+ ? await this.ports.restriction.allowedIdsFor(organizationId, 'deliveryMethodIds')
96
+ : null;
97
+ const payment = await this.keepIf(resolved.payment, (id) => this.isPaymentEligible(id, allowedPayment));
98
+ const delivery = await this.keepIf(resolved.delivery, (id) => this.isDeliveryEligible(id, allowedDelivery));
99
+ const billing = await this.keepIf(resolved.billing, (id) => this.isAddressEligible(id, organizationId, customerAccountId));
100
+ const shipping = await this.keepIf(resolved.shipping, (id) => this.isAddressEligible(id, organizationId, customerAccountId));
101
+ return {
102
+ paymentMethodId: payment.id,
103
+ deliveryMethodId: delivery.id,
104
+ billingAddressId: billing.id,
105
+ shippingAddressId: shipping.id,
106
+ source: {
107
+ payment: payment.source,
108
+ delivery: delivery.source,
109
+ billing: billing.source,
110
+ shipping: shipping.source,
111
+ },
112
+ };
113
+ }
114
+ async keepIf(field, eligible) {
115
+ if (!field.id)
116
+ return field;
117
+ return (await eligible(field.id)) ? field : { id: null, source: null };
118
+ }
119
+ async isPaymentEligible(id, allowed) {
120
+ const method = await this.ports.paymentMethods.findById(id);
121
+ if (!method || method.status !== 'active')
122
+ return false;
123
+ return unrestricted(allowed) || allowed.includes(id);
124
+ }
125
+ async isDeliveryEligible(id, allowed) {
126
+ const method = await this.ports.deliveryMethods.findById(id);
127
+ if (!method || method.status !== 'active')
128
+ return false;
129
+ return unrestricted(allowed) || allowed.includes(id);
130
+ }
131
+ async isAddressEligible(id, organizationId, customerAccountId) {
132
+ // Org-shared address belonging to the customer's organization. `liveOnly`
133
+ // because this answer feeds a choice rather than a rendering: without it
134
+ // `findById` returns soft-deleted rows and a deleted address becomes an
135
+ // eligible one-click default (AddressReadPort's doc block).
136
+ if (organizationId !== null) {
137
+ const orgAddress = await this.ports.addresses.findById(organizationId, id, {
138
+ liveOnly: true,
139
+ });
140
+ if (orgAddress)
141
+ return true;
142
+ }
143
+ // Feature 040 — personal address owned by the customer. Same `liveOnly`
144
+ // reason, and the port already scopes by account, so the ownership check
145
+ // cannot be forgotten.
146
+ const personal = await this.ports.customerAddresses.findById(customerAccountId, id, {
147
+ liveOnly: true,
148
+ });
149
+ return personal !== null;
150
+ }
151
+ async customerOrganizationId(customerAccountId) {
152
+ const customer = await this.ports.customerAccounts.findById(customerAccountId);
153
+ return customer?.organizationId ?? null;
154
+ }
155
+ rowFields(row) {
156
+ if (!row)
157
+ return null;
158
+ return {
159
+ defaultPaymentMethodId: row.defaultPaymentMethodId ?? null,
160
+ defaultDeliveryMethodId: row.defaultDeliveryMethodId ?? null,
161
+ defaultBillingAddressId: row.defaultBillingAddressId ?? null,
162
+ defaultShippingAddressId: row.defaultShippingAddressId ?? null,
163
+ };
164
+ }
165
+ toContract(row) {
166
+ return {
167
+ scope: row.scope,
168
+ scopeId: row.scopeId,
169
+ defaultPaymentMethodId: row.defaultPaymentMethodId ?? null,
170
+ defaultDeliveryMethodId: row.defaultDeliveryMethodId ?? null,
171
+ defaultBillingAddressId: row.defaultBillingAddressId ?? null,
172
+ defaultShippingAddressId: row.defaultShippingAddressId ?? null,
173
+ };
174
+ }
175
+ }
176
+ //# sourceMappingURL=default-preference-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-preference-service.js","sourceRoot":"","sources":["../../../src/backend/services/default-preference-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type ParseOutcome } from './import-rows.js';
2
+ /**
3
+ * Parse an `.xlsx` workbook into the shared `NormalizedRow[]` (feature 039,
4
+ * FR-001). Only the first worksheet is read (research §R1); the header +
5
+ * column semantics are identical to the CSV path via `rowsFromTable`.
6
+ *
7
+ * The dependency on `exceljs` is isolated to this adapter — the rest of the
8
+ * import pipeline only ever sees normalized rows.
9
+ */
10
+ export declare function parseXlsxRows(content: Buffer): Promise<ParseOutcome>;
11
+ //# sourceMappingURL=excel-importer.d.ts.map