@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,220 @@
1
+ import { SESSION_COOKIE_NAME, customerLoginRequestSchema, emailVerificationRequestSchema, normalizeEmailAddress, passwordResetConfirmSchema, passwordResetRequestSchema, registerOrganizationRequestSchema, } from '@endora-commerce/contracts';
2
+ import { currentSalesChannel } from '@endora-commerce/platform/kernel';
3
+ import { buildVerificationEmail } from './email-templates/verification.js';
4
+ export async function registerOrganizationsPublicRoutes(app, deps) {
5
+ const { registrationService, verificationService, customerAuthService, passwordResetService } = deps;
6
+ app.post('/api/v1/organizations/register', { schema: { body: registerOrganizationRequestSchema } }, async (request, reply) => {
7
+ const body = registerOrganizationRequestSchema.parse(request.body);
8
+ const result = await registrationService.registerOrganization(body);
9
+ // Stash the raw token for the test-only probe so integration tests can
10
+ // complete the verify step without reading the mail.
11
+ if (deps.latestTokenByEmail) {
12
+ deps.latestTokenByEmail.set(result.customerAccount.email, result.verificationToken);
13
+ deps.latestTokenByEmail.set('__latest__', result.verificationToken);
14
+ }
15
+ let emailVerificationSent = false;
16
+ try {
17
+ const message = buildVerificationEmail({
18
+ customerAccountId: result.customerAccount.id,
19
+ rawToken: result.verificationToken,
20
+ recipientEmail: result.customerAccount.email,
21
+ organizationName: result.organization.name,
22
+ storefrontBaseUrl: deps.storefrontBaseUrl,
23
+ });
24
+ const verifyUrl = message.meta?.verifyUrl ?? '';
25
+ const sentViaTemplate = deps.templateEmail
26
+ ? await deps.templateEmail.trySend({
27
+ code: 'email_verification',
28
+ to: result.customerAccount.email,
29
+ messageId: message.messageId,
30
+ variables: { organizationName: result.organization.name, verifyUrl },
31
+ meta: message.meta,
32
+ })
33
+ : false;
34
+ // The flag is in the response body, so it answers what the transport
35
+ // answered rather than "we reached this line" (D-59).
36
+ const outcome = sentViaTemplate
37
+ ? { status: 'sent' }
38
+ : await deps.mailer.send(message);
39
+ emailVerificationSent = outcome.status === 'sent';
40
+ }
41
+ catch (err) {
42
+ request.log.error({ err }, 'Failed to send verification email');
43
+ }
44
+ reply.status(201);
45
+ return {
46
+ data: {
47
+ organization: serializeOrganization(result.organization),
48
+ customerAccount: serializeCustomerAccount(result.customerAccount),
49
+ emailVerificationSent,
50
+ },
51
+ };
52
+ });
53
+ app.post('/api/v1/auth/email-verification/verify', { schema: { body: emailVerificationRequestSchema } }, async (request) => {
54
+ const body = emailVerificationRequestSchema.parse(request.body);
55
+ const result = await verificationService.verify(body.token);
56
+ return {
57
+ data: {
58
+ organizationId: result.organizationId,
59
+ customerAccountId: result.customerAccountId,
60
+ verifiedAt: result.verifiedAt.toISOString(),
61
+ },
62
+ };
63
+ });
64
+ app.post('/api/v1/auth/customer/login', { schema: { body: customerLoginRequestSchema } }, async (request, reply) => {
65
+ const body = customerLoginRequestSchema.parse(request.body);
66
+ const channelId = currentSalesChannel()?.id ?? null;
67
+ const result = await customerAuthService.login({
68
+ email: body.email,
69
+ password: body.password,
70
+ salesChannelId: channelId,
71
+ ...(request.ip ? { ip: request.ip } : {}),
72
+ ...(typeof request.headers['user-agent'] === 'string'
73
+ ? { userAgent: request.headers['user-agent'] }
74
+ : {}),
75
+ });
76
+ // Feature 042 — two-step login. The MFA cases carry no session cookie;
77
+ // the client completes the second step at the MFA verify endpoint.
78
+ if (result.status === 'mfaRequired') {
79
+ return { data: { status: 'mfaRequired', challengeId: result.challengeId } };
80
+ }
81
+ if (result.status === 'mfaSetupRequired') {
82
+ return { data: { status: 'mfaSetupRequired', setupTicket: result.setupTicket } };
83
+ }
84
+ setSessionCookie(reply, result.sessionCookieValue, result.sessionExpiresAt);
85
+ // Merge any anonymous cart the caller was carrying into the authenticated
86
+ // cart (feature 027 R-09 / feature 037-cart-merge-on-login). The hook is
87
+ // best-effort: a thrown error MUST NOT break login (037 FR-007/FR-008).
88
+ let cartMerge;
89
+ if (deps.onLogin) {
90
+ const cookies = request.cookies;
91
+ const anon = cookies?.['b2b_cart_anon'];
92
+ const compareAnon = cookies?.['compare_token'];
93
+ try {
94
+ const hookResult = await deps.onLogin({
95
+ customerAccountId: result.customerAccount.id,
96
+ organizationId: result.customerAccount.organizationId ?? null,
97
+ ...(anon ? { anonymousCartToken: anon } : {}),
98
+ ...(compareAnon ? { anonymousCompareToken: compareAnon } : {}),
99
+ });
100
+ cartMerge = hookResult.cartMerge;
101
+ }
102
+ catch (err) {
103
+ request.log.error({
104
+ err,
105
+ customerAccountId: result.customerAccount.id,
106
+ hadAnonToken: Boolean(anon),
107
+ }, 'cart_merge_on_login_failed');
108
+ }
109
+ }
110
+ // Clear the stale anon-cart cookie when the source token has been
111
+ // consumed (adopted/merged) or pointed at an empty cart (noop_empty).
112
+ // Failure and noop_no_anon both leave the cookie alone — failure so the
113
+ // buyer can retry on the next login, noop_no_anon because there was
114
+ // nothing to clear in the first place. See spec R-06.
115
+ if (cartMerge && cartMerge.outcome !== 'noop_no_anon') {
116
+ reply.clearCookie('b2b_cart_anon', { path: '/' });
117
+ }
118
+ return {
119
+ data: {
120
+ status: 'authenticated',
121
+ customerAccount: serializeCustomerAccount(result.customerAccount),
122
+ ...(cartMerge ? { cartMerge: narrowCartMergeForHttp(cartMerge) } : {}),
123
+ },
124
+ };
125
+ });
126
+ app.post('/api/v1/auth/password-reset/request', { schema: { body: passwordResetRequestSchema } }, async (request, reply) => {
127
+ const body = passwordResetRequestSchema.parse(request.body);
128
+ // The address is handed over as typed — the port folds it, as every
129
+ // other lookup on that table does. The probe key below is the one place
130
+ // the folded form is needed here, and it uses the same function so the
131
+ // key cannot drift from what the lookup matched.
132
+ const result = await passwordResetService.requestReset(body.email);
133
+ if (deps.latestTokenByEmail && result.rawToken) {
134
+ deps.latestTokenByEmail.set(`reset:${normalizeEmailAddress(body.email)}`, result.rawToken);
135
+ deps.latestTokenByEmail.set('__latest_reset__', result.rawToken);
136
+ }
137
+ // Always 202 — defends against account enumeration.
138
+ return reply.status(202).send();
139
+ });
140
+ app.post('/api/v1/auth/password-reset/confirm', { schema: { body: passwordResetConfirmSchema } }, async (request, reply) => {
141
+ const body = passwordResetConfirmSchema.parse(request.body);
142
+ await passwordResetService.confirmReset(body.token, body.newPassword);
143
+ return reply.status(200).send({ data: { ok: true } });
144
+ });
145
+ app.post('/api/v1/auth/customer/logout', async (request, reply) => {
146
+ const cookies = request.cookies;
147
+ const raw = cookies?.[SESSION_COOKIE_NAME];
148
+ if (raw) {
149
+ const dot = raw.indexOf('.');
150
+ if (dot !== -1) {
151
+ const sessionId = raw.slice(0, dot);
152
+ if (sessionId) {
153
+ await customerAuthService.logout(sessionId);
154
+ }
155
+ }
156
+ }
157
+ reply.clearCookie(SESSION_COOKIE_NAME, { path: '/' });
158
+ return reply.status(204).send();
159
+ });
160
+ // Test-only probe for the register→verify integration test.
161
+ if (deps.exposeTestProbe && deps.latestTokenByEmail) {
162
+ app.get('/api/v1/_test/latest-verification-token', async () => {
163
+ const token = deps.latestTokenByEmail.get('__latest__');
164
+ if (!token)
165
+ return { token: null };
166
+ return { token };
167
+ });
168
+ }
169
+ }
170
+ function setSessionCookie(reply, value, expiresAt) {
171
+ reply.setCookie(SESSION_COOKIE_NAME, value, {
172
+ path: '/',
173
+ httpOnly: true,
174
+ sameSite: 'lax',
175
+ secure: process.env['NODE_ENV'] === 'production',
176
+ expires: expiresAt,
177
+ signed: false,
178
+ });
179
+ }
180
+ function serializeOrganization(o) {
181
+ return {
182
+ id: o.id,
183
+ name: o.name,
184
+ taxId: o.taxId,
185
+ status: o.status,
186
+ vatStatus: o.vatStatus,
187
+ registeredAddress: o.registeredAddress,
188
+ createdAt: o.createdAt.toISOString(),
189
+ updatedAt: o.updatedAt.toISOString(),
190
+ };
191
+ }
192
+ function serializeCustomerAccount(c) {
193
+ return {
194
+ id: c.id,
195
+ organizationId: c.organizationId ?? null,
196
+ email: c.email,
197
+ firstName: c.firstName,
198
+ lastName: c.lastName,
199
+ role: c.role,
200
+ emailVerifiedAt: c.emailVerifiedAt?.toISOString() ?? null,
201
+ // The record already answers this; the entity carried the secret's
202
+ // confirmation timestamp and every caller derived the same boolean from it.
203
+ twoFactorEnabled: c.twoFactorEnabled,
204
+ createdAt: c.createdAt.toISOString(),
205
+ updatedAt: c.updatedAt.toISOString(),
206
+ };
207
+ }
208
+ /**
209
+ * Collapse the internal `CartMergeOutcome` (with the two `noop_*` variants
210
+ * and the line-count diagnostics) into the storefront-facing shape that
211
+ * exposes only `outcome ∈ {adopted, merged, noop}` and the destination
212
+ * cart id. See specs/037-cart-merge-on-login/contracts/cart-merge.md § 1.
213
+ */
214
+ function narrowCartMergeForHttp(internal) {
215
+ const outcome = internal.outcome === 'noop_empty' || internal.outcome === 'noop_no_anon'
216
+ ? 'noop'
217
+ : internal.outcome;
218
+ return { outcome, destinationCartId: internal.destinationCartId };
219
+ }
220
+ //# sourceMappingURL=routes.public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.public.js","sourceRoot":"","sources":["../../src/backend/routes.public.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type AdminUserReadPort } from '@endora-commerce/contracts';
3
+ import type { EntityManager } from '@mikro-orm/postgresql';
4
+ import type { SalesRepAssignmentPort } from './services/sales-rep-assignment-service.js';
5
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ /**
7
+ * Sales-rep ↔ organization assignment routes (feature 008 / T077).
8
+ *
9
+ * Owned **and registered** by `organizations` since D-166. Three of the four
10
+ * endpoints this file used to hold are here; the fourth —
11
+ * `GET /admin/sales-reps/:adminUserId/organizations`, the only one that reads a
12
+ * `QuoteRequest` — moved to `quote_requests/routes.sales-reps.ts`, which is the
13
+ * module that owns the fact it reports.
14
+ *
15
+ * The split is what makes the gate honest, and it is a consequence rather than
16
+ * a preference. The whole file used to be registered by `quote_requests`
17
+ * through a dynamic `import()`, so all four endpoints were gated by *that*
18
+ * module's effective state and permissioned `rfqs:handle`. Neither half of that
19
+ * arrangement survives inspection: assigning a sales representative qualifies an
20
+ * organisation and has nothing to do with quote requests, so switching quote
21
+ * requests off must not take the assignment screen with it — and registering the
22
+ * file here without splitting it would be worse, because `rfqs:handle` is
23
+ * declared `module: 'quote_requests'` and `organizations` is `nonDeactivatable`,
24
+ * which leaves a permanently mounted screen behind a permission that disappears
25
+ * from `/admin-roles` the moment quote requests is switched off. The gate travels
26
+ * with the routes.
27
+ *
28
+ * `organizations:assign-sales-rep` is the code, and it is the one this file has
29
+ * proposed in its own comment since feature 008 — the placeholder note said
30
+ * "a follow-up can add a dedicated `organizations:assign-sales-rep` permission",
31
+ * and this is that follow-up.
32
+ *
33
+ * Admins are read through `adminUserReadPort` rather than through the
34
+ * `AdminUser` entity, which is what retires this file's two entries in
35
+ * `organizations`' cross-module-import shard: the reads were always
36
+ * `findById`/`findByIds`, both published, and the only thing stopping the port
37
+ * from being injected was that another module built this file's `deps`.
38
+ */
39
+ export interface SalesRepRoutesDeps {
40
+ emFactory: () => EntityManager;
41
+ requireAdmin: RequireAdminFactory;
42
+ salesRepAssignment: SalesRepAssignmentPort;
43
+ adminUsers: AdminUserReadPort;
44
+ }
45
+ export declare function registerOrganizationsSalesRepRoutes(app: FastifyInstance, deps: SalesRepRoutesDeps): Promise<void>;
46
+ //# sourceMappingURL=routes.sales-reps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.sales-reps.d.ts","sourceRoot":"","sources":["../../src/backend/routes.sales-reps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,kBAAkB,EAAE,sBAAsB,CAAC;IAC3C,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAsCD,wBAAsB,mCAAmC,CACvD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
@@ -0,0 +1,114 @@
1
+ import { ERROR_CODES, assignSalesRepRequestSchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { getTenantContext } from '@endora-commerce/platform/tenancy';
4
+ import { Organization } from './entities/organization.entity.js';
5
+ /**
6
+ * D-260 — **a write that changes the acting principal's own authority cannot be
7
+ * authorised by that authority.**
8
+ *
9
+ * `OrganizationSalesRepAssignment` is the table that *defines* `allowed-set`, so
10
+ * a scoped admin holding `organizations:assign-sales-rep` could `POST` itself on
11
+ * to any organization and widen its own `allowedOrganizationIds` — measured at
12
+ * 201, `[orgA] → [orgA, orgB]`. No classification can save the write:
13
+ * `@OrgScoped` on this entity would be circular (D-259 category 2), and the id
14
+ * arrives from the path rather than from the actor, so no column filter reaches
15
+ * it. Nor is "the scoped role does not hold the code" a boundary —
16
+ * `PUT /admin/admin-roles/:code` upserts an arbitrary permission array onto any
17
+ * role, and the seed that grants this role its codes is a demo fixture.
18
+ *
19
+ * The refusal is therefore `mode: 'all'` / 403 and not the FR-008 not-found arm
20
+ * of the criterion: nothing about the organization's existence is being
21
+ * concealed, the actor is being told it may not write the graph at all. The
22
+ * idiom is `routes.admin.ts`'s credit-inheritance-mode handler, which already
23
+ * reaches for it on the weaker argument of a money-behaviour switch.
24
+ *
25
+ * It is categorical — the actor's *own* organization included. Assigning a
26
+ * second representative to an organization the actor holds still edits the graph
27
+ * that decides who holds what, and the graph is the boundary rather than an
28
+ * operation inside it.
29
+ */
30
+ function requireUnscopedActor() {
31
+ const ctx = getTenantContext();
32
+ if (!ctx || ctx.mode !== 'all') {
33
+ throw new HttpError(403, ERROR_CODES.FORBIDDEN, 'Only a platform administrator can change sales-representative assignments.');
34
+ }
35
+ }
36
+ export async function registerOrganizationsSalesRepRoutes(app, deps) {
37
+ const { emFactory, requireAdmin, salesRepAssignment, adminUsers } = deps;
38
+ const guard = requireAdmin('organizations:assign-sales-rep');
39
+ // GET /api/v1/admin/organizations/:organizationId/sales-reps
40
+ app.get('/api/v1/admin/organizations/:organizationId/sales-reps', { preHandler: guard }, async (request) => {
41
+ const em = emFactory();
42
+ const org = await em.findOne(Organization, { id: request.params.organizationId });
43
+ if (!org)
44
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Organization not found.');
45
+ const assignments = await salesRepAssignment.listForOrganization(org.id);
46
+ const adminIds = assignments.map((a) => a.adminUserId);
47
+ const admins = adminIds.length > 0 ? await adminUsers.findByIds(adminIds) : [];
48
+ const adminById = new Map(admins.map((a) => [a.id, a]));
49
+ return {
50
+ data: assignments.map((a) => {
51
+ const u = adminById.get(a.adminUserId);
52
+ return {
53
+ id: a.id,
54
+ organizationId: a.organizationId,
55
+ adminUserId: a.adminUserId,
56
+ displayName: u ? `${u.firstName} ${u.lastName}` : 'Unknown',
57
+ email: u?.email ?? '',
58
+ assignedAt: a.createdAt.toISOString(),
59
+ assignedByAdminUserId: a.assignedByAdminUserId ?? null,
60
+ };
61
+ }),
62
+ };
63
+ });
64
+ // POST /api/v1/admin/organizations/:organizationId/sales-reps
65
+ app.post('/api/v1/admin/organizations/:organizationId/sales-reps', { preHandler: guard, schema: { body: assignSalesRepRequestSchema } }, async (request, reply) => {
66
+ requireUnscopedActor(); // D-260 — the graph is the boundary.
67
+ const body = assignSalesRepRequestSchema.parse(request.body);
68
+ const em = emFactory();
69
+ const org = await em.findOne(Organization, { id: request.params.organizationId });
70
+ if (!org)
71
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Organization not found.');
72
+ const adminUser = await adminUsers.findById(body.adminUserId);
73
+ if (!adminUser) {
74
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Admin user not found.');
75
+ }
76
+ const actor = request;
77
+ const assignedBy = actor.actor?.kind === 'admin'
78
+ ? actor.actor.adminUserId ?? null
79
+ : actor.testActor?.kind === 'admin'
80
+ ? actor.testActor.adminUserId ?? null
81
+ : null;
82
+ const created = await salesRepAssignment.assign({
83
+ organizationId: org.id,
84
+ adminUserId: adminUser.id,
85
+ assignedByAdminUserId: assignedBy,
86
+ });
87
+ reply.code(201);
88
+ return {
89
+ data: {
90
+ id: created.id,
91
+ organizationId: created.organizationId,
92
+ adminUserId: created.adminUserId,
93
+ displayName: `${adminUser.firstName} ${adminUser.lastName}`,
94
+ email: adminUser.email,
95
+ assignedAt: created.createdAt.toISOString(),
96
+ assignedByAdminUserId: created.assignedByAdminUserId ?? null,
97
+ },
98
+ };
99
+ });
100
+ // DELETE /api/v1/admin/organizations/:organizationId/sales-reps/:adminUserId
101
+ app.delete('/api/v1/admin/organizations/:organizationId/sales-reps/:adminUserId', { preHandler: guard }, async (request, reply) => {
102
+ requireUnscopedActor(); // D-260 — the graph is the boundary.
103
+ const removed = await salesRepAssignment.unassign({
104
+ organizationId: request.params.organizationId,
105
+ adminUserId: request.params.adminUserId,
106
+ });
107
+ if (!removed) {
108
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Assignment not found.');
109
+ }
110
+ reply.code(204);
111
+ return null;
112
+ });
113
+ }
114
+ //# sourceMappingURL=routes.sales-reps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.sales-reps.js","sourceRoot":"","sources":["../../src/backend/routes.sales-reps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { OrganizationContextService } from './services/organization-context-service.js';
3
+ import type { OrganizationRestrictionService } from './services/organization-restriction-service.js';
4
+ export interface OrganizationsStorefrontDeps {
5
+ contextService: OrganizationContextService;
6
+ restrictionService: OrganizationRestrictionService;
7
+ resolveCustomerContext: (req: FastifyRequest) => {
8
+ customerAccountId: string;
9
+ /** Null for no-org Customer accounts (feature 026 US2). */
10
+ organizationId: string | null;
11
+ };
12
+ requireCustomer: (req: FastifyRequest, reply: unknown) => Promise<void>;
13
+ }
14
+ /**
15
+ * Storefront-side organization endpoints introduced by feature 026 US4.
16
+ *
17
+ * POST /api/v1/storefront/checkout/preflight
18
+ * Called by the checkout SSR route. Returns 200 with the resolved
19
+ * payment + delivery method allow-list IDs and assigned warehouse IDs
20
+ * when the Customer's Organization is active; 423 with status +
21
+ * localized reason when it cannot transact; 200 with empty allow-lists
22
+ * for no-org Customer accounts (they get the platform defaults).
23
+ */
24
+ export declare function registerOrganizationsStorefrontRoutes(app: FastifyInstance, deps: OrganizationsStorefrontDeps): Promise<void>;
25
+ //# sourceMappingURL=routes.storefront.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.d.ts","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAC;AAC7F,OAAO,KAAK,EACV,8BAA8B,EAC/B,MAAM,gDAAgD,CAAC;AAExD,MAAM,WAAW,2BAA2B;IAC1C,cAAc,EAAE,0BAA0B,CAAC;IAC3C,kBAAkB,EAAE,8BAA8B,CAAC;IACnD,sBAAsB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAC/C,iBAAiB,EAAE,MAAM,CAAC;QAC1B,2DAA2D;QAC3D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;IACF,eAAe,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE;AAED;;;;;;;;;GASG;AACH,wBAAsB,qCAAqC,CACzD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,IAAI,CAAC,CA0Cf"}
@@ -0,0 +1,65 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ /**
4
+ * Storefront-side organization endpoints introduced by feature 026 US4.
5
+ *
6
+ * POST /api/v1/storefront/checkout/preflight
7
+ * Called by the checkout SSR route. Returns 200 with the resolved
8
+ * payment + delivery method allow-list IDs and assigned warehouse IDs
9
+ * when the Customer's Organization is active; 423 with status +
10
+ * localized reason when it cannot transact; 200 with empty allow-lists
11
+ * for no-org Customer accounts (they get the platform defaults).
12
+ */
13
+ export async function registerOrganizationsStorefrontRoutes(app, deps) {
14
+ app.post('/api/v1/storefront/checkout/preflight', { preHandler: deps.requireCustomer }, async (request, reply) => {
15
+ const ctx = deps.resolveCustomerContext(request);
16
+ if (!ctx.organizationId) {
17
+ // No-org Customer — platform defaults apply (feature 026 US2).
18
+ reply.status(200);
19
+ return {
20
+ canTransact: true,
21
+ allowedPaymentMethodIds: [],
22
+ allowedDeliveryMethodIds: [],
23
+ assignedWarehouseIds: [],
24
+ };
25
+ }
26
+ const org = await deps.contextService.loadEffectiveOrganization(ctx.organizationId);
27
+ if (!org) {
28
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Organization not found.');
29
+ }
30
+ if (org.status !== 'active') {
31
+ reply.status(423);
32
+ return {
33
+ canTransact: false,
34
+ status: org.status,
35
+ reason: describeStatus(org.status),
36
+ };
37
+ }
38
+ const allowLists = await deps.restrictionService.readAllowLists(org.id);
39
+ reply.status(200);
40
+ return {
41
+ canTransact: true,
42
+ allowedPaymentMethodIds: allowLists.paymentMethodIds,
43
+ allowedDeliveryMethodIds: allowLists.deliveryMethodIds,
44
+ assignedWarehouseIds: allowLists.warehouseIds,
45
+ };
46
+ });
47
+ }
48
+ /**
49
+ * Customer-safe localization of the moderation status. The storefront
50
+ * displays this string verbatim; admin-only details (block-reason text)
51
+ * are intentionally NOT exposed here.
52
+ */
53
+ function describeStatus(status) {
54
+ switch (status) {
55
+ case 'pending_verification':
56
+ return 'Twoja Organizacja oczekuje na weryfikację. Zamówienia będą dostępne po jej zakończeniu.';
57
+ case 'blocked':
58
+ return 'Składanie Zamówień jest obecnie wstrzymane. Prosimy o kontakt z opiekunem konta.';
59
+ case 'rejected':
60
+ return 'Rejestracja Twojej Organizacji została odrzucona. Prosimy o kontakt z administracją platformy.';
61
+ default:
62
+ return 'Składanie Zamówień obecnie niedostępne.';
63
+ }
64
+ }
65
+ //# sourceMappingURL=routes.storefront.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.js","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAiB3D;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qCAAqC,CACzD,GAAoB,EACpB,IAAiC;IAEjC,GAAG,CAAC,IAAI,CACN,uCAAuC,EACvC,EAAE,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,EACpC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAEjD,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YACxB,+DAA+D;YAC/D,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO;gBACL,WAAW,EAAE,IAAI;gBACjB,uBAAuB,EAAE,EAAE;gBAC3B,wBAAwB,EAAE,EAAE;gBAC5B,oBAAoB,EAAE,EAAE;aACzB,CAAC;QACJ,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,yBAAyB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACpF,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO;gBACL,WAAW,EAAE,KAAK;gBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM,EAAE,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC;aACnC,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO;YACL,WAAW,EAAE,IAAI;YACjB,uBAAuB,EAAE,UAAU,CAAC,gBAAgB;YACpD,wBAAwB,EAAE,UAAU,CAAC,iBAAiB;YACtD,oBAAoB,EAAE,UAAU,CAAC,YAAY;SAC9C,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CACrB,MAAgE;IAEhE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,sBAAsB;YACzB,OAAO,yFAAyF,CAAC;QACnG,KAAK,SAAS;YACZ,OAAO,kFAAkF,CAAC;QAC5F,KAAK,UAAU;YACb,OAAO,gGAAgG,CAAC;QAC1G;YACE,OAAO,yCAAyC,CAAC;IACrD,CAAC;AACH,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schemas used by feature 026 — moderation, restrictions, and VAT
4
+ * validation. These live in the module (not in `@endora-commerce/contracts`) because
5
+ * they describe internal route bodies; the public contract for the
6
+ * Organization status enum is exported from `@endora-commerce/contracts` and
7
+ * re-imported here.
8
+ */
9
+ export declare const organizationStatusSchema: z.ZodEnum<{
10
+ pending_verification: "pending_verification";
11
+ active: "active";
12
+ blocked: "blocked";
13
+ rejected: "rejected";
14
+ }>;
15
+ export type OrganizationStatusInput = z.infer<typeof organizationStatusSchema>;
16
+ /** Tax ID — uppercased + stripped of whitespace and dashes at the Zod boundary. */
17
+ export declare const taxIdSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
18
+ export declare const approveOrganizationSchema: z.ZodObject<{
19
+ expectedVersion: z.ZodNumber;
20
+ }, z.core.$strip>;
21
+ export type ApproveOrganizationInput = z.infer<typeof approveOrganizationSchema>;
22
+ export declare const rejectOrganizationSchema: z.ZodObject<{
23
+ expectedVersion: z.ZodNumber;
24
+ reason: z.ZodString;
25
+ notifyCustomerEmail: z.ZodDefault<z.ZodBoolean>;
26
+ }, z.core.$strip>;
27
+ export type RejectOrganizationInput = z.infer<typeof rejectOrganizationSchema>;
28
+ export declare const blockOrganizationSchema: z.ZodObject<{
29
+ expectedVersion: z.ZodNumber;
30
+ reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
31
+ }, z.core.$strip>;
32
+ export type BlockOrganizationInput = z.infer<typeof blockOrganizationSchema>;
33
+ export declare const unblockOrganizationSchema: z.ZodObject<{
34
+ expectedVersion: z.ZodNumber;
35
+ }, z.core.$strip>;
36
+ export type UnblockOrganizationInput = z.infer<typeof unblockOrganizationSchema>;
37
+ export declare const replaceOrgRestrictionsSchema: z.ZodObject<{
38
+ expectedVersion: z.ZodNumber;
39
+ paymentMethodIds: z.ZodArray<z.ZodString>;
40
+ deliveryMethodIds: z.ZodArray<z.ZodString>;
41
+ warehouseIds: z.ZodArray<z.ZodString>;
42
+ }, z.core.$strip>;
43
+ export type ReplaceOrgRestrictionsInput = z.infer<typeof replaceOrgRestrictionsSchema>;
44
+ export declare const patchOrgRestrictionsSchema: z.ZodObject<{
45
+ expectedVersion: z.ZodNumber;
46
+ add: z.ZodOptional<z.ZodArray<z.ZodString>>;
47
+ remove: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
+ }, z.core.$strip>;
49
+ export type PatchOrgRestrictionsInput = z.infer<typeof patchOrgRestrictionsSchema>;
50
+ export declare const triggerVatValidationSchema: z.ZodObject<{
51
+ providerHint: z.ZodDefault<z.ZodEnum<{
52
+ vies: "vies";
53
+ mf_pl: "mf_pl";
54
+ auto: "auto";
55
+ }>>;
56
+ applyAutoFill: z.ZodDefault<z.ZodBoolean>;
57
+ }, z.core.$strip>;
58
+ export type TriggerVatValidationInput = z.infer<typeof triggerVatValidationSchema>;
59
+ //# sourceMappingURL=organization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organization.d.ts","sourceRoot":"","sources":["../../../src/backend/schemas/organization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,eAAO,MAAM,wBAAwB;;;;;EAKnC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE/E,mFAAmF;AACnF,eAAO,MAAM,WAAW,wDAImC,CAAC;AAI5D,eAAO,MAAM,yBAAyB;;iBAEpC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAEjF,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE/E,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE7E,eAAO,MAAM,yBAAyB;;iBAEpC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAMjF,eAAO,MAAM,4BAA4B;;;;;iBAKvC,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAEvF,eAAO,MAAM,0BAA0B;;;;iBAIrC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAInF,eAAO,MAAM,0BAA0B;;;;;;;iBAGrC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schemas used by feature 026 — moderation, restrictions, and VAT
4
+ * validation. These live in the module (not in `@endora-commerce/contracts`) because
5
+ * they describe internal route bodies; the public contract for the
6
+ * Organization status enum is exported from `@endora-commerce/contracts` and
7
+ * re-imported here.
8
+ */
9
+ export const organizationStatusSchema = z.enum([
10
+ 'pending_verification',
11
+ 'active',
12
+ 'blocked',
13
+ 'rejected',
14
+ ]);
15
+ /** Tax ID — uppercased + stripped of whitespace and dashes at the Zod boundary. */
16
+ export const taxIdSchema = z
17
+ .string()
18
+ .min(1)
19
+ .max(64)
20
+ .transform((s) => s.replace(/[\s-]+/g, '').toUpperCase());
21
+ // ── Moderation ────────────────────────────────────────────────────────────
22
+ export const approveOrganizationSchema = z.object({
23
+ expectedVersion: z.number().int().nonnegative(),
24
+ });
25
+ export const rejectOrganizationSchema = z.object({
26
+ expectedVersion: z.number().int().nonnegative(),
27
+ reason: z.string().min(1).max(2000),
28
+ notifyCustomerEmail: z.boolean().default(true),
29
+ });
30
+ export const blockOrganizationSchema = z.object({
31
+ expectedVersion: z.number().int().nonnegative(),
32
+ reason: z.string().max(2000).nullable().optional(),
33
+ });
34
+ export const unblockOrganizationSchema = z.object({
35
+ expectedVersion: z.number().int().nonnegative(),
36
+ });
37
+ // ── Restrictions (per-Organization allow-lists) ───────────────────────────
38
+ const uuidList = z.array(z.string().uuid()).max(500);
39
+ export const replaceOrgRestrictionsSchema = z.object({
40
+ expectedVersion: z.number().int().nonnegative(),
41
+ paymentMethodIds: uuidList,
42
+ deliveryMethodIds: uuidList,
43
+ warehouseIds: uuidList,
44
+ });
45
+ export const patchOrgRestrictionsSchema = z.object({
46
+ expectedVersion: z.number().int().nonnegative(),
47
+ add: uuidList.optional(),
48
+ remove: uuidList.optional(),
49
+ });
50
+ // ── VAT validation ────────────────────────────────────────────────────────
51
+ export const triggerVatValidationSchema = z.object({
52
+ providerHint: z.enum(['auto', 'vies', 'mf_pl']).default('auto'),
53
+ applyAutoFill: z.boolean().default(false),
54
+ });
55
+ //# sourceMappingURL=organization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organization.js","sourceRoot":"","sources":["../../../src/backend/schemas/organization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC7C,sBAAsB;IACtB,QAAQ;IACR,SAAS;IACT,UAAU;CACX,CAAC,CAAC;AAGH,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KACzB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,EAAE,CAAC;KACP,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAE5D,6EAA6E;AAE7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;CAChD,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC/C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IACnC,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CAC/C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC/C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACnD,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;CAChD,CAAC,CAAC;AAGH,6EAA6E;AAE7E,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC/C,gBAAgB,EAAE,QAAQ;IAC1B,iBAAiB,EAAE,QAAQ;IAC3B,YAAY,EAAE,QAAQ;CACvB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC/C,GAAG,EAAE,QAAQ,CAAC,QAAQ,EAAE;IACxB,MAAM,EAAE,QAAQ,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH,6EAA6E;AAE7E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IAC/D,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC1C,CAAC,CAAC"}