@endora-commerce/mod-customers 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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/admin/index.d.ts +26 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +68 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/CustomerDetail.d.ts +9 -0
  8. package/dist/admin/pages/CustomerDetail.d.ts.map +1 -0
  9. package/dist/admin/pages/CustomerDetail.js +72 -0
  10. package/dist/admin/pages/CustomerDetail.js.map +1 -0
  11. package/dist/admin/pages/CustomersList.d.ts +9 -0
  12. package/dist/admin/pages/CustomersList.d.ts.map +1 -0
  13. package/dist/admin/pages/CustomersList.js +115 -0
  14. package/dist/admin/pages/CustomersList.js.map +1 -0
  15. package/dist/admin/pages/OnlineCustomers.d.ts +9 -0
  16. package/dist/admin/pages/OnlineCustomers.d.ts.map +1 -0
  17. package/dist/admin/pages/OnlineCustomers.js +77 -0
  18. package/dist/admin/pages/OnlineCustomers.js.map +1 -0
  19. package/dist/admin/panels/HistoryPanels.d.ts +11 -0
  20. package/dist/admin/panels/HistoryPanels.d.ts.map +1 -0
  21. package/dist/admin/panels/HistoryPanels.js +64 -0
  22. package/dist/admin/panels/HistoryPanels.js.map +1 -0
  23. package/dist/admin/panels/ManagementPanels.d.ts +14 -0
  24. package/dist/admin/panels/ManagementPanels.d.ts.map +1 -0
  25. package/dist/admin/panels/ManagementPanels.js +93 -0
  26. package/dist/admin/panels/ManagementPanels.js.map +1 -0
  27. package/dist/backend/entities/customer-address.entity.d.ts +25 -0
  28. package/dist/backend/entities/customer-address.entity.d.ts.map +1 -0
  29. package/dist/backend/entities/customer-address.entity.js +95 -0
  30. package/dist/backend/entities/customer-address.entity.js.map +1 -0
  31. package/dist/backend/index.d.ts +91 -0
  32. package/dist/backend/index.d.ts.map +1 -0
  33. package/dist/backend/index.js +201 -0
  34. package/dist/backend/index.js.map +1 -0
  35. package/dist/backend/plugin.d.ts +168 -0
  36. package/dist/backend/plugin.d.ts.map +1 -0
  37. package/dist/backend/plugin.js +83 -0
  38. package/dist/backend/plugin.js.map +1 -0
  39. package/dist/backend/routes.admin.d.ts +70 -0
  40. package/dist/backend/routes.admin.d.ts.map +1 -0
  41. package/dist/backend/routes.admin.js +313 -0
  42. package/dist/backend/routes.admin.js.map +1 -0
  43. package/dist/backend/routes.register.d.ts +12 -0
  44. package/dist/backend/routes.register.d.ts.map +1 -0
  45. package/dist/backend/routes.register.js +40 -0
  46. package/dist/backend/routes.register.js.map +1 -0
  47. package/dist/backend/routes.self.d.ts +46 -0
  48. package/dist/backend/routes.self.d.ts.map +1 -0
  49. package/dist/backend/routes.self.js +145 -0
  50. package/dist/backend/routes.self.js.map +1 -0
  51. package/dist/backend/serializers.d.ts +13 -0
  52. package/dist/backend/serializers.d.ts.map +1 -0
  53. package/dist/backend/serializers.js +39 -0
  54. package/dist/backend/serializers.js.map +1 -0
  55. package/dist/backend/services/customer-address-read-port.d.ts +29 -0
  56. package/dist/backend/services/customer-address-read-port.d.ts.map +1 -0
  57. package/dist/backend/services/customer-address-read-port.js +53 -0
  58. package/dist/backend/services/customer-address-read-port.js.map +1 -0
  59. package/dist/backend/services/customer-address-service.d.ts +94 -0
  60. package/dist/backend/services/customer-address-service.d.ts.map +1 -0
  61. package/dist/backend/services/customer-address-service.js +176 -0
  62. package/dist/backend/services/customer-address-service.js.map +1 -0
  63. package/dist/backend/services/customer-admin-query-service.d.ts +57 -0
  64. package/dist/backend/services/customer-admin-query-service.d.ts.map +1 -0
  65. package/dist/backend/services/customer-admin-query-service.js +97 -0
  66. package/dist/backend/services/customer-admin-query-service.js.map +1 -0
  67. package/dist/backend/services/customer-authority-service.d.ts +46 -0
  68. package/dist/backend/services/customer-authority-service.d.ts.map +1 -0
  69. package/dist/backend/services/customer-authority-service.js +42 -0
  70. package/dist/backend/services/customer-authority-service.js.map +1 -0
  71. package/dist/backend/services/customer-defaults-service.d.ts +48 -0
  72. package/dist/backend/services/customer-defaults-service.d.ts.map +1 -0
  73. package/dist/backend/services/customer-defaults-service.js +71 -0
  74. package/dist/backend/services/customer-defaults-service.js.map +1 -0
  75. package/dist/backend/services/customer-deletion-service.d.ts +39 -0
  76. package/dist/backend/services/customer-deletion-service.d.ts.map +1 -0
  77. package/dist/backend/services/customer-deletion-service.js +94 -0
  78. package/dist/backend/services/customer-deletion-service.js.map +1 -0
  79. package/dist/backend/services/customer-moderation-service.d.ts +58 -0
  80. package/dist/backend/services/customer-moderation-service.d.ts.map +1 -0
  81. package/dist/backend/services/customer-moderation-service.js +70 -0
  82. package/dist/backend/services/customer-moderation-service.js.map +1 -0
  83. package/dist/backend/services/customer-org-assignment-service.d.ts +52 -0
  84. package/dist/backend/services/customer-org-assignment-service.d.ts.map +1 -0
  85. package/dist/backend/services/customer-org-assignment-service.js +106 -0
  86. package/dist/backend/services/customer-org-assignment-service.js.map +1 -0
  87. package/dist/backend/services/customer-presence-service.d.ts +44 -0
  88. package/dist/backend/services/customer-presence-service.d.ts.map +1 -0
  89. package/dist/backend/services/customer-presence-service.js +47 -0
  90. package/dist/backend/services/customer-presence-service.js.map +1 -0
  91. package/dist/backend/services/customer-registration-service.d.ts +56 -0
  92. package/dist/backend/services/customer-registration-service.d.ts.map +1 -0
  93. package/dist/backend/services/customer-registration-service.js +43 -0
  94. package/dist/backend/services/customer-registration-service.js.map +1 -0
  95. package/dist/backend/workers/anonymization-sweep-worker.d.ts +15 -0
  96. package/dist/backend/workers/anonymization-sweep-worker.d.ts.map +1 -0
  97. package/dist/backend/workers/anonymization-sweep-worker.js +20 -0
  98. package/dist/backend/workers/anonymization-sweep-worker.js.map +1 -0
  99. package/dist/manifest.d.ts +181 -0
  100. package/dist/manifest.d.ts.map +1 -0
  101. package/dist/manifest.js +192 -0
  102. package/dist/manifest.js.map +1 -0
  103. package/dist/migrations/20260611T140404_customers_customer_addresses_init.d.ts +14 -0
  104. package/dist/migrations/20260611T140404_customers_customer_addresses_init.d.ts.map +1 -0
  105. package/dist/migrations/20260611T140404_customers_customer_addresses_init.js +40 -0
  106. package/dist/migrations/20260611T140404_customers_customer_addresses_init.js.map +1 -0
  107. package/dist/migrations/index.d.ts +27 -0
  108. package/dist/migrations/index.d.ts.map +1 -0
  109. package/dist/migrations/index.js +29 -0
  110. package/dist/migrations/index.js.map +1 -0
  111. package/docs/customers.md +108 -0
  112. package/i18n/en.json +94 -0
  113. package/i18n/pl.json +94 -0
  114. package/package.json +101 -0
  115. package/tailwind.css +14 -0
@@ -0,0 +1,39 @@
1
+ /** Maps a persisted CustomerAddress to its API contract shape. */
2
+ export function serializeCustomerAddress(a) {
3
+ return {
4
+ id: a.id,
5
+ customerAccountId: a.customerAccountId,
6
+ kind: a.kind,
7
+ recipientName: a.recipientName,
8
+ street: a.street,
9
+ city: a.city,
10
+ postalCode: a.postalCode,
11
+ country: a.country,
12
+ phone: a.phone ?? null,
13
+ isDefault: a.isDefault,
14
+ createdAt: a.createdAt.toISOString(),
15
+ updatedAt: a.updatedAt.toISOString(),
16
+ };
17
+ }
18
+ /**
19
+ * Maps an org-shared address to the read-only reference shape.
20
+ *
21
+ * Feature 075 — the argument is `addresses`' published {@link AddressRecord},
22
+ * not its `Address` entity: this module only ever read the columns the record
23
+ * carries, and an entity crossing a boundary is what the port exists to stop.
24
+ */
25
+ export function serializeOrganizationAddress(a) {
26
+ return {
27
+ id: a.id,
28
+ organizationId: a.organizationId,
29
+ kind: a.kind,
30
+ recipientName: a.recipientName,
31
+ street: a.street,
32
+ city: a.city,
33
+ postalCode: a.postalCode,
34
+ country: a.country,
35
+ phone: a.phone ?? null,
36
+ isDefault: a.isDefault,
37
+ };
38
+ }
39
+ //# sourceMappingURL=serializers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializers.js","sourceRoot":"","sources":["../../src/backend/serializers.ts"],"names":[],"mappings":"AAOA,kEAAkE;AAClE,MAAM,UAAU,wBAAwB,CAAC,CAAkB;IACzD,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,iBAAiB,EAAE,CAAC,CAAC,iBAAiB;QACtC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;QACpC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,CAAgB;IAC3D,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,cAAc,EAAE,CAAC,CAAC,cAAc;QAChC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CustomerAddressReadPort, CustomerAddressRecord } from '@endora-commerce/contracts';
3
+ import { CustomerAddress } from '../entities/customer-address.entity.js';
4
+ /**
5
+ * The customer-address read model `customers` publishes (feature 075, Phase P).
6
+ *
7
+ * One consumer: `quick_order` resolves a buyer's saved personal address when
8
+ * it fills in their one-click defaults.
9
+ *
10
+ * The table is distinct from `addresses`' on purpose and the port keeps them
11
+ * distinct: this one hangs off a **customer account**, that one off an
12
+ * **organisation**. A B2C buyer keeps addresses that are theirs rather than
13
+ * their personal organisation's, and a merged shape would hide which of the
14
+ * two a caller is holding.
15
+ *
16
+ * `findById` is scoped by customer account for the reason its organisation
17
+ * twin gives: the caller already knows whose address it wants, and one query
18
+ * is what stops the ownership check being forgotten.
19
+ */
20
+ export declare class CustomerAddressReadService implements CustomerAddressReadPort {
21
+ private readonly emFactory;
22
+ constructor(emFactory: () => EntityManager);
23
+ findById(customerAccountId: string, addressId: string, options?: {
24
+ liveOnly?: boolean;
25
+ }): Promise<CustomerAddressRecord | null>;
26
+ listForCustomer(customerAccountId: string, kind?: 'delivery' | 'billing'): Promise<CustomerAddressRecord[]>;
27
+ }
28
+ export declare function toCustomerAddressRecord(address: CustomerAddress): CustomerAddressRecord;
29
+ //# sourceMappingURL=customer-address-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-address-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-address-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACjG,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAEzE;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,0BAA2B,YAAW,uBAAuB;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CACZ,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IASlC,eAAe,CACnB,iBAAiB,EAAE,MAAM,EACzB,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,GAC5B,OAAO,CAAC,qBAAqB,EAAE,CAAC;CAQpC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,qBAAqB,CAgBvF"}
@@ -0,0 +1,53 @@
1
+ import { CustomerAddress } from '../entities/customer-address.entity.js';
2
+ /**
3
+ * The customer-address read model `customers` publishes (feature 075, Phase P).
4
+ *
5
+ * One consumer: `quick_order` resolves a buyer's saved personal address when
6
+ * it fills in their one-click defaults.
7
+ *
8
+ * The table is distinct from `addresses`' on purpose and the port keeps them
9
+ * distinct: this one hangs off a **customer account**, that one off an
10
+ * **organisation**. A B2C buyer keeps addresses that are theirs rather than
11
+ * their personal organisation's, and a merged shape would hide which of the
12
+ * two a caller is holding.
13
+ *
14
+ * `findById` is scoped by customer account for the reason its organisation
15
+ * twin gives: the caller already knows whose address it wants, and one query
16
+ * is what stops the ownership check being forgotten.
17
+ */
18
+ export class CustomerAddressReadService {
19
+ emFactory;
20
+ constructor(emFactory) {
21
+ this.emFactory = emFactory;
22
+ }
23
+ async findById(customerAccountId, addressId, options) {
24
+ const address = await this.emFactory().findOne(CustomerAddress, {
25
+ id: addressId,
26
+ customerAccountId,
27
+ ...(options?.liveOnly ? { deletedAt: null } : {}),
28
+ });
29
+ return address ? toCustomerAddressRecord(address) : null;
30
+ }
31
+ async listForCustomer(customerAccountId, kind) {
32
+ const addresses = await this.emFactory().find(CustomerAddress, { customerAccountId, deletedAt: null, ...(kind === undefined ? {} : { kind }) }, { orderBy: { isDefault: 'desc', createdAt: 'asc' } });
33
+ return addresses.map(toCustomerAddressRecord);
34
+ }
35
+ }
36
+ export function toCustomerAddressRecord(address) {
37
+ return {
38
+ id: address.id,
39
+ customerAccountId: address.customerAccountId,
40
+ kind: address.kind,
41
+ recipientName: address.recipientName,
42
+ street: address.street,
43
+ city: address.city,
44
+ postalCode: address.postalCode,
45
+ country: address.country,
46
+ phone: address.phone ?? null,
47
+ isDefault: address.isDefault,
48
+ createdAt: address.createdAt,
49
+ updatedAt: address.updatedAt,
50
+ deletedAt: address.deletedAt ?? null,
51
+ };
52
+ }
53
+ //# sourceMappingURL=customer-address-read-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-address-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/customer-address-read-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAEzE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,0BAA0B;IACR;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CACZ,iBAAyB,EACzB,SAAiB,EACjB,OAAgC;QAEhC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,eAAe,EAAE;YAC9D,EAAE,EAAE,SAAS;YACb,iBAAiB;YACjB,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,iBAAyB,EACzB,IAA6B;QAE7B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAC3C,eAAe,EACf,EAAE,iBAAiB,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAC/E,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,CACrD,CAAC;QACF,OAAO,SAAS,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAChD,CAAC;CACF;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAwB;IAC9D,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,94 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type AddressReadPort, type AddressRecord, type CustomerAccountReadPort } from '@endora-commerce/contracts';
3
+ import { CustomerAddress } from '../entities/customer-address.entity.js';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * CustomerAddressService — the personal address book (feature 040, US2).
7
+ *
8
+ * Clones the org `AddressService` default-demotion pattern (transactional +
9
+ * partial unique index) but keyed by `customerAccountId`. For org-bound
10
+ * customers the read endpoints also surface the Organization's shared
11
+ * addresses (read-only) so they can be selected (FR-038).
12
+ */
13
+ export interface CustomerAddressInputView {
14
+ kind: 'delivery' | 'billing';
15
+ recipientName: string;
16
+ street: string;
17
+ city: string;
18
+ postalCode: string;
19
+ country: string;
20
+ phone?: string | undefined;
21
+ isDefault?: boolean | undefined;
22
+ }
23
+ /** Mutable fields on update — `kind` is immutable once created. */
24
+ export interface CustomerAddressPatch {
25
+ recipientName?: string | undefined;
26
+ street?: string | undefined;
27
+ city?: string | undefined;
28
+ postalCode?: string | undefined;
29
+ country?: string | undefined;
30
+ phone?: string | undefined;
31
+ isDefault?: boolean | undefined;
32
+ }
33
+ export declare class CustomerAddressService {
34
+ #private;
35
+ private readonly emFactory;
36
+ /**
37
+ * Feature 075 — `addresses`' published read port, where this service used
38
+ * to run `em.find(Address, …)` over that module's table. The org-shared
39
+ * list is the only thing it ever asked for, and the port already scopes
40
+ * every read by organisation.
41
+ */
42
+ private readonly organizationAddresses;
43
+ /**
44
+ * `customer_accounts`' published read model — the tenant boundary of every
45
+ * method on this service, and not a convenience.
46
+ *
47
+ * `CustomerAddress` is `@CustomerScoped`, and `customerFilterCond`
48
+ * contributes **no predicate at all** under `allowed-set`, the mode an
49
+ * assignment-scoped administrator resolves to. This table carries no
50
+ * organization column of its own, so nothing here can express the
51
+ * restriction — but its owner `CustomerAccount` is `@OrgScoped`, and this
52
+ * port reads it through a filtered EntityManager. Asking it whether the
53
+ * account exists *is* asking whether the caller may reach that customer.
54
+ *
55
+ * For the buyer's own surface it is a no-op: their own account is in their
56
+ * own scope. For a platform administrator it is a no-op too.
57
+ */
58
+ private readonly customerAccounts;
59
+ private readonly auditLog?;
60
+ constructor(emFactory: () => EntityManager,
61
+ /**
62
+ * Feature 075 — `addresses`' published read port, where this service used
63
+ * to run `em.find(Address, …)` over that module's table. The org-shared
64
+ * list is the only thing it ever asked for, and the port already scopes
65
+ * every read by organisation.
66
+ */
67
+ organizationAddresses: AddressReadPort,
68
+ /**
69
+ * `customer_accounts`' published read model — the tenant boundary of every
70
+ * method on this service, and not a convenience.
71
+ *
72
+ * `CustomerAddress` is `@CustomerScoped`, and `customerFilterCond`
73
+ * contributes **no predicate at all** under `allowed-set`, the mode an
74
+ * assignment-scoped administrator resolves to. This table carries no
75
+ * organization column of its own, so nothing here can express the
76
+ * restriction — but its owner `CustomerAccount` is `@OrgScoped`, and this
77
+ * port reads it through a filtered EntityManager. Asking it whether the
78
+ * account exists *is* asking whether the caller may reach that customer.
79
+ *
80
+ * For the buyer's own surface it is a no-op: their own account is in their
81
+ * own scope. For a platform administrator it is a no-op too.
82
+ */
83
+ customerAccounts: CustomerAccountReadPort, auditLog?: AuditPort | undefined);
84
+ listPersonal(customerAccountId: string, kind?: 'delivery' | 'billing'): Promise<CustomerAddress[]>;
85
+ /** Org-shared addresses, read-only, for org-bound customers (FR-038). */
86
+ listOrganizationAddresses(organizationId: string, kind?: 'delivery' | 'billing'): Promise<AddressRecord[]>;
87
+ create(customerAccountId: string, input: CustomerAddressInputView): Promise<CustomerAddress>;
88
+ update(customerAccountId: string, addressId: string, patch: CustomerAddressPatch): Promise<CustomerAddress>;
89
+ /** Marks the address the default for its kind (demoting the previous one). */
90
+ setDefault(customerAccountId: string, addressId: string): Promise<CustomerAddress>;
91
+ delete(customerAccountId: string, addressId: string): Promise<void>;
92
+ private findOwned;
93
+ }
94
+ //# sourceMappingURL=customer-address-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-address-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-address-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAEzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,mEAAmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,qBAAa,sBAAsB;;IAE/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IACtC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAxBT,SAAS,EAAE,MAAM,aAAa;IAC/C;;;;;OAKG;IACc,qBAAqB,EAAE,eAAe;IACvD;;;;;;;;;;;;;;OAcG;IACc,gBAAgB,EAAE,uBAAuB,EACzC,QAAQ,CAAC,EAAE,SAAS,YAAA;IAmDjC,YAAY,CAChB,iBAAiB,EAAE,MAAM,EACzB,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,GAC5B,OAAO,CAAC,eAAe,EAAE,CAAC;IAY7B,yEAAyE;IACnE,yBAAyB,CAC7B,cAAc,EAAE,MAAM,EACtB,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC;IAIrB,MAAM,CACV,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,eAAe,CAAC;IA+BrB,MAAM,CACV,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,eAAe,CAAC;IA2B3B,8EAA8E;IACxE,UAAU,CACd,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC;IAIrB,MAAM,CAAC,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAc3D,SAAS;CAqBxB"}
@@ -0,0 +1,176 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { withSystemScope } from '@endora-commerce/platform/tenancy';
4
+ import { CustomerAddress } from '../entities/customer-address.entity.js';
5
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
6
+ export class CustomerAddressService {
7
+ emFactory;
8
+ organizationAddresses;
9
+ customerAccounts;
10
+ auditLog;
11
+ constructor(emFactory,
12
+ /**
13
+ * Feature 075 — `addresses`' published read port, where this service used
14
+ * to run `em.find(Address, …)` over that module's table. The org-shared
15
+ * list is the only thing it ever asked for, and the port already scopes
16
+ * every read by organisation.
17
+ */
18
+ organizationAddresses,
19
+ /**
20
+ * `customer_accounts`' published read model — the tenant boundary of every
21
+ * method on this service, and not a convenience.
22
+ *
23
+ * `CustomerAddress` is `@CustomerScoped`, and `customerFilterCond`
24
+ * contributes **no predicate at all** under `allowed-set`, the mode an
25
+ * assignment-scoped administrator resolves to. This table carries no
26
+ * organization column of its own, so nothing here can express the
27
+ * restriction — but its owner `CustomerAccount` is `@OrgScoped`, and this
28
+ * port reads it through a filtered EntityManager. Asking it whether the
29
+ * account exists *is* asking whether the caller may reach that customer.
30
+ *
31
+ * For the buyer's own surface it is a no-op: their own account is in their
32
+ * own scope. For a platform administrator it is a no-op too.
33
+ */
34
+ customerAccounts, auditLog) {
35
+ this.emFactory = emFactory;
36
+ this.organizationAddresses = organizationAddresses;
37
+ this.customerAccounts = customerAccounts;
38
+ this.auditLog = auditLog;
39
+ }
40
+ /**
41
+ * Refuse a customer the caller's organizations do not reach.
42
+ *
43
+ * 404 rather than 403: an out-of-scope customer must read the same as one
44
+ * that is not there, which is what the account read already answers.
45
+ */
46
+ async #assertCustomerInScope(customerAccountId) {
47
+ const account = await this.customerAccounts.findById(customerAccountId);
48
+ if (!account) {
49
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer account not found.');
50
+ }
51
+ }
52
+ /**
53
+ * Run a `customer_addresses` statement for the account
54
+ * {@link CustomerAddressService.#assertCustomerInScope} has just authorised.
55
+ *
56
+ * Feature 087 gave `customerFilterCond` a real `allowed-set` arm, and this
57
+ * table is one of the fifteen that carry no `organization_id` yet — so the
58
+ * arm matches **nothing** here, which is the right floor for a guard that
59
+ * cannot determine the allowed set and the wrong answer for a caller who has
60
+ * already been shown to be inside it. Without this widening a sales
61
+ * representative's own customer's address book reads as empty, and the
62
+ * default-demotion `nativeUpdate` below silently demotes nothing, which
63
+ * leaves two defaults.
64
+ *
65
+ * The widening is the sanctioned crossing (feature 050, FR-005) and not a
66
+ * `catch`: every call is preceded, in the same method, by the
67
+ * `customer_accounts` read that decides whether this caller may reach this
68
+ * account at all — and `customer_accounts` is `@OrgScoped`, so that read is
69
+ * the organization check. It is also narrow by construction: every statement
70
+ * it covers names `customerAccountId` in its own `where`, so widening the
71
+ * filter widens the authority by exactly nothing. It stops being needed when
72
+ * feature 087 gives this table its own column.
73
+ */
74
+ #forAuthorisedCustomer(customerAccountId, fn) {
75
+ return withSystemScope(`customers: personal addresses of customer account ${customerAccountId}, authorised by its own @OrgScoped account read`, fn);
76
+ }
77
+ #audit(em, action, objectId, stateBefore, stateAfter) {
78
+ if (this.auditLog) {
79
+ recordAuditFromContext(this.auditLog, em, { action, objectType: 'customer_address', objectId, stateBefore, stateAfter });
80
+ }
81
+ }
82
+ async listPersonal(customerAccountId, kind) {
83
+ await this.#assertCustomerInScope(customerAccountId);
84
+ const em = this.emFactory();
85
+ const where = { customerAccountId, deletedAt: null };
86
+ if (kind)
87
+ where.kind = kind;
88
+ return this.#forAuthorisedCustomer(customerAccountId, () => em.find(CustomerAddress, where, {
89
+ orderBy: { isDefault: 'desc', createdAt: 'asc' },
90
+ }));
91
+ }
92
+ /** Org-shared addresses, read-only, for org-bound customers (FR-038). */
93
+ async listOrganizationAddresses(organizationId, kind) {
94
+ return this.organizationAddresses.listForOrganization(organizationId, kind);
95
+ }
96
+ async create(customerAccountId, input) {
97
+ await this.#assertCustomerInScope(customerAccountId);
98
+ const em = this.emFactory();
99
+ return em.transactional(async (txEm) => {
100
+ if (input.isDefault) {
101
+ await this.#forAuthorisedCustomer(customerAccountId, () => txEm.nativeUpdate(CustomerAddress, { customerAccountId, kind: input.kind, isDefault: true, deletedAt: null }, { isDefault: false }));
102
+ }
103
+ const address = txEm.create(CustomerAddress, {
104
+ customerAccountId,
105
+ kind: input.kind,
106
+ recipientName: input.recipientName,
107
+ street: input.street,
108
+ city: input.city,
109
+ postalCode: input.postalCode,
110
+ country: input.country,
111
+ phone: input.phone ?? null,
112
+ isDefault: input.isDefault ?? false,
113
+ });
114
+ txEm.persist(address);
115
+ this.#audit(txEm, 'customer_address.create', address.id, null, { customerAccountId, kind: address.kind });
116
+ await txEm.flush();
117
+ return address;
118
+ });
119
+ }
120
+ async update(customerAccountId, addressId, patch) {
121
+ await this.#assertCustomerInScope(customerAccountId);
122
+ const em = this.emFactory();
123
+ return em.transactional(async (txEm) => {
124
+ const address = await this.findOwned(txEm, customerAccountId, addressId);
125
+ if (patch.isDefault === true && !address.isDefault) {
126
+ await this.#forAuthorisedCustomer(customerAccountId, () => txEm.nativeUpdate(CustomerAddress, { customerAccountId, kind: address.kind, isDefault: true, deletedAt: null }, { isDefault: false }));
127
+ }
128
+ if (patch.recipientName !== undefined)
129
+ address.recipientName = patch.recipientName;
130
+ if (patch.street !== undefined)
131
+ address.street = patch.street;
132
+ if (patch.city !== undefined)
133
+ address.city = patch.city;
134
+ if (patch.postalCode !== undefined)
135
+ address.postalCode = patch.postalCode;
136
+ if (patch.country !== undefined)
137
+ address.country = patch.country;
138
+ if (patch.phone !== undefined)
139
+ address.phone = patch.phone ?? null;
140
+ if (patch.isDefault !== undefined)
141
+ address.isDefault = patch.isDefault;
142
+ this.#audit(txEm, 'customer_address.update', address.id, null, { customerAccountId, kind: address.kind });
143
+ await txEm.persistAndFlush(address);
144
+ return address;
145
+ });
146
+ }
147
+ /** Marks the address the default for its kind (demoting the previous one). */
148
+ async setDefault(customerAccountId, addressId) {
149
+ return this.update(customerAccountId, addressId, { isDefault: true });
150
+ }
151
+ async delete(customerAccountId, addressId) {
152
+ await this.#assertCustomerInScope(customerAccountId);
153
+ const em = this.emFactory();
154
+ await em.transactional(async (txEm) => {
155
+ const address = await this.findOwned(txEm, customerAccountId, addressId);
156
+ address.deletedAt = new Date();
157
+ // Deleting a default leaves no default of that kind (FR-009 graceful
158
+ // degrade); the storefront prompts re-selection.
159
+ address.isDefault = false;
160
+ this.#audit(txEm, 'customer_address.delete', address.id, { customerAccountId, kind: address.kind }, null);
161
+ await txEm.persistAndFlush(address);
162
+ });
163
+ }
164
+ async findOwned(em, customerAccountId, addressId) {
165
+ const address = await this.#forAuthorisedCustomer(customerAccountId, () => em.findOne(CustomerAddress, {
166
+ id: addressId,
167
+ customerAccountId,
168
+ deletedAt: null,
169
+ }));
170
+ if (!address) {
171
+ throw new HttpError(404, ERROR_CODES.CUSTOMER_ADDRESS_NOT_FOUND, 'Address not found.');
172
+ }
173
+ return address;
174
+ }
175
+ }
176
+ //# sourceMappingURL=customer-address-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-address-service.js","sourceRoot":"","sources":["../../../src/backend/services/customer-address-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { AdminCustomerDetail, AdminCustomerListItem, CustomerAccountAdminSearchPort, CustomerAccountReadPort, CustomerGroupReadPort, OrderReadPort, OrganizationDetailsPort } from '@endora-commerce/contracts';
2
+ import type { CustomerDefaultsService } from './customer-defaults-service.js';
3
+ /**
4
+ * CustomerAdminQueryService — the admin customer list + detail (feature 040,
5
+ * US5). Applies the same visibility scope as moderation: a Platform
6
+ * Administrator sees everyone; a Salesperson sees standalone customers plus
7
+ * customers of the Organizations they are assigned to.
8
+ *
9
+ * **Feature 075, Phase C — the three foreign tables are three ports now.** The
10
+ * account page, the organisation names beside it and the customer-group names
11
+ * were all `em.find` calls on other modules' entities. The scope decision stays
12
+ * here, because it is this module's: the port takes the organisations the actor
13
+ * may see and does not decide who the actor is.
14
+ *
15
+ * **Feature 080, T048 (D-169) — and so is the fourth.** The detail header's
16
+ * sales-channel list was the last `orders` entity this module named:
17
+ * `em.find(Order, { placedByCustomerAccountId }, { fields: ['salesChannelId'] })`.
18
+ * It is a plain read, so it took a read-port method
19
+ * (`OrderReadPort.salesChannelIdsForCustomer`) and not an
20
+ * `EntityManager`-taking apply port — handing a read a transaction handle
21
+ * re-opens a write seam to serve it. With that gone this service holds no
22
+ * `EntityManager` at all: it owns no table, and every question it answers is
23
+ * somebody's published read.
24
+ */
25
+ export interface AdminCustomerListScope {
26
+ isPlatformAdmin: boolean;
27
+ allowedOrganizationIds: string[];
28
+ }
29
+ export interface AdminCustomerListQuery {
30
+ q?: string | undefined;
31
+ status?: 'active' | 'blocked' | 'deleted' | undefined;
32
+ organizationId?: string | undefined;
33
+ customerGroupId?: string | undefined;
34
+ page: number;
35
+ pageSize: number;
36
+ }
37
+ export interface AdminCustomerListResult {
38
+ rows: AdminCustomerListItem[];
39
+ total: number;
40
+ }
41
+ export interface CustomerAdminQueryPorts {
42
+ accounts: CustomerAccountReadPort;
43
+ accountSearch: CustomerAccountAdminSearchPort;
44
+ organizations: OrganizationDetailsPort;
45
+ customerGroups: CustomerGroupReadPort;
46
+ orders: OrderReadPort;
47
+ }
48
+ export declare class CustomerAdminQueryService {
49
+ private readonly defaults;
50
+ private readonly ports;
51
+ constructor(defaults: CustomerDefaultsService, ports: CustomerAdminQueryPorts);
52
+ list(query: AdminCustomerListQuery, scope: AdminCustomerListScope): Promise<AdminCustomerListResult>;
53
+ getDetail(id: string): Promise<AdminCustomerDetail | null>;
54
+ private resolveNames;
55
+ private toListItem;
56
+ }
57
+ //# sourceMappingURL=customer-admin-query-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-admin-query-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-admin-query-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACrB,8BAA8B,EAC9B,uBAAuB,EAEvB,qBAAqB,EACrB,aAAa,EACb,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,OAAO,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,EAAE,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,uBAAuB,CAAC;IAClC,aAAa,EAAE,8BAA8B,CAAC;IAC9C,aAAa,EAAE,uBAAuB,CAAC;IACvC,cAAc,EAAE,qBAAqB,CAAC;IACtC,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,qBAAa,yBAAyB;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,uBAAuB;IAG3C,IAAI,CACR,KAAK,EAAE,sBAAsB,EAC7B,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,uBAAuB,CAAC;IAoB7B,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAuClD,YAAY;IAsB1B,OAAO,CAAC,UAAU;CAoBnB"}
@@ -0,0 +1,97 @@
1
+ export class CustomerAdminQueryService {
2
+ defaults;
3
+ ports;
4
+ constructor(defaults, ports) {
5
+ this.defaults = defaults;
6
+ this.ports = ports;
7
+ }
8
+ async list(query, scope) {
9
+ const { rows, total } = await this.ports.accountSearch.search({
10
+ q: query.q,
11
+ status: query.status,
12
+ organizationId: query.organizationId,
13
+ customerGroupId: query.customerGroupId,
14
+ // `null` is the unscoped read. A salesperson gets their territory, and
15
+ // org-less customers are visible to every salesperson by construction.
16
+ allowedOrganizationIds: scope.isPlatformAdmin ? null : scope.allowedOrganizationIds,
17
+ page: query.page,
18
+ pageSize: query.pageSize,
19
+ });
20
+ const { orgNames, groupNames } = await this.resolveNames(rows);
21
+ return {
22
+ rows: rows.map((c) => this.toListItem(c, orgNames, groupNames)),
23
+ total,
24
+ };
25
+ }
26
+ async getDetail(id) {
27
+ const customer = await this.ports.accounts.findById(id);
28
+ if (!customer)
29
+ return null;
30
+ const { orgNames, groupNames } = await this.resolveNames([customer]);
31
+ const base = this.toListItem(customer, orgNames, groupNames);
32
+ // Already distinct, newest-ordered-on first — the owner does both, because
33
+ // the deduplication is what makes the ordering mean anything.
34
+ const salesChannelIds = await this.ports.orders.salesChannelIdsForCustomer(customer.id);
35
+ const defaults = await this.defaults.getForCustomer(customer.id);
36
+ return {
37
+ ...base,
38
+ block: customer.blockedAt
39
+ ? {
40
+ blockedAt: customer.blockedAt.toISOString(),
41
+ blockSource: customer.blockSource ?? 'staff',
42
+ blockReason: customer.blockReason ?? null,
43
+ blockedByAdminUserId: customer.blockedByAdminUserId ?? null,
44
+ blockedByCustomerAccountId: customer.blockedByCustomerAccountId ?? null,
45
+ }
46
+ : null,
47
+ deletion: customer.deletedAt
48
+ ? {
49
+ deletedAt: customer.deletedAt.toISOString(),
50
+ anonymizedAt: customer.anonymizedAt ? customer.anonymizedAt.toISOString() : null,
51
+ restorableUntil: null,
52
+ }
53
+ : null,
54
+ emailVerifiedAt: customer.emailVerifiedAt ? customer.emailVerifiedAt.toISOString() : null,
55
+ twoFactorEnabled: customer.twoFactorEnabled,
56
+ salesChannelIds,
57
+ defaults,
58
+ customFieldValues: customer.customFieldValues ?? {},
59
+ };
60
+ }
61
+ async resolveNames(rows) {
62
+ const orgIds = [...new Set(rows.map((r) => r.organizationId).filter((x) => !!x))];
63
+ const groupIds = [
64
+ ...new Set(rows.map((r) => r.customerGroupId).filter((x) => !!x)),
65
+ ];
66
+ const orgNames = new Map();
67
+ const groupNames = new Map();
68
+ if (orgIds.length) {
69
+ for (const o of await this.ports.organizations.findByIds(orgIds)) {
70
+ orgNames.set(o.id, o.name);
71
+ }
72
+ }
73
+ if (groupIds.length) {
74
+ for (const g of await this.ports.customerGroups.findByIds(groupIds)) {
75
+ groupNames.set(g.id, g.name);
76
+ }
77
+ }
78
+ return { orgNames, groupNames };
79
+ }
80
+ toListItem(c, orgNames, groupNames) {
81
+ return {
82
+ id: c.id,
83
+ email: c.email,
84
+ firstName: c.firstName,
85
+ lastName: c.lastName,
86
+ organizationId: c.organizationId ?? null,
87
+ organizationName: c.organizationId ? orgNames.get(c.organizationId) ?? null : null,
88
+ customerGroupId: c.customerGroupId ?? null,
89
+ customerGroupName: c.customerGroupId ? groupNames.get(c.customerGroupId) ?? null : null,
90
+ blocked: c.blockedAt != null,
91
+ deleted: c.deletedAt != null,
92
+ createdAt: c.createdAt.toISOString(),
93
+ lastLoginAt: c.lastLoginAt ? c.lastLoginAt.toISOString() : null,
94
+ };
95
+ }
96
+ }
97
+ //# sourceMappingURL=customer-admin-query-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-admin-query-service.js","sourceRoot":"","sources":["../../../src/backend/services/customer-admin-query-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * CustomerAuthorityService — encodes WHICH staff member may act on a given
3
+ * Customer (block / unblock / delete / org-assign), per the clarified rule
4
+ * (spec FR-037, research §R2):
5
+ *
6
+ * - A Platform Administrator may act on any customer. Whether the actor IS
7
+ * a platform admin is decided by the caller (route layer) and passed in —
8
+ * mirroring `SalesRepAssignmentService.canSeeOrganization`, which checks
9
+ * the assignment table only.
10
+ * - Only the Salesperson responsible for that
11
+ * customer's Organization may act — "responsible" = assigned via
12
+ * `OrganizationSalesRepAssignment`, including the unassigned-org fallback
13
+ * (an org with zero assigned reps is visible to every Salesperson).
14
+ *
15
+ * **D-178 removed a third rule** — *"for a standalone (org-less) customer, any
16
+ * Salesperson may act"* — because there is no such customer:
17
+ * `customer_accounts.organization_id` is `NOT NULL` and an individual is backed
18
+ * by a personal organisation. The outcome is unchanged rather than merely
19
+ * unreachable: `SalesRepAssignmentService` refuses to assign a rep to a personal
20
+ * organisation, so such an organisation always has zero assigned reps and the
21
+ * unassigned-org fallback answers `true` for every Salesperson — which is what
22
+ * the deleted rule said, arrived at through the assignment table instead of
23
+ * through the absence of a tenant.
24
+ *
25
+ * The route layer is responsible for the coarse permission gate
26
+ * (`requireAdmin('customers:manage')`) before this finer check runs.
27
+ */
28
+ export interface SalesRepVisibility {
29
+ canSeeOrganization(adminUserId: string, organizationId: string): Promise<boolean>;
30
+ }
31
+ export interface CustomerAuthorityInput {
32
+ /** True when the acting admin is a Platform Administrator (caller-decided). */
33
+ isPlatformAdmin: boolean;
34
+ adminUserId: string;
35
+ /** The target customer's Organization. Never null since D-178. */
36
+ customerOrganizationId: string;
37
+ }
38
+ export declare class CustomerAuthorityService {
39
+ private readonly salesReps;
40
+ constructor(salesReps: SalesRepVisibility);
41
+ /**
42
+ * True when the acting staff member is authorized to manage the customer.
43
+ */
44
+ canManageCustomer(input: CustomerAuthorityInput): Promise<boolean>;
45
+ }
46
+ //# sourceMappingURL=customer-authority-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-authority-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-authority-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnF;AAED,MAAM,WAAW,sBAAsB;IACrC,+EAA+E;IAC/E,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,qBAAa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,kBAAkB;IAE1D;;OAEG;IACG,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC;CAOzE"}