@endora-commerce/mod-sales-channels 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/admin/api/sales-channels-client.d.ts +69 -0
  4. package/dist/admin/api/sales-channels-client.d.ts.map +1 -0
  5. package/dist/admin/api/sales-channels-client.js +74 -0
  6. package/dist/admin/api/sales-channels-client.js.map +1 -0
  7. package/dist/admin/components/ChannelIdentityForm.d.ts +24 -0
  8. package/dist/admin/components/ChannelIdentityForm.d.ts.map +1 -0
  9. package/dist/admin/components/ChannelIdentityForm.js +147 -0
  10. package/dist/admin/components/ChannelIdentityForm.js.map +1 -0
  11. package/dist/admin/components/DefaultChannelBadge.d.ts +21 -0
  12. package/dist/admin/components/DefaultChannelBadge.d.ts.map +1 -0
  13. package/dist/admin/components/DefaultChannelBadge.js +26 -0
  14. package/dist/admin/components/DefaultChannelBadge.js.map +1 -0
  15. package/dist/admin/components/EntityChannelMembership.d.ts +52 -0
  16. package/dist/admin/components/EntityChannelMembership.d.ts.map +1 -0
  17. package/dist/admin/components/EntityChannelMembership.js +98 -0
  18. package/dist/admin/components/EntityChannelMembership.js.map +1 -0
  19. package/dist/admin/index.d.ts +44 -0
  20. package/dist/admin/index.d.ts.map +1 -0
  21. package/dist/admin/index.js +148 -0
  22. package/dist/admin/index.js.map +1 -0
  23. package/dist/admin/pages/SalesChannelEditPage.d.ts +25 -0
  24. package/dist/admin/pages/SalesChannelEditPage.d.ts.map +1 -0
  25. package/dist/admin/pages/SalesChannelEditPage.js +185 -0
  26. package/dist/admin/pages/SalesChannelEditPage.js.map +1 -0
  27. package/dist/admin/pages/SalesChannelsListPage.d.ts +25 -0
  28. package/dist/admin/pages/SalesChannelsListPage.d.ts.map +1 -0
  29. package/dist/admin/pages/SalesChannelsListPage.js +91 -0
  30. package/dist/admin/pages/SalesChannelsListPage.js.map +1 -0
  31. package/dist/admin/zones/OrganizationChannelMembership.d.ts +34 -0
  32. package/dist/admin/zones/OrganizationChannelMembership.d.ts.map +1 -0
  33. package/dist/admin/zones/OrganizationChannelMembership.js +11 -0
  34. package/dist/admin/zones/OrganizationChannelMembership.js.map +1 -0
  35. package/dist/admin/zones/ProductChannelMembership.d.ts +23 -0
  36. package/dist/admin/zones/ProductChannelMembership.d.ts.map +1 -0
  37. package/dist/admin/zones/ProductChannelMembership.js +11 -0
  38. package/dist/admin/zones/ProductChannelMembership.js.map +1 -0
  39. package/dist/backend/commands/set-default.command.d.ts +53 -0
  40. package/dist/backend/commands/set-default.command.d.ts.map +1 -0
  41. package/dist/backend/commands/set-default.command.js +87 -0
  42. package/dist/backend/commands/set-default.command.js.map +1 -0
  43. package/dist/backend/index.d.ts +71 -0
  44. package/dist/backend/index.d.ts.map +1 -0
  45. package/dist/backend/index.js +81 -0
  46. package/dist/backend/index.js.map +1 -0
  47. package/dist/backend/routes.admin.d.ts +12 -0
  48. package/dist/backend/routes.admin.d.ts.map +1 -0
  49. package/dist/backend/routes.admin.js +302 -0
  50. package/dist/backend/routes.admin.js.map +1 -0
  51. package/dist/backend/routes.storefront.d.ts +36 -0
  52. package/dist/backend/routes.storefront.d.ts.map +1 -0
  53. package/dist/backend/routes.storefront.js +58 -0
  54. package/dist/backend/routes.storefront.js.map +1 -0
  55. package/dist/backend/services/index.d.ts +2 -0
  56. package/dist/backend/services/index.d.ts.map +1 -0
  57. package/dist/backend/services/index.js +2 -0
  58. package/dist/backend/services/index.js.map +1 -0
  59. package/dist/backend/services/sales-channel-attribution-registry.d.ts +28 -0
  60. package/dist/backend/services/sales-channel-attribution-registry.d.ts.map +1 -0
  61. package/dist/backend/services/sales-channel-attribution-registry.js +50 -0
  62. package/dist/backend/services/sales-channel-attribution-registry.js.map +1 -0
  63. package/dist/backend/services/sales-channels.service.d.ts +247 -0
  64. package/dist/backend/services/sales-channels.service.d.ts.map +1 -0
  65. package/dist/backend/services/sales-channels.service.js +535 -0
  66. package/dist/backend/services/sales-channels.service.js.map +1 -0
  67. package/dist/manifest.d.ts +185 -0
  68. package/dist/manifest.d.ts.map +1 -0
  69. package/dist/manifest.js +204 -0
  70. package/dist/manifest.js.map +1 -0
  71. package/docs/sales_channels/admin-usage.md +77 -0
  72. package/docs/sales_channels/developer-guide.md +107 -0
  73. package/docs/sales_channels/index.md +95 -0
  74. package/i18n/en.json +75 -0
  75. package/i18n/pl.json +75 -0
  76. package/package.json +88 -0
  77. package/tailwind.css +14 -0
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { Link } from 'react-router-dom';
4
+ import { ApiError, useAuth } from '@endora-commerce/admin-kit/lib';
5
+ import { Alert, AlertDescription, Badge, Button, Card, CardContent, Checkbox, Label, PageHeader, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@endora-commerce/admin-kit/ui';
6
+ import { salesChannelsClient } from '../api/sales-channels-client.js';
7
+ import { DefaultChannelBadge } from '../components/DefaultChannelBadge.js';
8
+ import { useTranslation } from '@endora-commerce/admin-kit/i18n';
9
+ /**
10
+ * SalesChannelsListPage — feature 005 / T041.
11
+ *
12
+ * Top-level list view. The system-default channel always lives in
13
+ * the list; the activeOnly filter toggles whether deactivated
14
+ * channels are hidden. The "+ New channel" CTA sends the operator
15
+ * to the create form (`/sales-channels/new`).
16
+ *
17
+ * The system-default column is where the flag moves (feature 072 / D-51). The
18
+ * list is the right surface for it because the decision is comparative — which
19
+ * of these channels should be the fallback — and because the current default
20
+ * has to be visible while another one is promoted. The current default's cell
21
+ * is deliberately inert: the flag is moved by promoting a different channel,
22
+ * never by clearing it here, which is what keeps "exactly one default" true at
23
+ * every moment.
24
+ */
25
+ export function SalesChannelsListPage() {
26
+ const t = useTranslation('sales_channels');
27
+ const { hasPermission } = useAuth();
28
+ const canWrite = hasPermission('sales_channels:write');
29
+ const [rows, setRows] = useState([]);
30
+ const [loading, setLoading] = useState(true);
31
+ const [error, setError] = useState(null);
32
+ const [activeOnly, setActiveOnly] = useState(false);
33
+ const [promoting, setPromoting] = useState(null);
34
+ const refresh = useCallback(async () => {
35
+ setLoading(true);
36
+ setError(null);
37
+ try {
38
+ const res = await salesChannelsClient.list({ activeOnly });
39
+ setRows(res.items);
40
+ }
41
+ catch (err) {
42
+ setError(err instanceof ApiError
43
+ ? err.envelope.error.message
44
+ : t('list.error.load'));
45
+ }
46
+ finally {
47
+ setLoading(false);
48
+ }
49
+ }, [activeOnly, t]);
50
+ useEffect(() => {
51
+ void refresh();
52
+ }, [refresh]);
53
+ const makeDefault = useCallback(async (channel) => {
54
+ const previous = rows.find((c) => c.systemDefault)?.code ?? '—';
55
+ if (!window.confirm(t('list.action.makeDefault.confirm', { code: channel.code, previous }))) {
56
+ return;
57
+ }
58
+ setPromoting(channel.code);
59
+ setError(null);
60
+ try {
61
+ await salesChannelsClient.setDefault(channel.code);
62
+ await refresh();
63
+ }
64
+ catch (err) {
65
+ setError(err instanceof ApiError
66
+ ? err.envelope.error.message
67
+ : t('list.error.makeDefault'));
68
+ }
69
+ finally {
70
+ setPromoting(null);
71
+ }
72
+ }, [refresh, rows, t]);
73
+ return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('list.page.title'), description: t('list.page.description'), actions:
74
+ // Gated on the code the create route now enforces (feature 091, batch
75
+ // 14). It was ungated while `/sales-channels/new` was `App.tsx`'s and
76
+ // therefore ungated too; the route is this module's own now and takes
77
+ // `sales_channels:write`, so an ungated button would be a dead end for
78
+ // a read-only operator rather than a refusal they could act on.
79
+ canWrite ? (_jsx(Button, { asChild: true, children: _jsx(Link, { to: "/sales-channels/new", children: t('list.action.newChannel') }) })) : null }), error && (_jsx(Alert, { variant: "destructive", className: "mb-4", children: _jsx(AlertDescription, { children: error }) })), _jsx(Card, { className: "mb-4", children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "sc-active-only", checked: activeOnly, onChange: (e) => setActiveOnly(e.target.checked) }), _jsx(Label, { htmlFor: "sc-active-only", className: "cursor-pointer", children: t('list.filter.activeOnly') })] }) }) }), _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('list.loading') })) : rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('list.empty') })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('list.column.code') }), _jsx(TableHead, { children: t('list.column.name') }), _jsx(TableHead, { children: t('list.column.defaultLangCurrency') }), _jsx(TableHead, { children: t('list.column.status') }), _jsx(TableHead, { className: "w-32 text-right", children: t('list.column.version') }), _jsx(TableHead, { className: "w-48 text-right", children: t('list.column.systemDefault') })] }) }), _jsx(TableBody, { children: rows.map((c) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { className: "font-mono text-xs", children: [_jsx(Link, { to: `/sales-channels/${encodeURIComponent(c.code)}`, className: "text-primary hover:underline", children: c.code }), _jsx(DefaultChannelBadge, { systemDefault: c.systemDefault })] }), _jsx(TableCell, { children: c.name['en-US'] ?? c.name['en'] ?? Object.values(c.name)[0] ?? '—' }), _jsxs(TableCell, { className: "text-xs text-muted-foreground", children: [c.defaultLanguage, " / ", c.defaultCurrency] }), _jsx(TableCell, { children: c.active ? (_jsx(Badge, { variant: "default", className: "text-[10px]", children: t('status.active') })) : (_jsx(Badge, { variant: "outline", className: "text-[10px]", children: t('status.inactive') })) }), _jsxs(TableCell, { className: "text-right text-xs text-muted-foreground", children: ["v", c.version] }), _jsx(TableCell, { className: "text-right", children: c.systemDefault ? (
80
+ // The current default: stated, not offered. Clearing the
81
+ // flag without giving it to another channel is the one
82
+ // state the platform must never be in.
83
+ _jsx("span", { className: "text-xs text-muted-foreground", children: t('list.action.makeDefault.current') })) : canWrite ? (_jsx(Button, { variant: "outline", size: "sm", disabled: !c.active || promoting !== null, title: c.active ? undefined : t('list.action.makeDefault.inactiveHelp'), onClick: () => void makeDefault(c), children: t('list.action.makeDefault') })) : null })] }, c.id))) })] })) }) })] }));
84
+ }
85
+ /**
86
+ * The registry loads a route component through a dynamic-import factory and
87
+ * reads its default export (feature 091, FR-013). The named export stays: it is
88
+ * the spelling this module's own code and its tests use.
89
+ */
90
+ export default SalesChannelsListPage;
91
+ //# sourceMappingURL=SalesChannelsListPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SalesChannelsListPage.js","sourceRoot":"","sources":["../../../src/admin/pages/SalesChannelsListPage.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AdminZoneProps } from '@endora-commerce/contracts';
3
+ /**
4
+ * The channels an organization belongs to (feature 091, P7b).
5
+ *
6
+ * `organizations`' `OrganizationDetail.tsx` used to import
7
+ * `EntityChannelMembership` by path and render it with
8
+ * `entityType="organization"` — one of the three keys of
9
+ * `backend/scripts/ledgers/cross-module-imports/organizations.ts`. The place is
10
+ * `organization.detail.after` now, and the `entityType` is *this module's*
11
+ * constant: an organization's detail screen is the only thing it could be.
12
+ *
13
+ * **Two contributions over one component, not one contribution with a `match`**
14
+ * (Z4, Z13). This wrapper and `ProductChannelMembership` differ only in the
15
+ * `entityType` they pass, and that is the whole cost of `organization.detail.after`
16
+ * and `product.editor.channels` being two places rather than one name narrowed
17
+ * per mount.
18
+ *
19
+ * **No `match`, and it is a decision rather than an omission.** This place has
20
+ * one host and one mount, so there is nothing to narrow.
21
+ * `admin/test/modules/sales_channels/organization-channels-zone.test.tsx`
22
+ * asserts it absent so a later author cannot add one quietly.
23
+ *
24
+ * The host used to pass no `onChanged`, and neither does this: the panel's own
25
+ * refresh is what the operator sees, and a zone has no callback to give it.
26
+ */
27
+ export type OrganizationChannelMembershipProps = AdminZoneProps<'organization.detail.after'>;
28
+ export declare function OrganizationChannelMembership({ organizationId, }: OrganizationChannelMembershipProps): ReactNode;
29
+ /**
30
+ * The zone renderer loads a contribution through a dynamic-import factory and
31
+ * reads its default export (feature 091, FR-013).
32
+ */
33
+ export default OrganizationChannelMembership;
34
+ //# sourceMappingURL=OrganizationChannelMembership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrganizationChannelMembership.d.ts","sourceRoot":"","sources":["../../../src/admin/zones/OrganizationChannelMembership.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,kCAAkC,GAAG,cAAc,CAAC,2BAA2B,CAAC,CAAC;AAE7F,wBAAgB,6BAA6B,CAAC,EAC5C,cAAc,GACf,EAAE,kCAAkC,GAAG,SAAS,CAEhD;AAED;;;GAGG;AACH,eAAe,6BAA6B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { EntityChannelMembership } from '../components/EntityChannelMembership.js';
3
+ export function OrganizationChannelMembership({ organizationId, }) {
4
+ return _jsx(EntityChannelMembership, { entityType: "organization", entityId: organizationId });
5
+ }
6
+ /**
7
+ * The zone renderer loads a contribution through a dynamic-import factory and
8
+ * reads its default export (feature 091, FR-013).
9
+ */
10
+ export default OrganizationChannelMembership;
11
+ //# sourceMappingURL=OrganizationChannelMembership.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrganizationChannelMembership.js","sourceRoot":"","sources":["../../../src/admin/zones/OrganizationChannelMembership.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AA4BnF,MAAM,UAAU,6BAA6B,CAAC,EAC5C,cAAc,GACqB;IACnC,OAAO,KAAC,uBAAuB,IAAC,UAAU,EAAC,cAAc,EAAC,QAAQ,EAAE,cAAc,GAAI,CAAC;AACzF,CAAC;AAED;;;GAGG;AACH,eAAe,6BAA6B,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AdminZoneProps } from '@endora-commerce/contracts';
3
+ /**
4
+ * The body of the product editor's Channels tab (feature 091, P7a).
5
+ *
6
+ * `catalog`'s `ProductEditor.tsx` used to import `EntityChannelMembership` and
7
+ * render it as the whole tab body, passing `entityType="product"`. The place is
8
+ * `product.editor.channels` now, and the `entityType` is *this module's*
9
+ * constant: the zone is the product editor's, so nothing else it could be.
10
+ *
11
+ * **No `match`, and it is a decision rather than an omission.** `match` narrows
12
+ * the mounts of one place (Z13); this place has one host and one mount.
13
+ * `admin/test/modules/sales_channels/product-channels-zone.test.tsx` asserts it
14
+ * absent so a later author cannot add one quietly.
15
+ */
16
+ export type ProductChannelMembershipProps = AdminZoneProps<'product.editor.channels'>;
17
+ export declare function ProductChannelMembership({ productId, }: ProductChannelMembershipProps): ReactNode;
18
+ /**
19
+ * The zone renderer loads a contribution through a dynamic-import factory and
20
+ * reads its default export (feature 091, FR-013).
21
+ */
22
+ export default ProductChannelMembership;
23
+ //# sourceMappingURL=ProductChannelMembership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductChannelMembership.d.ts","sourceRoot":"","sources":["../../../src/admin/zones/ProductChannelMembership.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,6BAA6B,GAAG,cAAc,CAAC,yBAAyB,CAAC,CAAC;AAEtF,wBAAgB,wBAAwB,CAAC,EACvC,SAAS,GACV,EAAE,6BAA6B,GAAG,SAAS,CAE3C;AAED;;;GAGG;AACH,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { EntityChannelMembership } from '../components/EntityChannelMembership.js';
3
+ export function ProductChannelMembership({ productId, }) {
4
+ return _jsx(EntityChannelMembership, { entityType: "product", entityId: productId });
5
+ }
6
+ /**
7
+ * The zone renderer loads a contribution through a dynamic-import factory and
8
+ * reads its default export (feature 091, FR-013).
9
+ */
10
+ export default ProductChannelMembership;
11
+ //# sourceMappingURL=ProductChannelMembership.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductChannelMembership.js","sourceRoot":"","sources":["../../../src/admin/zones/ProductChannelMembership.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAiBnF,MAAM,UAAU,wBAAwB,CAAC,EACvC,SAAS,GACqB;IAC9B,OAAO,KAAC,uBAAuB,IAAC,UAAU,EAAC,SAAS,EAAC,QAAQ,EAAE,SAAS,GAAI,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,53 @@
1
+ import type { Command } from '@endora-commerce/platform/commands';
2
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
3
+ /**
4
+ * Moving the `system_default` flag — feature 072 / D-51, Principle XIII.
5
+ *
6
+ * The third property of the product owner's invariant: exactly one channel is
7
+ * the default, and the flag is **movable**. The first two have been enforced
8
+ * since feature 005 (a partial unique index, plus a boot reconciler and three
9
+ * service refusals); this is the one nothing in the platform could do.
10
+ *
11
+ * It is a Command because it is the definition of a sensitive write: it changes
12
+ * where every unattributed order, every platform-wide fallback read and every
13
+ * orphan rebind lands, and the operator who did it has to be recoverable from
14
+ * the audit trail. The audit row names both channels — `stateBefore` the one
15
+ * that held the flag, `stateAfter` the one that holds it now — because "which
16
+ * channel was the default in March" is the question this row exists to answer.
17
+ *
18
+ * **Not `reversible`.** The registry's `reversible` marks commands wired into
19
+ * the operator-facing undo of feature 054 (a `RevertRecord` set on a bulk
20
+ * operation row), and there is no such surface for a single channel flag. The
21
+ * move is undone by making the same call against the previous default, whose
22
+ * code the response and the audit row both carry — an inverse an operator can
23
+ * perform, not a stored revert state.
24
+ */
25
+ export interface SetSystemDefaultChannelResult {
26
+ /** The channel that holds the flag once the command has run. */
27
+ readonly channel: SalesChannel;
28
+ /**
29
+ * The code that held the flag when the command started — the channel a
30
+ * subsequent `set-default` would move it back to. `null` only in the state the
31
+ * boot reconciler exists to repair: a table with rows but no default at all.
32
+ */
33
+ readonly previousDefaultCode: string | null;
34
+ /** False when the target already held the flag; the call wrote nothing. */
35
+ readonly changed: boolean;
36
+ }
37
+ /**
38
+ * Demote-then-promote, in this order, inside the Command Bus's transaction.
39
+ *
40
+ * `sales_channels_one_system_default` is a partial unique index and Postgres
41
+ * cannot defer one, so the two updates are two statements with the demote
42
+ * first — the flag has to be off the old row before it can land on the new one.
43
+ * Assigning both and flushing once is **not** equivalent: MikroORM decides the
44
+ * statement order for a change set, and the promote-first ordering trips the
45
+ * index. That is also why a concurrent second move blocks on the index and then
46
+ * fails closed rather than racing, which is the behaviour we want.
47
+ *
48
+ * Both guards live inside the transaction rather than in the caller: the
49
+ * caller's lookup produces the 404 and a readable message, this one is what is
50
+ * still true at the moment of the write.
51
+ */
52
+ export declare function makeSetSystemDefaultChannelCommand(channelId: string): Command<SetSystemDefaultChannelResult>;
53
+ //# sourceMappingURL=set-default.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-default.command.d.ts","sourceRoot":"","sources":["../../../src/backend/commands/set-default.command.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,6BAA6B;IAC5C,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,6BAA6B,CAAC,CAmFxC"}
@@ -0,0 +1,87 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ERROR_CODES } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * Demote-then-promote, in this order, inside the Command Bus's transaction.
7
+ *
8
+ * `sales_channels_one_system_default` is a partial unique index and Postgres
9
+ * cannot defer one, so the two updates are two statements with the demote
10
+ * first — the flag has to be off the old row before it can land on the new one.
11
+ * Assigning both and flushing once is **not** equivalent: MikroORM decides the
12
+ * statement order for a change set, and the promote-first ordering trips the
13
+ * index. That is also why a concurrent second move blocks on the index and then
14
+ * fails closed rather than racing, which is the behaviour we want.
15
+ *
16
+ * Both guards live inside the transaction rather than in the caller: the
17
+ * caller's lookup produces the 404 and a readable message, this one is what is
18
+ * still true at the moment of the write.
19
+ */
20
+ export function makeSetSystemDefaultChannelCommand(channelId) {
21
+ return {
22
+ action: 'sales_channel.set_default',
23
+ objectType: 'sales_channel',
24
+ objectId: channelId,
25
+ run: async ({ em }) => {
26
+ const target = await em.findOne(SalesChannel, { id: channelId });
27
+ if (target === null) {
28
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'The sales channel to promote no longer exists.');
29
+ }
30
+ const current = await em.findOne(SalesChannel, { systemDefault: true });
31
+ // Already the default: a no-op success, not an error. A retried request and
32
+ // a double-clicked button answer the same 200, and `skipAudit` keeps "no
33
+ // write ⇒ no audit row" honest.
34
+ if (target.systemDefault) {
35
+ return {
36
+ result: { channel: target, previousDefaultCode: target.code, changed: false },
37
+ skipAudit: true,
38
+ };
39
+ }
40
+ // A default nobody can transact on is not a default. The mirror of the
41
+ // three refusals that keep the default channel from being deactivated or
42
+ // deleted (`sales-channels.service.ts:231/272/317`): they stop the flag's
43
+ // holder from going inactive, this stops an inactive channel from becoming
44
+ // the holder.
45
+ if (!target.active) {
46
+ throw new HttpError(422, ERROR_CODES.INACTIVE_SALES_CHANNEL, `Sales channel "${target.code}" is inactive and cannot become the system default. ` +
47
+ `Activate it first.`);
48
+ }
49
+ const before = current === null ? null : { id: current.id, code: current.code };
50
+ // 1. Demote, and get it into the database before step 2 exists.
51
+ if (current !== null) {
52
+ current.systemDefault = false;
53
+ current.version += 1;
54
+ await em.flush();
55
+ }
56
+ // 2. Promote.
57
+ target.systemDefault = true;
58
+ target.version += 1;
59
+ await em.flush();
60
+ return {
61
+ result: {
62
+ channel: target,
63
+ previousDefaultCode: current?.code ?? null,
64
+ changed: true,
65
+ },
66
+ before,
67
+ after: { id: target.id, code: target.code },
68
+ };
69
+ },
70
+ event: (result) => result.changed
71
+ ? {
72
+ eventName: 'sales_channels.lifecycle_changed',
73
+ payload: {
74
+ eventId: randomUUID(),
75
+ occurredAt: new Date().toISOString(),
76
+ channelCode: result.channel.code,
77
+ // Two rows changed identity-bearing state, and the cache is keyed
78
+ // by code with `systemDefault` inside the cached value — so the
79
+ // demoted channel's entry is stale too. Same shape the delete path
80
+ // uses for the same reason.
81
+ invalidateAll: true,
82
+ },
83
+ }
84
+ : undefined,
85
+ };
86
+ }
87
+ //# sourceMappingURL=set-default.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-default.command.js","sourceRoot":"","sources":["../../../src/backend/commands/set-default.command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { FastifyRequest } from 'fastify';
3
+ import type { DictionaryValidator, SalesChannelAttributionRegistryPort } from '@endora-commerce/contracts';
4
+ import type { CommandBus } from '@endora-commerce/platform/commands';
5
+ import type { EventBus } from '@endora-commerce/platform/events';
6
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
7
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
8
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
9
+ import type { SalesChannelsCache } from '@endora-commerce/platform/kernel';
10
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
11
+ import { SalesChannelsService } from './services/sales-channels.service.js';
12
+ import type { AdminAuditContext } from './services/sales-channels.service.js';
13
+ /**
14
+ * `sales_channels` — what is left after the kernel took the resolution
15
+ * machinery (feature 072, wave 2, T110).
16
+ *
17
+ * T019 moved the cache, the resolver, the membership service, the middleware
18
+ * and the default-channel reconciler into `kernel/sales-channels/`, and this
19
+ * conversion finishes the thought: a composition root composes those through
20
+ * `composeSalesChannelsKernel`, and the module keeps only what an operator
21
+ * would recognise as the module — the admin CRUD service and its routes.
22
+ *
23
+ * The division is not cosmetic. Resolving which channel a request belongs to
24
+ * backs every channel-scoped read in the platform (Principle XII), so it must
25
+ * not be gated on this module: switching the administration screens off would
26
+ * otherwise leave every storefront request with no resolved channel.
27
+ *
28
+ * **`dictionaryValidator` was optional, and never once supplied.** Neither
29
+ * composition passed it, so `SalesChannelsService` has been skipping the
30
+ * language- and currency-code checks it declares — creating a channel with a
31
+ * language nothing in the platform speaks is accepted today, silently. It is a
32
+ * declared dependency now, not an option: its absence removes a check rather
33
+ * than a capability, which is the line these conversions have been drawing.
34
+ */
35
+ export interface SalesChannelsCradle {
36
+ readonly emFactory: () => EntityManager;
37
+ readonly eventBus: EventBus;
38
+ readonly auditLogService: AuditPort;
39
+ /** D-51 — moving the system-default flag is a Command (Principle XIII). */
40
+ readonly commandBus: CommandBus;
41
+ readonly requireAdmin: RequireAdminFactory;
42
+ readonly adminAuditActorResolver: (req: FastifyRequest) => AdminAuditContext;
43
+ /** Kernel-composed, so it is the same cache the resolver reads through. */
44
+ readonly salesChannelsCache: SalesChannelsCache;
45
+ readonly salesChannelMembershipPort: SalesChannelMembershipPort;
46
+ readonly dictionaryValidator: DictionaryValidator;
47
+ /**
48
+ * Owned here, contributed to from `orders` and `quote_requests` (feature 075,
49
+ * D-87). A plain registration, not a port: a contributor pushes into it from
50
+ * a boot hook, and a gate there would stop the backend from starting.
51
+ */
52
+ readonly salesChannelAttributionRegistry: SalesChannelAttributionRegistryPort;
53
+ readonly salesChannelsService: SalesChannelsService;
54
+ }
55
+ export declare function registerModule(ctx: ModuleContext): void;
56
+ /**
57
+ * This module owns **no persisted entity**, and says so with an empty array
58
+ * rather than by omission (D-168).
59
+ *
60
+ * The two are not the same thing to the platform. When the package is
61
+ * *installed*, `src/packages/package-runtime.ts` reads `exported['entities']`
62
+ * and answers a missing export with `[]` — so "this module has no table" and
63
+ * "somebody forgot the array" arrive at the host as the same silence, and the
64
+ * only symptom of the second is a query against a table nobody created. The
65
+ * declaration is what makes the first case a statement.
66
+ *
67
+ * A first entity added here goes in this array in the same merge request, and
68
+ * `test/unit/packages/module-package-entity-surface.test.ts` is what says so.
69
+ */
70
+ export declare const entities: readonly never[];
71
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EACV,mBAAmB,EACnB,mCAAmC,EACpC,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAEnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,sCAAsC,CAAC;AAIjG;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,iBAAiB,CAAC;IAC7E,2EAA2E;IAC3E,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAChD,QAAQ,CAAC,0BAA0B,EAAE,0BAA0B,CAAC;IAChE,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD;;;;OAIG;IACH,QAAQ,CAAC,+BAA+B,EAAE,mCAAmC,CAAC;IAC9E,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;CACrD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAkFvD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,KAAK,EAAO,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { SalesChannelAttributionRegistry } from './services/sales-channel-attribution-registry.js';
3
+ import { SalesChannelsService } from './services/sales-channels.service.js';
4
+ import { registerSalesChannelsAdminRoutes } from './routes.admin.js';
5
+ import { registerSalesChannelsStorefrontRoutes } from './routes.storefront.js';
6
+ export function registerModule(ctx) {
7
+ ctx.di.register({
8
+ /**
9
+ * "Who is still attributed to this channel?" — the contribution seam the
10
+ * FR-006 delete guard reads (feature 075, D-87). `SalesChannelsService`
11
+ * used to answer it here, with one statement naming `orders` and
12
+ * `quote_requests`; each owner counts its own rows now.
13
+ *
14
+ * `di.register`, not `di.providePort`: `orders` and `quote_requests` push
15
+ * into this from their own boot hooks, and a gate resolved there would
16
+ * refuse at a point in the boot that has nothing to answer. This module is
17
+ * `nonDeactivatable`, so there is no flip for a gate to protect either.
18
+ */
19
+ salesChannelAttributionRegistry: ctx
20
+ .asFunction(() => new SalesChannelAttributionRegistry())
21
+ .singleton(),
22
+ });
23
+ ctx.di.providePort('salesChannelsService', ctx
24
+ .asFunction(({ emFactory, eventBus, auditLogService, commandBus, salesChannelAttributionRegistry, }) => new SalesChannelsService(emFactory, eventBus,
25
+ // Another module's port, so it is resolved per call rather than
26
+ // captured. Required since feature 075 — the branch its absence
27
+ // used to take was a raw `select` over `languages` and
28
+ // `currencies`, and it is gone.
29
+ lazyPort(ctx, 'dictionaryValidator'),
30
+ // This module's own registration, so it is captured like `emFactory`.
31
+ salesChannelAttributionRegistry,
32
+ // Feature 120 (FR-015) — which table holds each entity type's
33
+ // memberships, said by the module that owns the entity type. A
34
+ // kernel registration a root contributes, resolved per call rather
35
+ // than captured for the same reason `salesChannelsCache` is: the
36
+ // registry is filled by every owning module's boot hook, so a value
37
+ // captured while this module registers would be the empty one.
38
+ lazyPort(ctx, 'salesChannelBridgeRegistry'), auditLogService,
39
+ // Resolved per call rather than captured: the cache is composed by a
40
+ // root. Narrowed to the invalidating half (D-93): this service drops
41
+ // the cache at its write seam and must not be able to seed it, which
42
+ // is the resolver's job.
43
+ lazyPort(ctx, 'salesChannelsCache'),
44
+ // A deployment input, like `emFactory` and `eventBus` — not a port,
45
+ // so it is captured with them.
46
+ commandBus))
47
+ .singleton());
48
+ ctx.routes(async (app) => {
49
+ const { salesChannelMembershipPort, requireAdmin } = ctx.cradle();
50
+ await registerSalesChannelsAdminRoutes(app, {
51
+ // Lazily, even though the module owns this port: route *registration* runs
52
+ // inside `buildServer` whatever the module's effective state is, so
53
+ // destructuring the gate here would stop the next start instead of
54
+ // stopping the routes (D-40).
55
+ salesChannelsService: lazyPort(ctx, 'salesChannelsService'),
56
+ membershipService: salesChannelMembershipPort,
57
+ requireAdmin,
58
+ resolveAdminAuditContext: (req) => ctx.cradle().adminAuditActorResolver(req),
59
+ });
60
+ // The public read of the resolved channel. It resolves nothing itself and
61
+ // takes no dependency, so it is registered beside the admin surface rather
62
+ // than in a second `ctx.routes` call.
63
+ await registerSalesChannelsStorefrontRoutes(app);
64
+ });
65
+ }
66
+ /**
67
+ * This module owns **no persisted entity**, and says so with an empty array
68
+ * rather than by omission (D-168).
69
+ *
70
+ * The two are not the same thing to the platform. When the package is
71
+ * *installed*, `src/packages/package-runtime.ts` reads `exported['entities']`
72
+ * and answers a missing export with `[]` — so "this module has no table" and
73
+ * "somebody forgot the array" arrive at the host as the same silence, and the
74
+ * only symptom of the second is a query against a table nobody created. The
75
+ * declaration is what makes the first case a statement.
76
+ *
77
+ * A first entity added here goes in this array in the same merge request, and
78
+ * `test/unit/packages/module-package-entity-surface.test.ts` is what says so.
79
+ */
80
+ export const entities = [];
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAO5D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kDAAkD,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5E,OAAO,EAAE,gCAAgC,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,qCAAqC,EAAE,MAAM,wBAAwB,CAAC;AA8C/E,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;QACd;;;;;;;;;;WAUG;QACH,+BAA+B,EAAE,GAAG;aACjC,UAAU,CACT,GAAwC,EAAE,CAAC,IAAI,+BAA+B,EAAE,CACjF;aACA,SAAS,EAAE;KACf,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,sBAAsB,EACtB,GAAG;SACA,UAAU,CACT,CAAC,EACC,SAAS,EACT,QAAQ,EACR,eAAe,EACf,UAAU,EACV,+BAA+B,GACX,EAAE,EAAE,CACxB,IAAI,oBAAoB,CACtB,SAAS,EACT,QAAQ;IACR,gEAAgE;IAChE,gEAAgE;IAChE,uDAAuD;IACvD,gCAAgC;IAChC,QAAQ,CAAsB,GAAG,EAAE,qBAAqB,CAAC;IACzD,sEAAsE;IACtE,+BAA+B;IAC/B,8DAA8D;IAC9D,+DAA+D;IAC/D,mEAAmE;IACnE,iEAAiE;IACjE,oEAAoE;IACpE,+DAA+D;IAC/D,QAAQ,CAAoB,GAAG,EAAE,4BAA4B,CAAC,EAC9D,eAAe;IACf,qEAAqE;IACrE,qEAAqE;IACrE,qEAAqE;IACrE,yBAAyB;IACzB,QAAQ,CAAiC,GAAG,EAAE,oBAAoB,CAAC;IACnE,oEAAoE;IACpE,+BAA+B;IAC/B,UAAU,CACX,CACJ;SACA,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,EAAE,0BAA0B,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,MAAM,EAAuB,CAAC;QAEvF,MAAM,gCAAgC,CAAC,GAAG,EAAE;YAC1C,2EAA2E;YAC3E,oEAAoE;YACpE,mEAAmE;YACnE,8BAA8B;YAC9B,oBAAoB,EAAE,QAAQ,CAAuB,GAAG,EAAE,sBAAsB,CAAC;YACjF,iBAAiB,EAAE,0BAA0B;YAC7C,YAAY;YACZ,wBAAwB,EAAE,CAAC,GAAG,EAAE,EAAE,CAChC,GAAG,CAAC,MAAM,EAAuB,CAAC,uBAAuB,CAAC,GAAG,CAAC;SACjE,CAAC,CAAC;QAEH,0EAA0E;QAC1E,2EAA2E;QAC3E,sCAAsC;QACtC,MAAM,qCAAqC,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAqB,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { AdminAuditContext, SalesChannelsService } from './services/sales-channels.service.js';
3
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
4
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
5
+ export interface SalesChannelsAdminDeps {
6
+ salesChannelsService: SalesChannelsService;
7
+ membershipService: SalesChannelMembershipPort;
8
+ requireAdmin: RequireAdminFactory;
9
+ resolveAdminAuditContext?: (req: FastifyRequest) => AdminAuditContext;
10
+ }
11
+ export declare function registerSalesChannelsAdminRoutes(app: FastifyInstance, deps: SalesChannelsAdminDeps): Promise<void>;
12
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,cAAc,EAAE,MAAM,SAAS,CAAC;AAW7E,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAEnF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AA4D5E,MAAM,WAAW,sBAAsB;IACrC,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,iBAAiB,EAAE,0BAA0B,CAAC;IAC9C,YAAY,EAAE,mBAAmB,CAAC;IAClC,wBAAwB,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,iBAAiB,CAAC;CACvE;AAED,wBAAsB,gCAAgC,CACpD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAoXf"}