@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,30 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schemas for the two settings keys this feature introduces.
4
+ *
5
+ * The Settings module persists values as `unknown` (it's a generic
6
+ * key-value store); these schemas are how the moderation service +
7
+ * notification dispatcher coerce + validate at the read-side.
8
+ */
9
+ export declare const moderationModeSchema: z.ZodEnum<{
10
+ manual: "manual";
11
+ auto: "auto";
12
+ }>;
13
+ export type ModerationMode = z.infer<typeof moderationModeSchema>;
14
+ export declare const notificationRecipientsSchema: z.ZodPipe<z.ZodArray<z.ZodString>, z.ZodTransform<string[], string[]>>;
15
+ export type NotificationRecipients = z.infer<typeof notificationRecipientsSchema>;
16
+ /**
17
+ * Feature 056 — how a parent Organization's credit limit is consumed by
18
+ * sub-organizations with no own limit.
19
+ *
20
+ * Feature 072 (T138) moved this here. Both composition roots spelled it as an
21
+ * inline `z.enum([…])` behind a dynamic `import('zod')`, once each — the only
22
+ * one of this module's three setting schemas that did not live beside its two
23
+ * siblings, which is why the duplication went unnoticed.
24
+ */
25
+ export declare const creditInheritanceModeSchema: z.ZodEnum<{
26
+ shared_pool: "shared_pool";
27
+ independent_default: "independent_default";
28
+ }>;
29
+ export type CreditInheritanceMode = z.infer<typeof creditInheritanceModeSchema>;
30
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/backend/schemas/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB;;;EAA6B,CAAC;AAC/D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,4BAA4B,wEAarC,CAAC;AACL,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B;;;EAAiD,CAAC;AAC1F,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schemas for the two settings keys this feature introduces.
4
+ *
5
+ * The Settings module persists values as `unknown` (it's a generic
6
+ * key-value store); these schemas are how the moderation service +
7
+ * notification dispatcher coerce + validate at the read-side.
8
+ */
9
+ export const moderationModeSchema = z.enum(['manual', 'auto']);
10
+ export const notificationRecipientsSchema = z
11
+ .array(z.string().email().max(320))
12
+ .max(50)
13
+ .transform((entries) => {
14
+ const seen = new Set();
15
+ const out = [];
16
+ for (const raw of entries) {
17
+ const key = raw.trim().toLowerCase();
18
+ if (!key || seen.has(key))
19
+ continue;
20
+ seen.add(key);
21
+ out.push(raw.trim());
22
+ }
23
+ return out;
24
+ });
25
+ /**
26
+ * Feature 056 — how a parent Organization's credit limit is consumed by
27
+ * sub-organizations with no own limit.
28
+ *
29
+ * Feature 072 (T138) moved this here. Both composition roots spelled it as an
30
+ * inline `z.enum([…])` behind a dynamic `import('zod')`, once each — the only
31
+ * one of this module's three setting schemas that did not live beside its two
32
+ * siblings, which is why the duplication went unnoticed.
33
+ */
34
+ export const creditInheritanceModeSchema = z.enum(['shared_pool', 'independent_default']);
35
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/backend/schemas/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAG/D,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC;KAC1C,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;KAClC,GAAG,CAAC,EAAE,CAAC;KACP,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE;IACrB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC,CAAC;AAGL;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CustomerAccountMemberWritePort, type CustomerAccountReadPort } from '@endora-commerce/contracts';
3
+ import type { OrganizationEventBus } from './registration-service.js';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * Email verification flow (T118).
7
+ *
8
+ * Consumes a raw token, looks up the matching EmailVerificationToken row by
9
+ * sha256 hash, marks it consumed (one-shot), sets CustomerAccount.emailVerifiedAt,
10
+ * and transitions the Organization to `active`. Emits organization.verified.v1.
11
+ */
12
+ export interface EmailVerificationAccountPorts {
13
+ /**
14
+ * `customer_accounts`' published read and write (feature 075, Phase C).
15
+ * Verifying an address stamps a column on *that* module's row; this service
16
+ * used to load the entity and assign to it.
17
+ *
18
+ * With `customer_accounts` off the verification fails closed, which is the
19
+ * only coherent answer for a flow whose whole subject is an account.
20
+ */
21
+ read: CustomerAccountReadPort;
22
+ write: CustomerAccountMemberWritePort;
23
+ }
24
+ export declare class EmailVerificationService {
25
+ private readonly emFactory;
26
+ private readonly events;
27
+ private readonly accounts;
28
+ private readonly auditLog?;
29
+ constructor(emFactory: () => EntityManager, events: OrganizationEventBus, accounts: EmailVerificationAccountPorts, auditLog?: AuditPort | undefined);
30
+ verify(rawToken: string): Promise<{
31
+ organizationId: string;
32
+ customerAccountId: string;
33
+ verifiedAt: Date;
34
+ }>;
35
+ /**
36
+ * Test-only helper used by the `/api/v1/_test/latest-verification-token`
37
+ * probe — exposes the most recent UNCONSUMED, unexpired token so integration
38
+ * tests can complete the verification step without touching Mailhog.
39
+ *
40
+ * Returns null if no such token exists. Callers gate this behind an env
41
+ * check so the probe is never exposed outside of tests.
42
+ */
43
+ latestUnconsumedTokenId(): Promise<string | null>;
44
+ }
45
+ //# sourceMappingURL=email-verification-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-verification-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-verification-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAIpC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;;;;;;OAOG;IACH,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,8BAA8B,CAAC;CACvC;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAHT,SAAS,EAAE,MAAM,aAAa,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,6BAA6B,EACvC,QAAQ,CAAC,EAAE,SAAS,YAAA;IAGjC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,IAAI,CAAC;KAClB,CAAC;IAyFF;;;;;;;OAOG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CASxD"}
@@ -0,0 +1,95 @@
1
+ import { createHash, randomUUID } from 'crypto';
2
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { Organization } from '../entities/organization.entity.js';
5
+ import { EmailVerificationToken } from '../entities/email-verification-token.entity.js';
6
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
7
+ export class EmailVerificationService {
8
+ emFactory;
9
+ events;
10
+ accounts;
11
+ auditLog;
12
+ constructor(emFactory, events, accounts, auditLog) {
13
+ this.emFactory = emFactory;
14
+ this.events = events;
15
+ this.accounts = accounts;
16
+ this.auditLog = auditLog;
17
+ }
18
+ async verify(rawToken) {
19
+ const em = this.emFactory();
20
+ const tokenHash = sha256Hex(rawToken);
21
+ const token = await em.findOne(EmailVerificationToken, { tokenHash });
22
+ if (!token) {
23
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification token is invalid or expired.');
24
+ }
25
+ if (token.consumedAt) {
26
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification token has already been consumed.');
27
+ }
28
+ if (token.expiresAt.getTime() <= Date.now()) {
29
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification token has expired.');
30
+ }
31
+ const customer = await this.accounts.read.findById(token.customerAccountId);
32
+ if (!customer) {
33
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Associated account was removed.');
34
+ }
35
+ // Feature 026 US2 — Customer may have no Organization (post-026 schema).
36
+ // Verification through this flow always carries an org today
37
+ // (RegistrationService.registerOrganization always creates the pair),
38
+ // but guard defensively in case a no-org Customer ever lands here.
39
+ if (!customer.organizationId) {
40
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification flow requires an Organization.');
41
+ }
42
+ const organization = await em.findOne(Organization, { id: customer.organizationId });
43
+ if (!organization) {
44
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Associated organization was removed.');
45
+ }
46
+ const now = new Date();
47
+ // Two units of work rather than one since the boundary cut: the account
48
+ // column belongs to `customer_accounts`, the Organization status and the
49
+ // token to this module. The account is stamped **first** because that half
50
+ // is the recoverable one — the token is still unconsumed if the second
51
+ // flush fails, so a retried verification re-runs the whole flow, and
52
+ // `markEmailVerified` keeps the first timestamp rather than moving it.
53
+ await this.accounts.write.markEmailVerified(customer.id, now);
54
+ organization.status = 'active';
55
+ token.consumedAt = now;
56
+ if (this.auditLog) {
57
+ recordAuditFromContext(this.auditLog, em, {
58
+ action: 'organization.email_verified',
59
+ objectType: 'organization',
60
+ objectId: organization.id,
61
+ stateBefore: { status: 'pending' },
62
+ stateAfter: { status: 'active', customerAccountId: customer.id },
63
+ });
64
+ }
65
+ await em.flush();
66
+ this.events.emit('organization.verified.v1', {
67
+ eventId: randomUUID(),
68
+ occurredAt: now.toISOString(),
69
+ organizationId: organization.id,
70
+ customerAccountId: customer.id,
71
+ });
72
+ return {
73
+ organizationId: organization.id,
74
+ customerAccountId: customer.id,
75
+ verifiedAt: now,
76
+ };
77
+ }
78
+ /**
79
+ * Test-only helper used by the `/api/v1/_test/latest-verification-token`
80
+ * probe — exposes the most recent UNCONSUMED, unexpired token so integration
81
+ * tests can complete the verification step without touching Mailhog.
82
+ *
83
+ * Returns null if no such token exists. Callers gate this behind an env
84
+ * check so the probe is never exposed outside of tests.
85
+ */
86
+ async latestUnconsumedTokenId() {
87
+ const em = this.emFactory();
88
+ const row = await em.findOne(EmailVerificationToken, { consumedAt: null }, { orderBy: { createdAt: 'desc' } });
89
+ return row?.id ?? null;
90
+ }
91
+ }
92
+ function sha256Hex(input) {
93
+ return createHash('sha256').update(input, 'utf8').digest('hex');
94
+ }
95
+ //# sourceMappingURL=email-verification-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-verification-service.js","sourceRoot":"","sources":["../../../src/backend/services/email-verification-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CustomerAccountMemberWritePort, type CustomerAccountReadPort, type CustomerAccountRecord, type EmailMailerPort } from '@endora-commerce/contracts';
3
+ import { OrganizationInvitation } from '../entities/organization-invitation.entity.js';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ import { type OrgTemplateEmail } from './org-template-email.js';
6
+ import { type OrganizationEventBus } from './registration-service.js';
7
+ export interface InvitationResult {
8
+ invitation: OrganizationInvitation;
9
+ rawToken: string;
10
+ }
11
+ export interface InvitationServiceOptions {
12
+ /** Storefront base used when building the redemption link. */
13
+ acceptBaseUrl?: string;
14
+ }
15
+ /**
16
+ * `customer_accounts`' published read and write (feature 075, Phase C).
17
+ *
18
+ * An invitation is this module's row; the member it creates is that module's.
19
+ * The membership pre-checks used to be `em.findOne`/`em.count` on its entity
20
+ * and the accept used to `em.create` one, with a `hashPassword` imported from
21
+ * `auth`.
22
+ *
23
+ * With `customer_accounts` off every invitation path fails closed — inviting a
24
+ * member the platform cannot see, or accepting into an account it cannot
25
+ * create, are both incoherent rather than degraded.
26
+ */
27
+ export interface InvitationAccountPorts {
28
+ read: CustomerAccountReadPort;
29
+ write: CustomerAccountMemberWritePort;
30
+ }
31
+ export declare class InvitationService {
32
+ private readonly emFactory;
33
+ private readonly accounts;
34
+ private readonly events?;
35
+ private readonly auditLog?;
36
+ private readonly mailer;
37
+ private readonly acceptBaseUrl;
38
+ private readonly templateEmail;
39
+ constructor(emFactory: () => EntityManager, accounts: InvitationAccountPorts, mailer?: EmailMailerPort | null, options?: InvitationServiceOptions, events?: OrganizationEventBus | undefined, templateEmail?: OrgTemplateEmail, auditLog?: AuditPort | undefined);
40
+ invite(actor: {
41
+ organizationId: string;
42
+ customerAccountId?: string | null;
43
+ }, input: {
44
+ email: string;
45
+ role?: 'organization_admin' | 'regular_user';
46
+ }): Promise<InvitationResult>;
47
+ /**
48
+ * Lists every pending invitation on the actor's organization. "Pending"
49
+ * means not yet consumed and not revoked; expired rows are intentionally
50
+ * still surfaced so the admin can see what needs cleaning up.
51
+ */
52
+ listPending(actor: {
53
+ organizationId: string;
54
+ }): Promise<OrganizationInvitation[]>;
55
+ revoke(actor: {
56
+ organizationId: string;
57
+ }, invitationId: string): Promise<void>;
58
+ /**
59
+ * The account insert and the invitation's `consumedAt` were one flush until
60
+ * feature 075's Phase C, and they are two units of work now: the account is
61
+ * `customer_accounts`' row, the invitation is this module's, and no port
62
+ * parameter may be a unit of work (D-78). The two modules share no foreign
63
+ * key between *these* two rows, so D-78's second answer does not apply and
64
+ * the seam is split rather than declared.
65
+ *
66
+ * The account is created **first**, deliberately. If the consume then fails,
67
+ * the buyer has the account they just set a password for and can sign in;
68
+ * what is left behind is a pending invitation an org admin can revoke. The
69
+ * other order loses the account instead, which is the half that cannot be
70
+ * recovered from the screen the buyer is looking at.
71
+ */
72
+ acceptByToken(rawToken: string, input: {
73
+ password: string;
74
+ firstName: string;
75
+ lastName: string;
76
+ }): Promise<{
77
+ customerAccount: CustomerAccountRecord;
78
+ }>;
79
+ /**
80
+ * Latest unconsumed token id — exposed only by the test probe so
81
+ * integration tests can complete the accept step without the email.
82
+ */
83
+ latestUnconsumedTokenInvitationId(): Promise<string | null>;
84
+ }
85
+ //# sourceMappingURL=invitation-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invitation-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,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAEvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAmBnC,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,sBAAsB,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,8BAA8B,CAAC;CACvC;AAED,qBAAa,iBAAiB;IAO1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAGzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAExB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAZ5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;gBAG9B,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,EAAE,sBAAsB,EACjD,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,EAC/B,OAAO,CAAC,EAAE,wBAAwB,EACjB,MAAM,CAAC,EAAE,oBAAoB,YAAA,EAC9C,aAAa,CAAC,EAAE,gBAAgB,EACf,QAAQ,CAAC,EAAE,SAAS,YAAA;IAOjC,MAAM,CACV,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EACpE,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,oBAAoB,GAAG,cAAc,CAAA;KAAE,GACrE,OAAO,CAAC,gBAAgB,CAAC;IAoH5B;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAajF,MAAM,CACV,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,EACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAgDhB;;;;;;;;;;;;;OAaG;IACG,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAC/D,OAAO,CAAC;QAAE,eAAe,EAAE,qBAAqB,CAAA;KAAE,CAAC;IAiEtD;;;OAGG;IACG,iCAAiC,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CASlE"}
@@ -0,0 +1,266 @@
1
+ import { createHash, randomBytes, randomUUID } from 'crypto';
2
+ import { UniqueConstraintViolationException } from '@mikro-orm/core';
3
+ import { ERROR_CODES, normalizeEmailAddress, } from '@endora-commerce/contracts';
4
+ import { HttpError } from '@endora-commerce/platform/http';
5
+ import { Organization } from '../entities/organization.entity.js';
6
+ import { OrganizationInvitation } from '../entities/organization-invitation.entity.js';
7
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
8
+ import { buildInvitationEmail } from '../email-templates/invitation.js';
9
+ import { noopOrgTemplateEmail } from './org-template-email.js';
10
+ import { emitCustomerAccountCreated, } from './registration-service.js';
11
+ /**
12
+ * InvitationService (T173, FR-043).
13
+ *
14
+ * Rules:
15
+ * - Org Admin invites by email + role. Existing CustomerAccount with that
16
+ * email rejects with EMAIL_ALREADY_IN_ORGANIZATION (same org) or
17
+ * EMAIL_BELONGS_TO_ANOTHER_ORGANIZATION (different org).
18
+ * - One pending invitation per (org, email) — partial unique index in
19
+ * migration 005 enforces this.
20
+ * - acceptByToken: anonymous flow that needs password + first/last name.
21
+ * Creates the CustomerAccount with role from the invitation, marks the
22
+ * invitation consumed.
23
+ * - revoke: soft-clears the pending invitation.
24
+ */
25
+ const INVITATION_TTL_DAYS = 14;
26
+ export class InvitationService {
27
+ emFactory;
28
+ accounts;
29
+ events;
30
+ auditLog;
31
+ mailer;
32
+ acceptBaseUrl;
33
+ templateEmail;
34
+ constructor(emFactory, accounts, mailer, options, events, templateEmail, auditLog) {
35
+ this.emFactory = emFactory;
36
+ this.accounts = accounts;
37
+ this.events = events;
38
+ this.auditLog = auditLog;
39
+ this.mailer = mailer ?? null;
40
+ this.acceptBaseUrl = options?.acceptBaseUrl ?? 'https://storefront.local';
41
+ this.templateEmail = templateEmail ?? noopOrgTemplateEmail;
42
+ }
43
+ async invite(actor, input) {
44
+ const em = this.emFactory();
45
+ const role = input.role ?? 'regular_user';
46
+ // This module keeps a row of its own on the same address, so it folds it
47
+ // the same way the account table does rather than by a private rule.
48
+ const invitedEmail = normalizeEmailAddress(input.email);
49
+ // Feature 051 — a personal (B2C) organization is single-member by
50
+ // definition; it cannot invite additional members.
51
+ const actorOrg = await em.findOne(Organization, { id: actor.organizationId });
52
+ if (actorOrg?.isPersonal) {
53
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'A personal (individual) account cannot invite members.');
54
+ }
55
+ // Pre-check existing membership.
56
+ const existingByEmail = await this.accounts.read.findByEmail(invitedEmail);
57
+ if (existingByEmail) {
58
+ if (existingByEmail.organizationId === actor.organizationId) {
59
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_IN_ORGANIZATION, 'This email already belongs to a member of your organization.');
60
+ }
61
+ throw new HttpError(409, ERROR_CODES.EMAIL_BELONGS_TO_ANOTHER_ORGANIZATION, 'This email belongs to a member of another organization.');
62
+ }
63
+ const rawToken = randomBytes(32).toString('base64url');
64
+ const invitation = em.create(OrganizationInvitation, {
65
+ organizationId: actor.organizationId,
66
+ invitedByCustomerAccountId: actor.customerAccountId ?? null,
67
+ email: invitedEmail,
68
+ role,
69
+ tokenHash: sha256Hex(rawToken),
70
+ expiresAt: new Date(Date.now() + INVITATION_TTL_DAYS * 24 * 60 * 60 * 1_000),
71
+ });
72
+ if (this.auditLog) {
73
+ recordAuditFromContext(this.auditLog, em, {
74
+ action: 'organization.invite',
75
+ objectType: 'organization',
76
+ objectId: actor.organizationId,
77
+ stateBefore: null,
78
+ stateAfter: { email: invitedEmail, role },
79
+ });
80
+ }
81
+ try {
82
+ await em.persistAndFlush(invitation);
83
+ }
84
+ catch (err) {
85
+ if (err instanceof UniqueConstraintViolationException) {
86
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_IN_ORGANIZATION, 'A pending invitation for this email already exists.');
87
+ }
88
+ throw err;
89
+ }
90
+ if (this.mailer || this.templateEmail !== noopOrgTemplateEmail) {
91
+ const organization = await em.findOne(Organization, { id: actor.organizationId });
92
+ const inviter = actor.customerAccountId
93
+ ? await this.accounts.read.findById(actor.customerAccountId)
94
+ : null;
95
+ const inviterName = inviter
96
+ ? [inviter.firstName, inviter.lastName].filter(Boolean).join(' ').trim() || inviter.email
97
+ : 'Platform support';
98
+ const organizationName = organization?.name ?? 'your organization';
99
+ const message = buildInvitationEmail({
100
+ invitationId: invitation.id,
101
+ rawToken,
102
+ inviteeEmail: invitation.email,
103
+ inviterName,
104
+ organizationName,
105
+ role: invitation.role,
106
+ expiresAt: invitation.expiresAt,
107
+ acceptBaseUrl: this.acceptBaseUrl,
108
+ });
109
+ const acceptUrl = message.meta?.acceptUrl ?? '';
110
+ const sentViaTemplate = await this.templateEmail.trySend({
111
+ code: 'organization_invitation',
112
+ to: invitation.email,
113
+ messageId: message.messageId,
114
+ variables: {
115
+ organizationName,
116
+ inviterName,
117
+ roleLabel: invitation.role === 'organization_admin' ? 'Organization Admin' : 'Member',
118
+ acceptUrl,
119
+ expiresOn: invitation.expiresAt.toISOString().slice(0, 10),
120
+ },
121
+ meta: message.meta,
122
+ });
123
+ if (!sentViaTemplate && this.mailer) {
124
+ const outcome = await this.mailer.send(message);
125
+ if (outcome.status !== 'sent') {
126
+ // This method answers the invitation, not the message, so the
127
+ // non-send is named rather than returned — and since D-59 the same
128
+ // fact is a row an operator can read after the log has rotated.
129
+ console.warn('[organizations] the invitation e-mail was not sent', {
130
+ invitationId: invitation.id,
131
+ reason: outcome.reason,
132
+ });
133
+ }
134
+ }
135
+ }
136
+ return { invitation, rawToken };
137
+ }
138
+ /**
139
+ * Lists every pending invitation on the actor's organization. "Pending"
140
+ * means not yet consumed and not revoked; expired rows are intentionally
141
+ * still surfaced so the admin can see what needs cleaning up.
142
+ */
143
+ async listPending(actor) {
144
+ const em = this.emFactory();
145
+ return em.find(OrganizationInvitation, {
146
+ organizationId: actor.organizationId,
147
+ consumedAt: null,
148
+ revokedAt: null,
149
+ }, { orderBy: { createdAt: 'desc' } });
150
+ }
151
+ async revoke(actor, invitationId) {
152
+ const em = this.emFactory();
153
+ const invitation = await em.findOne(OrganizationInvitation, {
154
+ id: invitationId,
155
+ organizationId: actor.organizationId,
156
+ });
157
+ if (!invitation) {
158
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Invitation not found.');
159
+ }
160
+ if (invitation.consumedAt || invitation.revokedAt)
161
+ return;
162
+ if (invitation.role === 'organization_admin') {
163
+ const adminCount = await this.accounts.read.countByOrganizationRole(actor.organizationId, 'organization_admin', { activeOnly: true });
164
+ if (adminCount === 0) {
165
+ const otherPending = await em.count(OrganizationInvitation, {
166
+ organizationId: actor.organizationId,
167
+ role: 'organization_admin',
168
+ consumedAt: null,
169
+ revokedAt: null,
170
+ id: { $ne: invitation.id },
171
+ });
172
+ if (otherPending === 0) {
173
+ throw new HttpError(409, ERROR_CODES.CANNOT_REVOKE_LAST_ADMIN_INVITE, 'Cannot revoke the only pending administrator invitation while the organization has no active administrator.');
174
+ }
175
+ }
176
+ }
177
+ invitation.revokedAt = new Date();
178
+ if (this.auditLog) {
179
+ recordAuditFromContext(this.auditLog, em, {
180
+ action: 'organization.invite_revoke',
181
+ objectType: 'organization',
182
+ objectId: invitation.organizationId,
183
+ stateBefore: { email: invitation.email, role: invitation.role },
184
+ stateAfter: null,
185
+ });
186
+ }
187
+ await em.flush();
188
+ }
189
+ /**
190
+ * The account insert and the invitation's `consumedAt` were one flush until
191
+ * feature 075's Phase C, and they are two units of work now: the account is
192
+ * `customer_accounts`' row, the invitation is this module's, and no port
193
+ * parameter may be a unit of work (D-78). The two modules share no foreign
194
+ * key between *these* two rows, so D-78's second answer does not apply and
195
+ * the seam is split rather than declared.
196
+ *
197
+ * The account is created **first**, deliberately. If the consume then fails,
198
+ * the buyer has the account they just set a password for and can sign in;
199
+ * what is left behind is a pending invitation an org admin can revoke. The
200
+ * other order loses the account instead, which is the half that cannot be
201
+ * recovered from the screen the buyer is looking at.
202
+ */
203
+ async acceptByToken(rawToken, input) {
204
+ const em = this.emFactory();
205
+ const tokenHash = sha256Hex(rawToken);
206
+ const invitation = await em.findOne(OrganizationInvitation, { tokenHash });
207
+ if (!invitation || invitation.consumedAt || invitation.revokedAt) {
208
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Invitation is invalid or has already been consumed.');
209
+ }
210
+ if (invitation.expiresAt.getTime() <= Date.now()) {
211
+ throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Invitation has expired.');
212
+ }
213
+ // FR-041: at most one CustomerAccount per email installation-wide. The
214
+ // owner re-checks inside `create`; this one keeps the invitation
215
+ // unconsumed when the address is already taken.
216
+ const existing = await this.accounts.read.findByEmail(invitation.email);
217
+ if (existing) {
218
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
219
+ }
220
+ const organization = await em.findOne(Organization, { id: invitation.organizationId });
221
+ if (!organization) {
222
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Organization no longer exists.');
223
+ }
224
+ const customer = await this.accounts.write.create({
225
+ organizationId: invitation.organizationId,
226
+ email: invitation.email,
227
+ password: input.password,
228
+ firstName: input.firstName,
229
+ lastName: input.lastName,
230
+ role: invitation.role,
231
+ // Accepting an invitation implies a confirmed address: it was delivered
232
+ // to that address.
233
+ emailVerified: true,
234
+ });
235
+ invitation.consumedAt = new Date();
236
+ if (this.auditLog) {
237
+ recordAuditFromContext(this.auditLog, em, {
238
+ action: 'organization.invite_accept',
239
+ objectType: 'organization',
240
+ objectId: invitation.organizationId,
241
+ stateBefore: null,
242
+ stateAfter: { email: invitation.email, role: invitation.role, customerAccountId: customer.id },
243
+ });
244
+ }
245
+ await em.flush();
246
+ if (this.events) {
247
+ emitCustomerAccountCreated(this.events, customer.id, invitation.organizationId);
248
+ }
249
+ return { customerAccount: customer };
250
+ }
251
+ /**
252
+ * Latest unconsumed token id — exposed only by the test probe so
253
+ * integration tests can complete the accept step without the email.
254
+ */
255
+ async latestUnconsumedTokenInvitationId() {
256
+ const em = this.emFactory();
257
+ const row = await em.findOne(OrganizationInvitation, { consumedAt: null, revokedAt: null }, { orderBy: { createdAt: 'desc' } });
258
+ return row?.id ?? null;
259
+ }
260
+ }
261
+ function sha256Hex(input) {
262
+ return createHash('sha256').update(input, 'utf8').digest('hex');
263
+ }
264
+ // silence unused — randomUUID may be needed by callers later
265
+ void randomUUID;
266
+ //# sourceMappingURL=invitation-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-service.js","sourceRoot":"","sources":["../../../src/backend/services/invitation-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `normalizeOrganizationName` moved to `@endora-commerce/contracts` in feature 075's
3
+ * Phase P: it is pure over its argument, so a gated port answering 503 to
4
+ * "fold these diacritics" would be a bug rather than a degrade (FR-013).
5
+ *
6
+ * It has to be one implementation, not two — the entity's `@BeforeCreate` /
7
+ * `@BeforeUpdate` hooks write `name_search` with it and `orders`' list filter
8
+ * matches against that column with it, so a copy that drifted would make the
9
+ * filter stop matching silently.
10
+ *
11
+ * Re-exported here for the length of Phase P, which cuts no consumer.
12
+ */
13
+ export { normalizeOrganizationName } from '@endora-commerce/contracts';
14
+ //# sourceMappingURL=normalize-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-name.d.ts","sourceRoot":"","sources":["../../../src/backend/services/normalize-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `normalizeOrganizationName` moved to `@endora-commerce/contracts` in feature 075's
3
+ * Phase P: it is pure over its argument, so a gated port answering 503 to
4
+ * "fold these diacritics" would be a bug rather than a degrade (FR-013).
5
+ *
6
+ * It has to be one implementation, not two — the entity's `@BeforeCreate` /
7
+ * `@BeforeUpdate` hooks write `name_search` with it and `orders`' list filter
8
+ * matches against that column with it, so a copy that drifted would make the
9
+ * filter stop matching silently.
10
+ *
11
+ * Re-exported here for the length of Phase P, which cuts no consumer.
12
+ */
13
+ export { normalizeOrganizationName } from '@endora-commerce/contracts';
14
+ //# sourceMappingURL=normalize-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-name.js","sourceRoot":"","sources":["../../../src/backend/services/normalize-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}