@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,337 @@
|
|
|
1
|
+
import { OptimisticLockError } from '@mikro-orm/core';
|
|
2
|
+
import { Organization } from '../entities/organization.entity.js';
|
|
3
|
+
import { withSystemScope } from '@endora-commerce/platform/tenancy';
|
|
4
|
+
import { noopOrgTemplateEmail } from './org-template-email.js';
|
|
5
|
+
export class OrganizationModerationService {
|
|
6
|
+
emFactory;
|
|
7
|
+
auditLogService;
|
|
8
|
+
events;
|
|
9
|
+
mailer;
|
|
10
|
+
customerAccounts;
|
|
11
|
+
resolveModerationMode;
|
|
12
|
+
templateEmail;
|
|
13
|
+
constructor(emFactory, auditLogService, events, mailer,
|
|
14
|
+
/**
|
|
15
|
+
* `customer_accounts`' published read, for the one question this service
|
|
16
|
+
* asks it: who receives the approval or rejection message.
|
|
17
|
+
*/
|
|
18
|
+
customerAccounts,
|
|
19
|
+
/** Resolver for the moderation-mode setting. Returns 'auto' or 'manual'. */
|
|
20
|
+
resolveModerationMode,
|
|
21
|
+
/**
|
|
22
|
+
* The transactional-template seam, which is where the language comes from.
|
|
23
|
+
*
|
|
24
|
+
* Defaulted to the no-op rather than made required so a composition that
|
|
25
|
+
* does not wire it keeps sending — silence would be worse than the
|
|
26
|
+
* fallback language, which is the whole argument for keeping the in-code
|
|
27
|
+
* builders below.
|
|
28
|
+
*/
|
|
29
|
+
templateEmail = noopOrgTemplateEmail) {
|
|
30
|
+
this.emFactory = emFactory;
|
|
31
|
+
this.auditLogService = auditLogService;
|
|
32
|
+
this.events = events;
|
|
33
|
+
this.mailer = mailer;
|
|
34
|
+
this.customerAccounts = customerAccounts;
|
|
35
|
+
this.resolveModerationMode = resolveModerationMode;
|
|
36
|
+
this.templateEmail = templateEmail;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Invoked by the `organization.registered.v1` subscriber. When the
|
|
40
|
+
* setting is `auto`, the freshly created org is approved immediately
|
|
41
|
+
* (system actor = null adminUserId). When `manual`, this is a no-op
|
|
42
|
+
* — the org stays in `pending_verification` until an admin acts.
|
|
43
|
+
*/
|
|
44
|
+
async handleNewlyRegistered(organizationId) {
|
|
45
|
+
let mode = 'manual';
|
|
46
|
+
try {
|
|
47
|
+
mode = await this.resolveModerationMode();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Settings not yet seeded or unreachable — degrade safely to manual.
|
|
51
|
+
}
|
|
52
|
+
if (mode !== 'auto')
|
|
53
|
+
return;
|
|
54
|
+
const em = this.emFactory();
|
|
55
|
+
const org = await em.findOne(Organization, { id: organizationId, deletedAt: null });
|
|
56
|
+
if (!org)
|
|
57
|
+
return;
|
|
58
|
+
if (org.status !== 'pending_verification')
|
|
59
|
+
return;
|
|
60
|
+
await this.approve(organizationId, {
|
|
61
|
+
expectedVersion: org.version,
|
|
62
|
+
actorAdminUserId: null,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async approve(organizationId, options) {
|
|
66
|
+
return this.transition({
|
|
67
|
+
organizationId,
|
|
68
|
+
expectedVersion: options.expectedVersion,
|
|
69
|
+
requireStatus: 'pending_verification',
|
|
70
|
+
mutate: (org) => {
|
|
71
|
+
org.status = 'active';
|
|
72
|
+
org.approvedAt = new Date();
|
|
73
|
+
org.approvedByAdminUserId = options.actorAdminUserId ?? null;
|
|
74
|
+
},
|
|
75
|
+
action: 'organization.approved',
|
|
76
|
+
actorAdminUserId: options.actorAdminUserId ?? null,
|
|
77
|
+
requestId: options.requestId ?? null,
|
|
78
|
+
eventNewStatus: 'active',
|
|
79
|
+
afterCommit: async (org) => {
|
|
80
|
+
await this.notifyCustomer(org, {
|
|
81
|
+
code: 'organization_approved',
|
|
82
|
+
variables: { organizationName: org.name },
|
|
83
|
+
fallbackSubject: 'Your organization has been verified',
|
|
84
|
+
fallbackText: `Hi,\n\nOrganization "${org.name}" has been verified successfully and can now place Orders and Requests for Quotation.\n\nThank you.`,
|
|
85
|
+
messageIdSuffix: 'approved',
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async reject(organizationId, options) {
|
|
91
|
+
if (!options.reason || options.reason.trim().length === 0) {
|
|
92
|
+
throw new Error('OrganizationModerationService.reject: reason is required.');
|
|
93
|
+
}
|
|
94
|
+
return this.transition({
|
|
95
|
+
organizationId,
|
|
96
|
+
expectedVersion: options.expectedVersion,
|
|
97
|
+
requireStatus: 'pending_verification',
|
|
98
|
+
mutate: (org) => {
|
|
99
|
+
org.status = 'rejected';
|
|
100
|
+
org.rejectedAt = new Date();
|
|
101
|
+
org.rejectedReason = options.reason;
|
|
102
|
+
},
|
|
103
|
+
action: 'organization.rejected',
|
|
104
|
+
actorAdminUserId: options.actorAdminUserId ?? null,
|
|
105
|
+
requestId: options.requestId ?? null,
|
|
106
|
+
eventNewStatus: 'rejected',
|
|
107
|
+
reason: options.reason,
|
|
108
|
+
afterCommit: async (org) => {
|
|
109
|
+
if (options.notifyCustomerEmail === false)
|
|
110
|
+
return;
|
|
111
|
+
await this.notifyCustomer(org, {
|
|
112
|
+
code: 'organization_rejected',
|
|
113
|
+
variables: { organizationName: org.name, reason: options.reason },
|
|
114
|
+
fallbackSubject: 'Organization registration rejected',
|
|
115
|
+
fallbackText: `Hi,\n\nUnfortunately, the registration of organization "${org.name}" was rejected for the following reason:\n\n${options.reason}\n\nIf you have any questions, please contact the platform administrators.`,
|
|
116
|
+
messageIdSuffix: 'rejected',
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async block(organizationId, options) {
|
|
122
|
+
return this.transition({
|
|
123
|
+
organizationId,
|
|
124
|
+
expectedVersion: options.expectedVersion,
|
|
125
|
+
requireStatus: 'active',
|
|
126
|
+
mutate: (org) => {
|
|
127
|
+
org.status = 'blocked';
|
|
128
|
+
org.blockedAt = new Date();
|
|
129
|
+
org.blockedReason = options.reason ?? null;
|
|
130
|
+
},
|
|
131
|
+
action: 'organization.blocked',
|
|
132
|
+
actorAdminUserId: options.actorAdminUserId ?? null,
|
|
133
|
+
requestId: options.requestId ?? null,
|
|
134
|
+
eventNewStatus: 'blocked',
|
|
135
|
+
reason: options.reason ?? null,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async unblock(organizationId, options) {
|
|
139
|
+
return this.transition({
|
|
140
|
+
organizationId,
|
|
141
|
+
expectedVersion: options.expectedVersion,
|
|
142
|
+
requireStatus: 'blocked',
|
|
143
|
+
mutate: (org) => {
|
|
144
|
+
org.status = 'active';
|
|
145
|
+
org.blockedAt = null;
|
|
146
|
+
org.blockedReason = null;
|
|
147
|
+
},
|
|
148
|
+
action: 'organization.unblocked',
|
|
149
|
+
actorAdminUserId: options.actorAdminUserId ?? null,
|
|
150
|
+
requestId: options.requestId ?? null,
|
|
151
|
+
eventNewStatus: 'active',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// ── internals ──────────────────────────────────────────────────────────
|
|
155
|
+
async transition(input) {
|
|
156
|
+
const em = this.emFactory();
|
|
157
|
+
const captured = { snapshotBefore: null, snapshotAfter: null, org: null };
|
|
158
|
+
try {
|
|
159
|
+
await em.transactional(async (tem) => {
|
|
160
|
+
const org = await tem.findOne(Organization, { id: input.organizationId, deletedAt: null });
|
|
161
|
+
if (!org) {
|
|
162
|
+
throw new OrganizationNotFoundError(input.organizationId);
|
|
163
|
+
}
|
|
164
|
+
if (org.version !== input.expectedVersion) {
|
|
165
|
+
throw new OrganizationVersionMismatchError(org.id, input.expectedVersion, org.version);
|
|
166
|
+
}
|
|
167
|
+
if (org.status !== input.requireStatus) {
|
|
168
|
+
throw new OrganizationStatusGuardError(org.id, org.status, input.requireStatus);
|
|
169
|
+
}
|
|
170
|
+
captured.snapshotBefore = snapshot(org);
|
|
171
|
+
input.mutate(org);
|
|
172
|
+
await tem.flush();
|
|
173
|
+
captured.snapshotAfter = snapshot(org);
|
|
174
|
+
captured.org = org;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (err instanceof OptimisticLockError) {
|
|
179
|
+
throw new OrganizationVersionMismatchError(input.organizationId, input.expectedVersion, -1);
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
const { snapshotBefore, snapshotAfter, org: approvedOrg } = captured;
|
|
184
|
+
// Audit + event + email run AFTER the DB commit so a downstream failure
|
|
185
|
+
// never poisons the status transition. We accept best-effort semantics
|
|
186
|
+
// for those (mailer is idempotent via messageId).
|
|
187
|
+
await this.auditLogService.record({
|
|
188
|
+
actorAdminUserId: input.actorAdminUserId,
|
|
189
|
+
action: input.action,
|
|
190
|
+
objectType: 'organization',
|
|
191
|
+
objectId: input.organizationId,
|
|
192
|
+
stateBefore: snapshotBefore ? { ...snapshotBefore } : null,
|
|
193
|
+
stateAfter: snapshotAfter ? { ...snapshotAfter } : null,
|
|
194
|
+
requestId: input.requestId,
|
|
195
|
+
});
|
|
196
|
+
this.events.emit('organization.status_changed.v1', {
|
|
197
|
+
eventId: crypto.randomUUID(),
|
|
198
|
+
occurredAt: new Date().toISOString(),
|
|
199
|
+
organizationId: input.organizationId,
|
|
200
|
+
newStatus: input.eventNewStatus,
|
|
201
|
+
});
|
|
202
|
+
if (input.afterCommit && approvedOrg) {
|
|
203
|
+
try {
|
|
204
|
+
await input.afterCommit(approvedOrg);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Customer email is best-effort. The transition has already happened.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return approvedOrg;
|
|
211
|
+
}
|
|
212
|
+
async notifyCustomer(org, input) {
|
|
213
|
+
/**
|
|
214
|
+
* Feature 075 (D-87) — `customer_accounts`' port, where this was a
|
|
215
|
+
* `getConnection().getKnex().raw('select "email" from "customer_accounts" …')`.
|
|
216
|
+
* Raw SQL names no import specifier, so the reach compiled and returned
|
|
217
|
+
* rows while every boundary check read clean.
|
|
218
|
+
*
|
|
219
|
+
* **The system scope is load-bearing and is not incidental to the port.**
|
|
220
|
+
* `CustomerAccount` is `@OrgScoped`, so the read carries the acting
|
|
221
|
+
* admin's tenant filter, and a moderator with an `allowed-set` context has
|
|
222
|
+
* by definition not been assigned the organisation they are approving —
|
|
223
|
+
* it is still `pending_verification`. Filtered, the lookup would find no
|
|
224
|
+
* recipient and the approval mail would silently not go out. The raw
|
|
225
|
+
* statement crossed organisations by bypassing the filter entirely and
|
|
226
|
+
* telling nobody; `withSystemScope` crosses them the sanctioned way
|
|
227
|
+
* (Constitution XI, FR-005), with a reason and one audit record.
|
|
228
|
+
*
|
|
229
|
+
* `listByOrganization` orders by role then creation date, so the first
|
|
230
|
+
* `organization_admin` in the result is the row the `order by
|
|
231
|
+
* "created_at" asc limit 1` picked.
|
|
232
|
+
*/
|
|
233
|
+
const members = await withSystemScope('organizations: notify the moderated organisation of its status change', () => this.customerAccounts.listByOrganization(org.id, { activeOnly: true }));
|
|
234
|
+
const recipient = members.find((m) => m.role === 'organization_admin')?.email;
|
|
235
|
+
if (!recipient)
|
|
236
|
+
return;
|
|
237
|
+
const messageId = `organization.${input.messageIdSuffix}.${org.id}.${org.version}`;
|
|
238
|
+
const meta = { organizationId: org.id, kind: input.messageIdSuffix };
|
|
239
|
+
/**
|
|
240
|
+
* The language this message is written in, and where it comes from
|
|
241
|
+
* (`specs/093-backend-delivered-prose/` § Out of scope — the seam-B
|
|
242
|
+
* carve-out).
|
|
243
|
+
*
|
|
244
|
+
* Both of these messages were finished Polish sentences composed right
|
|
245
|
+
* here and handed straight to the transport, so a buyer on an English
|
|
246
|
+
* sales channel was sent Polish unconditionally. They carry a code now,
|
|
247
|
+
* and `templateEmailPort` resolves the language from the **system-default
|
|
248
|
+
* sales channel's `defaultLanguage`**, falling back to `en-US`.
|
|
249
|
+
*
|
|
250
|
+
* That is the honest derivation available at this call site and not a
|
|
251
|
+
* convenience. There is no per-buyer language to read: neither
|
|
252
|
+
* `CustomerAccountRecord` nor `Organization` carries one, and
|
|
253
|
+
* `specs/083-buyer-language-resolution/` records the reserved rung for it
|
|
254
|
+
* as deliberately absent. Nor is the request's language usable — this runs
|
|
255
|
+
* `afterCommit`, and on the two paths that reach it the request either
|
|
256
|
+
* belongs to the moderating **administrator**, whose language is not the
|
|
257
|
+
* recipient's, or does not exist at all, `handleNewlyRegistered` being
|
|
258
|
+
* driven by an event subscriber.
|
|
259
|
+
*
|
|
260
|
+
* `trySend` answers `false` only for a code with no definition, and then
|
|
261
|
+
* the English builders below send. A message that silently fails to go out
|
|
262
|
+
* is worse than one in a language the recipient did not choose.
|
|
263
|
+
*/
|
|
264
|
+
const sentViaTemplate = await this.templateEmail.trySend({
|
|
265
|
+
code: input.code,
|
|
266
|
+
to: recipient,
|
|
267
|
+
messageId,
|
|
268
|
+
variables: input.variables,
|
|
269
|
+
meta,
|
|
270
|
+
});
|
|
271
|
+
if (sentViaTemplate)
|
|
272
|
+
return;
|
|
273
|
+
const outcome = await this.mailer.send({
|
|
274
|
+
messageId,
|
|
275
|
+
to: recipient,
|
|
276
|
+
subject: input.fallbackSubject,
|
|
277
|
+
text: input.fallbackText,
|
|
278
|
+
meta,
|
|
279
|
+
});
|
|
280
|
+
if (outcome.status !== 'sent') {
|
|
281
|
+
// The moderation transition has committed and this notification runs
|
|
282
|
+
// after it, so the non-send is named rather than raised — D-59's record
|
|
283
|
+
// is what survives to answer "was the customer told".
|
|
284
|
+
console.warn('[organizations] the moderation e-mail was not sent', {
|
|
285
|
+
organizationId: org.id,
|
|
286
|
+
kind: input.messageIdSuffix,
|
|
287
|
+
reason: outcome.reason,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function snapshot(org) {
|
|
293
|
+
return {
|
|
294
|
+
id: org.id,
|
|
295
|
+
status: org.status,
|
|
296
|
+
approvedAt: org.approvedAt ? org.approvedAt.toISOString() : null,
|
|
297
|
+
approvedByAdminUserId: org.approvedByAdminUserId ?? null,
|
|
298
|
+
blockedAt: org.blockedAt ? org.blockedAt.toISOString() : null,
|
|
299
|
+
blockedReason: org.blockedReason ?? null,
|
|
300
|
+
rejectedAt: org.rejectedAt ? org.rejectedAt.toISOString() : null,
|
|
301
|
+
rejectedReason: org.rejectedReason ?? null,
|
|
302
|
+
version: org.version,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export class OrganizationNotFoundError extends Error {
|
|
306
|
+
organizationId;
|
|
307
|
+
constructor(organizationId) {
|
|
308
|
+
super(`Organization ${organizationId} not found.`);
|
|
309
|
+
this.organizationId = organizationId;
|
|
310
|
+
this.name = 'OrganizationNotFoundError';
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export class OrganizationVersionMismatchError extends Error {
|
|
314
|
+
organizationId;
|
|
315
|
+
expectedVersion;
|
|
316
|
+
currentVersion;
|
|
317
|
+
constructor(organizationId, expectedVersion, currentVersion) {
|
|
318
|
+
super(`Organization ${organizationId} version mismatch (expected ${expectedVersion}, currently ${currentVersion}).`);
|
|
319
|
+
this.organizationId = organizationId;
|
|
320
|
+
this.expectedVersion = expectedVersion;
|
|
321
|
+
this.currentVersion = currentVersion;
|
|
322
|
+
this.name = 'OrganizationVersionMismatchError';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
export class OrganizationStatusGuardError extends Error {
|
|
326
|
+
organizationId;
|
|
327
|
+
currentStatus;
|
|
328
|
+
requiredStatus;
|
|
329
|
+
constructor(organizationId, currentStatus, requiredStatus) {
|
|
330
|
+
super(`Organization ${organizationId} is in status '${currentStatus}', cannot perform an action that requires '${requiredStatus}'.`);
|
|
331
|
+
this.organizationId = organizationId;
|
|
332
|
+
this.currentStatus = currentStatus;
|
|
333
|
+
this.requiredStatus = requiredStatus;
|
|
334
|
+
this.name = 'OrganizationStatusGuardError';
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=organization-moderation-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-moderation-service.js","sourceRoot":"","sources":["../../../src/backend/services/organization-moderation-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
3
|
+
export interface AllowLists {
|
|
4
|
+
paymentMethodIds: string[];
|
|
5
|
+
deliveryMethodIds: string[];
|
|
6
|
+
warehouseIds: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface AllowListsRead extends AllowLists {
|
|
9
|
+
organizationId: string;
|
|
10
|
+
/** Organization's optimistic-lock version after the read. Pass this on the next write. */
|
|
11
|
+
version: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ReplaceAllowListsInput extends AllowLists {
|
|
14
|
+
expectedVersion: number;
|
|
15
|
+
}
|
|
16
|
+
export type RestrictionKind = 'payment_method' | 'delivery_method' | 'warehouse';
|
|
17
|
+
export interface PatchAllowListInput {
|
|
18
|
+
expectedVersion: number;
|
|
19
|
+
add?: string[] | undefined;
|
|
20
|
+
remove?: string[] | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Owns the three per-Organization allow-list bridges:
|
|
24
|
+
* - `organization_payment_methods`
|
|
25
|
+
* - `organization_delivery_methods`
|
|
26
|
+
* - `organization_warehouses`
|
|
27
|
+
*
|
|
28
|
+
* Empty allow-list ⇒ "platform defaults apply" (FR-014 / FR-015 / FR-016).
|
|
29
|
+
*
|
|
30
|
+
* Every write is gated by the Organization's `version` column (optimistic
|
|
31
|
+
* lock per research R7). Mismatch raises `OrganizationVersionMismatchError`
|
|
32
|
+
* which the route handler maps to HTTP 409.
|
|
33
|
+
*
|
|
34
|
+
* Storefront-side cache invalidation is layered on top by US4 — this
|
|
35
|
+
* foundational service only owns the persisted state.
|
|
36
|
+
*/
|
|
37
|
+
export declare class OrganizationRestrictionService {
|
|
38
|
+
private readonly emFactory;
|
|
39
|
+
private readonly auditLog?;
|
|
40
|
+
constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined);
|
|
41
|
+
readAllowLists(organizationId: string): Promise<AllowListsRead>;
|
|
42
|
+
/**
|
|
43
|
+
* The allow-list for one restriction kind, or `null` for "no restriction".
|
|
44
|
+
*
|
|
45
|
+
* Feature 072 (T138). Three composition roots' worth of callers —
|
|
46
|
+
* `payment_methods`, `delivery_methods` and `inventory` — used to reach this
|
|
47
|
+
* service through a root closure that wrapped {@link readAllowLists} in a
|
|
48
|
+
* bare `catch` and read "organization not found" as "unrestricted". That is
|
|
49
|
+
* the right policy and the wrong place for it: a `catch` around a *port* call
|
|
50
|
+
* also swallows `ModuleDisabledError`, turning a fail-closed gate into a
|
|
51
|
+
* fail-open restriction check — on the one code path whose entire job is to
|
|
52
|
+
* restrict.
|
|
53
|
+
*
|
|
54
|
+
* So the degrade is expressed here, in the return type, by the module that
|
|
55
|
+
* owns the policy. A caller needs no `catch`, and a genuine module-disabled
|
|
56
|
+
* error propagates as the 503 it is.
|
|
57
|
+
*/
|
|
58
|
+
allowedIdsFor(organizationId: string, kind: 'paymentMethodIds' | 'deliveryMethodIds' | 'warehouseIds'): Promise<string[] | null>;
|
|
59
|
+
replaceAllowLists(organizationId: string, input: ReplaceAllowListsInput): Promise<AllowListsRead>;
|
|
60
|
+
patchAllowList(organizationId: string, kind: RestrictionKind, input: PatchAllowListInput): Promise<AllowListsRead>;
|
|
61
|
+
private guardVersion;
|
|
62
|
+
private translateOptimisticLockError;
|
|
63
|
+
}
|
|
64
|
+
export declare class OrganizationVersionMismatchError extends Error {
|
|
65
|
+
readonly organizationId: string;
|
|
66
|
+
readonly expectedVersion: number;
|
|
67
|
+
readonly currentVersion: number;
|
|
68
|
+
constructor(organizationId: string, expectedVersion: number, currentVersion: number);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=organization-restriction-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-restriction-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/organization-restriction-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAO3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,MAAM,WAAW,UAAU;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,cAAc,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAuB,SAAQ,UAAU;IACxD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAEjF,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAC/B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,8BAA8B;IAEvC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA;IAGjC,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAmBrE;;;;;;;;;;;;;;;OAeG;IACG,aAAa,CACjB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,kBAAkB,GAAG,mBAAmB,GAAG,cAAc,GAC9D,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAcrB,iBAAiB,CACrB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,cAAc,CAAC;IAiDpB,cAAc,CAClB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,cAAc,CAAC;IAgE1B,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,4BAA4B;CAMrC;AAED,qBAAa,gCAAiC,SAAQ,KAAK;aAEvC,cAAc,EAAE,MAAM;aACtB,eAAe,EAAE,MAAM;aACvB,cAAc,EAAE,MAAM;gBAFtB,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM;CAOzC"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { NotFoundError, OptimisticLockError } from '@mikro-orm/core';
|
|
2
|
+
import { Organization } from '../entities/organization.entity.js';
|
|
3
|
+
import { OrganizationPaymentMethodLink } from '../entities/organization-payment-method-link.entity.js';
|
|
4
|
+
import { OrganizationDeliveryMethodLink } from '../entities/organization-delivery-method-link.entity.js';
|
|
5
|
+
import { OrganizationWarehouseLink } from '../entities/organization-warehouse-link.entity.js';
|
|
6
|
+
import { recordAuditFromContext } from '@endora-commerce/platform/commands';
|
|
7
|
+
/**
|
|
8
|
+
* Owns the three per-Organization allow-list bridges:
|
|
9
|
+
* - `organization_payment_methods`
|
|
10
|
+
* - `organization_delivery_methods`
|
|
11
|
+
* - `organization_warehouses`
|
|
12
|
+
*
|
|
13
|
+
* Empty allow-list ⇒ "platform defaults apply" (FR-014 / FR-015 / FR-016).
|
|
14
|
+
*
|
|
15
|
+
* Every write is gated by the Organization's `version` column (optimistic
|
|
16
|
+
* lock per research R7). Mismatch raises `OrganizationVersionMismatchError`
|
|
17
|
+
* which the route handler maps to HTTP 409.
|
|
18
|
+
*
|
|
19
|
+
* Storefront-side cache invalidation is layered on top by US4 — this
|
|
20
|
+
* foundational service only owns the persisted state.
|
|
21
|
+
*/
|
|
22
|
+
export class OrganizationRestrictionService {
|
|
23
|
+
emFactory;
|
|
24
|
+
auditLog;
|
|
25
|
+
constructor(emFactory, auditLog) {
|
|
26
|
+
this.emFactory = emFactory;
|
|
27
|
+
this.auditLog = auditLog;
|
|
28
|
+
}
|
|
29
|
+
async readAllowLists(organizationId) {
|
|
30
|
+
const em = this.emFactory();
|
|
31
|
+
const org = await em.findOneOrFail(Organization, { id: organizationId, deletedAt: null });
|
|
32
|
+
const [payment, delivery, warehouses] = await Promise.all([
|
|
33
|
+
em.find(OrganizationPaymentMethodLink, { organizationId }),
|
|
34
|
+
em.find(OrganizationDeliveryMethodLink, { organizationId }),
|
|
35
|
+
em.find(OrganizationWarehouseLink, { organizationId }),
|
|
36
|
+
]);
|
|
37
|
+
return {
|
|
38
|
+
organizationId,
|
|
39
|
+
paymentMethodIds: payment.map((r) => r.paymentMethodId).sort(),
|
|
40
|
+
deliveryMethodIds: delivery.map((r) => r.deliveryMethodId).sort(),
|
|
41
|
+
warehouseIds: warehouses.map((r) => r.warehouseId).sort(),
|
|
42
|
+
version: org.version,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The allow-list for one restriction kind, or `null` for "no restriction".
|
|
47
|
+
*
|
|
48
|
+
* Feature 072 (T138). Three composition roots' worth of callers —
|
|
49
|
+
* `payment_methods`, `delivery_methods` and `inventory` — used to reach this
|
|
50
|
+
* service through a root closure that wrapped {@link readAllowLists} in a
|
|
51
|
+
* bare `catch` and read "organization not found" as "unrestricted". That is
|
|
52
|
+
* the right policy and the wrong place for it: a `catch` around a *port* call
|
|
53
|
+
* also swallows `ModuleDisabledError`, turning a fail-closed gate into a
|
|
54
|
+
* fail-open restriction check — on the one code path whose entire job is to
|
|
55
|
+
* restrict.
|
|
56
|
+
*
|
|
57
|
+
* So the degrade is expressed here, in the return type, by the module that
|
|
58
|
+
* owns the policy. A caller needs no `catch`, and a genuine module-disabled
|
|
59
|
+
* error propagates as the 503 it is.
|
|
60
|
+
*/
|
|
61
|
+
async allowedIdsFor(organizationId, kind) {
|
|
62
|
+
let lists;
|
|
63
|
+
try {
|
|
64
|
+
lists = await this.readAllowLists(organizationId);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// The one condition this may absorb: the Organization is unknown or
|
|
68
|
+
// soft-deleted, which `readAllowLists` reports as a `findOneOrFail`
|
|
69
|
+
// rejection. Anything else is a real failure and must surface.
|
|
70
|
+
if (err instanceof NotFoundError)
|
|
71
|
+
return null;
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
return lists[kind];
|
|
75
|
+
}
|
|
76
|
+
async replaceAllowLists(organizationId, input) {
|
|
77
|
+
const em = this.emFactory();
|
|
78
|
+
try {
|
|
79
|
+
await em.transactional(async (tem) => {
|
|
80
|
+
const org = await tem.findOneOrFail(Organization, {
|
|
81
|
+
id: organizationId,
|
|
82
|
+
deletedAt: null,
|
|
83
|
+
});
|
|
84
|
+
this.guardVersion(org, input.expectedVersion);
|
|
85
|
+
await tem.nativeDelete(OrganizationPaymentMethodLink, { organizationId });
|
|
86
|
+
await tem.nativeDelete(OrganizationDeliveryMethodLink, { organizationId });
|
|
87
|
+
await tem.nativeDelete(OrganizationWarehouseLink, { organizationId });
|
|
88
|
+
for (const paymentMethodId of dedupe(input.paymentMethodIds)) {
|
|
89
|
+
tem.persist(tem.create(OrganizationPaymentMethodLink, { organizationId, paymentMethodId }));
|
|
90
|
+
}
|
|
91
|
+
for (const deliveryMethodId of dedupe(input.deliveryMethodIds)) {
|
|
92
|
+
tem.persist(tem.create(OrganizationDeliveryMethodLink, { organizationId, deliveryMethodId }));
|
|
93
|
+
}
|
|
94
|
+
for (const warehouseId of dedupe(input.warehouseIds)) {
|
|
95
|
+
tem.persist(tem.create(OrganizationWarehouseLink, { organizationId, warehouseId }));
|
|
96
|
+
}
|
|
97
|
+
// Touch the Organization so the version column advances. MikroORM's
|
|
98
|
+
// optimistic-lock semantics bump `version` automatically on flush.
|
|
99
|
+
org.updatedAt = new Date();
|
|
100
|
+
if (this.auditLog) {
|
|
101
|
+
recordAuditFromContext(this.auditLog, tem, {
|
|
102
|
+
action: 'organization.allow_lists_replace',
|
|
103
|
+
objectType: 'organization',
|
|
104
|
+
objectId: organizationId,
|
|
105
|
+
stateBefore: null,
|
|
106
|
+
stateAfter: {
|
|
107
|
+
paymentMethodIds: dedupe(input.paymentMethodIds),
|
|
108
|
+
deliveryMethodIds: dedupe(input.deliveryMethodIds),
|
|
109
|
+
warehouseIds: dedupe(input.warehouseIds),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
await tem.flush();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
throw this.translateOptimisticLockError(err, organizationId);
|
|
118
|
+
}
|
|
119
|
+
return this.readAllowLists(organizationId);
|
|
120
|
+
}
|
|
121
|
+
async patchAllowList(organizationId, kind, input) {
|
|
122
|
+
const em = this.emFactory();
|
|
123
|
+
try {
|
|
124
|
+
await em.transactional(async (tem) => {
|
|
125
|
+
const org = await tem.findOneOrFail(Organization, {
|
|
126
|
+
id: organizationId,
|
|
127
|
+
deletedAt: null,
|
|
128
|
+
});
|
|
129
|
+
this.guardVersion(org, input.expectedVersion);
|
|
130
|
+
const add = dedupe(input.add ?? []);
|
|
131
|
+
const remove = dedupe(input.remove ?? []);
|
|
132
|
+
if (kind === 'payment_method') {
|
|
133
|
+
if (remove.length) {
|
|
134
|
+
await tem.nativeDelete(OrganizationPaymentMethodLink, {
|
|
135
|
+
organizationId,
|
|
136
|
+
paymentMethodId: { $in: remove },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
for (const id of add) {
|
|
140
|
+
tem.persist(tem.create(OrganizationPaymentMethodLink, { organizationId, paymentMethodId: id }));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (kind === 'delivery_method') {
|
|
144
|
+
if (remove.length) {
|
|
145
|
+
await tem.nativeDelete(OrganizationDeliveryMethodLink, {
|
|
146
|
+
organizationId,
|
|
147
|
+
deliveryMethodId: { $in: remove },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
for (const id of add) {
|
|
151
|
+
tem.persist(tem.create(OrganizationDeliveryMethodLink, { organizationId, deliveryMethodId: id }));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
if (remove.length) {
|
|
156
|
+
await tem.nativeDelete(OrganizationWarehouseLink, {
|
|
157
|
+
organizationId,
|
|
158
|
+
warehouseId: { $in: remove },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
for (const id of add) {
|
|
162
|
+
tem.persist(tem.create(OrganizationWarehouseLink, { organizationId, warehouseId: id }));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
org.updatedAt = new Date();
|
|
166
|
+
if (this.auditLog) {
|
|
167
|
+
recordAuditFromContext(this.auditLog, tem, {
|
|
168
|
+
action: 'organization.allow_list_patch',
|
|
169
|
+
objectType: 'organization',
|
|
170
|
+
objectId: organizationId,
|
|
171
|
+
stateBefore: null,
|
|
172
|
+
stateAfter: { kind, add: dedupe(input.add ?? []), remove: dedupe(input.remove ?? []) },
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
await tem.flush();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
throw this.translateOptimisticLockError(err, organizationId);
|
|
180
|
+
}
|
|
181
|
+
return this.readAllowLists(organizationId);
|
|
182
|
+
}
|
|
183
|
+
guardVersion(org, expected) {
|
|
184
|
+
if (org.version !== expected) {
|
|
185
|
+
throw new OrganizationVersionMismatchError(org.id, expected, org.version);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
translateOptimisticLockError(err, organizationId) {
|
|
189
|
+
if (err instanceof OptimisticLockError) {
|
|
190
|
+
return new OrganizationVersionMismatchError(organizationId, -1, -1);
|
|
191
|
+
}
|
|
192
|
+
return err;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export class OrganizationVersionMismatchError extends Error {
|
|
196
|
+
organizationId;
|
|
197
|
+
expectedVersion;
|
|
198
|
+
currentVersion;
|
|
199
|
+
constructor(organizationId, expectedVersion, currentVersion) {
|
|
200
|
+
super(`Organization ${organizationId} version mismatch (expected ${expectedVersion}, currently ${currentVersion}).`);
|
|
201
|
+
this.organizationId = organizationId;
|
|
202
|
+
this.expectedVersion = expectedVersion;
|
|
203
|
+
this.currentVersion = currentVersion;
|
|
204
|
+
this.name = 'OrganizationVersionMismatchError';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function dedupe(arr) {
|
|
208
|
+
return Array.from(new Set(arr));
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=organization-restriction-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-restriction-service.js","sourceRoot":"","sources":["../../../src/backend/services/organization-restriction-service.ts"],"names":[],"mappings":"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"}
|