@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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/components/OrganizationSalesRepsTab.d.ts +27 -0
- package/dist/admin/components/OrganizationSalesRepsTab.d.ts.map +1 -0
- package/dist/admin/components/OrganizationSalesRepsTab.js +170 -0
- package/dist/admin/components/OrganizationSalesRepsTab.js.map +1 -0
- package/dist/admin/index.d.ts +30 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +59 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/OrganizationDetail.d.ts +9 -0
- package/dist/admin/pages/OrganizationDetail.d.ts.map +1 -0
- package/dist/admin/pages/OrganizationDetail.js +247 -0
- package/dist/admin/pages/OrganizationDetail.js.map +1 -0
- package/dist/admin/pages/OrganizationsList.d.ts +9 -0
- package/dist/admin/pages/OrganizationsList.d.ts.map +1 -0
- package/dist/admin/pages/OrganizationsList.js +73 -0
- package/dist/admin/pages/OrganizationsList.js.map +1 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.d.ts +12 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.d.ts.map +1 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.js +49 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.js.map +1 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.d.ts +16 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.d.ts.map +1 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.js +29 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.js.map +1 -0
- package/dist/admin/panels/HierarchyPanel.d.ts +15 -0
- package/dist/admin/panels/HierarchyPanel.d.ts.map +1 -0
- package/dist/admin/panels/HierarchyPanel.js +65 -0
- package/dist/admin/panels/HierarchyPanel.js.map +1 -0
- package/dist/admin/panels/ModerationActionsPanel.d.ts +28 -0
- package/dist/admin/panels/ModerationActionsPanel.d.ts.map +1 -0
- package/dist/admin/panels/ModerationActionsPanel.js +111 -0
- package/dist/admin/panels/ModerationActionsPanel.js.map +1 -0
- package/dist/admin/panels/RestrictionsPanel.d.ts +24 -0
- package/dist/admin/panels/RestrictionsPanel.d.ts.map +1 -0
- package/dist/admin/panels/RestrictionsPanel.js +121 -0
- package/dist/admin/panels/RestrictionsPanel.js.map +1 -0
- package/dist/admin/panels/VatValidationPanel.d.ts +28 -0
- package/dist/admin/panels/VatValidationPanel.d.ts.map +1 -0
- package/dist/admin/panels/VatValidationPanel.js +79 -0
- package/dist/admin/panels/VatValidationPanel.js.map +1 -0
- package/dist/backend/commands/move.command.d.ts +11 -0
- package/dist/backend/commands/move.command.d.ts.map +1 -0
- package/dist/backend/commands/move.command.js +11 -0
- package/dist/backend/commands/move.command.js.map +1 -0
- package/dist/backend/commands/set-cart-approval-policy.command.d.ts +31 -0
- package/dist/backend/commands/set-cart-approval-policy.command.d.ts.map +1 -0
- package/dist/backend/commands/set-cart-approval-policy.command.js +56 -0
- package/dist/backend/commands/set-cart-approval-policy.command.js.map +1 -0
- package/dist/backend/commands/set-credit-mode.command.d.ts +16 -0
- package/dist/backend/commands/set-credit-mode.command.d.ts.map +1 -0
- package/dist/backend/commands/set-credit-mode.command.js +25 -0
- package/dist/backend/commands/set-credit-mode.command.js.map +1 -0
- package/dist/backend/commands/set-parent.command.d.ts +26 -0
- package/dist/backend/commands/set-parent.command.d.ts.map +1 -0
- package/dist/backend/commands/set-parent.command.js +51 -0
- package/dist/backend/commands/set-parent.command.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +20 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +11 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +44 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +44 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +20 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +28 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/email-templates/invitation.d.ts +23 -0
- package/dist/backend/email-templates/invitation.d.ts.map +1 -0
- package/dist/backend/email-templates/invitation.js +35 -0
- package/dist/backend/email-templates/invitation.js.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts +54 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.js +242 -0
- package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
- package/dist/backend/email-templates/verification.d.ts +14 -0
- package/dist/backend/email-templates/verification.d.ts.map +1 -0
- package/dist/backend/email-templates/verification.js +34 -0
- package/dist/backend/email-templates/verification.js.map +1 -0
- package/dist/backend/entities/email-verification-token.entity.d.ts +17 -0
- package/dist/backend/entities/email-verification-token.entity.d.ts.map +1 -0
- package/dist/backend/entities/email-verification-token.entity.js +60 -0
- package/dist/backend/entities/email-verification-token.entity.js.map +1 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.d.ts +14 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.js +41 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.js.map +1 -0
- package/dist/backend/entities/organization-invitation.entity.d.ts +21 -0
- package/dist/backend/entities/organization-invitation.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-invitation.entity.js +85 -0
- package/dist/backend/entities/organization-invitation.entity.js.map +1 -0
- package/dist/backend/entities/organization-payment-method-link.entity.d.ts +14 -0
- package/dist/backend/entities/organization-payment-method-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-payment-method-link.entity.js +41 -0
- package/dist/backend/entities/organization-payment-method-link.entity.js.map +1 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.d.ts +17 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.js +56 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.js.map +1 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.d.ts +29 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.js +78 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.js.map +1 -0
- package/dist/backend/entities/organization-warehouse-link.entity.d.ts +15 -0
- package/dist/backend/entities/organization-warehouse-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-warehouse-link.entity.js +42 -0
- package/dist/backend/entities/organization-warehouse-link.entity.js.map +1 -0
- package/dist/backend/entities/organization.entity.d.ts +125 -0
- package/dist/backend/entities/organization.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization.entity.js +282 -0
- package/dist/backend/entities/organization.entity.js.map +1 -0
- package/dist/backend/index.d.ts +250 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +506 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.d.ts +22 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.d.ts.map +1 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.js +172 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.js.map +1 -0
- package/dist/backend/integrations/vies-client.d.ts +18 -0
- package/dist/backend/integrations/vies-client.d.ts.map +1 -0
- package/dist/backend/integrations/vies-client.js +133 -0
- package/dist/backend/integrations/vies-client.js.map +1 -0
- package/dist/backend/plugin.d.ts +126 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +139 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +66 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +711 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.customer.d.ts +33 -0
- package/dist/backend/routes.customer.d.ts.map +1 -0
- package/dist/backend/routes.customer.js +139 -0
- package/dist/backend/routes.customer.js.map +1 -0
- package/dist/backend/routes.members.d.ts +31 -0
- package/dist/backend/routes.members.d.ts.map +1 -0
- package/dist/backend/routes.members.js +109 -0
- package/dist/backend/routes.members.js.map +1 -0
- package/dist/backend/routes.public.d.ts +49 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +220 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/routes.sales-reps.d.ts +46 -0
- package/dist/backend/routes.sales-reps.d.ts.map +1 -0
- package/dist/backend/routes.sales-reps.js +114 -0
- package/dist/backend/routes.sales-reps.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +25 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +65 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/schemas/organization.d.ts +59 -0
- package/dist/backend/schemas/organization.d.ts.map +1 -0
- package/dist/backend/schemas/organization.js +55 -0
- package/dist/backend/schemas/organization.js.map +1 -0
- package/dist/backend/schemas/settings.d.ts +30 -0
- package/dist/backend/schemas/settings.d.ts.map +1 -0
- package/dist/backend/schemas/settings.js +35 -0
- package/dist/backend/schemas/settings.js.map +1 -0
- package/dist/backend/services/email-verification-service.d.ts +45 -0
- package/dist/backend/services/email-verification-service.d.ts.map +1 -0
- package/dist/backend/services/email-verification-service.js +95 -0
- package/dist/backend/services/email-verification-service.js.map +1 -0
- package/dist/backend/services/invitation-service.d.ts +85 -0
- package/dist/backend/services/invitation-service.d.ts.map +1 -0
- package/dist/backend/services/invitation-service.js +266 -0
- package/dist/backend/services/invitation-service.js.map +1 -0
- package/dist/backend/services/normalize-name.d.ts +14 -0
- package/dist/backend/services/normalize-name.d.ts.map +1 -0
- package/dist/backend/services/normalize-name.js +14 -0
- package/dist/backend/services/normalize-name.js.map +1 -0
- package/dist/backend/services/org-registration-notifier.d.ts +36 -0
- package/dist/backend/services/org-registration-notifier.d.ts.map +1 -0
- package/dist/backend/services/org-registration-notifier.js +109 -0
- package/dist/backend/services/org-registration-notifier.js.map +1 -0
- package/dist/backend/services/org-template-email.d.ts +18 -0
- package/dist/backend/services/org-template-email.d.ts.map +1 -0
- package/dist/backend/services/org-template-email.js +16 -0
- package/dist/backend/services/org-template-email.js.map +1 -0
- package/dist/backend/services/organization-context-service.d.ts +52 -0
- package/dist/backend/services/organization-context-service.d.ts.map +1 -0
- package/dist/backend/services/organization-context-service.js +70 -0
- package/dist/backend/services/organization-context-service.js.map +1 -0
- package/dist/backend/services/organization-country-reference.d.ts +11 -0
- package/dist/backend/services/organization-country-reference.d.ts.map +1 -0
- package/dist/backend/services/organization-country-reference.js +24 -0
- package/dist/backend/services/organization-country-reference.js.map +1 -0
- package/dist/backend/services/organization-details-port.d.ts +33 -0
- package/dist/backend/services/organization-details-port.d.ts.map +1 -0
- package/dist/backend/services/organization-details-port.js +95 -0
- package/dist/backend/services/organization-details-port.js.map +1 -0
- package/dist/backend/services/organization-effective-pricelists-service.d.ts +50 -0
- package/dist/backend/services/organization-effective-pricelists-service.d.ts.map +1 -0
- package/dist/backend/services/organization-effective-pricelists-service.js +68 -0
- package/dist/backend/services/organization-effective-pricelists-service.js.map +1 -0
- package/dist/backend/services/organization-inheritance-service.d.ts +73 -0
- package/dist/backend/services/organization-inheritance-service.d.ts.map +1 -0
- package/dist/backend/services/organization-inheritance-service.js +63 -0
- package/dist/backend/services/organization-inheritance-service.js.map +1 -0
- package/dist/backend/services/organization-moderation-service.d.ts +124 -0
- package/dist/backend/services/organization-moderation-service.d.ts.map +1 -0
- package/dist/backend/services/organization-moderation-service.js +337 -0
- package/dist/backend/services/organization-moderation-service.js.map +1 -0
- package/dist/backend/services/organization-restriction-service.d.ts +70 -0
- package/dist/backend/services/organization-restriction-service.d.ts.map +1 -0
- package/dist/backend/services/organization-restriction-service.js +210 -0
- package/dist/backend/services/organization-restriction-service.js.map +1 -0
- package/dist/backend/services/organization-tax-id-validation-service.d.ts +59 -0
- package/dist/backend/services/organization-tax-id-validation-service.d.ts.map +1 -0
- package/dist/backend/services/organization-tax-id-validation-service.js +115 -0
- package/dist/backend/services/organization-tax-id-validation-service.js.map +1 -0
- package/dist/backend/services/organization-tree-service.d.ts +102 -0
- package/dist/backend/services/organization-tree-service.d.ts.map +1 -0
- package/dist/backend/services/organization-tree-service.js +184 -0
- package/dist/backend/services/organization-tree-service.js.map +1 -0
- package/dist/backend/services/personal-organization-service.d.ts +130 -0
- package/dist/backend/services/personal-organization-service.d.ts.map +1 -0
- package/dist/backend/services/personal-organization-service.js +207 -0
- package/dist/backend/services/personal-organization-service.js.map +1 -0
- package/dist/backend/services/registration-service.d.ts +80 -0
- package/dist/backend/services/registration-service.d.ts.map +1 -0
- package/dist/backend/services/registration-service.js +126 -0
- package/dist/backend/services/registration-service.js.map +1 -0
- package/dist/backend/services/sales-rep-assignment-service.d.ts +89 -0
- package/dist/backend/services/sales-rep-assignment-service.d.ts.map +1 -0
- package/dist/backend/services/sales-rep-assignment-service.js +176 -0
- package/dist/backend/services/sales-rep-assignment-service.js.map +1 -0
- package/dist/backend/services/vat-validator-port.d.ts +20 -0
- package/dist/backend/services/vat-validator-port.d.ts.map +1 -0
- package/dist/backend/services/vat-validator-port.js +2 -0
- package/dist/backend/services/vat-validator-port.js.map +1 -0
- package/dist/manifest.d.ts +176 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +503 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260424T205317_organizations_init.d.ts +13 -0
- package/dist/migrations/20260424T205317_organizations_init.d.ts.map +1 -0
- package/dist/migrations/20260424T205317_organizations_init.js +100 -0
- package/dist/migrations/20260424T205317_organizations_init.js.map +1 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.d.ts +13 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.d.ts.map +1 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.js +39 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.js.map +1 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.d.ts +35 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.d.ts.map +1 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.js +180 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.js.map +1 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.d.ts +10 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.d.ts.map +1 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.js +14 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.js.map +1 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.d.ts +14 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.d.ts.map +1 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.js +20 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.js.map +1 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.d.ts +24 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.d.ts.map +1 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.js +63 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.js.map +1 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.d.ts +12 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.d.ts.map +1 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.js +16 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.js.map +1 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.d.ts +23 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.d.ts.map +1 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.js +37 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.js.map +1 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.d.ts +27 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.d.ts.map +1 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.js +44 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.js.map +1 -0
- package/dist/migrations/index.d.ts +35 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +45 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +53 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/organization-hierarchy.md +117 -0
- package/docs/organizations.md +312 -0
- package/i18n/en.json +17 -0
- package/i18n/pl.json +17 -0
- package/package.json +107 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* The `sales_channel_organizations` sales-channel bridge — the organizations a channel is visible to.
|
|
4
|
+
*
|
|
5
|
+
* It was created by the platform's frozen `Migration20260430T170044CoreSalesChannelsPromote`
|
|
6
|
+
* until `specs/120-migration-closure-bridge-ownership/` Phase 2. Under D-226 a
|
|
7
|
+
* bridge between an always-present near side (`sales_channels`, a kernel table)
|
|
8
|
+
* and a switchable far side belongs to the far side, so this module creates it:
|
|
9
|
+
* an instance that does not install `organizations` no longer has a migration corpus
|
|
10
|
+
* naming a table nothing builds.
|
|
11
|
+
*
|
|
12
|
+
* `if not exists`, because every database that has already applied the frozen
|
|
13
|
+
* migration has this table. The storage keys on the class name and holds no
|
|
14
|
+
* checksum, so nothing is re-offered there and this migration is the no-op it
|
|
15
|
+
* reads as; on a fresh database it is the creation.
|
|
16
|
+
*
|
|
17
|
+
* The statements are the frozen ones verbatim — same columns, same primary key,
|
|
18
|
+
* same two foreign keys, same index — so the two paths reach one schema. Its
|
|
19
|
+
* position needs nothing declared: `sales_channels` is the kernel's and
|
|
20
|
+
* `organizations` is created inside the frozen prefix, which every
|
|
21
|
+
* above-watermark migration runs after.
|
|
22
|
+
*/
|
|
23
|
+
export class Migration20260912T094646OrganizationsSalesChannelOrganizations extends Migration {
|
|
24
|
+
async up() {
|
|
25
|
+
this.addSql(`
|
|
26
|
+
create table if not exists "sales_channel_organizations" (
|
|
27
|
+
"sales_channel_id" uuid not null,
|
|
28
|
+
"organization_id" uuid not null,
|
|
29
|
+
constraint "sales_channel_organizations_pkey"
|
|
30
|
+
primary key ("sales_channel_id", "organization_id"),
|
|
31
|
+
constraint "sales_channel_organizations_channel_fk"
|
|
32
|
+
foreign key ("sales_channel_id") references "sales_channels" ("id") on delete cascade,
|
|
33
|
+
constraint "sales_channel_organizations_organization_fk"
|
|
34
|
+
foreign key ("organization_id") references "organizations" ("id") on delete cascade
|
|
35
|
+
);
|
|
36
|
+
`);
|
|
37
|
+
this.addSql('create index if not exists "sales_channel_organizations_organization_id_index" ' +
|
|
38
|
+
'on "sales_channel_organizations" ("organization_id");');
|
|
39
|
+
}
|
|
40
|
+
async down() {
|
|
41
|
+
this.addSql('drop table if exists "sales_channel_organizations" cascade;');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=20260912T094646_organizations_sales_channel_organizations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260912T094646_organizations_sales_channel_organizations.js","sourceRoot":"","sources":["../../src/migrations/20260912T094646_organizations_sales_channel_organizations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,8DAA+D,SAAQ,SAAS;IAClF,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;KAWX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,iFAAiF;YAC/E,uDAAuD,CAC1D,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,6DAA6D,CAAC,CAAC;IAC7E,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which orders **this module's own** migrations
|
|
10
|
+
* and nothing else (feature 081). Where the block sits relative to every other
|
|
11
|
+
* module's is decided by the manifest `dependencies` graph.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay, and the asymmetry with `./backend` — which
|
|
14
|
+
* publishes an array and no entity class by name (D-168) — is deliberate.
|
|
15
|
+
* `db/migrations-registry.generated.ts` imports each class by name from this
|
|
16
|
+
* specifier, and a migration class name is contract in a way an entity class
|
|
17
|
+
* name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom
|
|
22
|
+
* is a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260424T205317OrganizationsInit } from './20260424T205317_organizations_init.js';
|
|
25
|
+
import { Migration20260425T051700OrganizationsInvitationsInit } from './20260425T051700_organizations_invitations_init.js';
|
|
26
|
+
import { Migration20260611T140349OrganizationsConsolidation } from './20260611T140349_organizations_consolidation.js';
|
|
27
|
+
import { Migration20260611T140358OrganizationsOrgOrderConfirmationEmails } from './20260611T140358_organizations_org_order_confirmation_emails.js';
|
|
28
|
+
import { Migration20260611T140402OrganizationsOrgFulfilmentStrategy } from './20260611T140402_organizations_org_fulfilment_strategy.js';
|
|
29
|
+
import { Migration20260717T151403OrganizationsPersonalOrganizations } from './20260717T151403_organizations_personal_organizations.js';
|
|
30
|
+
import { Migration20260718T200340OrganizationsOrganizationCustomFieldValues } from './20260718T200340_organizations_organization_custom_field_values.js';
|
|
31
|
+
import { Migration20260720T044254OrganizationsOrgHierarchy } from './20260720T044254_organizations_org_hierarchy.js';
|
|
32
|
+
import { Migration20260912T094646OrganizationsSalesChannelOrganizations } from './20260912T094646_organizations_sales_channel_organizations.js';
|
|
33
|
+
export declare const migrations: (typeof Migration20260424T205317OrganizationsInit)[];
|
|
34
|
+
export { Migration20260424T205317OrganizationsInit, Migration20260425T051700OrganizationsInvitationsInit, Migration20260611T140349OrganizationsConsolidation, Migration20260611T140358OrganizationsOrgOrderConfirmationEmails, Migration20260611T140402OrganizationsOrgFulfilmentStrategy, Migration20260717T151403OrganizationsPersonalOrganizations, Migration20260718T200340OrganizationsOrganizationCustomFieldValues, Migration20260720T044254OrganizationsOrgHierarchy, Migration20260912T094646OrganizationsSalesChannelOrganizations, };
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yCAAyC,EAAE,MAAM,yCAAyC,CAAC;AACpG,OAAO,EAAE,oDAAoD,EAAE,MAAM,qDAAqD,CAAC;AAC3H,OAAO,EAAE,kDAAkD,EAAE,MAAM,kDAAkD,CAAC;AACtH,OAAO,EAAE,+DAA+D,EAAE,MAAM,kEAAkE,CAAC;AACnJ,OAAO,EAAE,0DAA0D,EAAE,MAAM,4DAA4D,CAAC;AACxI,OAAO,EAAE,0DAA0D,EAAE,MAAM,2DAA2D,CAAC;AACvI,OAAO,EAAE,kEAAkE,EAAE,MAAM,qEAAqE,CAAC;AACzJ,OAAO,EAAE,iDAAiD,EAAE,MAAM,kDAAkD,CAAC;AACrH,OAAO,EAAE,8DAA8D,EAAE,MAAM,gEAAgE,CAAC;AAEhJ,eAAO,MAAM,UAAU,sDAUtB,CAAC;AAEF,OAAO,EACL,yCAAyC,EACzC,oDAAoD,EACpD,kDAAkD,EAClD,+DAA+D,EAC/D,0DAA0D,EAC1D,0DAA0D,EAC1D,kEAAkE,EAClE,iDAAiD,EACjD,8DAA8D,GAC/D,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which orders **this module's own** migrations
|
|
10
|
+
* and nothing else (feature 081). Where the block sits relative to every other
|
|
11
|
+
* module's is decided by the manifest `dependencies` graph.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay, and the asymmetry with `./backend` — which
|
|
14
|
+
* publishes an array and no entity class by name (D-168) — is deliberate.
|
|
15
|
+
* `db/migrations-registry.generated.ts` imports each class by name from this
|
|
16
|
+
* specifier, and a migration class name is contract in a way an entity class
|
|
17
|
+
* name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom
|
|
22
|
+
* is a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260424T205317OrganizationsInit } from './20260424T205317_organizations_init.js';
|
|
25
|
+
import { Migration20260425T051700OrganizationsInvitationsInit } from './20260425T051700_organizations_invitations_init.js';
|
|
26
|
+
import { Migration20260611T140349OrganizationsConsolidation } from './20260611T140349_organizations_consolidation.js';
|
|
27
|
+
import { Migration20260611T140358OrganizationsOrgOrderConfirmationEmails } from './20260611T140358_organizations_org_order_confirmation_emails.js';
|
|
28
|
+
import { Migration20260611T140402OrganizationsOrgFulfilmentStrategy } from './20260611T140402_organizations_org_fulfilment_strategy.js';
|
|
29
|
+
import { Migration20260717T151403OrganizationsPersonalOrganizations } from './20260717T151403_organizations_personal_organizations.js';
|
|
30
|
+
import { Migration20260718T200340OrganizationsOrganizationCustomFieldValues } from './20260718T200340_organizations_organization_custom_field_values.js';
|
|
31
|
+
import { Migration20260720T044254OrganizationsOrgHierarchy } from './20260720T044254_organizations_org_hierarchy.js';
|
|
32
|
+
import { Migration20260912T094646OrganizationsSalesChannelOrganizations } from './20260912T094646_organizations_sales_channel_organizations.js';
|
|
33
|
+
export const migrations = [
|
|
34
|
+
Migration20260424T205317OrganizationsInit,
|
|
35
|
+
Migration20260425T051700OrganizationsInvitationsInit,
|
|
36
|
+
Migration20260611T140349OrganizationsConsolidation,
|
|
37
|
+
Migration20260611T140358OrganizationsOrgOrderConfirmationEmails,
|
|
38
|
+
Migration20260611T140402OrganizationsOrgFulfilmentStrategy,
|
|
39
|
+
Migration20260717T151403OrganizationsPersonalOrganizations,
|
|
40
|
+
Migration20260718T200340OrganizationsOrganizationCustomFieldValues,
|
|
41
|
+
Migration20260720T044254OrganizationsOrgHierarchy,
|
|
42
|
+
Migration20260912T094646OrganizationsSalesChannelOrganizations,
|
|
43
|
+
];
|
|
44
|
+
export { Migration20260424T205317OrganizationsInit, Migration20260425T051700OrganizationsInvitationsInit, Migration20260611T140349OrganizationsConsolidation, Migration20260611T140358OrganizationsOrgOrderConfirmationEmails, Migration20260611T140402OrganizationsOrgFulfilmentStrategy, Migration20260717T151403OrganizationsPersonalOrganizations, Migration20260718T200340OrganizationsOrganizationCustomFieldValues, Migration20260720T044254OrganizationsOrgHierarchy, Migration20260912T094646OrganizationsSalesChannelOrganizations, };
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yCAAyC,EAAE,MAAM,yCAAyC,CAAC;AACpG,OAAO,EAAE,oDAAoD,EAAE,MAAM,qDAAqD,CAAC;AAC3H,OAAO,EAAE,kDAAkD,EAAE,MAAM,kDAAkD,CAAC;AACtH,OAAO,EAAE,+DAA+D,EAAE,MAAM,kEAAkE,CAAC;AACnJ,OAAO,EAAE,0DAA0D,EAAE,MAAM,4DAA4D,CAAC;AACxI,OAAO,EAAE,0DAA0D,EAAE,MAAM,2DAA2D,CAAC;AACvI,OAAO,EAAE,kEAAkE,EAAE,MAAM,qEAAqE,CAAC;AACzJ,OAAO,EAAE,iDAAiD,EAAE,MAAM,kDAAkD,CAAC;AACrH,OAAO,EAAE,8DAA8D,EAAE,MAAM,gEAAgE,CAAC;AAEhJ,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,yCAAyC;IACzC,oDAAoD;IACpD,kDAAkD;IAClD,+DAA+D;IAC/D,0DAA0D;IAC1D,0DAA0D;IAC1D,kEAAkE;IAClE,iDAAiD;IACjD,8DAA8D;CAC/D,CAAC;AAEF,OAAO,EACL,yCAAyC,EACzC,oDAAoD,EACpD,kDAAkD,EAClD,+DAA+D,EAC/D,0DAA0D,EAC1D,0DAA0D,EAC1D,kEAAkE,EAClE,iDAAiD,EACjD,8DAA8D,GAC/D,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
/**
|
|
3
|
+
* The one co-transactional seam this module publishes (D-178, under D-78
|
|
4
|
+
* point 2).
|
|
5
|
+
*
|
|
6
|
+
* It is declared here rather than in `@endora-commerce/contracts` for the same
|
|
7
|
+
* reason `credit_limits`' `CreditLimitPort` and `promotions`'
|
|
8
|
+
* `PromotionUsageFinalizer` are: its signature carries the caller's MikroORM
|
|
9
|
+
* `EntityManager`, and FR-034 keeps a MikroORM type out of that package. A
|
|
10
|
+
* branded stand-in would publish the coupling instead of removing it, which
|
|
11
|
+
* D-78 refuses in writing.
|
|
12
|
+
*
|
|
13
|
+
* **What holds it co-transactional is a foreign key, not a convention.**
|
|
14
|
+
* `customer_accounts_organization_fk` (`on delete restrict`) points
|
|
15
|
+
* `customer_accounts.organization_id` at `organizations.id`, and since D-178
|
|
16
|
+
* that column is `NOT NULL`. So the account row cannot be inserted before the
|
|
17
|
+
* organisation exists, and a second transaction cannot satisfy a foreign key
|
|
18
|
+
* against a row it cannot see. The two writes are one unit of work or the
|
|
19
|
+
* invariant has a window in it — which is precisely the window D-178 was taken
|
|
20
|
+
* to close: `createStandalone` used to commit the account with a null
|
|
21
|
+
* organisation and provision the organisation afterwards, and nothing retried
|
|
22
|
+
* the second half.
|
|
23
|
+
*
|
|
24
|
+
* `customer_accounts` is the caller and it already declares `organizations` in
|
|
25
|
+
* its manifest `dependencies` — the tenancy direction feature 051 established —
|
|
26
|
+
* so the edge this seam needs is the one the foreign key already required.
|
|
27
|
+
*/
|
|
28
|
+
export interface PersonalOrganizationProvisionInput {
|
|
29
|
+
/**
|
|
30
|
+
* The id the account **will** carry. It is generated by the caller before the
|
|
31
|
+
* account row exists, because the personal organisation's `taxId` is derived
|
|
32
|
+
* from it and that derivation is what makes the provisioning re-runnable.
|
|
33
|
+
*/
|
|
34
|
+
readonly customerAccountId: string;
|
|
35
|
+
readonly email: string;
|
|
36
|
+
readonly firstName?: string | null;
|
|
37
|
+
readonly lastName?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface PersonalOrganizationProvisionApi {
|
|
40
|
+
/**
|
|
41
|
+
* Find-or-create the personal organisation for an account that is about to be
|
|
42
|
+
* written, on the caller's `EntityManager` and inside the caller's
|
|
43
|
+
* transaction. Writes nothing on `customer_accounts`: the membership column is
|
|
44
|
+
* the caller's own, and it sets it on the row it is inserting.
|
|
45
|
+
*
|
|
46
|
+
* Idempotent by `taxId`, so a retry after a rolled-back attempt re-finds
|
|
47
|
+
* rather than duplicating.
|
|
48
|
+
*/
|
|
49
|
+
provisionFor(em: EntityManager, input: PersonalOrganizationProvisionInput): Promise<{
|
|
50
|
+
id: string;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,kCAAkC;IACjD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,gCAAgC;IAC/C;;;;;;;;OAQG;IACH,YAAY,CACV,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,kCAAkC,GACxC,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Organization hierarchy
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Organization hierarchy
|
|
6
|
+
|
|
7
|
+
Organizations can be nested into a **tree**: a head-office organization owns
|
|
8
|
+
branch organizations, which may own sub-branches. Two things flow along the
|
|
9
|
+
tree — **scope** (a roll-up-enabled parent-org user or scoped sales-rep sees the
|
|
10
|
+
whole subtree) and **commercial terms** (a descendant with no own price list or
|
|
11
|
+
credit limit resolves the nearest ancestor's).
|
|
12
|
+
|
|
13
|
+
This is an additive layer over the flat organization model. Every organization
|
|
14
|
+
starts as a **root** (no parent), and a flat installation behaves byte-for-byte
|
|
15
|
+
as it did before the hierarchy existed.
|
|
16
|
+
|
|
17
|
+
## The tree
|
|
18
|
+
|
|
19
|
+
Each organization records a nullable self-referential `parent_id` (NULL ⇒ root)
|
|
20
|
+
plus a server-maintained materialized `path` (`/<rootId>/…/<thisId>/`). A single
|
|
21
|
+
indexed prefix scan answers "descendants of X" and "ancestors of X" — there is
|
|
22
|
+
no per-node walk.
|
|
23
|
+
|
|
24
|
+
- **Assign / move a parent** — `POST /api/v1/admin/organizations/:id/parent`
|
|
25
|
+
with `{ "parentId": "<uuid>" | null }`. `null` detaches the organization back
|
|
26
|
+
to a root. The move is validated against **cycles** (a node may not become a
|
|
27
|
+
child of its own descendant) and a **maximum depth of 10 levels**, and is
|
|
28
|
+
audited through the Command Bus (`organization.set_parent` / `organization.move`,
|
|
29
|
+
both reversible).
|
|
30
|
+
- **Read the subtree** — `GET /api/v1/admin/organizations/:id/subtree` returns
|
|
31
|
+
the descendants (including the node itself) pre-order, each as
|
|
32
|
+
`{ id, name, parentId, depth, status }`.
|
|
33
|
+
- **Read the ancestor chain** — `GET /api/v1/admin/organizations/:id/ancestors`
|
|
34
|
+
returns the parent → … → root chain, nearest-first.
|
|
35
|
+
- **Deletion is blocked** — an organization with any child cannot be deleted
|
|
36
|
+
(`409 has_children`), backed by the `parent_id … ON DELETE RESTRICT` foreign
|
|
37
|
+
key. Reassign or remove the children first.
|
|
38
|
+
|
|
39
|
+
Re-parenting is **retroactive**: subtree membership, roll-up visibility, and
|
|
40
|
+
term inheritance are always computed against the *current* tree, so after a move
|
|
41
|
+
a branch's history becomes visible to the new parent's roll-up users and it
|
|
42
|
+
inherits the new parent's terms.
|
|
43
|
+
|
|
44
|
+
The admin UI exposes a parent picker plus a subtree/ancestor view on the
|
|
45
|
+
organization detail page.
|
|
46
|
+
|
|
47
|
+
## Roll-up scope
|
|
48
|
+
|
|
49
|
+
Visibility across descendants is **permission-gated** by the
|
|
50
|
+
`organizations:rollup` capability ("Act across organization descendants").
|
|
51
|
+
|
|
52
|
+
- A **scoped sales-rep** holding the capability has each assignment expanded to
|
|
53
|
+
the assigned node's subtree. A descendant that carries its **own** assignment
|
|
54
|
+
overrides the inherited one for its subtree — the **nearest assignment on the
|
|
55
|
+
ancestor chain wins**. Without the capability the rep stays confined to the
|
|
56
|
+
organizations directly assigned to them.
|
|
57
|
+
- A branch-only user (no roll-up) sees only their own organization.
|
|
58
|
+
|
|
59
|
+
Expansion is derived **server-side** and widens the existing tenant-scope guard
|
|
60
|
+
(`allowedOrganizationIds`) to exactly the subtree — never a sibling, cousin, or
|
|
61
|
+
an ancestor outside the granted node. An out-of-subtree record is
|
|
62
|
+
indistinguishable from "does not exist". The three admin read surfaces — orders,
|
|
63
|
+
quotes, and customers — read the already-widened scope and need no change of
|
|
64
|
+
their own.
|
|
65
|
+
|
|
66
|
+
## Term inheritance
|
|
67
|
+
|
|
68
|
+
A descendant with no own commercial term resolves the nearest ancestor's.
|
|
69
|
+
|
|
70
|
+
### Price lists
|
|
71
|
+
|
|
72
|
+
Price lists target organizations through their application rule (there is no
|
|
73
|
+
organization foreign key). The resolver builds the org candidate set as
|
|
74
|
+
`[thisOrg, …ancestors]` (nearest-first) and keeps the existing priority order
|
|
75
|
+
(`organization` > `customerGroup` > `category` > `salesChannel`):
|
|
76
|
+
|
|
77
|
+
- a list naming a **nearer** organization outranks one naming a farther
|
|
78
|
+
ancestor, so a **branch override always wins**;
|
|
79
|
+
- an inherited ancestor org-named list still outranks a customer-group,
|
|
80
|
+
category, or sales-channel list;
|
|
81
|
+
- a branch with no own list falls through to the nearest ancestor's org-named
|
|
82
|
+
list before dropping to the non-org priority levels.
|
|
83
|
+
|
|
84
|
+
Only that branch diverges — siblings are unaffected. Resolution stays a single
|
|
85
|
+
pass (no per-ancestor re-run).
|
|
86
|
+
|
|
87
|
+
### Credit limits
|
|
88
|
+
|
|
89
|
+
A descendant with no own credit limit transacts against the **nearest
|
|
90
|
+
ancestor** that has one. How the ancestor's limit is consumed is a
|
|
91
|
+
**per-organization mode**, set only by a platform administrator:
|
|
92
|
+
|
|
93
|
+
- **`shared_pool`** — every organization in the subtree draws against one shared
|
|
94
|
+
pool on the owning ancestor's row. Concurrent draws serialize on that row, so
|
|
95
|
+
the pool can never be over-spent (no double-spend).
|
|
96
|
+
- **`independent_default`** — the inherited amount is each branch's own limit;
|
|
97
|
+
every branch may draw the full inherited amount independently of its siblings.
|
|
98
|
+
|
|
99
|
+
A branch with its **own** credit limit overrides the inherited one.
|
|
100
|
+
|
|
101
|
+
#### The credit-inheritance-mode setting
|
|
102
|
+
|
|
103
|
+
The factory default is the platform-wide Settings value
|
|
104
|
+
`organizations.hierarchy.credit_inheritance_mode` (default **`shared_pool`**). A
|
|
105
|
+
per-organization override is stored on the organization and set via
|
|
106
|
+
`PUT /api/v1/admin/organizations/:id/credit-inheritance-mode` with
|
|
107
|
+
`{ "mode": "shared_pool" | "independent_default" | null }` (`null` falls back to
|
|
108
|
+
the Settings default). This endpoint is **platform-admin only** — a scoped or
|
|
109
|
+
roll-up actor is rejected with `403` — and the change is audited
|
|
110
|
+
(`organization.set_credit_mode`).
|
|
111
|
+
|
|
112
|
+
## Flat-behavior guarantee
|
|
113
|
+
|
|
114
|
+
For a root organization the subtree is `{itself}`, the price-list org chain is
|
|
115
|
+
`[itself]`, and the credit owner is the organization itself (or none). All
|
|
116
|
+
scope, pricing, and credit resolution therefore collapse to the pre-feature
|
|
117
|
+
single-organization behavior, byte-for-byte.
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: organizations
|
|
3
|
+
description: Customer Organizations, registration, invitations
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `organizations`
|
|
7
|
+
|
|
8
|
+
Customer Organizations — registration, email verification, member management,
|
|
9
|
+
invitations, and the suspension state. The first user of a registering
|
|
10
|
+
Organization becomes its `organization_admin`.
|
|
11
|
+
|
|
12
|
+
## Public surface
|
|
13
|
+
|
|
14
|
+
Org-Admin-only routes are enforced server-side via the
|
|
15
|
+
`assertOrganizationAdmin` helper. Admin routes (`/api/v1/admin/*`) are
|
|
16
|
+
gated by `customers:manage`.
|
|
17
|
+
|
|
18
|
+
| Verb + Path | Audience | Purpose |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `POST /api/v1/organizations/register` | anon | Register Org + first member, email verification dispatched |
|
|
21
|
+
| `POST /api/v1/auth/email-verification/verify` | anon | Redeem verification token |
|
|
22
|
+
| `POST /api/v1/auth/customer/login` | anon | Customer login → sets `b2b_session` cookie; merges anonymous cart |
|
|
23
|
+
| `POST /api/v1/auth/customer/logout` | customer | Destroy session |
|
|
24
|
+
| `POST /api/v1/auth/password-reset/request` | anon | Always 202 (defends against account enumeration) |
|
|
25
|
+
| `POST /api/v1/auth/password-reset/confirm` | anon | Redeem the emailed reset token |
|
|
26
|
+
| `GET /api/v1/me` | customer | Current customer + their organization, plus `impersonation: { impersonatorAdminUserId }` when an admin is acting as the buyer |
|
|
27
|
+
| `POST /api/v1/me/password` | customer | Change password (rejects wrong `currentPassword`) |
|
|
28
|
+
| `GET /api/v1/organizations/mine/members` | org admin | List members |
|
|
29
|
+
| `DELETE /api/v1/organizations/mine/members/:id` | org admin | Remove member (last-admin guard) |
|
|
30
|
+
| `PATCH /api/v1/organizations/mine/members/:id/role` | org admin | Promote / demote (last-admin guard) |
|
|
31
|
+
| `GET /api/v1/organizations/mine/invitations` | org admin | List pending invitations |
|
|
32
|
+
| `POST /api/v1/organizations/mine/invitations` | org admin | Invite a new user; emails the redemption link via the injected Mailer |
|
|
33
|
+
| `DELETE /api/v1/organizations/mine/invitations/:id` | org admin | Revoke a pending invitation |
|
|
34
|
+
| `POST /api/v1/organizations/invitations/:token/accept` | anon | Redeem invitation, mint Customer Account |
|
|
35
|
+
| `GET /api/v1/organizations/mine/addresses` | customer | List delivery / billing addresses |
|
|
36
|
+
| `POST /api/v1/organizations/mine/addresses` | customer | Create address |
|
|
37
|
+
| `PATCH /api/v1/organizations/mine/addresses/:id` | customer | Update |
|
|
38
|
+
| `DELETE /api/v1/organizations/mine/addresses/:id` | customer | Remove |
|
|
39
|
+
| `GET /api/v1/admin/organizations` | admin | List with `filter[status]` / `filter[vatStatus]` / `q` |
|
|
40
|
+
| `GET /api/v1/admin/organizations/:id` | admin | Org + member roster (`updatedAt`, members include `lastLoginAt`) |
|
|
41
|
+
| `PATCH /api/v1/admin/organizations/:id` | admin | Update name / `status` / `vatStatus`; optional `expectedUpdatedAt` → `409 VERSION_CONFLICT` when stale |
|
|
42
|
+
| `POST /api/v1/admin/organizations/:id/members/invite` | admin | Invite by email + role (platform-scope) |
|
|
43
|
+
| `POST /api/v1/admin/organizations/:id/members` | admin | Direct-create member with password |
|
|
44
|
+
| `PATCH /api/v1/admin/organizations/:id/members/:customerAccountId/role` | admin | Change role; optional `expectedUpdatedAt` per member |
|
|
45
|
+
| `DELETE /api/v1/admin/organizations/:id/members/:customerAccountId` | admin | Soft-remove member (last-admin guard) |
|
|
46
|
+
| `POST /api/v1/admin/organizations/:id/recover-admin-access` | admin | Break-glass — promote existing member to `organization_admin` |
|
|
47
|
+
|
|
48
|
+
Configure **`SMTP_URL`** in the backend environment so outbound mail uses SMTP instead of the console logger.
|
|
49
|
+
|
|
50
|
+
## Entities
|
|
51
|
+
|
|
52
|
+
`Organization`, `OrganizationInvitation`, `EmailVerificationToken`. Tax-ID
|
|
53
|
+
uniqueness is enforced at the DB level; duplicate registrations return
|
|
54
|
+
`409 ORGANIZATION_TAX_ID_EXISTS`.
|
|
55
|
+
|
|
56
|
+
## Events emitted
|
|
57
|
+
|
|
58
|
+
`organization.registered.v1`, `organization.verified.v1`,
|
|
59
|
+
`organization.suspended.v1`, `organization.member_invited.v1`,
|
|
60
|
+
`organization.member_role_changed.v1`.
|
|
61
|
+
|
|
62
|
+
## Extension points
|
|
63
|
+
|
|
64
|
+
- **Verification dispatch** — `email-verification-service.ts` exposes a
|
|
65
|
+
pluggable mailer interface; swap the dev-mode console mailer for a real
|
|
66
|
+
SMTP/SendGrid driver in production composition.
|
|
67
|
+
- **Last-admin guard** — codified in `role-service.ts#changeRole` and
|
|
68
|
+
`invitation-service.ts#revoke`; add new "must keep at least one admin"
|
|
69
|
+
call sites here.
|
|
70
|
+
|
|
71
|
+
## Commercial party and moderation
|
|
72
|
+
|
|
73
|
+
Organization is a first-class commercial party. This section describes
|
|
74
|
+
the runtime surface.
|
|
75
|
+
|
|
76
|
+
### Lifecycle status (`pending_verification` → `active` → `blocked` / `rejected`)
|
|
77
|
+
|
|
78
|
+
Every newly-registered Organization starts in `pending_verification`. The
|
|
79
|
+
platform-wide setting `organizations.moderation.mode` (`manual` /
|
|
80
|
+
`auto`) controls whether an admin must approve manually before the
|
|
81
|
+
Organization can transact. While the status is anything other than
|
|
82
|
+
`active`, the platform refuses Order placement, RFQ submission, and
|
|
83
|
+
cart-line addition with HTTP 423.
|
|
84
|
+
|
|
85
|
+
The legacy `suspended` status was renamed to `blocked` by migration 047
|
|
86
|
+
with an audit-log breadcrumb on every remapped row.
|
|
87
|
+
|
|
88
|
+
Admin endpoints:
|
|
89
|
+
|
|
90
|
+
| Verb + Path | Purpose |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `POST /api/v1/admin/organizations/:id/approve` | Transition `pending_verification` → `active` |
|
|
93
|
+
| `POST /api/v1/admin/organizations/:id/reject` | Transition `pending_verification` → `rejected` (terminal) |
|
|
94
|
+
| `POST /api/v1/admin/organizations/:id/block` | Transition `active` → `blocked` (operator lever) |
|
|
95
|
+
| `POST /api/v1/admin/organizations/:id/unblock` | Transition `blocked` → `active` |
|
|
96
|
+
|
|
97
|
+
Every body carries `expectedVersion: number` (optimistic-lock token from
|
|
98
|
+
the `organizations.version` column) and is wrapped in
|
|
99
|
+
`em.transactional`. A stale `expectedVersion` returns `409
|
|
100
|
+
VERSION_CONFLICT` with the `currentVersion` in the body. A status guard
|
|
101
|
+
violation (e.g. approving an already-active org) returns `422
|
|
102
|
+
VALIDATION_FAILED`.
|
|
103
|
+
|
|
104
|
+
Customer-side gate: the storefront receives the localized
|
|
105
|
+
"why-you-can't-transact" message via `GET /api/v1/me`'s
|
|
106
|
+
`organization.canTransact` + `organization.moderationMessage` fields.
|
|
107
|
+
The cart and checkout pages render `<OrganizationModerationBanner>`
|
|
108
|
+
above the form when `canTransact === false`.
|
|
109
|
+
|
|
110
|
+
### Admin notifications
|
|
111
|
+
|
|
112
|
+
A small `admin_notifications` module owns the bell surface. On every
|
|
113
|
+
new Organization registration, `OrgRegistrationNotifier` writes one
|
|
114
|
+
broadcast notification (`audience='all_admins'`,
|
|
115
|
+
`kind='organization.registered'`) and dispatches one e-mail per entry
|
|
116
|
+
in the `organizations.notifications.new_registration_recipients`
|
|
117
|
+
setting. The bell polls every 30 s via
|
|
118
|
+
`GET /api/v1/admin/notifications`.
|
|
119
|
+
|
|
120
|
+
| Verb + Path | Purpose |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `GET /api/v1/admin/notifications` | Paged feed, per-admin `isRead` resolution |
|
|
123
|
+
| `POST /api/v1/admin/notifications/:id/read` | Mark one entry read |
|
|
124
|
+
| `POST /api/v1/admin/notifications/mark-all-read` | Mark every visible entry read |
|
|
125
|
+
|
|
126
|
+
### Per-organization commercial scoping
|
|
127
|
+
|
|
128
|
+
Three allow-list bridges control what an Organization may use at
|
|
129
|
+
checkout:
|
|
130
|
+
|
|
131
|
+
- `organization_payment_methods` (pivot: `(organization_id, payment_method_id)`)
|
|
132
|
+
- `organization_delivery_methods` (pivot: `(organization_id, delivery_method_id)`)
|
|
133
|
+
- `organization_warehouses` (pivot: `(organization_id, warehouse_id)`)
|
|
134
|
+
|
|
135
|
+
**Empty list ⇒ platform defaults apply.** A non-empty list filters the
|
|
136
|
+
storefront `GET /api/v1/payment-methods`, `GET /api/v1/delivery-methods`,
|
|
137
|
+
and inventory stock-figure endpoints intersected with the caller's
|
|
138
|
+
Organization assignment.
|
|
139
|
+
|
|
140
|
+
Admin endpoints:
|
|
141
|
+
|
|
142
|
+
| Verb + Path | Purpose |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `GET /api/v1/admin/organizations/:id/restrictions` | Read all three allow-lists + the org's `version` |
|
|
145
|
+
| `PUT /api/v1/admin/organizations/:id/restrictions` | Atomic replace of all three |
|
|
146
|
+
| `PATCH .../restrictions/payment-methods` | Surgical `{ add?, remove? }` |
|
|
147
|
+
| `PATCH .../restrictions/delivery-methods` | Same |
|
|
148
|
+
| `PATCH .../restrictions/warehouses` | Same |
|
|
149
|
+
|
|
150
|
+
Storefront preflight:
|
|
151
|
+
|
|
152
|
+
| Verb + Path | Purpose |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `POST /api/v1/storefront/checkout/preflight` | Returns `{ canTransact, allowedPaymentMethodIds, allowedDeliveryMethodIds, assignedWarehouseIds }` or 423 when the org cannot transact |
|
|
155
|
+
|
|
156
|
+
### Applicable price lists + promotion targeting
|
|
157
|
+
|
|
158
|
+
`OrganizationEffectivePriceListsService.listApplicable(orgId)` reuses
|
|
159
|
+
the existing `application-rule-evaluator` from the `price_lists` module
|
|
160
|
+
to compute every Price List that currently applies to the Organization,
|
|
161
|
+
each tagged with a `reasons[]` array
|
|
162
|
+
(`direct_organization_match` / `customer_group_match` /
|
|
163
|
+
`sales_channel_inheritance` / `segment_rule_match`). Surfaced at
|
|
164
|
+
`GET /api/v1/admin/organizations/:id/applicable-price-lists` and
|
|
165
|
+
rendered as a read-only table in the admin Organization detail page.
|
|
166
|
+
|
|
167
|
+
Promotions: when a promotion targets a specific Organization
|
|
168
|
+
(`promotions.organization_id` is set), the platform applies the rule
|
|
169
|
+
only when the cart's Organization is `active`. The check is wired
|
|
170
|
+
through `PromotionService`'s optional `resolveOrganizationStatus`
|
|
171
|
+
constructor argument; composition.ts passes a raw SQL lookup.
|
|
172
|
+
|
|
173
|
+
### Sales-rep ownership
|
|
174
|
+
|
|
175
|
+
`organization_sales_rep_assignments` (pivot: `(organization_id,
|
|
176
|
+
admin_user_id)`) binds sales reps to organizations. When the
|
|
177
|
+
caller's admin role is `sales_representative`, the admin Orders list
|
|
178
|
+
and RFQ list are filtered to the orgs the rep owns. Platform admins
|
|
179
|
+
see everything.
|
|
180
|
+
|
|
181
|
+
Three endpoints maintain the relation, and this module owns and
|
|
182
|
+
registers all three:
|
|
183
|
+
|
|
184
|
+
| Verb + Path | Purpose |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| `GET /api/v1/admin/organizations/:id/sales-reps` | List the reps assigned to an organization. |
|
|
187
|
+
| `POST /api/v1/admin/organizations/:id/sales-reps` | Assign a rep. |
|
|
188
|
+
| `DELETE /api/v1/admin/organizations/:id/sales-reps/:adminUserId` | Remove an assignment. |
|
|
189
|
+
|
|
190
|
+
They are gated by `organizations:assign-sales-rep`. Until 2026-08 they
|
|
191
|
+
were registered by the quote-requests module and gated by
|
|
192
|
+
`rfqs:handle`, which meant switching quote requests off also removed
|
|
193
|
+
the ability to assign a sales representative — and the code gating
|
|
194
|
+
the screen disappeared from the roles matrix with it. Assigning a rep
|
|
195
|
+
qualifies an organization, so it belongs here, with a code this
|
|
196
|
+
module declares. The one endpoint that stayed behind is the reverse
|
|
197
|
+
listing, `GET /api/v1/admin/sales-reps/:adminUserId/organizations`:
|
|
198
|
+
it reports how many quote requests are open per organization, which
|
|
199
|
+
is that module's fact.
|
|
200
|
+
|
|
201
|
+
Other modules read the relation through this module's
|
|
202
|
+
`organizationSalesRepScopePort`, never by querying the pivot.
|
|
203
|
+
|
|
204
|
+
### VAT-ID / NIP validation
|
|
205
|
+
|
|
206
|
+
Two production HTTP clients implement the `VatValidator` port:
|
|
207
|
+
|
|
208
|
+
- `ViesClient` → `POST` against the VIES REST endpoint
|
|
209
|
+
(`/check-vat-number`). 5-second timeout; single abort on network
|
|
210
|
+
error.
|
|
211
|
+
- `MinisterstwoFinansowClient` → `GET` against `wl-api.mf.gov.pl/api/search/nip/{nip}`.
|
|
212
|
+
In-process 10-rps throttle; 7-day cache key on `(nip, today)` is
|
|
213
|
+
baked into the service-side `OrganizationTaxIdValidation` history
|
|
214
|
+
(one row per attempt).
|
|
215
|
+
|
|
216
|
+
`OrganizationTaxIdValidationService` auto-picks the provider per
|
|
217
|
+
tax-id prefix (Polish 10-digit → MF; other ISO-2 prefix → VIES;
|
|
218
|
+
anything else → format-only). When `applyAutoFill=true` AND the
|
|
219
|
+
result is `validated`, the org's `legalName` is updated and `version`
|
|
220
|
+
bumps so the next admin edit honors the optimistic-lock.
|
|
221
|
+
|
|
222
|
+
All adapters degrade safely on provider outage:
|
|
223
|
+
`outcome: 'deferred'`. The org save never fails because of a
|
|
224
|
+
third-party hiccup.
|
|
225
|
+
|
|
226
|
+
| Verb + Path | Purpose |
|
|
227
|
+
| --- | --- |
|
|
228
|
+
| `POST /api/v1/admin/organizations/:id/vat-validations` | Trigger one validation attempt (`providerHint`, `applyAutoFill`) |
|
|
229
|
+
| `GET /api/v1/admin/organizations/:id/vat-validations` | History list, newest first |
|
|
230
|
+
|
|
231
|
+
### Picker primitive + diacritic-insensitive search
|
|
232
|
+
|
|
233
|
+
The admin app ships a reusable `<OrganizationPicker>` (single-select)
|
|
234
|
+
and `<OrganizationPickerMulti>` (multi-select) on top of the existing
|
|
235
|
+
`<Combobox>`. They consume `GET /api/v1/admin/organizations?q=` whose
|
|
236
|
+
`q` parameter is diacritic-insensitive: a query of `lodz` finds
|
|
237
|
+
"Bauhaus Łódź" via the denormalized `name_search` column populated
|
|
238
|
+
by the Organization entity's `@BeforeCreate` / `@BeforeUpdate` hooks.
|
|
239
|
+
`normalizeOrganizationName` is that fold plus the whitespace policy the
|
|
240
|
+
column needs. The fold itself is `foldDiacritics`
|
|
241
|
+
(`packages/contracts/src/text-normalization.ts`), shared with the admin
|
|
242
|
+
panel: NFD decomposition, a strip of the combining
|
|
243
|
+
marks, then an explicit table for the precomposed Latin letters NFD
|
|
244
|
+
doesn't split (`ł`/`Ł`, `ø`/`Ø`, `đ`/`Đ`, `ð`/`Ð`, `þ`/`Þ`, `ß`, `æ`,
|
|
245
|
+
`œ`). Changing that table re-folds new rows differently from old ones,
|
|
246
|
+
so it is a migration of `name_search`, not an edit.
|
|
247
|
+
|
|
248
|
+
### New settings (declared on the manifest)
|
|
249
|
+
|
|
250
|
+
| Code | Type | Default | Purpose |
|
|
251
|
+
| --- | --- | --- | --- |
|
|
252
|
+
| `organizations.moderation.mode` | `string` enum | `'manual'` | `manual` ⇒ pending_verification; `auto` ⇒ active on registration |
|
|
253
|
+
| `organizations.notifications.new_registration_recipients` | `json` array | `[]` | E-mail recipients for new-Organization notifications |
|
|
254
|
+
|
|
255
|
+
### Migrations
|
|
256
|
+
|
|
257
|
+
- `047_organizations_consolidation.ts` — adds `legal_name`, VAT-validation
|
|
258
|
+
columns, blocked / rejected / approved audit columns, `version`
|
|
259
|
+
optimistic-lock, `name_search` denormalized column, three allow-list
|
|
260
|
+
bridges, the validation-history table, the `organizations_name_search_idx`
|
|
261
|
+
B-Tree index, and remaps every `suspended` row to `blocked`.
|
|
262
|
+
- `048_admin_notifications_init.ts` — adds the `admin_notifications`
|
|
263
|
+
table + the per-admin `admin_notification_reads` bridge.
|
|
264
|
+
- `049_customer_accounts_organization_optional.ts` — relaxed
|
|
265
|
+
`customer_accounts.organization_id` to nullable so guest-style
|
|
266
|
+
Customer accounts were representable. **That design is
|
|
267
|
+
dead**: personal organizations replaced it and the column was re-tightened — see
|
|
268
|
+
`customer_accounts`' `20260825T141659_customer_accounts_organization_required`.
|
|
269
|
+
- `089_personal_organizations.ts` — adds `organizations.is_personal`
|
|
270
|
+
and backfills a personal organization for every pre-existing no-org
|
|
271
|
+
customer account (see "Personal organizations" below).
|
|
272
|
+
|
|
273
|
+
### Personal organizations (B2C)
|
|
274
|
+
|
|
275
|
+
The Organization is the platform's single tenant concept. A B2C /
|
|
276
|
+
individual customer is **not** a null-org special case: every standalone
|
|
277
|
+
customer registration provisions a single-member **personal
|
|
278
|
+
organization** (`is_personal = true`). The organization and the
|
|
279
|
+
account are written **in one transaction**, by the module that owns the account
|
|
280
|
+
row, on both paths that create one — self-registration and federated sign-in.
|
|
281
|
+
This means:
|
|
282
|
+
|
|
283
|
+
- **Transacting works unchanged.** `organization_id` is `NOT NULL`,
|
|
284
|
+
so ordering, RFQs, credit, invoices and addresses need no null-org path — and
|
|
285
|
+
the column, not a guard, is what refuses one: MikroORM applies its tenant
|
|
286
|
+
filter to `SELECT` / `UPDATE` / `DELETE` and not to `INSERT`.
|
|
287
|
+
- **Isolation is structural.** The tenant guard isolates each
|
|
288
|
+
personal org as its own tenant — two B2C customers can never see each
|
|
289
|
+
other's data, with zero null-org special-casing.
|
|
290
|
+
- **Individual defaults.** `status = active`, `vat_status = vat_exempt`,
|
|
291
|
+
`name` from the customer's name (falling back to the email local-part),
|
|
292
|
+
and a synthetic 32-hex `tax_id` derived from the account id (the column
|
|
293
|
+
is globally `UNIQUE`; an individual has no company tax id).
|
|
294
|
+
- **Invisible in B2B admin.** Personal orgs are excluded by default from
|
|
295
|
+
the admin org list/pickers, cannot receive a sales rep, and never enter
|
|
296
|
+
the moderation queue (they are created `active`). The admin org list
|
|
297
|
+
accepts `?includePersonal=true` to surface them when needed.
|
|
298
|
+
- **Per-channel gate.** Standalone (B2C) registration is controlled per
|
|
299
|
+
sales channel by the `customers.allow_registration_without_organization`
|
|
300
|
+
setting; a B2B-only channel refuses the registration and provisions
|
|
301
|
+
nothing. The setting's name is a leftover from an earlier design — what it
|
|
302
|
+
gates is registration outside a *company* organization, not registration
|
|
303
|
+
without one.
|
|
304
|
+
- **Detaching a member from a company moves them here.** The admin
|
|
305
|
+
`DELETE /api/v1/admin/customers/:id/organization` used to write
|
|
306
|
+
`organization_id = NULL`; it now provisions (or re-finds) the
|
|
307
|
+
customer's own personal organization and moves them into it, keeping the
|
|
308
|
+
`customer_account.organization_unassigned` audit verb.
|
|
309
|
+
|
|
310
|
+
Company (B2B) organizations are unaffected — the single-member invariant
|
|
311
|
+
(`assertMembershipAllowed`) only rejects adding a second member to a
|
|
312
|
+
personal org.
|