@open-mercato/core 0.7.1-develop.7154.1.981330c924 → 0.7.1-develop.7171.1.9b31dbba45

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 (155) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -2
  3. package/dist/modules/auth/commands/users.js +14 -1
  4. package/dist/modules/auth/commands/users.js.map +2 -2
  5. package/dist/modules/auth/data/entities.js +2 -2
  6. package/dist/modules/auth/data/entities.js.map +2 -2
  7. package/dist/modules/auth/lib/backendChrome.js +26 -3
  8. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  9. package/dist/modules/auth/lib/profile-sections.js +1 -1
  10. package/dist/modules/auth/lib/profile-sections.js.map +2 -2
  11. package/dist/modules/auth/migrations/Migration20260907120000_auth.js +19 -0
  12. package/dist/modules/auth/migrations/Migration20260907120000_auth.js.map +7 -0
  13. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +2 -2
  14. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +1 -1
  15. package/dist/modules/customer_accounts/api/admin/demo-accounts.js +118 -0
  16. package/dist/modules/customer_accounts/api/admin/demo-accounts.js.map +7 -0
  17. package/dist/modules/customer_accounts/api/invitations/accept.js +23 -6
  18. package/dist/modules/customer_accounts/api/invitations/accept.js.map +2 -2
  19. package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js +10 -13
  20. package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js.map +2 -2
  21. package/dist/modules/customer_accounts/backend/customer_accounts/useDemoPortalAccounts.js +28 -0
  22. package/dist/modules/customer_accounts/backend/customer_accounts/useDemoPortalAccounts.js.map +7 -0
  23. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +6 -1
  24. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
  25. package/dist/modules/customer_accounts/data/entities.js +4 -1
  26. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  27. package/dist/modules/customer_accounts/lib/exampleAccounts.js +9 -0
  28. package/dist/modules/customer_accounts/lib/exampleAccounts.js.map +7 -0
  29. package/dist/modules/customer_accounts/migrations/Migration20260827160000_customer_accounts.js +16 -0
  30. package/dist/modules/customer_accounts/migrations/Migration20260827160000_customer_accounts.js.map +7 -0
  31. package/dist/modules/customer_accounts/services/customerInvitationService.js +53 -6
  32. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  33. package/dist/modules/customer_accounts/setup.js +2 -6
  34. package/dist/modules/customer_accounts/setup.js.map +2 -2
  35. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +55 -40
  36. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  37. package/dist/modules/customers/commands/people.js +16 -1
  38. package/dist/modules/customers/commands/people.js.map +2 -2
  39. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +18 -36
  40. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  41. package/dist/modules/entities/api/records.js +18 -15
  42. package/dist/modules/entities/api/records.js.map +3 -3
  43. package/dist/modules/entities/lib/scoped-field-defs.js +52 -0
  44. package/dist/modules/entities/lib/scoped-field-defs.js.map +7 -0
  45. package/dist/modules/entities/lib/validation.js +4 -35
  46. package/dist/modules/entities/lib/validation.js.map +2 -2
  47. package/dist/modules/integrations/backend/integrations/[id]/page.js +32 -54
  48. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  49. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +12 -29
  50. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  51. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +2 -2
  52. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +1 -1
  53. package/dist/modules/progress/lib/progressServiceImpl.js +14 -3
  54. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  55. package/dist/modules/sales/backend/sales/documents/[id]/page.js +9 -9
  56. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  57. package/dist/modules/sales/components/documents/LineItemDialog.js +50 -10
  58. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  59. package/dist/modules/sales/components/documents/PaymentsSection.js +8 -3
  60. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  61. package/dist/modules/sales/components/documents/ReturnEditDialog.js +2 -7
  62. package/dist/modules/sales/components/documents/ReturnEditDialog.js.map +2 -2
  63. package/dist/modules/sales/components/documents/ReturnsSection.js +2 -7
  64. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  65. package/dist/modules/sales/components/documents/ShipmentsSection.js +3 -8
  66. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  67. package/dist/modules/wms/commands/sales-order-assignment.js +12 -0
  68. package/dist/modules/wms/commands/sales-order-assignment.js.map +2 -2
  69. package/dist/modules/wms/data/enrichers.js +25 -0
  70. package/dist/modules/wms/data/enrichers.js.map +2 -2
  71. package/dist/modules/wms/lib/enricherCacheTags.js +9 -0
  72. package/dist/modules/wms/lib/enricherCacheTags.js.map +7 -0
  73. package/dist/modules/wms/lib/invalidateInventoryEnricherCache.js +37 -0
  74. package/dist/modules/wms/lib/invalidateInventoryEnricherCache.js.map +7 -0
  75. package/dist/modules/wms/lib/inventoryEnricherCacheSubscriber.js +14 -0
  76. package/dist/modules/wms/lib/inventoryEnricherCacheSubscriber.js.map +7 -0
  77. package/dist/modules/wms/subscribers/invalidate-enricher-cache-balance.js +12 -0
  78. package/dist/modules/wms/subscribers/invalidate-enricher-cache-balance.js.map +7 -0
  79. package/dist/modules/wms/subscribers/invalidate-enricher-cache-inventory.js +12 -0
  80. package/dist/modules/wms/subscribers/invalidate-enricher-cache-inventory.js.map +7 -0
  81. package/dist/modules/wms/subscribers/invalidate-enricher-cache-movement.js +12 -0
  82. package/dist/modules/wms/subscribers/invalidate-enricher-cache-movement.js.map +7 -0
  83. package/dist/modules/wms/subscribers/invalidate-enricher-cache-profile.js +12 -0
  84. package/dist/modules/wms/subscribers/invalidate-enricher-cache-profile.js.map +7 -0
  85. package/dist/modules/wms/subscribers/invalidate-enricher-cache-reservation.js +12 -0
  86. package/dist/modules/wms/subscribers/invalidate-enricher-cache-reservation.js.map +7 -0
  87. package/dist/modules/wms/subscribers/invalidate-enricher-cache-sales-line.js +12 -0
  88. package/dist/modules/wms/subscribers/invalidate-enricher-cache-sales-line.js.map +7 -0
  89. package/dist/modules/wms/subscribers/invalidate-enricher-cache-sales-order.js +12 -0
  90. package/dist/modules/wms/subscribers/invalidate-enricher-cache-sales-order.js.map +7 -0
  91. package/dist/modules/wms/subscribers/invalidate-enricher-cache-variant.js +12 -0
  92. package/dist/modules/wms/subscribers/invalidate-enricher-cache-variant.js.map +7 -0
  93. package/dist/modules/wms/subscribers/invalidate-enricher-cache-warehouse.js +12 -0
  94. package/dist/modules/wms/subscribers/invalidate-enricher-cache-warehouse.js.map +7 -0
  95. package/package.json +7 -7
  96. package/src/modules/auth/commands/users.ts +14 -1
  97. package/src/modules/auth/data/entities.ts +2 -2
  98. package/src/modules/auth/lib/backendChrome.tsx +46 -2
  99. package/src/modules/auth/lib/profile-sections.tsx +14 -3
  100. package/src/modules/auth/migrations/.snapshot-open-mercato.json +4 -2
  101. package/src/modules/auth/migrations/Migration20260907120000_auth.ts +21 -0
  102. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +2 -2
  103. package/src/modules/customer_accounts/AGENTS.md +3 -1
  104. package/src/modules/customer_accounts/api/admin/demo-accounts.ts +149 -0
  105. package/src/modules/customer_accounts/api/invitations/accept.ts +23 -6
  106. package/src/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.tsx +43 -41
  107. package/src/modules/customer_accounts/backend/customer_accounts/useDemoPortalAccounts.ts +42 -0
  108. package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +10 -3
  109. package/src/modules/customer_accounts/data/entities.ts +5 -1
  110. package/src/modules/customer_accounts/i18n/de.json +4 -2
  111. package/src/modules/customer_accounts/i18n/en.json +4 -2
  112. package/src/modules/customer_accounts/i18n/es.json +4 -2
  113. package/src/modules/customer_accounts/i18n/ko.json +4 -2
  114. package/src/modules/customer_accounts/i18n/pl.json +4 -2
  115. package/src/modules/customer_accounts/lib/exampleAccounts.ts +18 -0
  116. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +5 -7
  117. package/src/modules/customer_accounts/migrations/Migration20260827160000_customer_accounts.ts +23 -0
  118. package/src/modules/customer_accounts/services/customerInvitationService.ts +89 -5
  119. package/src/modules/customer_accounts/setup.ts +2 -6
  120. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +74 -41
  121. package/src/modules/customers/commands/people.ts +15 -0
  122. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +19 -44
  123. package/src/modules/entities/api/records.ts +24 -15
  124. package/src/modules/entities/lib/scoped-field-defs.ts +76 -0
  125. package/src/modules/entities/lib/validation.ts +5 -45
  126. package/src/modules/integrations/backend/integrations/[id]/page.tsx +33 -62
  127. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +13 -37
  128. package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +2 -2
  129. package/src/modules/progress/AGENTS.md +1 -1
  130. package/src/modules/progress/lib/progressServiceImpl.ts +26 -3
  131. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +19 -25
  132. package/src/modules/sales/components/documents/LineItemDialog.tsx +53 -9
  133. package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -8
  134. package/src/modules/sales/components/documents/ReturnEditDialog.tsx +2 -8
  135. package/src/modules/sales/components/documents/ReturnsSection.tsx +8 -7
  136. package/src/modules/sales/components/documents/ShipmentsSection.tsx +9 -8
  137. package/src/modules/sales/i18n/de.json +2 -0
  138. package/src/modules/sales/i18n/en.json +2 -0
  139. package/src/modules/sales/i18n/es.json +2 -0
  140. package/src/modules/sales/i18n/ko.json +2 -0
  141. package/src/modules/sales/i18n/pl.json +2 -0
  142. package/src/modules/wms/commands/sales-order-assignment.ts +20 -0
  143. package/src/modules/wms/data/enrichers.ts +25 -0
  144. package/src/modules/wms/lib/enricherCacheTags.ts +29 -0
  145. package/src/modules/wms/lib/invalidateInventoryEnricherCache.ts +71 -0
  146. package/src/modules/wms/lib/inventoryEnricherCacheSubscriber.ts +30 -0
  147. package/src/modules/wms/subscribers/invalidate-enricher-cache-balance.ts +9 -0
  148. package/src/modules/wms/subscribers/invalidate-enricher-cache-inventory.ts +9 -0
  149. package/src/modules/wms/subscribers/invalidate-enricher-cache-movement.ts +9 -0
  150. package/src/modules/wms/subscribers/invalidate-enricher-cache-profile.ts +9 -0
  151. package/src/modules/wms/subscribers/invalidate-enricher-cache-reservation.ts +9 -0
  152. package/src/modules/wms/subscribers/invalidate-enricher-cache-sales-line.ts +14 -0
  153. package/src/modules/wms/subscribers/invalidate-enricher-cache-sales-order.ts +18 -0
  154. package/src/modules/wms/subscribers/invalidate-enricher-cache-variant.ts +9 -0
  155. package/src/modules/wms/subscribers/invalidate-enricher-cache-warehouse.ts +9 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/lib/enricherCacheTags.ts"],
4
+ "sourcesContent": ["/**\n * Cache tags shared by the WMS inventory enrichers and the write surfaces that\n * invalidate them. Read side and write side MUST use these constants so the two\n * cannot drift \u2014 a tag typo on either side is silent, and the only symptom is\n * inventory that stays stale until the TTL expires.\n *\n * The enricher runner adds `tenant:`, `organization:` and `enricher:` tags of\n * its own, and the cache service prefixes every tag with the current cache\n * tenant, so these are deliberately coarse collection tags rather than\n * per-warehouse ones: `ResponseEnricher.cache.tags` is a static array read from\n * the enricher definition and cannot vary per call. Collection tags\n * over-invalidate slightly; a per-warehouse scheme that the write side could\n * miss would under-invalidate, which is the failure that actually shows wrong\n * stock to a user.\n */\n\n/** Inventory quantities: balances, reservations, movements and profiles. */\nexport const WMS_INVENTORY_CACHE_TAG = 'wms:inventory'\n\n/** Warehouse identity and sales-order warehouse assignment. */\nexport const WMS_WAREHOUSE_CACHE_TAG = 'wms:warehouse'\n\n/**\n * Short by design. The tag invalidations below are the primary freshness\n * mechanism; this TTL is the unconditional backstop for anything they miss \u2014\n * a write that failed to resolve the cache service, a cross-process race, or a\n * write surface added later without wiring invalidation.\n */\nexport const WMS_ENRICHER_CACHE_TTL_MS = 30_000\n"],
5
+ "mappings": "AAiBO,MAAM,0BAA0B;AAGhC,MAAM,0BAA0B;AAQhC,MAAM,4BAA4B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,37 @@
1
+ import { runWithCacheTenant } from "@open-mercato/cache";
2
+ import { createLogger } from "@open-mercato/shared/lib/logger";
3
+ import {
4
+ WMS_INVENTORY_CACHE_TAG,
5
+ WMS_WAREHOUSE_CACHE_TAG
6
+ } from "./enricherCacheTags.js";
7
+ const logger = createLogger("wms").child({ component: "enricher-cache" });
8
+ function resolveCache(container) {
9
+ if (!container?.resolve) return null;
10
+ for (const name of ["cache", "cacheService"]) {
11
+ try {
12
+ const candidate = container.resolve(name);
13
+ if (candidate && typeof candidate.deleteByTags === "function") return candidate;
14
+ } catch {
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ function tagsForScope(scope) {
20
+ if (scope === "inventory") return [WMS_INVENTORY_CACHE_TAG];
21
+ if (scope === "warehouse") return [WMS_WAREHOUSE_CACHE_TAG];
22
+ return [WMS_INVENTORY_CACHE_TAG, WMS_WAREHOUSE_CACHE_TAG];
23
+ }
24
+ async function invalidateWmsInventoryEnricherCache(container, tenantId, scope = "all") {
25
+ if (!tenantId) return;
26
+ const cache = resolveCache(container);
27
+ if (!cache) return;
28
+ try {
29
+ await runWithCacheTenant(tenantId, () => cache.deleteByTags(tagsForScope(scope)));
30
+ } catch (err) {
31
+ logger.warn("Failed to invalidate WMS inventory enricher cache", { tenantId, scope, err });
32
+ }
33
+ }
34
+ export {
35
+ invalidateWmsInventoryEnricherCache
36
+ };
37
+ //# sourceMappingURL=invalidateInventoryEnricherCache.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/lib/invalidateInventoryEnricherCache.ts"],
4
+ "sourcesContent": ["// Drop the cached WMS inventory enrichment for a tenant after a write that\n// changed the data it was derived from.\n//\n// The three WMS inventory enrichers cache their cross-module reads read-through\n// with a short TTL (see `enricherCacheTags.ts`). Every write surface that moves\n// balances, reservations, movements, profiles, warehouses or the sales-order\n// warehouse assignment drops the matching tags here, so a user sees the new\n// stock on the next request rather than after the TTL.\n//\n// The cache service prefixes tags with the *current* cache tenant, which is an\n// AsyncLocalStorage value set on the request path. A subscriber or command side\n// effect running outside that scope would otherwise write its invalidation\n// against the `tenant:global:` prefix and silently miss every entry the request\n// path stored. `runWithCacheTenant` re-enters the right scope, mirroring\n// `directory/subscribers/invalidateOrgScopeCache.ts`.\n\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n WMS_INVENTORY_CACHE_TAG,\n WMS_WAREHOUSE_CACHE_TAG,\n} from './enricherCacheTags'\n\nconst logger = createLogger('wms').child({ component: 'enricher-cache' })\n\ntype CacheService = {\n deleteByTags(tags: string[]): Promise<number>\n}\n\nexport type EnricherCacheScope = 'inventory' | 'warehouse' | 'all'\n\ntype Resolver = { resolve: <T = unknown>(name: string) => T }\n\nfunction resolveCache(container: Resolver | null | undefined): CacheService | null {\n if (!container?.resolve) return null\n for (const name of ['cache', 'cacheService']) {\n try {\n const candidate = container.resolve<CacheService>(name)\n if (candidate && typeof candidate.deleteByTags === 'function') return candidate\n } catch {\n // try the next registration name\n }\n }\n return null\n}\n\nfunction tagsForScope(scope: EnricherCacheScope): string[] {\n if (scope === 'inventory') return [WMS_INVENTORY_CACHE_TAG]\n if (scope === 'warehouse') return [WMS_WAREHOUSE_CACHE_TAG]\n return [WMS_INVENTORY_CACHE_TAG, WMS_WAREHOUSE_CACHE_TAG]\n}\n\n/**\n * Best-effort tag invalidation. A failure is logged and swallowed: an\n * invalidation that throws must never fail the write that triggered it, and the\n * enricher TTL bounds how long a missed drop can serve stale data.\n */\nexport async function invalidateWmsInventoryEnricherCache(\n container: Resolver | null | undefined,\n tenantId: string | null | undefined,\n scope: EnricherCacheScope = 'all',\n): Promise<void> {\n if (!tenantId) return\n const cache = resolveCache(container)\n if (!cache) return\n try {\n await runWithCacheTenant(tenantId, () => cache.deleteByTags(tagsForScope(scope)))\n } catch (err) {\n logger.warn('Failed to invalidate WMS inventory enricher cache', { tenantId, scope, err })\n }\n}\n"],
5
+ "mappings": "AAgBA,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS,aAAa,KAAK,EAAE,MAAM,EAAE,WAAW,iBAAiB,CAAC;AAUxE,SAAS,aAAa,WAA6D;AACjF,MAAI,CAAC,WAAW,QAAS,QAAO;AAChC,aAAW,QAAQ,CAAC,SAAS,cAAc,GAAG;AAC5C,QAAI;AACF,YAAM,YAAY,UAAU,QAAsB,IAAI;AACtD,UAAI,aAAa,OAAO,UAAU,iBAAiB,WAAY,QAAO;AAAA,IACxE,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAAqC;AACzD,MAAI,UAAU,YAAa,QAAO,CAAC,uBAAuB;AAC1D,MAAI,UAAU,YAAa,QAAO,CAAC,uBAAuB;AAC1D,SAAO,CAAC,yBAAyB,uBAAuB;AAC1D;AAOA,eAAsB,oCACpB,WACA,UACA,QAA4B,OACb;AACf,MAAI,CAAC,SAAU;AACf,QAAM,QAAQ,aAAa,SAAS;AACpC,MAAI,CAAC,MAAO;AACZ,MAAI;AACF,UAAM,mBAAmB,UAAU,MAAM,MAAM,aAAa,aAAa,KAAK,CAAC,CAAC;AAAA,EAClF,SAAS,KAAK;AACZ,WAAO,KAAK,qDAAqD,EAAE,UAAU,OAAO,IAAI,CAAC;AAAA,EAC3F;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ import {
2
+ invalidateWmsInventoryEnricherCache
3
+ } from "./invalidateInventoryEnricherCache.js";
4
+ function createInventoryEnricherCacheHandler(scope) {
5
+ return async function handle(payload, ctx) {
6
+ const data = payload ?? {};
7
+ const tenantId = typeof data.tenantId === "string" && data.tenantId.length > 0 ? data.tenantId : ctx.tenantId ?? null;
8
+ await invalidateWmsInventoryEnricherCache(ctx, tenantId, scope);
9
+ };
10
+ }
11
+ export {
12
+ createInventoryEnricherCacheHandler
13
+ };
14
+ //# sourceMappingURL=inventoryEnricherCacheSubscriber.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/lib/inventoryEnricherCacheSubscriber.ts"],
4
+ "sourcesContent": ["// Shared handler behind the WMS inventory enricher-cache invalidation\n// subscribers. Each subscriber file binds it to one event pattern, because\n// `metadata.event` takes a single pattern and the matcher is single-segment \u2014\n// `wms.*` does NOT match `wms.inventory_balance.created`, so every event family\n// the enrichers read from is registered explicitly.\n//\n// It lives here rather than under `subscribers/` because the module generator\n// scans that directory recursively and treats every file it finds as a\n// subscriber.\n\nimport {\n invalidateWmsInventoryEnricherCache,\n type EnricherCacheScope,\n} from './invalidateInventoryEnricherCache'\n\ntype SubscriberContext = {\n resolve: <T = unknown>(name: string) => T\n tenantId?: string | null\n}\n\nexport function createInventoryEnricherCacheHandler(scope: EnricherCacheScope) {\n return async function handle(payload: unknown, ctx: SubscriberContext): Promise<void> {\n const data = (payload ?? {}) as Record<string, unknown>\n const tenantId =\n typeof data.tenantId === 'string' && data.tenantId.length > 0\n ? data.tenantId\n : ctx.tenantId ?? null\n await invalidateWmsInventoryEnricherCache(ctx, tenantId, scope)\n }\n}\n"],
5
+ "mappings": "AAUA;AAAA,EACE;AAAA,OAEK;AAOA,SAAS,oCAAoC,OAA2B;AAC7E,SAAO,eAAe,OAAO,SAAkB,KAAuC;AACpF,UAAM,OAAQ,WAAW,CAAC;AAC1B,UAAM,WACJ,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,IACxD,KAAK,WACL,IAAI,YAAY;AACtB,UAAM,oCAAoC,KAAK,UAAU,KAAK;AAAA,EAChE;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "wms.inventory_balance.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-balance"
6
+ };
7
+ var invalidate_enricher_cache_balance_default = createInventoryEnricherCacheHandler("inventory");
8
+ export {
9
+ invalidate_enricher_cache_balance_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-balance.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-balance.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'wms.inventory_balance.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-balance',\n}\n\nexport default createInventoryEnricherCacheHandler('inventory')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,4CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "wms.inventory.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-inventory"
6
+ };
7
+ var invalidate_enricher_cache_inventory_default = createInventoryEnricherCacheHandler("inventory");
8
+ export {
9
+ invalidate_enricher_cache_inventory_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-inventory.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-inventory.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'wms.inventory.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-inventory',\n}\n\nexport default createInventoryEnricherCacheHandler('inventory')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,8CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "wms.inventory_movement.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-movement"
6
+ };
7
+ var invalidate_enricher_cache_movement_default = createInventoryEnricherCacheHandler("inventory");
8
+ export {
9
+ invalidate_enricher_cache_movement_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-movement.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-movement.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'wms.inventory_movement.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-movement',\n}\n\nexport default createInventoryEnricherCacheHandler('inventory')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,6CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "wms.inventory_profile.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-profile"
6
+ };
7
+ var invalidate_enricher_cache_profile_default = createInventoryEnricherCacheHandler("inventory");
8
+ export {
9
+ invalidate_enricher_cache_profile_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-profile.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-profile.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'wms.inventory_profile.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-profile',\n}\n\nexport default createInventoryEnricherCacheHandler('inventory')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,4CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "wms.inventory_reservation.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-reservation"
6
+ };
7
+ var invalidate_enricher_cache_reservation_default = createInventoryEnricherCacheHandler("inventory");
8
+ export {
9
+ invalidate_enricher_cache_reservation_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-reservation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-reservation.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'wms.inventory_reservation.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-reservation',\n}\n\nexport default createInventoryEnricherCacheHandler('inventory')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,gDAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "sales.line.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-sales-line"
6
+ };
7
+ var invalidate_enricher_cache_sales_line_default = createInventoryEnricherCacheHandler("warehouse");
8
+ export {
9
+ invalidate_enricher_cache_sales_line_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-sales-line.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-sales-line.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\n// The sales module declares `sales.line.*` in its event registry and documents\n// it publicly, but no write surface emits it today \u2014 line writes ride the order\n// commands' `sales.order.*`. Registering the pattern anyway costs one ephemeral\n// subscription and means that wiring those events later cannot silently\n// reintroduce the staleness `invalidate-enricher-cache-sales-order.ts` closes.\nexport const metadata = {\n event: 'sales.line.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-sales-line',\n}\n\nexport default createInventoryEnricherCacheHandler('warehouse')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAO7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,+CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "sales.order.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-sales-order"
6
+ };
7
+ var invalidate_enricher_cache_sales_order_default = createInventoryEnricherCacheHandler("warehouse");
8
+ export {
9
+ invalidate_enricher_cache_sales_order_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-sales-order.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-sales-order.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\n// `wms.sales-order-inventory` reads the order's lines, and both the required\n// quantity behind `reservationSummary.status` and the variant list behind\n// `stockSummary` come from them. Order lines are written inside the sales order\n// commands, which emit `sales.order.*` \u2014 not a WMS event \u2014 so without this the\n// enrichment would stay stale for the whole TTL after a line edit.\n//\n// Scope `warehouse` rather than `inventory`: `wms:warehouse` is declared by the\n// sales-order enricher alone, so this drops exactly its entries and leaves the\n// two catalog enrichers' caches intact.\nexport const metadata = {\n event: 'sales.order.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-sales-order',\n}\n\nexport default createInventoryEnricherCacheHandler('warehouse')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAW7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,gDAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "catalog.variant.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-variant"
6
+ };
7
+ var invalidate_enricher_cache_variant_default = createInventoryEnricherCacheHandler("inventory");
8
+ export {
9
+ invalidate_enricher_cache_variant_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-variant.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-variant.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'catalog.variant.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-variant',\n}\n\nexport default createInventoryEnricherCacheHandler('inventory')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,4CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from "../lib/inventoryEnricherCacheSubscriber.js";
2
+ const metadata = {
3
+ event: "wms.warehouse.*",
4
+ persistent: false,
5
+ id: "wms:invalidate-enricher-cache-warehouse"
6
+ };
7
+ var invalidate_enricher_cache_warehouse_default = createInventoryEnricherCacheHandler("warehouse");
8
+ export {
9
+ invalidate_enricher_cache_warehouse_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=invalidate-enricher-cache-warehouse.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/wms/subscribers/invalidate-enricher-cache-warehouse.ts"],
4
+ "sourcesContent": ["import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'\n\nexport const metadata = {\n event: 'wms.warehouse.*',\n persistent: false,\n id: 'wms:invalidate-enricher-cache-warehouse',\n}\n\nexport default createInventoryEnricherCacheHandler('warehouse')\n"],
5
+ "mappings": "AAAA,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,8CAAQ,oCAAoC,WAAW;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.7.1-develop.7154.1.981330c924",
3
+ "version": "0.7.1-develop.7171.1.9b31dbba45",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -252,16 +252,16 @@
252
252
  "zod": "^4.4.3"
253
253
  },
254
254
  "peerDependencies": {
255
- "@open-mercato/ai-assistant": "0.7.1-develop.7154.1.981330c924",
256
- "@open-mercato/shared": "0.7.1-develop.7154.1.981330c924",
257
- "@open-mercato/ui": "0.7.1-develop.7154.1.981330c924",
255
+ "@open-mercato/ai-assistant": "0.7.1-develop.7171.1.9b31dbba45",
256
+ "@open-mercato/shared": "0.7.1-develop.7171.1.9b31dbba45",
257
+ "@open-mercato/ui": "0.7.1-develop.7171.1.9b31dbba45",
258
258
  "react": "^19.0.0",
259
259
  "react-dom": "^19.0.0"
260
260
  },
261
261
  "devDependencies": {
262
- "@open-mercato/ai-assistant": "0.7.1-develop.7154.1.981330c924",
263
- "@open-mercato/shared": "0.7.1-develop.7154.1.981330c924",
264
- "@open-mercato/ui": "0.7.1-develop.7154.1.981330c924",
262
+ "@open-mercato/ai-assistant": "0.7.1-develop.7171.1.9b31dbba45",
263
+ "@open-mercato/shared": "0.7.1-develop.7171.1.9b31dbba45",
264
+ "@open-mercato/ui": "0.7.1-develop.7171.1.9b31dbba45",
265
265
  "@testing-library/dom": "^10.4.1",
266
266
  "@testing-library/jest-dom": "^7.0.0",
267
267
  "@testing-library/react": "^16.3.1",
@@ -15,7 +15,16 @@ import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
15
15
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
16
16
  import { UniqueConstraintViolationException, LockMode } from '@mikro-orm/core'
17
17
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
18
- import { User, UserRole, Role, UserAcl, Session, PasswordReset } from '@open-mercato/core/modules/auth/data/entities'
18
+ import {
19
+ User,
20
+ UserRole,
21
+ Role,
22
+ UserAcl,
23
+ Session,
24
+ PasswordReset,
25
+ UserSidebarPreference,
26
+ SidebarVariant,
27
+ } from '@open-mercato/core/modules/auth/data/entities'
19
28
  import { Organization } from '@open-mercato/core/modules/directory/data/entities'
20
29
  import { resolveOrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'
21
30
  import { E } from '#generated/entities.ids.generated'
@@ -358,6 +367,8 @@ const createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResul
358
367
  await em.nativeDelete(UserRole, { user: userId })
359
368
  await em.nativeDelete(Session, { user: userId })
360
369
  await em.nativeDelete(PasswordReset, { user: userId })
370
+ await em.nativeDelete(UserSidebarPreference, { user: userId })
371
+ await em.nativeDelete(SidebarVariant, { user: userId })
361
372
 
362
373
  if (snapshot?.custom && Object.keys(snapshot.custom).length) {
363
374
  const reset = buildCustomFieldResetMap(undefined, snapshot.custom)
@@ -937,6 +948,8 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
937
948
  await em.nativeDelete(UserRole, { user: id })
938
949
  await em.nativeDelete(Session, { user: id })
939
950
  await em.nativeDelete(PasswordReset, { user: id })
951
+ await em.nativeDelete(UserSidebarPreference, { user: id })
952
+ await em.nativeDelete(SidebarVariant, { user: id })
940
953
  const removed = await de.deleteOrmEntity({
941
954
  entity: User,
942
955
  where: deleteWhere as FilterQuery<User>,
@@ -84,7 +84,7 @@ export class UserSidebarPreference {
84
84
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
85
85
  id!: string
86
86
 
87
- @ManyToOne(() => User)
87
+ @ManyToOne(() => User, { deleteRule: 'cascade' })
88
88
  user!: User
89
89
 
90
90
  @Property({ name: 'tenant_id', type: 'uuid', nullable: true })
@@ -149,7 +149,7 @@ export class SidebarVariant {
149
149
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
150
150
  id!: string
151
151
 
152
- @ManyToOne(() => User)
152
+ @ManyToOne(() => User, { deleteRule: 'cascade' })
153
153
  user!: User
154
154
 
155
155
  @Property({ name: 'tenant_id', type: 'uuid', nullable: true })
@@ -13,10 +13,14 @@ import type {
13
13
  } from '@open-mercato/shared/modules/navigation/backendChrome'
14
14
  import {
15
15
  buildAdminNav,
16
+ buildProfileSections,
16
17
  buildSettingsSections,
17
18
  computeSettingsPathPrefixes,
18
19
  convertToSectionNavGroups,
20
+ mergeSectionsWithDiscovered,
19
21
  type AdminNavItem,
22
+ type SettingsSection,
23
+ type SettingsSectionItem,
20
24
  } from '@open-mercato/ui/backend/utils/nav'
21
25
  import { resolveRegisteredLucideIconNode } from '@open-mercato/ui/backend/icons/lucideRegistry'
22
26
  import { profilePathPrefixes, profileSections } from './profile-sections'
@@ -106,6 +110,36 @@ export const settingsSectionOrder: Record<string, number> = {
106
110
  'settings.sections.featureToggles': 8,
107
111
  }
108
112
 
113
+ /**
114
+ * Profile section weights, keyed by the untranslated group id, exactly like `settingsSectionOrder`.
115
+ *
116
+ * Only the built-in account section is ranked; a module contributing its own profile group sorts
117
+ * after it on the shared 999 fallback rather than displacing the account pages.
118
+ */
119
+ export const profileSectionOrder: Record<string, number> = {
120
+ 'profile.sections.account': 1,
121
+ }
122
+
123
+ /**
124
+ * Path prefixes that put the shell into profile mode: the static list plus every href the resolved
125
+ * profile sections actually link to.
126
+ *
127
+ * `computeSettingsPathPrefixes` is deliberately not reused — it also registers each item's parent
128
+ * directory, which is harmless for `/backend/settings/*` but would hand profile mode a whole
129
+ * unrelated subtree if a module hosts its profile page outside `/backend/profile/`.
130
+ */
131
+ function resolveProfilePathPrefixes(sections: SettingsSection[]): string[] {
132
+ const prefixes = new Set(profilePathPrefixes)
133
+ const visitItems = (items: SettingsSectionItem[]) => {
134
+ for (const item of items) {
135
+ prefixes.add(item.href)
136
+ if (item.children?.length) visitItems(item.children)
137
+ }
138
+ }
139
+ for (const section of sections) visitItems(section.items)
140
+ return Array.from(prefixes)
141
+ }
142
+
109
143
  type NavGroupWithWeight = Omit<BackendChromeNavGroup, 'id' | 'defaultName' | 'items'> & {
110
144
  id: string
111
145
  defaultName: string
@@ -460,6 +494,14 @@ export async function resolveBackendChromePayload({
460
494
  ),
461
495
  )
462
496
 
497
+ // The profile sidebar mirrors the profile dropdown: the static baseline covers the `navHidden`
498
+ // pages route discovery drops, and every other `pageContext: 'profile'` page is derived from the
499
+ // manifest so a module adding one lands in both surfaces at once (#5594).
500
+ const resolvedProfileSections = mergeSectionsWithDiscovered(
501
+ profileSections,
502
+ buildProfileSections(entries, profileSectionOrder),
503
+ )
504
+
463
505
  const requestOrganizationId = request ? getSelectedOrganizationFromRequest(request) : null
464
506
  const fallbackOrganizationId = selectedOrganizationId ?? requestOrganizationId ?? auth.orgId ?? null
465
507
  const brandOrganizationId = scopedOrganizationId
@@ -507,8 +549,10 @@ export async function resolveBackendChromePayload({
507
549
  groups: appliedGroups.map(({ weight: _weight, ...group }) => group),
508
550
  settingsSections,
509
551
  settingsPathPrefixes: computeSettingsPathPrefixes(buildSettingsSections(entries, settingsSectionOrder)),
510
- profileSections: await serializeSectionGroups(profileSections),
511
- profilePathPrefixes,
552
+ profileSections: await serializeSectionGroups(
553
+ convertToSectionNavGroups(resolvedProfileSections, translate),
554
+ ),
555
+ profilePathPrefixes: resolveProfilePathPrefixes(resolvedProfileSections),
512
556
  grantedFeatures,
513
557
  roles: Array.isArray(auth.roles) ? auth.roles : [],
514
558
  brand,
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react'
2
- import type { SectionNavGroup } from '@open-mercato/ui/backend/section-page'
2
+ import type { SettingsSection } from '@open-mercato/ui/backend/utils/nav'
3
3
 
4
4
  const KeyIcon = (
5
5
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -7,9 +7,20 @@ const KeyIcon = (
7
7
  </svg>
8
8
  )
9
9
 
10
- export const profileSections: SectionNavGroup[] = [
10
+ /**
11
+ * Baseline profile sidebar sections for pages route discovery cannot see.
12
+ *
13
+ * `/backend/profile/change-password` is `navHidden`, so `buildAdminNav` drops it and
14
+ * `buildProfileSections` can never derive it. Everything else in the profile sidebar comes from
15
+ * pages declaring `pageContext: 'profile'` and is merged in by `resolveBackendChromePayload`.
16
+ *
17
+ * The section id is the untranslated group id (`profile.sections.account`) — the same convention the
18
+ * settings sidebar adopted in 0.6.8 — so a page declaring `pageGroupKey: 'profile.sections.account'`
19
+ * lands in this section instead of opening a duplicate one.
20
+ */
21
+ export const profileSections: SettingsSection[] = [
11
22
  {
12
- id: 'account',
23
+ id: 'profile.sections.account',
13
24
  label: 'Account',
14
25
  labelKey: 'profile.sections.account',
15
26
  order: 1,
@@ -913,7 +913,8 @@
913
913
  "referencedColumnNames": [
914
914
  "id"
915
915
  ],
916
- "referencedTableName": "public.users"
916
+ "referencedTableName": "public.users",
917
+ "deleteRule": "cascade"
917
918
  }
918
919
  },
919
920
  "nativeEnums": {}
@@ -1978,7 +1979,8 @@
1978
1979
  "referencedColumnNames": [
1979
1980
  "id"
1980
1981
  ],
1981
- "referencedTableName": "public.users"
1982
+ "referencedTableName": "public.users",
1983
+ "deleteRule": "cascade"
1982
1984
  }
1983
1985
  },
1984
1986
  "nativeEnums": {}
@@ -0,0 +1,21 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260907120000_auth extends Migration {
4
+
5
+ override up(): void | Promise<void> {
6
+ this.addSql(`alter table "user_sidebar_preferences" drop constraint "user_sidebar_preferences_user_id_foreign";`);
7
+ this.addSql(`alter table "user_sidebar_preferences" add constraint "user_sidebar_preferences_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade on delete cascade;`);
8
+
9
+ this.addSql(`alter table "sidebar_variants" drop constraint "sidebar_variants_user_id_foreign";`);
10
+ this.addSql(`alter table "sidebar_variants" add constraint "sidebar_variants_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade on delete cascade;`);
11
+ }
12
+
13
+ override down(): void | Promise<void> {
14
+ this.addSql(`alter table "user_sidebar_preferences" drop constraint "user_sidebar_preferences_user_id_foreign";`);
15
+ this.addSql(`alter table "user_sidebar_preferences" add constraint "user_sidebar_preferences_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade;`);
16
+
17
+ this.addSql(`alter table "sidebar_variants" drop constraint "sidebar_variants_user_id_foreign";`);
18
+ this.addSql(`alter table "sidebar_variants" add constraint "sidebar_variants_user_id_foreign" foreign key ("user_id") references "users" ("id");`);
19
+ }
20
+
21
+ }
@@ -21,8 +21,8 @@ export const metadata = {
21
21
  requireFeatures: ['communication_channels.connect_user_channel'],
22
22
  pageTitle: 'My communication channels',
23
23
  pageTitleKey: 'communication_channels.profile.title',
24
- pageGroup: 'Profile',
25
- pageGroupKey: 'communication_channels.profile.group',
24
+ pageGroup: 'Account',
25
+ pageGroupKey: 'profile.sections.account',
26
26
  pageOrder: 20,
27
27
  icon: channelsIcon,
28
28
  pageContext: 'profile' as const,
@@ -371,7 +371,9 @@ Configurable via environment variables (`CUSTOMER_LOGIN_POINTS`, `CUSTOMER_LOGIN
371
371
  ### Email Privacy
372
372
 
373
373
  - Emails stored in plaintext but lookups use `hashForLookup` (deterministic hash)
374
- - Unique constraint on `(tenantId, emailHash)` prevents duplicates
374
+ - Partial unique index on `(tenantId, emailHash) WHERE deleted_at IS NULL` prevents duplicate **active** accounts — a soft-deleted row no longer blocks re-inviting its email (#5532)
375
+ - Every `CustomerUser` email lookup MUST therefore filter `deletedAt: null`; that predicate is load-bearing, not defensive, because two rows can legitimately share `(tenantId, emailHash)` once one is soft-deleted
376
+ - Match lookups against `lookupHashCandidates(email)` rather than a single `hashForLookup(email)`, so rows still carrying the legacy digest are found
375
377
  - Error messages never confirm whether an email is registered
376
378
 
377
379
  ## Lib Utilities