@endora-commerce/mod-customer-accounts 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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/customer-groups-client.d.ts +27 -0
  4. package/dist/admin/api/customer-groups-client.d.ts.map +1 -0
  5. package/dist/admin/api/customer-groups-client.js +17 -0
  6. package/dist/admin/api/customer-groups-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +33 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +38 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/CustomerGroupsPage.d.ts +18 -0
  12. package/dist/admin/pages/CustomerGroupsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/CustomerGroupsPage.js +108 -0
  14. package/dist/admin/pages/CustomerGroupsPage.js.map +1 -0
  15. package/dist/backend/entities/customer-account.entity.d.ts +111 -0
  16. package/dist/backend/entities/customer-account.entity.d.ts.map +1 -0
  17. package/dist/backend/entities/customer-account.entity.js +230 -0
  18. package/dist/backend/entities/customer-account.entity.js.map +1 -0
  19. package/dist/backend/entities/customer-group.entity.d.ts +29 -0
  20. package/dist/backend/entities/customer-group.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/customer-group.entity.js +70 -0
  22. package/dist/backend/entities/customer-group.entity.js.map +1 -0
  23. package/dist/backend/entities/password-reset-token.entity.d.ts +16 -0
  24. package/dist/backend/entities/password-reset-token.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/password-reset-token.entity.js +59 -0
  26. package/dist/backend/entities/password-reset-token.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +144 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +407 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/routes.admin.d.ts +26 -0
  32. package/dist/backend/routes.admin.d.ts.map +1 -0
  33. package/dist/backend/routes.admin.js +35 -0
  34. package/dist/backend/routes.admin.js.map +1 -0
  35. package/dist/backend/services/audit-references.d.ts +22 -0
  36. package/dist/backend/services/audit-references.d.ts.map +1 -0
  37. package/dist/backend/services/audit-references.js +46 -0
  38. package/dist/backend/services/audit-references.js.map +1 -0
  39. package/dist/backend/services/customer-account-lifecycle-ports.d.ts +168 -0
  40. package/dist/backend/services/customer-account-lifecycle-ports.d.ts.map +1 -0
  41. package/dist/backend/services/customer-account-lifecycle-ports.js +420 -0
  42. package/dist/backend/services/customer-account-lifecycle-ports.js.map +1 -0
  43. package/dist/backend/services/customer-account-ports.d.ts +143 -0
  44. package/dist/backend/services/customer-account-ports.d.ts.map +1 -0
  45. package/dist/backend/services/customer-account-ports.js +422 -0
  46. package/dist/backend/services/customer-account-ports.js.map +1 -0
  47. package/dist/backend/services/customer-auth-service.d.ts +62 -0
  48. package/dist/backend/services/customer-auth-service.d.ts.map +1 -0
  49. package/dist/backend/services/customer-auth-service.js +105 -0
  50. package/dist/backend/services/customer-auth-service.js.map +1 -0
  51. package/dist/backend/services/customer-group-read-port.d.ts +24 -0
  52. package/dist/backend/services/customer-group-read-port.d.ts.map +1 -0
  53. package/dist/backend/services/customer-group-read-port.js +44 -0
  54. package/dist/backend/services/customer-group-read-port.js.map +1 -0
  55. package/dist/backend/services/customer-group-service.d.ts +21 -0
  56. package/dist/backend/services/customer-group-service.d.ts.map +1 -0
  57. package/dist/backend/services/customer-group-service.js +97 -0
  58. package/dist/backend/services/customer-group-service.js.map +1 -0
  59. package/dist/backend/services/customer-rollup-scope.d.ts +30 -0
  60. package/dist/backend/services/customer-rollup-scope.d.ts.map +1 -0
  61. package/dist/backend/services/customer-rollup-scope.js +42 -0
  62. package/dist/backend/services/customer-rollup-scope.js.map +1 -0
  63. package/dist/backend/services/password-reset-service.d.ts +15 -0
  64. package/dist/backend/services/password-reset-service.d.ts.map +1 -0
  65. package/dist/backend/services/password-reset-service.js +90 -0
  66. package/dist/backend/services/password-reset-service.js.map +1 -0
  67. package/dist/backend/services/role-service.d.ts +22 -0
  68. package/dist/backend/services/role-service.d.ts.map +1 -0
  69. package/dist/backend/services/role-service.js +83 -0
  70. package/dist/backend/services/role-service.js.map +1 -0
  71. package/dist/backend/services/two-factor-enrolments.d.ts +18 -0
  72. package/dist/backend/services/two-factor-enrolments.d.ts.map +1 -0
  73. package/dist/backend/services/two-factor-enrolments.js +2 -0
  74. package/dist/backend/services/two-factor-enrolments.js.map +1 -0
  75. package/dist/manifest.d.ts +169 -0
  76. package/dist/manifest.d.ts.map +1 -0
  77. package/dist/manifest.js +212 -0
  78. package/dist/manifest.js.map +1 -0
  79. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.d.ts +6 -0
  80. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.d.ts.map +1 -0
  81. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.js +25 -0
  82. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.js.map +1 -0
  83. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.d.ts +34 -0
  84. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.d.ts.map +1 -0
  85. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.js +43 -0
  86. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.js.map +1 -0
  87. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.d.ts +54 -0
  88. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.d.ts.map +1 -0
  89. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.js +97 -0
  90. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.js.map +1 -0
  91. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.d.ts +12 -0
  92. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.d.ts.map +1 -0
  93. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.js +16 -0
  94. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.js.map +1 -0
  95. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.d.ts +14 -0
  96. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.d.ts.map +1 -0
  97. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.js +18 -0
  98. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.js.map +1 -0
  99. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.d.ts +22 -0
  100. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.d.ts.map +1 -0
  101. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.js +26 -0
  102. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.js.map +1 -0
  103. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.d.ts +46 -0
  104. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.d.ts.map +1 -0
  105. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.js +97 -0
  106. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.js.map +1 -0
  107. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.d.ts +31 -0
  108. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.d.ts.map +1 -0
  109. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js +35 -0
  110. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js.map +1 -0
  111. package/dist/migrations/20260825T141659_customer_accounts_organization_required.d.ts +58 -0
  112. package/dist/migrations/20260825T141659_customer_accounts_organization_required.d.ts.map +1 -0
  113. package/dist/migrations/20260825T141659_customer_accounts_organization_required.js +133 -0
  114. package/dist/migrations/20260825T141659_customer_accounts_organization_required.js.map +1 -0
  115. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts +27 -0
  116. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts.map +1 -0
  117. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.js +44 -0
  118. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.js.map +1 -0
  119. package/dist/migrations/index.d.ts +36 -0
  120. package/dist/migrations/index.d.ts.map +1 -0
  121. package/dist/migrations/index.js +47 -0
  122. package/dist/migrations/index.js.map +1 -0
  123. package/docs/customer_accounts.md +38 -0
  124. package/i18n/en.json +30 -0
  125. package/i18n/pl.json +30 -0
  126. package/package.json +94 -0
  127. package/tailwind.css +14 -0
@@ -0,0 +1,168 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CustomerAccountAdminSearchCriteria, type CustomerAccountAdminSearchPort, type CustomerAccountAdminSearchResult, type CustomerAccountLifecycleWritePort, type CustomerAccountRecord, type CustomerAccountStandaloneCreateInput, type CustomerAccountWriteRequestMeta } from '@endora-commerce/contracts';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ import type { PersonalOrganizationProvisionApi } from '@endora-commerce/mod-organizations/ports';
6
+ import type { TwoFactorEnrolmentReader } from './two-factor-enrolments.js';
7
+ /**
8
+ * The account lifecycle `customers` runs over this module's table
9
+ * (feature 075, Phase C).
10
+ *
11
+ * It ran it by loading and mutating the `CustomerAccount` entity in five of its
12
+ * own services and two route files — block, unblock, soft-delete, restore,
13
+ * anonymise, organisation assignment, group assignment, the standalone
14
+ * registration and the admin custom-field patch. Each is one method here, each
15
+ * one unit of work on this table alone, because a write port that took the
16
+ * caller's `EntityManager` would publish the coupling instead of removing it
17
+ * (D-78).
18
+ *
19
+ * **The policy stayed with the caller.** Whether the acting staff member may
20
+ * touch this customer, and whether the write would strand an organisation
21
+ * without an administrator, are `customers`' questions and are still asked
22
+ * there; the counting half of the second is
23
+ * `CustomerAccountReadPort.countByOrganizationRole`, which already existed.
24
+ *
25
+ * **The audit row came here** — one row, with the action string the caller used
26
+ * to write, so nothing an operator can see moved. Unlike
27
+ * `CustomerAccountMemberWriteService` the caller keeps no second row: there,
28
+ * "an operator edited this member on the organisation panel" and "this
29
+ * account's e-mail changed" are two facts; here "an operator blocked this
30
+ * customer" *is* the write, and recording it twice would be a defect rather
31
+ * than a pair.
32
+ */
33
+ /** The name a scrubbed account's first and last name are replaced with. */
34
+ export declare const ANONYMIZED_CUSTOMER_NAME = "Deleted customer";
35
+ /**
36
+ * The placeholder written over `password_hash`. It is deliberately not a hash
37
+ * of anything: the column is NOT NULL, and nothing may authenticate against a
38
+ * scrubbed account. `passwordSetAt` is cleared beside it (issue #222).
39
+ */
40
+ export declare const ANONYMIZED_PASSWORD_HASH = "anonymized-no-login";
41
+ export declare class CustomerAccountLifecycleWriteService implements CustomerAccountLifecycleWritePort {
42
+ #private;
43
+ private readonly emFactory;
44
+ private readonly auditLog;
45
+ /**
46
+ * `mfa`'s answer to which of these accounts hold a second factor. Every
47
+ * method here answers with a `CustomerAccountRecord`, and that record
48
+ * carries `twoFactorEnabled` — so the alternative to threading it is a
49
+ * record that says `false` about an enrolled buyer, which is the defect
50
+ * this parameter exists to remove.
51
+ */
52
+ private readonly twoFactorEnrolments;
53
+ /**
54
+ * D-178 — `organizations`' co-transactional provisioning seam, held here
55
+ * because {@link createStandalone} writes the account **and** the individual's
56
+ * personal organisation in one transaction. See
57
+ * `organizations/ports/personal-organization-provision.ts` for the foreign
58
+ * key that makes that a requirement rather than a preference.
59
+ */
60
+ private readonly personalOrganizations;
61
+ /**
62
+ * Only {@link setCustomFieldValues} needs it, because that one write is a
63
+ * read-modify-write and has to stay in one transaction. The method says so
64
+ * and refuses without it.
65
+ */
66
+ private readonly commandBus?;
67
+ constructor(emFactory: () => EntityManager, auditLog: AuditPort,
68
+ /**
69
+ * `mfa`'s answer to which of these accounts hold a second factor. Every
70
+ * method here answers with a `CustomerAccountRecord`, and that record
71
+ * carries `twoFactorEnabled` — so the alternative to threading it is a
72
+ * record that says `false` about an enrolled buyer, which is the defect
73
+ * this parameter exists to remove.
74
+ */
75
+ twoFactorEnrolments: TwoFactorEnrolmentReader,
76
+ /**
77
+ * D-178 — `organizations`' co-transactional provisioning seam, held here
78
+ * because {@link createStandalone} writes the account **and** the individual's
79
+ * personal organisation in one transaction. See
80
+ * `organizations/ports/personal-organization-provision.ts` for the foreign
81
+ * key that makes that a requirement rather than a preference.
82
+ */
83
+ personalOrganizations: PersonalOrganizationProvisionApi,
84
+ /**
85
+ * Only {@link setCustomFieldValues} needs it, because that one write is a
86
+ * read-modify-write and has to stay in one transaction. The method says so
87
+ * and refuses without it.
88
+ */
89
+ commandBus?: CommandBus | undefined);
90
+ /**
91
+ * Self-registration: the account row **and** the tenant that scopes it, in one
92
+ * transaction (D-178).
93
+ *
94
+ * It used to be two committed units of work — this method wrote
95
+ * `organizationId: null` and flushed, and `customers`' registration service
96
+ * then called `personalOrganizationPort.ensureForCustomerAccount`. A failure
97
+ * between them (the port disabled, the database blipping, the process killed)
98
+ * left a durable account with no organisation, no session for the visitor to
99
+ * retry from, and nothing anywhere that re-ran the provisioning. That is one
100
+ * of the three producers D-178 closed, and the column is `NOT NULL` now, so
101
+ * the old order would fail at the insert rather than leave a bad row.
102
+ *
103
+ * The account id is generated here rather than by the entity default because
104
+ * the organisation has to exist first — its `taxId` is derived from that id,
105
+ * which is what makes the provisioning re-runnable — and the account's foreign
106
+ * key has to point at it.
107
+ */
108
+ createStandalone(input: CustomerAccountStandaloneCreateInput): Promise<CustomerAccountRecord>;
109
+ block(customerAccountId: string, input: {
110
+ actorAdminUserId: string;
111
+ reason?: string | null;
112
+ audit?: CustomerAccountWriteRequestMeta;
113
+ }): Promise<CustomerAccountRecord>;
114
+ unblock(customerAccountId: string, input: {
115
+ actorAdminUserId: string;
116
+ audit?: CustomerAccountWriteRequestMeta;
117
+ }): Promise<CustomerAccountRecord>;
118
+ softDelete(customerAccountId: string, input: {
119
+ actorAdminUserId: string;
120
+ }): Promise<CustomerAccountRecord>;
121
+ restore(customerAccountId: string, input: {
122
+ actorAdminUserId: string;
123
+ }): Promise<CustomerAccountRecord>;
124
+ setOrganization(customerAccountId: string, organizationId: string, input: {
125
+ actorAdminUserId: string;
126
+ }): Promise<CustomerAccountRecord>;
127
+ /**
128
+ * D-178 — an operator detaching a member from a company.
129
+ *
130
+ * The write is byte-identical to {@link setOrganization}'s; only the audit
131
+ * verb differs, and it is the verb this operation has always recorded. What
132
+ * changed is what it writes: `organization_id = NULL` until D-178, which left
133
+ * an account that could not place an order, submit an RFQ or read an address —
134
+ * and which the column now refuses outright.
135
+ *
136
+ * The destination is the account's own personal organisation, resolved by the
137
+ * caller through `organizations`. This module does not resolve it, because
138
+ * that would make the account table's owner responsible for the tenancy
139
+ * policy of the module it points at.
140
+ */
141
+ detachToPersonalOrganization(customerAccountId: string, personalOrganizationId: string, input: {
142
+ actorAdminUserId: string;
143
+ }): Promise<CustomerAccountRecord>;
144
+ setCustomerGroup(customerAccountId: string, customerGroupId: string | null, input: {
145
+ actorAdminUserId: string;
146
+ }): Promise<CustomerAccountRecord>;
147
+ setCustomFieldValues(customerAccountId: string, merge: (current: Record<string, unknown>) => Promise<Record<string, unknown>>): Promise<CustomerAccountRecord>;
148
+ listDueForAnonymization(cutoff: Date): Promise<CustomerAccountRecord[]>;
149
+ anonymize(customerAccountId: string): Promise<CustomerAccountRecord>;
150
+ }
151
+ /**
152
+ * The admin customer list (feature 040, US5).
153
+ *
154
+ * `customers` built the whole query itself — the lifecycle filter, the
155
+ * sales-rep visibility scope, the free-text predicate and the page — against
156
+ * this module's entity. The **shape** of the answer is that module's screen and
157
+ * the **statement** is this module's table, so the criteria cross as plain data
158
+ * and the SQL stays here. It has to be one statement: narrowing a page after
159
+ * the fact returns fewer rows than the caller asked for, and paging after
160
+ * narrowing reads the whole table.
161
+ */
162
+ export declare class CustomerAccountAdminSearchService implements CustomerAccountAdminSearchPort {
163
+ private readonly emFactory;
164
+ private readonly twoFactorEnrolments;
165
+ constructor(emFactory: () => EntityManager, twoFactorEnrolments: TwoFactorEnrolmentReader);
166
+ search(criteria: CustomerAccountAdminSearchCriteria): Promise<CustomerAccountAdminSearchResult>;
167
+ }
168
+ //# sourceMappingURL=customer-account-lifecycle-ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-account-lifecycle-ports.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-account-lifecycle-ports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,420 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { UniqueConstraintViolationException } from '@mikro-orm/core';
3
+ import { ERROR_CODES, normalizeEmailAddress, } from '@endora-commerce/contracts';
4
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
5
+ import { HttpError } from '@endora-commerce/platform/http';
6
+ import { hashPassword } from '@endora-commerce/platform/kernel';
7
+ import { CustomerAccount } from '../entities/customer-account.entity.js';
8
+ import { toCustomerAccountRecord, toCustomerAccountRecords, toOneCustomerAccountRecord, } from './customer-account-ports.js';
9
+ /**
10
+ * The account lifecycle `customers` runs over this module's table
11
+ * (feature 075, Phase C).
12
+ *
13
+ * It ran it by loading and mutating the `CustomerAccount` entity in five of its
14
+ * own services and two route files — block, unblock, soft-delete, restore,
15
+ * anonymise, organisation assignment, group assignment, the standalone
16
+ * registration and the admin custom-field patch. Each is one method here, each
17
+ * one unit of work on this table alone, because a write port that took the
18
+ * caller's `EntityManager` would publish the coupling instead of removing it
19
+ * (D-78).
20
+ *
21
+ * **The policy stayed with the caller.** Whether the acting staff member may
22
+ * touch this customer, and whether the write would strand an organisation
23
+ * without an administrator, are `customers`' questions and are still asked
24
+ * there; the counting half of the second is
25
+ * `CustomerAccountReadPort.countByOrganizationRole`, which already existed.
26
+ *
27
+ * **The audit row came here** — one row, with the action string the caller used
28
+ * to write, so nothing an operator can see moved. Unlike
29
+ * `CustomerAccountMemberWriteService` the caller keeps no second row: there,
30
+ * "an operator edited this member on the organisation panel" and "this
31
+ * account's e-mail changed" are two facts; here "an operator blocked this
32
+ * customer" *is* the write, and recording it twice would be a defect rather
33
+ * than a pair.
34
+ */
35
+ /** The name a scrubbed account's first and last name are replaced with. */
36
+ export const ANONYMIZED_CUSTOMER_NAME = 'Deleted customer';
37
+ /**
38
+ * The placeholder written over `password_hash`. It is deliberately not a hash
39
+ * of anything: the column is NOT NULL, and nothing may authenticate against a
40
+ * scrubbed account. `passwordSetAt` is cleared beside it (issue #222).
41
+ */
42
+ export const ANONYMIZED_PASSWORD_HASH = 'anonymized-no-login';
43
+ function blockSnapshot(account) {
44
+ return {
45
+ blockedAt: account.blockedAt ? account.blockedAt.toISOString() : null,
46
+ blockSource: account.blockSource ?? null,
47
+ blockReason: account.blockReason ?? null,
48
+ };
49
+ }
50
+ export class CustomerAccountLifecycleWriteService {
51
+ emFactory;
52
+ auditLog;
53
+ twoFactorEnrolments;
54
+ personalOrganizations;
55
+ commandBus;
56
+ constructor(emFactory, auditLog,
57
+ /**
58
+ * `mfa`'s answer to which of these accounts hold a second factor. Every
59
+ * method here answers with a `CustomerAccountRecord`, and that record
60
+ * carries `twoFactorEnabled` — so the alternative to threading it is a
61
+ * record that says `false` about an enrolled buyer, which is the defect
62
+ * this parameter exists to remove.
63
+ */
64
+ twoFactorEnrolments,
65
+ /**
66
+ * D-178 — `organizations`' co-transactional provisioning seam, held here
67
+ * because {@link createStandalone} writes the account **and** the individual's
68
+ * personal organisation in one transaction. See
69
+ * `organizations/ports/personal-organization-provision.ts` for the foreign
70
+ * key that makes that a requirement rather than a preference.
71
+ */
72
+ personalOrganizations,
73
+ /**
74
+ * Only {@link setCustomFieldValues} needs it, because that one write is a
75
+ * read-modify-write and has to stay in one transaction. The method says so
76
+ * and refuses without it.
77
+ */
78
+ commandBus) {
79
+ this.emFactory = emFactory;
80
+ this.auditLog = auditLog;
81
+ this.twoFactorEnrolments = twoFactorEnrolments;
82
+ this.personalOrganizations = personalOrganizations;
83
+ this.commandBus = commandBus;
84
+ }
85
+ /**
86
+ * Self-registration: the account row **and** the tenant that scopes it, in one
87
+ * transaction (D-178).
88
+ *
89
+ * It used to be two committed units of work — this method wrote
90
+ * `organizationId: null` and flushed, and `customers`' registration service
91
+ * then called `personalOrganizationPort.ensureForCustomerAccount`. A failure
92
+ * between them (the port disabled, the database blipping, the process killed)
93
+ * left a durable account with no organisation, no session for the visitor to
94
+ * retry from, and nothing anywhere that re-ran the provisioning. That is one
95
+ * of the three producers D-178 closed, and the column is `NOT NULL` now, so
96
+ * the old order would fail at the insert rather than leave a bad row.
97
+ *
98
+ * The account id is generated here rather than by the entity default because
99
+ * the organisation has to exist first — its `taxId` is derived from that id,
100
+ * which is what makes the provisioning re-runnable — and the account's foreign
101
+ * key has to point at it.
102
+ */
103
+ async createStandalone(input) {
104
+ const em = this.emFactory();
105
+ // Folded, like the other write on this table and like every read of it.
106
+ // This entrance stored the address verbatim while
107
+ // `CustomerAccountMemberWriteService.create` folded it, so which spelling a
108
+ // row held depended on which door the buyer came through; the login read
109
+ // matched neither reliably. One normalisation now answers for all of them.
110
+ const email = normalizeEmailAddress(input.email);
111
+ const existing = await em.findOne(CustomerAccount, { email });
112
+ if (existing) {
113
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
114
+ }
115
+ const customerAccountId = randomUUID();
116
+ const passwordHash = await hashPassword(input.password);
117
+ return em.transactional(async (tem) => {
118
+ const organization = await this.personalOrganizations.provisionFor(tem, {
119
+ customerAccountId,
120
+ email,
121
+ firstName: input.firstName,
122
+ lastName: input.lastName,
123
+ });
124
+ const account = tem.create(CustomerAccount, {
125
+ id: customerAccountId,
126
+ email,
127
+ passwordHash,
128
+ // Issue #222 — the visitor typed this password on the registration form.
129
+ passwordSetAt: new Date(),
130
+ firstName: input.firstName,
131
+ lastName: input.lastName,
132
+ organizationId: organization.id,
133
+ });
134
+ // Self-registration is pre-auth, so there is no ambient actor and the row
135
+ // is recorded with a null one — as it was before this write moved here.
136
+ recordAuditFromContext(this.auditLog, tem, {
137
+ action: 'customer_account.register_standalone',
138
+ objectType: 'customer_account',
139
+ objectId: account.id,
140
+ stateBefore: null,
141
+ stateAfter: { email: account.email },
142
+ });
143
+ try {
144
+ await tem.flush();
145
+ }
146
+ catch (err) {
147
+ // The unique index is the real arbiter; the pre-check above only buys a
148
+ // better message for the common case. The port call is deliberately
149
+ // outside this `catch`: a `ModuleDisabledError` from it must reach the
150
+ // caller as the 503 it is, not as a duplicate-address 409.
151
+ if (err instanceof UniqueConstraintViolationException) {
152
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
153
+ }
154
+ throw err;
155
+ }
156
+ // `false` is **derived** here rather than defaulted: the account was
157
+ // created microseconds ago inside this transaction, `mfa_enrolments` is
158
+ // keyed on its id and nobody has had a chance to enrol against it. Asking
159
+ // `mfa` would take a second pooled connection while this transaction
160
+ // holds one, on the pre-auth registration path, to be told the same
161
+ // thing.
162
+ return toCustomerAccountRecord(account, false);
163
+ });
164
+ }
165
+ async block(customerAccountId, input) {
166
+ const em = this.emFactory();
167
+ const account = await this.#loadLive(em, customerAccountId);
168
+ if (account.blockedAt)
169
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
170
+ const before = blockSnapshot(account);
171
+ account.blockedAt = new Date();
172
+ account.blockSource = 'staff';
173
+ account.blockedByAdminUserId = input.actorAdminUserId;
174
+ account.blockedByCustomerAccountId = null;
175
+ account.blockReason = input.reason ?? null;
176
+ await em.flush();
177
+ await this.#record('customer_account.blocked', account.id, input.actorAdminUserId, before, blockSnapshot(account), input.audit);
178
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
179
+ }
180
+ async unblock(customerAccountId, input) {
181
+ const em = this.emFactory();
182
+ const account = await this.#loadLive(em, customerAccountId);
183
+ if (!account.blockedAt) {
184
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
185
+ }
186
+ const before = blockSnapshot(account);
187
+ account.blockedAt = null;
188
+ account.blockSource = null;
189
+ account.blockedByAdminUserId = null;
190
+ account.blockedByCustomerAccountId = null;
191
+ account.blockReason = null;
192
+ await em.flush();
193
+ await this.#record('customer_account.unblocked', account.id, input.actorAdminUserId, before, blockSnapshot(account), input.audit);
194
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
195
+ }
196
+ async softDelete(customerAccountId, input) {
197
+ const em = this.emFactory();
198
+ const account = await this.#load(em, customerAccountId);
199
+ if (account.deletedAt) {
200
+ throw new HttpError(409, ERROR_CODES.CUSTOMER_ALREADY_DELETED, 'Customer is already deleted.');
201
+ }
202
+ account.deletedAt = new Date();
203
+ account.deletionRequestedByAdminUserId = input.actorAdminUserId;
204
+ await em.flush();
205
+ await this.#record('customer_account.deleted', account.id, input.actorAdminUserId, { deletedAt: null }, { deletedAt: account.deletedAt.toISOString() });
206
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
207
+ }
208
+ async restore(customerAccountId, input) {
209
+ const em = this.emFactory();
210
+ const account = await this.#load(em, customerAccountId);
211
+ if (!account.deletedAt) {
212
+ throw new HttpError(409, ERROR_CODES.CUSTOMER_NOT_DELETED, 'Customer is not deleted.');
213
+ }
214
+ if (account.anonymizedAt) {
215
+ throw new HttpError(409, ERROR_CODES.CUSTOMER_RESTORE_WINDOW_ELAPSED, 'The restore window has elapsed; this account has been permanently anonymized.');
216
+ }
217
+ account.deletedAt = null;
218
+ account.deletionRequestedByAdminUserId = null;
219
+ await em.flush();
220
+ await this.#record('customer_account.restored', account.id, input.actorAdminUserId, { deletedAt: 'set' }, { deletedAt: null });
221
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
222
+ }
223
+ async setOrganization(customerAccountId, organizationId, input) {
224
+ return this.#writeOrganization('customer_account.organization_assigned', customerAccountId, organizationId, input);
225
+ }
226
+ /**
227
+ * D-178 — an operator detaching a member from a company.
228
+ *
229
+ * The write is byte-identical to {@link setOrganization}'s; only the audit
230
+ * verb differs, and it is the verb this operation has always recorded. What
231
+ * changed is what it writes: `organization_id = NULL` until D-178, which left
232
+ * an account that could not place an order, submit an RFQ or read an address —
233
+ * and which the column now refuses outright.
234
+ *
235
+ * The destination is the account's own personal organisation, resolved by the
236
+ * caller through `organizations`. This module does not resolve it, because
237
+ * that would make the account table's owner responsible for the tenancy
238
+ * policy of the module it points at.
239
+ */
240
+ async detachToPersonalOrganization(customerAccountId, personalOrganizationId, input) {
241
+ return this.#writeOrganization('customer_account.organization_unassigned', customerAccountId, personalOrganizationId, input);
242
+ }
243
+ async #writeOrganization(action, customerAccountId, organizationId, input) {
244
+ const em = this.emFactory();
245
+ const account = await this.#loadLive(em, customerAccountId);
246
+ const before = { organizationId: account.organizationId };
247
+ account.organizationId = organizationId;
248
+ await em.flush();
249
+ await this.#record(action, account.id, input.actorAdminUserId, before, { organizationId });
250
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
251
+ }
252
+ async setCustomerGroup(customerAccountId, customerGroupId, input) {
253
+ const em = this.emFactory();
254
+ const account = await this.#loadLive(em, customerAccountId);
255
+ const before = { customerGroupId: account.customerGroupId ?? null };
256
+ account.customerGroupId = customerGroupId;
257
+ await em.flush();
258
+ await this.#record('customer_account.group_assigned', account.id, input.actorAdminUserId, before, { customerGroupId });
259
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
260
+ }
261
+ async setCustomFieldValues(customerAccountId, merge) {
262
+ const commandBus = this.commandBus;
263
+ if (!commandBus) {
264
+ throw new Error('CustomerAccountLifecycleWriteService: the custom-field write runs a Command; construct the service with a CommandBus.');
265
+ }
266
+ // Asked **before** the Command opens its transaction. The answer belongs to
267
+ // another module's table and is only needed to shape the response, so
268
+ // reading it inside `run` would put a foreign read on the caller's
269
+ // transaction for no benefit.
270
+ const enrolled = await this.twoFactorEnrolments([customerAccountId]);
271
+ const command = {
272
+ action: 'customer.custom_fields.update',
273
+ objectType: 'customer_account',
274
+ objectId: customerAccountId,
275
+ capture: async ({ em }) => {
276
+ const account = await em.findOne(CustomerAccount, { id: customerAccountId });
277
+ return account ? { customFieldValues: account.customFieldValues ?? {} } : null;
278
+ },
279
+ run: async ({ em }) => {
280
+ const account = await em.findOne(CustomerAccount, { id: customerAccountId });
281
+ if (!account) {
282
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer not found.');
283
+ }
284
+ account.customFieldValues = await merge(account.customFieldValues ?? {});
285
+ return {
286
+ result: toCustomerAccountRecord(account, enrolled.has(account.id)),
287
+ after: { customFieldValues: account.customFieldValues },
288
+ };
289
+ },
290
+ };
291
+ return commandBus.run(command);
292
+ }
293
+ async listDueForAnonymization(cutoff) {
294
+ const accounts = await this.emFactory().find(CustomerAccount, {
295
+ deletedAt: { $ne: null, $lt: cutoff },
296
+ anonymizedAt: null,
297
+ });
298
+ return toCustomerAccountRecords(this.twoFactorEnrolments, accounts);
299
+ }
300
+ async anonymize(customerAccountId) {
301
+ const em = this.emFactory();
302
+ const account = await this.#load(em, customerAccountId);
303
+ if (account.anonymizedAt) {
304
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
305
+ }
306
+ const before = { email: account.email };
307
+ account.email = `deleted+${account.id}@anonymized.invalid`;
308
+ account.firstName = ANONYMIZED_CUSTOMER_NAME;
309
+ account.lastName = ANONYMIZED_CUSTOMER_NAME;
310
+ account.passwordHash = ANONYMIZED_PASSWORD_HASH;
311
+ // Issue #222 — the scrubbed hash is a placeholder, not a credential, so the
312
+ // account has no password on record any more. Leaving the stamp would say
313
+ // the opposite of what the scrub just did.
314
+ account.passwordSetAt = null;
315
+ account.twoFactorConfirmedAt = null;
316
+ account.anonymizedAt = new Date();
317
+ await em.flush();
318
+ // The sweep runs on a timer, so there is no operator to attribute it to.
319
+ await this.#record('customer_account.anonymized', account.id, null, before, {
320
+ anonymizedAt: account.anonymizedAt.toISOString(),
321
+ });
322
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
323
+ }
324
+ async #record(action, objectId, actorAdminUserId, stateBefore, stateAfter, meta) {
325
+ await this.auditLog.record({
326
+ actorAdminUserId,
327
+ action,
328
+ objectType: 'customer_account',
329
+ objectId,
330
+ stateBefore,
331
+ stateAfter,
332
+ ipAddress: meta?.ipAddress ?? null,
333
+ userAgent: meta?.userAgent ?? null,
334
+ requestId: meta?.requestId ?? null,
335
+ });
336
+ }
337
+ async #load(em, id) {
338
+ const account = await em.findOne(CustomerAccount, { id });
339
+ if (!account) {
340
+ throw new HttpError(404, ERROR_CODES.CUSTOMER_NOT_FOUND, 'Customer not found.');
341
+ }
342
+ return account;
343
+ }
344
+ async #loadLive(em, id) {
345
+ const account = await em.findOne(CustomerAccount, { id, deletedAt: null });
346
+ if (!account) {
347
+ throw new HttpError(404, ERROR_CODES.CUSTOMER_NOT_FOUND, 'Customer not found.');
348
+ }
349
+ return account;
350
+ }
351
+ }
352
+ /**
353
+ * The admin customer list (feature 040, US5).
354
+ *
355
+ * `customers` built the whole query itself — the lifecycle filter, the
356
+ * sales-rep visibility scope, the free-text predicate and the page — against
357
+ * this module's entity. The **shape** of the answer is that module's screen and
358
+ * the **statement** is this module's table, so the criteria cross as plain data
359
+ * and the SQL stays here. It has to be one statement: narrowing a page after
360
+ * the fact returns fewer rows than the caller asked for, and paging after
361
+ * narrowing reads the whole table.
362
+ */
363
+ export class CustomerAccountAdminSearchService {
364
+ emFactory;
365
+ twoFactorEnrolments;
366
+ constructor(emFactory, twoFactorEnrolments) {
367
+ this.emFactory = emFactory;
368
+ this.twoFactorEnrolments = twoFactorEnrolments;
369
+ }
370
+ async search(criteria) {
371
+ const and = [];
372
+ if (criteria.status === 'blocked') {
373
+ and.push({ blockedAt: { $ne: null }, deletedAt: null });
374
+ }
375
+ else if (criteria.status === 'deleted') {
376
+ and.push({ deletedAt: { $ne: null } });
377
+ }
378
+ else {
379
+ // The default "active" view excludes deleted accounts.
380
+ and.push({ deletedAt: null });
381
+ }
382
+ if (criteria.organizationId)
383
+ and.push({ organizationId: criteria.organizationId });
384
+ if (criteria.customerGroupId)
385
+ and.push({ customerGroupId: criteria.customerGroupId });
386
+ // `null` is the platform administrator's unscoped read. Anything else is a
387
+ // sales rep's territory.
388
+ //
389
+ // D-178 removed an `$or` arm here that also matched `organizationId: null`.
390
+ // Its comment said a standalone account belonged to nobody's territory and
391
+ // the predicate put it in **everybody's**: every sales representative's
392
+ // customer list contained every null-organisation account on the platform,
393
+ // and the population grew on every federated sign-in. That was a live
394
+ // cross-territory disclosure, not an optimisation, and it is gone rather
395
+ // than left unreachable — a predicate with no population still reads as
396
+ // policy to whoever finds it next.
397
+ if (criteria.allowedOrganizationIds !== null) {
398
+ and.push({ organizationId: { $in: [...criteria.allowedOrganizationIds] } });
399
+ }
400
+ const q = criteria.q?.trim();
401
+ if (q) {
402
+ const like = `%${q}%`;
403
+ and.push({
404
+ $or: [
405
+ { email: { $ilike: like } },
406
+ { firstName: { $ilike: like } },
407
+ { lastName: { $ilike: like } },
408
+ ],
409
+ });
410
+ }
411
+ const where = and.length ? { $and: and } : {};
412
+ const [rows, total] = await this.emFactory().findAndCount(CustomerAccount, where, {
413
+ orderBy: { createdAt: 'desc' },
414
+ limit: criteria.pageSize,
415
+ offset: (criteria.page - 1) * criteria.pageSize,
416
+ });
417
+ return { rows: await toCustomerAccountRecords(this.twoFactorEnrolments, rows), total };
418
+ }
419
+ }
420
+ //# sourceMappingURL=customer-account-lifecycle-ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-account-lifecycle-ports.js","sourceRoot":"","sources":["../../../src/backend/services/customer-account-lifecycle-ports.ts"],"names":[],"mappings":"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"}