@endora-commerce/mod-organizations 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 (288) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/admin/components/OrganizationSalesRepsTab.d.ts +27 -0
  4. package/dist/admin/components/OrganizationSalesRepsTab.d.ts.map +1 -0
  5. package/dist/admin/components/OrganizationSalesRepsTab.js +170 -0
  6. package/dist/admin/components/OrganizationSalesRepsTab.js.map +1 -0
  7. package/dist/admin/index.d.ts +30 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +59 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/OrganizationDetail.d.ts +9 -0
  12. package/dist/admin/pages/OrganizationDetail.d.ts.map +1 -0
  13. package/dist/admin/pages/OrganizationDetail.js +247 -0
  14. package/dist/admin/pages/OrganizationDetail.js.map +1 -0
  15. package/dist/admin/pages/OrganizationsList.d.ts +9 -0
  16. package/dist/admin/pages/OrganizationsList.d.ts.map +1 -0
  17. package/dist/admin/pages/OrganizationsList.js +73 -0
  18. package/dist/admin/pages/OrganizationsList.js.map +1 -0
  19. package/dist/admin/panels/ApplicablePriceListsPanel.d.ts +12 -0
  20. package/dist/admin/panels/ApplicablePriceListsPanel.d.ts.map +1 -0
  21. package/dist/admin/panels/ApplicablePriceListsPanel.js +49 -0
  22. package/dist/admin/panels/ApplicablePriceListsPanel.js.map +1 -0
  23. package/dist/admin/panels/FulfilmentStrategyPanel.d.ts +16 -0
  24. package/dist/admin/panels/FulfilmentStrategyPanel.d.ts.map +1 -0
  25. package/dist/admin/panels/FulfilmentStrategyPanel.js +29 -0
  26. package/dist/admin/panels/FulfilmentStrategyPanel.js.map +1 -0
  27. package/dist/admin/panels/HierarchyPanel.d.ts +15 -0
  28. package/dist/admin/panels/HierarchyPanel.d.ts.map +1 -0
  29. package/dist/admin/panels/HierarchyPanel.js +65 -0
  30. package/dist/admin/panels/HierarchyPanel.js.map +1 -0
  31. package/dist/admin/panels/ModerationActionsPanel.d.ts +28 -0
  32. package/dist/admin/panels/ModerationActionsPanel.d.ts.map +1 -0
  33. package/dist/admin/panels/ModerationActionsPanel.js +111 -0
  34. package/dist/admin/panels/ModerationActionsPanel.js.map +1 -0
  35. package/dist/admin/panels/RestrictionsPanel.d.ts +24 -0
  36. package/dist/admin/panels/RestrictionsPanel.d.ts.map +1 -0
  37. package/dist/admin/panels/RestrictionsPanel.js +121 -0
  38. package/dist/admin/panels/RestrictionsPanel.js.map +1 -0
  39. package/dist/admin/panels/VatValidationPanel.d.ts +28 -0
  40. package/dist/admin/panels/VatValidationPanel.d.ts.map +1 -0
  41. package/dist/admin/panels/VatValidationPanel.js +79 -0
  42. package/dist/admin/panels/VatValidationPanel.js.map +1 -0
  43. package/dist/backend/commands/move.command.d.ts +11 -0
  44. package/dist/backend/commands/move.command.d.ts.map +1 -0
  45. package/dist/backend/commands/move.command.js +11 -0
  46. package/dist/backend/commands/move.command.js.map +1 -0
  47. package/dist/backend/commands/set-cart-approval-policy.command.d.ts +31 -0
  48. package/dist/backend/commands/set-cart-approval-policy.command.d.ts.map +1 -0
  49. package/dist/backend/commands/set-cart-approval-policy.command.js +56 -0
  50. package/dist/backend/commands/set-cart-approval-policy.command.js.map +1 -0
  51. package/dist/backend/commands/set-credit-mode.command.d.ts +16 -0
  52. package/dist/backend/commands/set-credit-mode.command.d.ts.map +1 -0
  53. package/dist/backend/commands/set-credit-mode.command.js +25 -0
  54. package/dist/backend/commands/set-credit-mode.command.js.map +1 -0
  55. package/dist/backend/commands/set-parent.command.d.ts +26 -0
  56. package/dist/backend/commands/set-parent.command.d.ts.map +1 -0
  57. package/dist/backend/commands/set-parent.command.js +51 -0
  58. package/dist/backend/commands/set-parent.command.js.map +1 -0
  59. package/dist/backend/demo/reset.d.ts +20 -0
  60. package/dist/backend/demo/reset.d.ts.map +1 -0
  61. package/dist/backend/demo/reset.js +11 -0
  62. package/dist/backend/demo/reset.js.map +1 -0
  63. package/dist/backend/demo/rows.d.ts +44 -0
  64. package/dist/backend/demo/rows.d.ts.map +1 -0
  65. package/dist/backend/demo/rows.js +44 -0
  66. package/dist/backend/demo/rows.js.map +1 -0
  67. package/dist/backend/demo/seed.d.ts +20 -0
  68. package/dist/backend/demo/seed.d.ts.map +1 -0
  69. package/dist/backend/demo/seed.js +28 -0
  70. package/dist/backend/demo/seed.js.map +1 -0
  71. package/dist/backend/email-templates/invitation.d.ts +23 -0
  72. package/dist/backend/email-templates/invitation.d.ts.map +1 -0
  73. package/dist/backend/email-templates/invitation.js +35 -0
  74. package/dist/backend/email-templates/invitation.js.map +1 -0
  75. package/dist/backend/email-templates/transactional-defaults.d.ts +54 -0
  76. package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
  77. package/dist/backend/email-templates/transactional-defaults.js +242 -0
  78. package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
  79. package/dist/backend/email-templates/verification.d.ts +14 -0
  80. package/dist/backend/email-templates/verification.d.ts.map +1 -0
  81. package/dist/backend/email-templates/verification.js +34 -0
  82. package/dist/backend/email-templates/verification.js.map +1 -0
  83. package/dist/backend/entities/email-verification-token.entity.d.ts +17 -0
  84. package/dist/backend/entities/email-verification-token.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/email-verification-token.entity.js +60 -0
  86. package/dist/backend/entities/email-verification-token.entity.js.map +1 -0
  87. package/dist/backend/entities/organization-delivery-method-link.entity.d.ts +14 -0
  88. package/dist/backend/entities/organization-delivery-method-link.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/organization-delivery-method-link.entity.js +41 -0
  90. package/dist/backend/entities/organization-delivery-method-link.entity.js.map +1 -0
  91. package/dist/backend/entities/organization-invitation.entity.d.ts +21 -0
  92. package/dist/backend/entities/organization-invitation.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/organization-invitation.entity.js +85 -0
  94. package/dist/backend/entities/organization-invitation.entity.js.map +1 -0
  95. package/dist/backend/entities/organization-payment-method-link.entity.d.ts +14 -0
  96. package/dist/backend/entities/organization-payment-method-link.entity.d.ts.map +1 -0
  97. package/dist/backend/entities/organization-payment-method-link.entity.js +41 -0
  98. package/dist/backend/entities/organization-payment-method-link.entity.js.map +1 -0
  99. package/dist/backend/entities/organization-sales-rep-assignment.entity.d.ts +17 -0
  100. package/dist/backend/entities/organization-sales-rep-assignment.entity.d.ts.map +1 -0
  101. package/dist/backend/entities/organization-sales-rep-assignment.entity.js +56 -0
  102. package/dist/backend/entities/organization-sales-rep-assignment.entity.js.map +1 -0
  103. package/dist/backend/entities/organization-tax-id-validation.entity.d.ts +29 -0
  104. package/dist/backend/entities/organization-tax-id-validation.entity.d.ts.map +1 -0
  105. package/dist/backend/entities/organization-tax-id-validation.entity.js +78 -0
  106. package/dist/backend/entities/organization-tax-id-validation.entity.js.map +1 -0
  107. package/dist/backend/entities/organization-warehouse-link.entity.d.ts +15 -0
  108. package/dist/backend/entities/organization-warehouse-link.entity.d.ts.map +1 -0
  109. package/dist/backend/entities/organization-warehouse-link.entity.js +42 -0
  110. package/dist/backend/entities/organization-warehouse-link.entity.js.map +1 -0
  111. package/dist/backend/entities/organization.entity.d.ts +125 -0
  112. package/dist/backend/entities/organization.entity.d.ts.map +1 -0
  113. package/dist/backend/entities/organization.entity.js +282 -0
  114. package/dist/backend/entities/organization.entity.js.map +1 -0
  115. package/dist/backend/index.d.ts +250 -0
  116. package/dist/backend/index.d.ts.map +1 -0
  117. package/dist/backend/index.js +506 -0
  118. package/dist/backend/index.js.map +1 -0
  119. package/dist/backend/integrations/ministerstwo-finansow-client.d.ts +22 -0
  120. package/dist/backend/integrations/ministerstwo-finansow-client.d.ts.map +1 -0
  121. package/dist/backend/integrations/ministerstwo-finansow-client.js +172 -0
  122. package/dist/backend/integrations/ministerstwo-finansow-client.js.map +1 -0
  123. package/dist/backend/integrations/vies-client.d.ts +18 -0
  124. package/dist/backend/integrations/vies-client.d.ts.map +1 -0
  125. package/dist/backend/integrations/vies-client.js +133 -0
  126. package/dist/backend/integrations/vies-client.js.map +1 -0
  127. package/dist/backend/plugin.d.ts +126 -0
  128. package/dist/backend/plugin.d.ts.map +1 -0
  129. package/dist/backend/plugin.js +139 -0
  130. package/dist/backend/plugin.js.map +1 -0
  131. package/dist/backend/routes.admin.d.ts +66 -0
  132. package/dist/backend/routes.admin.d.ts.map +1 -0
  133. package/dist/backend/routes.admin.js +711 -0
  134. package/dist/backend/routes.admin.js.map +1 -0
  135. package/dist/backend/routes.customer.d.ts +33 -0
  136. package/dist/backend/routes.customer.d.ts.map +1 -0
  137. package/dist/backend/routes.customer.js +139 -0
  138. package/dist/backend/routes.customer.js.map +1 -0
  139. package/dist/backend/routes.members.d.ts +31 -0
  140. package/dist/backend/routes.members.d.ts.map +1 -0
  141. package/dist/backend/routes.members.js +109 -0
  142. package/dist/backend/routes.members.js.map +1 -0
  143. package/dist/backend/routes.public.d.ts +49 -0
  144. package/dist/backend/routes.public.d.ts.map +1 -0
  145. package/dist/backend/routes.public.js +220 -0
  146. package/dist/backend/routes.public.js.map +1 -0
  147. package/dist/backend/routes.sales-reps.d.ts +46 -0
  148. package/dist/backend/routes.sales-reps.d.ts.map +1 -0
  149. package/dist/backend/routes.sales-reps.js +114 -0
  150. package/dist/backend/routes.sales-reps.js.map +1 -0
  151. package/dist/backend/routes.storefront.d.ts +25 -0
  152. package/dist/backend/routes.storefront.d.ts.map +1 -0
  153. package/dist/backend/routes.storefront.js +65 -0
  154. package/dist/backend/routes.storefront.js.map +1 -0
  155. package/dist/backend/schemas/organization.d.ts +59 -0
  156. package/dist/backend/schemas/organization.d.ts.map +1 -0
  157. package/dist/backend/schemas/organization.js +55 -0
  158. package/dist/backend/schemas/organization.js.map +1 -0
  159. package/dist/backend/schemas/settings.d.ts +30 -0
  160. package/dist/backend/schemas/settings.d.ts.map +1 -0
  161. package/dist/backend/schemas/settings.js +35 -0
  162. package/dist/backend/schemas/settings.js.map +1 -0
  163. package/dist/backend/services/email-verification-service.d.ts +45 -0
  164. package/dist/backend/services/email-verification-service.d.ts.map +1 -0
  165. package/dist/backend/services/email-verification-service.js +95 -0
  166. package/dist/backend/services/email-verification-service.js.map +1 -0
  167. package/dist/backend/services/invitation-service.d.ts +85 -0
  168. package/dist/backend/services/invitation-service.d.ts.map +1 -0
  169. package/dist/backend/services/invitation-service.js +266 -0
  170. package/dist/backend/services/invitation-service.js.map +1 -0
  171. package/dist/backend/services/normalize-name.d.ts +14 -0
  172. package/dist/backend/services/normalize-name.d.ts.map +1 -0
  173. package/dist/backend/services/normalize-name.js +14 -0
  174. package/dist/backend/services/normalize-name.js.map +1 -0
  175. package/dist/backend/services/org-registration-notifier.d.ts +36 -0
  176. package/dist/backend/services/org-registration-notifier.d.ts.map +1 -0
  177. package/dist/backend/services/org-registration-notifier.js +109 -0
  178. package/dist/backend/services/org-registration-notifier.js.map +1 -0
  179. package/dist/backend/services/org-template-email.d.ts +18 -0
  180. package/dist/backend/services/org-template-email.d.ts.map +1 -0
  181. package/dist/backend/services/org-template-email.js +16 -0
  182. package/dist/backend/services/org-template-email.js.map +1 -0
  183. package/dist/backend/services/organization-context-service.d.ts +52 -0
  184. package/dist/backend/services/organization-context-service.d.ts.map +1 -0
  185. package/dist/backend/services/organization-context-service.js +70 -0
  186. package/dist/backend/services/organization-context-service.js.map +1 -0
  187. package/dist/backend/services/organization-country-reference.d.ts +11 -0
  188. package/dist/backend/services/organization-country-reference.d.ts.map +1 -0
  189. package/dist/backend/services/organization-country-reference.js +24 -0
  190. package/dist/backend/services/organization-country-reference.js.map +1 -0
  191. package/dist/backend/services/organization-details-port.d.ts +33 -0
  192. package/dist/backend/services/organization-details-port.d.ts.map +1 -0
  193. package/dist/backend/services/organization-details-port.js +95 -0
  194. package/dist/backend/services/organization-details-port.js.map +1 -0
  195. package/dist/backend/services/organization-effective-pricelists-service.d.ts +50 -0
  196. package/dist/backend/services/organization-effective-pricelists-service.d.ts.map +1 -0
  197. package/dist/backend/services/organization-effective-pricelists-service.js +68 -0
  198. package/dist/backend/services/organization-effective-pricelists-service.js.map +1 -0
  199. package/dist/backend/services/organization-inheritance-service.d.ts +73 -0
  200. package/dist/backend/services/organization-inheritance-service.d.ts.map +1 -0
  201. package/dist/backend/services/organization-inheritance-service.js +63 -0
  202. package/dist/backend/services/organization-inheritance-service.js.map +1 -0
  203. package/dist/backend/services/organization-moderation-service.d.ts +124 -0
  204. package/dist/backend/services/organization-moderation-service.d.ts.map +1 -0
  205. package/dist/backend/services/organization-moderation-service.js +337 -0
  206. package/dist/backend/services/organization-moderation-service.js.map +1 -0
  207. package/dist/backend/services/organization-restriction-service.d.ts +70 -0
  208. package/dist/backend/services/organization-restriction-service.d.ts.map +1 -0
  209. package/dist/backend/services/organization-restriction-service.js +210 -0
  210. package/dist/backend/services/organization-restriction-service.js.map +1 -0
  211. package/dist/backend/services/organization-tax-id-validation-service.d.ts +59 -0
  212. package/dist/backend/services/organization-tax-id-validation-service.d.ts.map +1 -0
  213. package/dist/backend/services/organization-tax-id-validation-service.js +115 -0
  214. package/dist/backend/services/organization-tax-id-validation-service.js.map +1 -0
  215. package/dist/backend/services/organization-tree-service.d.ts +102 -0
  216. package/dist/backend/services/organization-tree-service.d.ts.map +1 -0
  217. package/dist/backend/services/organization-tree-service.js +184 -0
  218. package/dist/backend/services/organization-tree-service.js.map +1 -0
  219. package/dist/backend/services/personal-organization-service.d.ts +130 -0
  220. package/dist/backend/services/personal-organization-service.d.ts.map +1 -0
  221. package/dist/backend/services/personal-organization-service.js +207 -0
  222. package/dist/backend/services/personal-organization-service.js.map +1 -0
  223. package/dist/backend/services/registration-service.d.ts +80 -0
  224. package/dist/backend/services/registration-service.d.ts.map +1 -0
  225. package/dist/backend/services/registration-service.js +126 -0
  226. package/dist/backend/services/registration-service.js.map +1 -0
  227. package/dist/backend/services/sales-rep-assignment-service.d.ts +89 -0
  228. package/dist/backend/services/sales-rep-assignment-service.d.ts.map +1 -0
  229. package/dist/backend/services/sales-rep-assignment-service.js +176 -0
  230. package/dist/backend/services/sales-rep-assignment-service.js.map +1 -0
  231. package/dist/backend/services/vat-validator-port.d.ts +20 -0
  232. package/dist/backend/services/vat-validator-port.d.ts.map +1 -0
  233. package/dist/backend/services/vat-validator-port.js +2 -0
  234. package/dist/backend/services/vat-validator-port.js.map +1 -0
  235. package/dist/manifest.d.ts +176 -0
  236. package/dist/manifest.d.ts.map +1 -0
  237. package/dist/manifest.js +503 -0
  238. package/dist/manifest.js.map +1 -0
  239. package/dist/migrations/20260424T205317_organizations_init.d.ts +13 -0
  240. package/dist/migrations/20260424T205317_organizations_init.d.ts.map +1 -0
  241. package/dist/migrations/20260424T205317_organizations_init.js +100 -0
  242. package/dist/migrations/20260424T205317_organizations_init.js.map +1 -0
  243. package/dist/migrations/20260425T051700_organizations_invitations_init.d.ts +13 -0
  244. package/dist/migrations/20260425T051700_organizations_invitations_init.d.ts.map +1 -0
  245. package/dist/migrations/20260425T051700_organizations_invitations_init.js +39 -0
  246. package/dist/migrations/20260425T051700_organizations_invitations_init.js.map +1 -0
  247. package/dist/migrations/20260611T140349_organizations_consolidation.d.ts +35 -0
  248. package/dist/migrations/20260611T140349_organizations_consolidation.d.ts.map +1 -0
  249. package/dist/migrations/20260611T140349_organizations_consolidation.js +180 -0
  250. package/dist/migrations/20260611T140349_organizations_consolidation.js.map +1 -0
  251. package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.d.ts +10 -0
  252. package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.d.ts.map +1 -0
  253. package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.js +14 -0
  254. package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.js.map +1 -0
  255. package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.d.ts +14 -0
  256. package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.d.ts.map +1 -0
  257. package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.js +20 -0
  258. package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.js.map +1 -0
  259. package/dist/migrations/20260717T151403_organizations_personal_organizations.d.ts +24 -0
  260. package/dist/migrations/20260717T151403_organizations_personal_organizations.d.ts.map +1 -0
  261. package/dist/migrations/20260717T151403_organizations_personal_organizations.js +63 -0
  262. package/dist/migrations/20260717T151403_organizations_personal_organizations.js.map +1 -0
  263. package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.d.ts +12 -0
  264. package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.d.ts.map +1 -0
  265. package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.js +16 -0
  266. package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.js.map +1 -0
  267. package/dist/migrations/20260720T044254_organizations_org_hierarchy.d.ts +23 -0
  268. package/dist/migrations/20260720T044254_organizations_org_hierarchy.d.ts.map +1 -0
  269. package/dist/migrations/20260720T044254_organizations_org_hierarchy.js +37 -0
  270. package/dist/migrations/20260720T044254_organizations_org_hierarchy.js.map +1 -0
  271. package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.d.ts +27 -0
  272. package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.d.ts.map +1 -0
  273. package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.js +44 -0
  274. package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.js.map +1 -0
  275. package/dist/migrations/index.d.ts +35 -0
  276. package/dist/migrations/index.d.ts.map +1 -0
  277. package/dist/migrations/index.js +45 -0
  278. package/dist/migrations/index.js.map +1 -0
  279. package/dist/ports/index.d.ts +53 -0
  280. package/dist/ports/index.d.ts.map +1 -0
  281. package/dist/ports/index.js +2 -0
  282. package/dist/ports/index.js.map +1 -0
  283. package/docs/organization-hierarchy.md +117 -0
  284. package/docs/organizations.md +312 -0
  285. package/i18n/en.json +17 -0
  286. package/i18n/pl.json +17 -0
  287. package/package.json +107 -0
  288. package/tailwind.css +14 -0
@@ -0,0 +1,207 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { Organization } from '../entities/organization.entity.js';
4
+ /**
5
+ * The name a scrubbed personal organisation is renamed to. It matches the one
6
+ * `customer_accounts` writes over the member's own first and last name,
7
+ * because a personal organisation's name *is* that person's name.
8
+ */
9
+ export const ANONYMIZED_ORGANIZATION_NAME = 'Deleted customer';
10
+ /**
11
+ * Feature 051 — provisions and guards single-member Personal Organizations for
12
+ * B2C (individual) customers. One provisioning rule, reused by live registration
13
+ * and the backfill migration's mental model (the migration duplicates the SQL).
14
+ */
15
+ export class PersonalOrganizationService {
16
+ emFactory;
17
+ accounts;
18
+ auditLog;
19
+ constructor(emFactory,
20
+ /**
21
+ * Required since `customers`' Phase-C cut: the `ensureFor` overload that
22
+ * needed neither port — it took the caller's managed `CustomerAccount` and
23
+ * its `EntityManager` — went with that cut, and every method left reads
24
+ * this module's neighbour through its ports.
25
+ */
26
+ accounts,
27
+ /**
28
+ * Feature 075 — `anonymizeIfOrphaned` records the scrub. It used to be
29
+ * written by `customers`' deletion sweep, on a row that module does not
30
+ * own; the write and its one audit row moved here together.
31
+ */
32
+ auditLog) {
33
+ this.emFactory = emFactory;
34
+ this.accounts = accounts;
35
+ this.auditLog = auditLog;
36
+ }
37
+ /** Name an individual's organization from their profile, falling back to the email local-part. */
38
+ static personalName(account) {
39
+ const full = `${account.firstName ?? ''} ${account.lastName ?? ''}`.trim();
40
+ return full.length > 0 ? full : account.email.split('@')[0];
41
+ }
42
+ /**
43
+ * The provisioning itself — find-or-create the personal organisation for an
44
+ * account, on the `EntityManager` the caller hands in.
45
+ *
46
+ * D-178 made this the one place the row is written, and gave it a caller that
47
+ * has no account row yet: `customer_accounts` inserts the account and its
48
+ * organisation in one transaction, so it passes the id it is *about* to write
49
+ * and the name components it already holds. Nothing here reads
50
+ * `customer_accounts` and nothing here writes it.
51
+ *
52
+ * Idempotent by `taxId`, which is derived from the account id — a retry after
53
+ * a rolled-back attempt re-finds the row rather than creating a second one.
54
+ *
55
+ * command-coverage-ignore: idempotent auto-provisioning of a customer's
56
+ * personal (B2C) organization — a system invariant the constitution mandates
57
+ * (Principle XI), not an operator-initiated write. Its caller's own write is
58
+ * the audited event.
59
+ */
60
+ async provisionFor(em, input) {
61
+ const taxId = personalTaxId(input.customerAccountId);
62
+ const existingByTaxId = await em.findOne(Organization, { taxId });
63
+ if (existingByTaxId)
64
+ return existingByTaxId;
65
+ const org = em.create(Organization, {
66
+ name: PersonalOrganizationService.personalName({
67
+ firstName: input.firstName ?? '',
68
+ lastName: input.lastName ?? '',
69
+ email: input.email,
70
+ }),
71
+ taxId,
72
+ status: 'active',
73
+ vatStatus: 'vat_exempt',
74
+ isPersonal: true,
75
+ registeredAddress: { street: '-', city: '-', postalCode: '-', country: 'PL' },
76
+ });
77
+ // Flushed here rather than left pending, because the caller's next statement
78
+ // inserts a row whose `organization_id` foreign key points at this one. It
79
+ // is the caller's `EntityManager` and therefore the caller's transaction, so
80
+ // a rollback takes this row with it.
81
+ await em.persistAndFlush(org);
82
+ return org;
83
+ }
84
+ /**
85
+ * Return the customer's organization, provisioning a personal one iff none is
86
+ * linked. Idempotent by customer — a customer that already has an organization
87
+ * is returned as-is (never a second personal org).
88
+ *
89
+ * **Since D-178 this repairs rather than provisions.** Every write path now
90
+ * creates the account and its organisation in one transaction, so a live
91
+ * account always has one and this method's provisioning arm is reached only by
92
+ * a caller holding an account written before that ruling. It is kept because
93
+ * it is idempotent and because `ensureForCustomerAccount` is a published port.
94
+ */
95
+ async ensureForCustomerAccountId(customerAccountId) {
96
+ const accounts = this.#accounts();
97
+ const em = this.emFactory();
98
+ const account = await accounts.read.findById(customerAccountId);
99
+ if (!account) {
100
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Customer account ${customerAccountId} not found.`);
101
+ }
102
+ if (account.organizationId) {
103
+ const existing = await em.findOne(Organization, { id: account.organizationId });
104
+ if (existing)
105
+ return existing;
106
+ }
107
+ const org = await this.provisionFor(em, { ...account, customerAccountId: account.id });
108
+ await accounts.write.attachToOrganization(account.id, org.id);
109
+ return org;
110
+ }
111
+ /**
112
+ * D-178 — the account's **own** personal organisation, whatever it currently
113
+ * belongs to, provisioned if it has never existed.
114
+ *
115
+ * `unassign` on the admin customer screen is what wants it: an operator
116
+ * detaching a member from a company used to write `organization_id = NULL`,
117
+ * and the account it produced could never transact. The operation is now the
118
+ * move it always meant, and this is the destination. The **binding** is not
119
+ * written here — `customers` writes it through
120
+ * `CustomerAccountLifecycleWritePort.detachToPersonalOrganization` once its
121
+ * own authority and org-administrator-depletion guards have passed.
122
+ */
123
+ async provisionPersonalOrganizationFor(customerAccountId) {
124
+ const account = await this.#accounts().read.findById(customerAccountId);
125
+ if (!account) {
126
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Customer account ${customerAccountId} not found.`);
127
+ }
128
+ return this.provisionFor(this.emFactory(), { ...account, customerAccountId: account.id });
129
+ }
130
+ /**
131
+ * Feature 051, the other end of the same rule — the retention sweep's
132
+ * cascade, moved here with `customers`' Phase-C cut.
133
+ *
134
+ * That module ran it by loading this module's `Organization`, renaming it,
135
+ * blanking its registered address and stamping `deletedAt` — three writes on
136
+ * a row it does not own, plus the audit entry describing them. The decision
137
+ * is this module's: a personal organisation whose single member has been
138
+ * anonymised has nobody in it and carries that person's name.
139
+ *
140
+ * Writes nothing and answers `null` whenever the cascade does not apply, so a
141
+ * caller cannot get it wrong by asking too often.
142
+ */
143
+ async anonymizeIfOrphaned(customerAccountId) {
144
+ const account = await this.#accounts().read.findById(customerAccountId);
145
+ if (!account?.organizationId)
146
+ return null;
147
+ const em = this.emFactory();
148
+ const org = await em.findOne(Organization, { id: account.organizationId });
149
+ if (!org || !org.isPersonal || org.deletedAt)
150
+ return null;
151
+ // Any surviving (non-deleted) member keeps the organisation alive. For a
152
+ // personal one this is empty once its single member has been soft-deleted.
153
+ const members = await this.#accounts().read.listByOrganization(org.id, { activeOnly: true });
154
+ if (members.length > 0)
155
+ return null;
156
+ // command-coverage-ignore: the retention sweep's cascade over a
157
+ // member-less personal organization — a system-initiated scrub on a timer,
158
+ // audited below with the null actor that fact deserves, not an
159
+ // operator-initiated write with an actor to attribute it to.
160
+ const before = { name: org.name };
161
+ org.name = ANONYMIZED_ORGANIZATION_NAME;
162
+ org.registeredAddress = {
163
+ street: '-',
164
+ city: '-',
165
+ postalCode: '-',
166
+ country: org.registeredAddress.country,
167
+ };
168
+ org.deletedAt = new Date();
169
+ await em.flush();
170
+ await this.auditLog?.record({
171
+ actorAdminUserId: null,
172
+ action: 'organization.anonymized',
173
+ objectType: 'organization',
174
+ objectId: org.id,
175
+ stateBefore: before,
176
+ stateAfter: { anonymizedAt: org.deletedAt.toISOString(), isPersonal: true },
177
+ });
178
+ return org;
179
+ }
180
+ /**
181
+ * Guard: a personal organization is single-member and MUST never gain a second
182
+ * customer. Call before attaching a customer to an organization.
183
+ */
184
+ async assertMembershipAllowed(organizationId, em = this.emFactory()) {
185
+ const org = await em.findOne(Organization, { id: organizationId });
186
+ if (!org?.isPersonal)
187
+ return; // company orgs are multi-member
188
+ const members = await this.#accounts().read.listByOrganization(organizationId, {
189
+ activeOnly: true,
190
+ });
191
+ if (members.length > 0) {
192
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'A personal organization is single-member and cannot take another customer.');
193
+ }
194
+ }
195
+ #accounts() {
196
+ return this.accounts;
197
+ }
198
+ }
199
+ /**
200
+ * `tax_id` is globally unique (varchar(32)); an individual has no company tax
201
+ * id, so the account's UUID without dashes is used — 32 hex chars, unique, and
202
+ * derived, which is what makes the provisioning re-runnable.
203
+ */
204
+ function personalTaxId(customerAccountId) {
205
+ return customerAccountId.replace(/-/g, '');
206
+ }
207
+ //# sourceMappingURL=personal-organization-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personal-organization-service.js","sourceRoot":"","sources":["../../../src/backend/services/personal-organization-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CustomerAccountMemberWritePort, type CustomerAccountReadPort, type CustomerAccountRecord, type DictionaryValidator, type RegisterOrganizationRequest } from '@endora-commerce/contracts';
3
+ import type { EventBase, EventBus } from '@endora-commerce/platform/events';
4
+ import { Organization } from '../entities/organization.entity.js';
5
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
6
+ /**
7
+ * Registration flow (T117, FR-040).
8
+ *
9
+ * - Creates the Organization in `pending_verification`.
10
+ * - Creates the first CustomerAccount with `role='organization_admin'`, email
11
+ * unverified, argon2-hashed password.
12
+ * - Issues an EmailVerificationToken; the raw token is returned so callers
13
+ * can dispatch the email. Only its sha256 hash is stored.
14
+ * - Emits `organization.registered.v1`.
15
+ *
16
+ * Uniqueness conflicts raise:
17
+ * 409 ORGANIZATION_TAX_ID_EXISTS if the taxId is taken.
18
+ * 409 EMAIL_ALREADY_REGISTERED if the email is taken by another account.
19
+ */
20
+ export interface OrganizationEvents extends Record<string, EventBase> {
21
+ 'organization.registered.v1': EventBase & {
22
+ organizationId: string;
23
+ };
24
+ 'organization.verified.v1': EventBase & {
25
+ organizationId: string;
26
+ customerAccountId: string;
27
+ };
28
+ 'organization.status_changed.v1': EventBase & {
29
+ organizationId: string;
30
+ newStatus: string;
31
+ };
32
+ /**
33
+ * A CustomerAccount belonging to an Organization was created (self-service
34
+ * registration, admin direct-create, or invitation accept). Consumed by the
35
+ * shopping_lists module to provision the customer's default list eagerly.
36
+ */
37
+ 'customer_account.created.v1': EventBase & {
38
+ customerAccountId: string;
39
+ organizationId: string;
40
+ };
41
+ }
42
+ export type OrganizationEventBus = EventBus<OrganizationEvents>;
43
+ /**
44
+ * Emit `customer_account.created.v1` for a newly-created org-attached customer.
45
+ * Shared by every creation chokepoint so provisioning side effects (e.g. the
46
+ * default shopping list) happen consistently regardless of entry path.
47
+ */
48
+ export declare function emitCustomerAccountCreated(events: OrganizationEventBus, customerAccountId: string, organizationId: string): void;
49
+ export interface RegistrationResult {
50
+ organization: Organization;
51
+ customerAccount: CustomerAccountRecord;
52
+ /** Raw token to include in the verification email — shown once. */
53
+ verificationToken: string;
54
+ }
55
+ export interface RegistrationAccountPorts {
56
+ /**
57
+ * `customer_accounts`' published read and write (feature 075, Phase C). The
58
+ * first user of a new Organization is a row in *that* module's table, and
59
+ * this service used to create it with `em.create(CustomerAccount, …)` and a
60
+ * `hashPassword` imported from `auth`. Both are on the owner's side of the
61
+ * port now, which is also why the plain password crosses and no hash does.
62
+ *
63
+ * With `customer_accounts` off registration fails closed. That is right: an
64
+ * Organization whose administrator could not be created is not a
65
+ * registration, and the module is in any case non-deactivatable.
66
+ */
67
+ read: CustomerAccountReadPort;
68
+ write: CustomerAccountMemberWritePort;
69
+ }
70
+ export declare class RegistrationService {
71
+ private readonly emFactory;
72
+ private readonly events;
73
+ private readonly accounts;
74
+ private readonly dictionaryValidator?;
75
+ private readonly auditLog?;
76
+ constructor(emFactory: () => EntityManager, events: OrganizationEventBus, accounts: RegistrationAccountPorts, dictionaryValidator?: DictionaryValidator | undefined, auditLog?: AuditPort | undefined);
77
+ registerOrganization(req: RegisterOrganizationRequest): Promise<RegistrationResult>;
78
+ private validateCountry;
79
+ }
80
+ //# sourceMappingURL=registration-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registration-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/registration-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EACjC,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,kBAAmB,SAAQ,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IACnE,4BAA4B,EAAE,SAAS,GAAG;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,0BAA0B,EAAE,SAAS,GAAG;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9F,gCAAgC,EAAE,SAAS,GAAG;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5F;;;;OAIG;IACH,6BAA6B,EAAE,SAAS,GAAG;QACzC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AACD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAC;AAEhE;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,oBAAoB,EAC5B,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,GACrB,IAAI,CAON;AAID,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,qBAAqB,CAAC;IACvC,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;;;;;;OAUG;IACH,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,8BAA8B,CAAC;CACvC;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAJT,SAAS,EAAE,MAAM,aAAa,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,wBAAwB,EAClC,mBAAmB,CAAC,EAAE,mBAAmB,YAAA,EACzC,QAAQ,CAAC,EAAE,SAAS,YAAA;IAGjC,oBAAoB,CAAC,GAAG,EAAE,2BAA2B,GAAG,OAAO,CAAC,kBAAkB,CAAC;YA0F3E,eAAe;CAkB9B"}
@@ -0,0 +1,126 @@
1
+ import { createHash, randomBytes } from 'crypto';
2
+ import { UniqueConstraintViolationException } from '@mikro-orm/core';
3
+ import { DictionaryReferenceError, ERROR_CODES, } from '@endora-commerce/contracts';
4
+ import { HttpError } from '@endora-commerce/platform/http';
5
+ import { Organization } from '../entities/organization.entity.js';
6
+ import { EmailVerificationToken } from '../entities/email-verification-token.entity.js';
7
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
8
+ /**
9
+ * Emit `customer_account.created.v1` for a newly-created org-attached customer.
10
+ * Shared by every creation chokepoint so provisioning side effects (e.g. the
11
+ * default shopping list) happen consistently regardless of entry path.
12
+ */
13
+ export function emitCustomerAccountCreated(events, customerAccountId, organizationId) {
14
+ events.emit('customer_account.created.v1', {
15
+ eventId: crypto.randomUUID(),
16
+ occurredAt: new Date().toISOString(),
17
+ customerAccountId,
18
+ organizationId,
19
+ });
20
+ }
21
+ const TOKEN_TTL_HOURS = 48;
22
+ export class RegistrationService {
23
+ emFactory;
24
+ events;
25
+ accounts;
26
+ dictionaryValidator;
27
+ auditLog;
28
+ constructor(emFactory, events, accounts, dictionaryValidator, auditLog) {
29
+ this.emFactory = emFactory;
30
+ this.events = events;
31
+ this.accounts = accounts;
32
+ this.dictionaryValidator = dictionaryValidator;
33
+ this.auditLog = auditLog;
34
+ }
35
+ async registerOrganization(req) {
36
+ const em = this.emFactory();
37
+ await this.validateCountry(req.organization.registeredAddress.country);
38
+ // Pre-check the easy cases so we can return the right error code before
39
+ // hashing the password (avoids a wasted ~50 ms of argon2 work — the hash
40
+ // is computed inside `create` below, after both checks have passed).
41
+ const existingByTaxId = await em.findOne(Organization, { taxId: req.organization.taxId });
42
+ if (existingByTaxId) {
43
+ throw new HttpError(409, ERROR_CODES.ORGANIZATION_TAX_ID_EXISTS, 'An organization with this taxId already exists.');
44
+ }
45
+ const existingByEmail = await this.accounts.read.findByEmail(req.firstUser.email);
46
+ if (existingByEmail) {
47
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
48
+ }
49
+ const organization = em.create(Organization, {
50
+ name: req.organization.name,
51
+ taxId: req.organization.taxId,
52
+ status: 'pending_verification',
53
+ vatStatus: req.organization.vatStatus ?? 'vat_payer',
54
+ registeredAddress: {
55
+ street: req.organization.registeredAddress.street,
56
+ city: req.organization.registeredAddress.city,
57
+ postalCode: req.organization.registeredAddress.postalCode,
58
+ country: req.organization.registeredAddress.country,
59
+ },
60
+ });
61
+ try {
62
+ await em.persistAndFlush(organization);
63
+ }
64
+ catch (err) {
65
+ if (err instanceof UniqueConstraintViolationException) {
66
+ throw new HttpError(409, ERROR_CODES.ORGANIZATION_TAX_ID_EXISTS, 'An organization with this taxId already exists.');
67
+ }
68
+ throw err;
69
+ }
70
+ // `create` raises the same 409 `EMAIL_ALREADY_REGISTERED` on the unique
71
+ // index that the `catch` here used to, so the translation moved with the
72
+ // write rather than being dropped.
73
+ const customerAccount = await this.accounts.write.create({
74
+ organizationId: organization.id,
75
+ email: req.firstUser.email,
76
+ password: req.firstUser.password,
77
+ firstName: req.firstUser.firstName,
78
+ lastName: req.firstUser.lastName,
79
+ role: 'organization_admin',
80
+ });
81
+ const rawToken = randomBytes(32).toString('base64url');
82
+ const token = em.create(EmailVerificationToken, {
83
+ customerAccountId: customerAccount.id,
84
+ tokenHash: sha256Hex(rawToken),
85
+ expiresAt: new Date(Date.now() + TOKEN_TTL_HOURS * 60 * 60 * 1_000),
86
+ });
87
+ if (this.auditLog) {
88
+ // Self-registration is pre-auth (no ambient actor) — the entry records the
89
+ // organization creation with a null actor.
90
+ recordAuditFromContext(this.auditLog, em, {
91
+ action: 'organization.register',
92
+ objectType: 'organization',
93
+ objectId: organization.id,
94
+ stateBefore: null,
95
+ stateAfter: { name: organization.name, taxId: organization.taxId, status: organization.status },
96
+ });
97
+ }
98
+ await em.persistAndFlush(token);
99
+ this.events.emit('organization.registered.v1', {
100
+ eventId: crypto.randomUUID(),
101
+ occurredAt: new Date().toISOString(),
102
+ organizationId: organization.id,
103
+ });
104
+ emitCustomerAccountCreated(this.events, customerAccount.id, organization.id);
105
+ return { organization, customerAccount, verificationToken: rawToken };
106
+ }
107
+ async validateCountry(country) {
108
+ if (!this.dictionaryValidator)
109
+ return;
110
+ try {
111
+ await this.dictionaryValidator.validateCountryCode(country, 'create-or-change');
112
+ }
113
+ catch (err) {
114
+ if (err instanceof DictionaryReferenceError) {
115
+ throw new HttpError(409, err.code, err.code === 'DICTIONARY_ENTRY_INACTIVE'
116
+ ? `Country code ${err.entryCode} is no longer available for organization registration.`
117
+ : `Country code ${err.entryCode} is not recognised.`, [{ path: 'organization.registeredAddress.country', issue: err.code }]);
118
+ }
119
+ throw err;
120
+ }
121
+ }
122
+ }
123
+ function sha256Hex(input) {
124
+ return createHash('sha256').update(input, 'utf8').digest('hex');
125
+ }
126
+ //# sourceMappingURL=registration-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registration-service.js","sourceRoot":"","sources":["../../../src/backend/services/registration-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { OrganizationSalesRepAssignment } from '../entities/organization-sales-rep-assignment.entity.js';
3
+ import type { SalesRepAssignmentPort, SalesRepAssignmentRow } from '@endora-commerce/contracts';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ import type { OrganizationTreeService } from './organization-tree-service.js';
6
+ /**
7
+ * Feature 056 US2 — optional roll-up wiring. When present, a sales-rep whose
8
+ * actor holds the `organizations:rollup` capability has their assignment scope
9
+ * expanded to the subtree of each assigned node (with per-descendant override,
10
+ * FR-011). Absent (flat deployments / no capability), behavior is byte-for-byte
11
+ * the pre-feature flat set.
12
+ */
13
+ export interface SalesRepSubtreeDeps {
14
+ readonly treeService: OrganizationTreeService;
15
+ readonly hasRollupCapability: (adminUserId: string) => Promise<boolean>;
16
+ }
17
+ /**
18
+ * The sales-rep assignment relation as **other modules** see it:
19
+ * `organizations`' `organizationSalesRepScopePort` (T143a, widened for issue
20
+ * #108).
21
+ *
22
+ * One implementation, five questions. Before this port existed, every consumer
23
+ * built its own `SalesRepAssignmentService`, and each build was free to omit the
24
+ * optional third constructor argument — which `customers` did, so
25
+ * `CustomerAuthorityService` applied the flat pre-056 rule and the roll-up was
26
+ * skipped for every block / unblock / delete / org-assign decision. `tsc` cannot
27
+ * see an omitted optional argument; a single port removes the chance to omit it.
28
+ *
29
+ * Structural rather than the entity type on purpose: a consumer wants the reps,
30
+ * not `organizations`' ORM rows.
31
+ *
32
+ * Both declarations moved to `@endora-commerce/contracts` in feature 075's Phase P, so the
33
+ * two consuming modules can name a package rather than this file. Re-exported
34
+ * here for the length of Phase P, which cuts no consumer.
35
+ */
36
+ export type { SalesRepAssignmentPort, SalesRepAssignmentRow };
37
+ /**
38
+ * Centralised visibility predicate for the sales-rep ↔ organization
39
+ * relation. Every endpoint that lists or operates on Quote Requests
40
+ * (and any future module that wants the same scoping) MUST go through
41
+ * this service so the rule is implemented exactly once.
42
+ *
43
+ * Rules (research §R2):
44
+ * - A platform admin sees every organization (decided by the caller —
45
+ * we only check assignment, not platform-admin role).
46
+ * - An admin user with an explicit assignment row sees that org.
47
+ * - An organization with ZERO assignment rows is in
48
+ * "unassigned-org fallback" mode and is visible to every sales rep.
49
+ */
50
+ export declare class SalesRepAssignmentService implements SalesRepAssignmentPort {
51
+ #private;
52
+ private readonly emFactory;
53
+ private readonly auditLog?;
54
+ private readonly subtree?;
55
+ constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined, subtree?: SalesRepSubtreeDeps | undefined);
56
+ /**
57
+ * Returns true when the admin user is allowed to see the
58
+ * organization's RFQs based on the assignment table.
59
+ *
60
+ * Feature 056 — when the actor holds the roll-up capability, the rule is
61
+ * "nearest assignment on the ancestor-or-self chain wins" (FR-011): the org
62
+ * is visible iff the nearest ancestor-or-self carrying any assignment row
63
+ * assigns it to this rep. An org whose entire chain has zero assignments is
64
+ * visible to any rep (unassigned-org fallback, preserved). Without the
65
+ * capability, the legacy exact-assignment + fallback rule applies.
66
+ */
67
+ canSeeOrganization(adminUserId: string, organizationId: string): Promise<boolean>;
68
+ /**
69
+ * Returns the list of organization IDs the admin user can act on.
70
+ *
71
+ * Feature 056 — when the actor holds the roll-up capability, each assigned
72
+ * node expands to its subtree MINUS any descendant subtree governed by its
73
+ * own (nearer) assignment (FR-011). Without the capability, returns the flat
74
+ * assigned rows (pre-feature behavior).
75
+ */
76
+ listAssignedOrganizationIds(adminUserId: string): Promise<string[]>;
77
+ /** Inserts an assignment if not already present. Returns the row. */
78
+ assign(input: {
79
+ organizationId: string;
80
+ adminUserId: string;
81
+ assignedByAdminUserId?: string | null;
82
+ }): Promise<OrganizationSalesRepAssignment>;
83
+ unassign(input: {
84
+ organizationId: string;
85
+ adminUserId: string;
86
+ }): Promise<boolean>;
87
+ listForOrganization(organizationId: string): Promise<OrganizationSalesRepAssignment[]>;
88
+ }
89
+ //# sourceMappingURL=sales-rep-assignment-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sales-rep-assignment-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/sales-rep-assignment-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,8BAA8B,EAAE,MAAM,yDAAyD,CAAC;AAIzG,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEhG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,EAAE,uBAAuB,CAAC;IAC9C,QAAQ,CAAC,mBAAmB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;;IAEpE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAFR,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA,EACpB,OAAO,CAAC,EAAE,mBAAmB,YAAA;IAGhD;;;;;;;;;;OAUG;IACG,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2BvF;;;;;;;OAOG;IACG,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA8CzE,qEAAqE;IAC/D,MAAM,CAAC,KAAK,EAAE;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvC,GAAG,OAAO,CAAC,8BAA8B,CAAC;IAoCrC,QAAQ,CAAC,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBlF,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,8BAA8B,EAAE,CAAC;CAI7F"}