@open-mercato/core 0.6.7-develop.6696.1.4236709f7e → 0.6.7-develop.6725.1.07a748cfc2

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 (154) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/admin/nav.js +14 -4
  3. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  4. package/dist/modules/auth/api/users/route.js +8 -19
  5. package/dist/modules/auth/api/users/route.js.map +2 -2
  6. package/dist/modules/auth/frontend/login.js +4 -1
  7. package/dist/modules/auth/frontend/login.js.map +2 -2
  8. package/dist/modules/auth/lib/backendChrome.js +31 -14
  9. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  10. package/dist/modules/customers/api/companies/[id]/route.js +3 -4
  11. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  12. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +3 -2
  13. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
  14. package/dist/modules/customers/api/utils.js +12 -18
  15. package/dist/modules/customers/api/utils.js.map +2 -2
  16. package/dist/modules/customers/cli.js +11 -4
  17. package/dist/modules/customers/cli.js.map +2 -2
  18. package/dist/modules/customers/components/detail/ActiveDealCard.js +2 -1
  19. package/dist/modules/customers/components/detail/ActiveDealCard.js.map +2 -2
  20. package/dist/modules/customers/components/detail/CompanyKpiBar.js +6 -5
  21. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
  22. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +4 -2
  23. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  24. package/dist/modules/customers/components/detail/dashboard/helpers.js +4 -3
  25. package/dist/modules/customers/components/detail/dashboard/helpers.js.map +2 -2
  26. package/dist/modules/customers/components/formConfig.js +14 -10
  27. package/dist/modules/customers/components/formConfig.js.map +2 -2
  28. package/dist/modules/customers/lib/dealStatus.js +38 -0
  29. package/dist/modules/customers/lib/dealStatus.js.map +7 -0
  30. package/dist/modules/dashboards/api/widgets/data/batch/route.js +4 -2
  31. package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +2 -2
  32. package/dist/modules/dashboards/api/widgets/data/route.js +20 -2
  33. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  34. package/dist/modules/dashboards/lib/aggregations.js +76 -40
  35. package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
  36. package/dist/modules/dashboards/lib/exactDecimal.js +69 -0
  37. package/dist/modules/dashboards/lib/exactDecimal.js.map +7 -0
  38. package/dist/modules/dashboards/services/widgetDataService.js +228 -5
  39. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  40. package/dist/modules/feature_toggles/components/FeatureGuard.js +3 -2
  41. package/dist/modules/feature_toggles/components/FeatureGuard.js.map +2 -2
  42. package/dist/modules/feature_toggles/components/hooks/staleTime.js +5 -0
  43. package/dist/modules/feature_toggles/components/hooks/staleTime.js.map +7 -0
  44. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js +5 -2
  45. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js.map +2 -2
  46. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js +5 -2
  47. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js.map +2 -2
  48. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js +5 -2
  49. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js.map +2 -2
  50. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js +5 -2
  51. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js.map +2 -2
  52. package/dist/modules/inbox_ops/api/proposals/route.js +16 -1
  53. package/dist/modules/inbox_ops/api/proposals/route.js.map +2 -2
  54. package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js +32 -0
  55. package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js.map +7 -0
  56. package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +0 -5
  57. package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
  58. package/dist/modules/integrations/widgets/injection/external-ids/widget.js +14 -0
  59. package/dist/modules/integrations/widgets/injection/external-ids/widget.js.map +7 -0
  60. package/dist/modules/messages/lib/searchLookup.js +8 -16
  61. package/dist/modules/messages/lib/searchLookup.js.map +2 -2
  62. package/dist/modules/payment_gateways/api/sessions/route.js +5 -0
  63. package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
  64. package/dist/modules/payment_gateways/di.js +17 -1
  65. package/dist/modules/payment_gateways/di.js.map +2 -2
  66. package/dist/modules/payment_gateways/lib/gateway-service.js +8 -0
  67. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  68. package/dist/modules/payment_gateways/lib/order-amount-reconciliation.js +58 -0
  69. package/dist/modules/payment_gateways/lib/order-amount-reconciliation.js.map +7 -0
  70. package/dist/modules/query_index/api/openapi.js +13 -1
  71. package/dist/modules/query_index/api/openapi.js.map +2 -2
  72. package/dist/modules/query_index/api/status.js +165 -105
  73. package/dist/modules/query_index/api/status.js.map +2 -2
  74. package/dist/modules/query_index/components/QueryIndexesTable.js +128 -41
  75. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  76. package/dist/modules/query_index/lib/batch.js +11 -4
  77. package/dist/modules/query_index/lib/batch.js.map +2 -2
  78. package/dist/modules/query_index/lib/document.js +12 -5
  79. package/dist/modules/query_index/lib/document.js.map +2 -2
  80. package/dist/modules/query_index/lib/engine.js +36 -4
  81. package/dist/modules/query_index/lib/engine.js.map +2 -2
  82. package/dist/modules/query_index/lib/indexer.js +1 -1
  83. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  84. package/dist/modules/query_index/lib/search-tokens.js +7 -4
  85. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  86. package/dist/modules/sales/api/documents/factory.js +14 -3
  87. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  88. package/dist/modules/sales/commands/documents.js +22 -1
  89. package/dist/modules/sales/commands/documents.js.map +2 -2
  90. package/dist/modules/sales/components/useSalesChannelsEnabled.js +2 -35
  91. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +2 -2
  92. package/dist/modules/sales/data/validators.js +25 -4
  93. package/dist/modules/sales/data/validators.js.map +2 -2
  94. package/dist/modules/sales/di.js +7 -0
  95. package/dist/modules/sales/di.js.map +2 -2
  96. package/dist/modules/sales/services/paymentOrderTotalResolver.js +52 -0
  97. package/dist/modules/sales/services/paymentOrderTotalResolver.js.map +7 -0
  98. package/package.json +7 -7
  99. package/src/modules/auth/api/admin/nav.ts +24 -4
  100. package/src/modules/auth/api/users/route.ts +8 -28
  101. package/src/modules/auth/frontend/login.tsx +4 -1
  102. package/src/modules/auth/lib/backendChrome.tsx +51 -13
  103. package/src/modules/customers/api/companies/[id]/route.ts +3 -4
  104. package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +3 -2
  105. package/src/modules/customers/api/utils.ts +12 -31
  106. package/src/modules/customers/cli.ts +11 -4
  107. package/src/modules/customers/components/detail/ActiveDealCard.tsx +2 -1
  108. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +6 -5
  109. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +4 -0
  110. package/src/modules/customers/components/detail/dashboard/helpers.ts +4 -3
  111. package/src/modules/customers/components/formConfig.tsx +12 -8
  112. package/src/modules/customers/lib/dealStatus.ts +48 -0
  113. package/src/modules/dashboards/api/widgets/data/batch/route.ts +4 -0
  114. package/src/modules/dashboards/api/widgets/data/route.ts +21 -0
  115. package/src/modules/dashboards/lib/aggregations.ts +122 -43
  116. package/src/modules/dashboards/lib/exactDecimal.ts +96 -0
  117. package/src/modules/dashboards/services/widgetDataService.ts +342 -4
  118. package/src/modules/feature_toggles/components/FeatureGuard.tsx +9 -2
  119. package/src/modules/feature_toggles/components/hooks/staleTime.ts +5 -0
  120. package/src/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.ts +13 -1
  121. package/src/modules/feature_toggles/components/hooks/useFeatureFlagJson.ts +12 -3
  122. package/src/modules/feature_toggles/components/hooks/useFeatureFlagNumber.ts +10 -1
  123. package/src/modules/feature_toggles/components/hooks/useFeatureFlagString.ts +10 -1
  124. package/src/modules/inbox_ops/api/proposals/route.ts +20 -1
  125. package/src/modules/inbox_ops/subscribers/search-reindex-proposal-created.ts +39 -0
  126. package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +0 -5
  127. package/src/modules/integrations/widgets/injection/external-ids/widget.ts +13 -0
  128. package/src/modules/messages/lib/searchLookup.ts +11 -34
  129. package/src/modules/payment_gateways/api/sessions/route.ts +5 -0
  130. package/src/modules/payment_gateways/di.ts +28 -2
  131. package/src/modules/payment_gateways/i18n/de.json +3 -0
  132. package/src/modules/payment_gateways/i18n/en.json +3 -0
  133. package/src/modules/payment_gateways/i18n/es.json +3 -0
  134. package/src/modules/payment_gateways/i18n/pl.json +3 -0
  135. package/src/modules/payment_gateways/lib/gateway-service.ts +14 -0
  136. package/src/modules/payment_gateways/lib/order-amount-reconciliation.ts +93 -0
  137. package/src/modules/query_index/api/openapi.ts +12 -0
  138. package/src/modules/query_index/api/status.ts +238 -136
  139. package/src/modules/query_index/components/QueryIndexesTable.tsx +192 -46
  140. package/src/modules/query_index/i18n/de.json +5 -3
  141. package/src/modules/query_index/i18n/en.json +5 -3
  142. package/src/modules/query_index/i18n/es.json +5 -3
  143. package/src/modules/query_index/i18n/pl.json +5 -3
  144. package/src/modules/query_index/lib/batch.ts +11 -4
  145. package/src/modules/query_index/lib/document.ts +35 -4
  146. package/src/modules/query_index/lib/engine.ts +53 -5
  147. package/src/modules/query_index/lib/indexer.ts +1 -1
  148. package/src/modules/query_index/lib/search-tokens.ts +13 -4
  149. package/src/modules/sales/api/documents/factory.ts +28 -3
  150. package/src/modules/sales/commands/documents.ts +26 -2
  151. package/src/modules/sales/components/useSalesChannelsEnabled.ts +2 -48
  152. package/src/modules/sales/data/validators.ts +50 -5
  153. package/src/modules/sales/di.ts +9 -0
  154. package/src/modules/sales/services/paymentOrderTotalResolver.ts +67 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/feature_toggles/components/hooks/useFeatureFlagNumber.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport { useQuery } from '@tanstack/react-query'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\nexport type UseFeatureFlagNumberOptions = {\n id: string\n}\n\nexport type UseFeatureFlagNumberResult = {\n value: number | null\n isLoading: boolean\n}\n\ntype Result<T> = {\n ok: true\n value: T\n} | {\n ok: false\n error: unknown\n}\n\nexport function useFeatureFlagNumber(options: UseFeatureFlagNumberOptions): UseFeatureFlagNumberResult {\n const query = useQuery({\n queryKey: ['featureToggles', 'check', 'number', options?.id],\n queryFn: async () => {\n const params = new URLSearchParams({\n identifier: options.id,\n })\n\n const result = await readApiResultOrThrow<Result<number>>(\n `/api/feature_toggles/check/number?${params.toString()}`,\n undefined,\n { errorMessage: 'Failed to check feature flag.' },\n )\n\n return result\n },\n enabled: !!options.id,\n })\n\n const value = query.data?.ok ? query.data.value : null\n const isLoading = query.isLoading\n\n return {\n value,\n isLoading,\n }\n}\n"],
5
- "mappings": ";AAEA,SAAS,gBAAgB;AACzB,SAAS,4BAA4B;AAmB9B,SAAS,qBAAqB,SAAkE;AACnG,QAAM,QAAQ,SAAS;AAAA,IACnB,UAAU,CAAC,kBAAkB,SAAS,UAAU,SAAS,EAAE;AAAA,IAC3D,SAAS,YAAY;AACjB,YAAM,SAAS,IAAI,gBAAgB;AAAA,QAC/B,YAAY,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,SAAS,MAAM;AAAA,QACjB,qCAAqC,OAAO,SAAS,CAAC;AAAA,QACtD;AAAA,QACA,EAAE,cAAc,gCAAgC;AAAA,MACpD;AAEA,aAAO;AAAA,IACX;AAAA,IACA,SAAS,CAAC,CAAC,QAAQ;AAAA,EACvB,CAAC;AAED,QAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,KAAK,QAAQ;AAClD,QAAM,YAAY,MAAM;AAExB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport { useQuery } from '@tanstack/react-query'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { FEATURE_FLAG_STALE_TIME_MS } from './staleTime'\n\nexport type UseFeatureFlagNumberOptions = {\n id: string\n /**\n * Value used while the check is in flight and whenever the toggle cannot be\n * resolved (undefined toggle, type mismatch, network or permission failure).\n * Defaults to `null`.\n */\n defaultValue?: number | null\n}\n\nexport type UseFeatureFlagNumberResult = {\n value: number | null\n isLoading: boolean\n}\n\ntype Result<T> = {\n ok: true\n value: T\n} | {\n ok: false\n error: unknown\n}\n\nexport function useFeatureFlagNumber(options: UseFeatureFlagNumberOptions): UseFeatureFlagNumberResult {\n const defaultValue = options.defaultValue ?? null\n const query = useQuery({\n queryKey: ['featureToggles', 'check', 'number', options?.id],\n queryFn: async () => {\n const params = new URLSearchParams({\n identifier: options.id,\n })\n\n const result = await readApiResultOrThrow<Result<number>>(\n `/api/feature_toggles/check/number?${params.toString()}`,\n undefined,\n { errorMessage: 'Failed to check feature flag.' },\n )\n\n return result\n },\n enabled: !!options.id,\n staleTime: FEATURE_FLAG_STALE_TIME_MS,\n })\n\n const value = query.data?.ok ? query.data.value : defaultValue\n const isLoading = query.isLoading\n\n return {\n value,\n isLoading,\n }\n}\n"],
5
+ "mappings": ";AAEA,SAAS,gBAAgB;AACzB,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,qBAAqB,SAAkE;AACnG,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,QAAQ,SAAS;AAAA,IACnB,UAAU,CAAC,kBAAkB,SAAS,UAAU,SAAS,EAAE;AAAA,IAC3D,SAAS,YAAY;AACjB,YAAM,SAAS,IAAI,gBAAgB;AAAA,QAC/B,YAAY,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,SAAS,MAAM;AAAA,QACjB,qCAAqC,OAAO,SAAS,CAAC;AAAA,QACtD;AAAA,QACA,EAAE,cAAc,gCAAgC;AAAA,MACpD;AAEA,aAAO;AAAA,IACX;AAAA,IACA,SAAS,CAAC,CAAC,QAAQ;AAAA,IACnB,WAAW;AAAA,EACf,CAAC;AAED,QAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,KAAK,QAAQ;AAClD,QAAM,YAAY,MAAM;AAExB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { FEATURE_FLAG_STALE_TIME_MS } from "./staleTime.js";
4
5
  function useFeatureFlagString(options) {
6
+ const defaultValue = options.defaultValue ?? null;
5
7
  const query = useQuery({
6
8
  queryKey: ["featureToggles", "check", "string", options?.id],
7
9
  queryFn: async () => {
@@ -15,9 +17,10 @@ function useFeatureFlagString(options) {
15
17
  );
16
18
  return result;
17
19
  },
18
- enabled: !!options.id
20
+ enabled: !!options.id,
21
+ staleTime: FEATURE_FLAG_STALE_TIME_MS
19
22
  });
20
- const value = query.data?.ok ? query.data.value : null;
23
+ const value = query.data?.ok ? query.data.value : defaultValue;
21
24
  const isLoading = query.isLoading;
22
25
  return {
23
26
  value,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/feature_toggles/components/hooks/useFeatureFlagString.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport { useQuery } from '@tanstack/react-query'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\nexport type UseFeatureFlagStringOptions = {\n id: string\n}\n\nexport type UseFeatureFlagStringResult = {\n value: string | null\n isLoading: boolean\n}\n\ntype Result<T> = {\n ok: true\n value: T\n} | {\n ok: false\n error: unknown\n}\n\nexport function useFeatureFlagString(options: UseFeatureFlagStringOptions): UseFeatureFlagStringResult {\n const query = useQuery({\n queryKey: ['featureToggles', 'check', 'string', options?.id],\n queryFn: async () => {\n const params = new URLSearchParams({\n identifier: options.id,\n })\n\n const result = await readApiResultOrThrow<Result<string>>(\n `/api/feature_toggles/check/string?${params.toString()}`,\n undefined,\n { errorMessage: 'Failed to check feature flag.' },\n )\n\n return result\n },\n enabled: !!options.id,\n })\n\n const value = query.data?.ok ? query.data.value : null\n const isLoading = query.isLoading\n\n return {\n value,\n isLoading,\n }\n}\n"],
5
- "mappings": ";AAEA,SAAS,gBAAgB;AACzB,SAAS,4BAA4B;AAmB9B,SAAS,qBAAqB,SAAkE;AACnG,QAAM,QAAQ,SAAS;AAAA,IACnB,UAAU,CAAC,kBAAkB,SAAS,UAAU,SAAS,EAAE;AAAA,IAC3D,SAAS,YAAY;AACjB,YAAM,SAAS,IAAI,gBAAgB;AAAA,QAC/B,YAAY,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,SAAS,MAAM;AAAA,QACjB,qCAAqC,OAAO,SAAS,CAAC;AAAA,QACtD;AAAA,QACA,EAAE,cAAc,gCAAgC;AAAA,MACpD;AAEA,aAAO;AAAA,IACX;AAAA,IACA,SAAS,CAAC,CAAC,QAAQ;AAAA,EACvB,CAAC;AAED,QAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,KAAK,QAAQ;AAClD,QAAM,YAAY,MAAM;AAExB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport { useQuery } from '@tanstack/react-query'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { FEATURE_FLAG_STALE_TIME_MS } from './staleTime'\n\nexport type UseFeatureFlagStringOptions = {\n id: string\n /**\n * Value used while the check is in flight and whenever the toggle cannot be\n * resolved (undefined toggle, type mismatch, network or permission failure).\n * Defaults to `null`.\n */\n defaultValue?: string | null\n}\n\nexport type UseFeatureFlagStringResult = {\n value: string | null\n isLoading: boolean\n}\n\ntype Result<T> = {\n ok: true\n value: T\n} | {\n ok: false\n error: unknown\n}\n\nexport function useFeatureFlagString(options: UseFeatureFlagStringOptions): UseFeatureFlagStringResult {\n const defaultValue = options.defaultValue ?? null\n const query = useQuery({\n queryKey: ['featureToggles', 'check', 'string', options?.id],\n queryFn: async () => {\n const params = new URLSearchParams({\n identifier: options.id,\n })\n\n const result = await readApiResultOrThrow<Result<string>>(\n `/api/feature_toggles/check/string?${params.toString()}`,\n undefined,\n { errorMessage: 'Failed to check feature flag.' },\n )\n\n return result\n },\n enabled: !!options.id,\n staleTime: FEATURE_FLAG_STALE_TIME_MS,\n })\n\n const value = query.data?.ok ? query.data.value : defaultValue\n const isLoading = query.isLoading\n\n return {\n value,\n isLoading,\n }\n}\n"],
5
+ "mappings": ";AAEA,SAAS,gBAAgB;AACzB,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,qBAAqB,SAAkE;AACnG,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,QAAQ,SAAS;AAAA,IACnB,UAAU,CAAC,kBAAkB,SAAS,UAAU,SAAS,EAAE;AAAA,IAC3D,SAAS,YAAY;AACjB,YAAM,SAAS,IAAI,gBAAgB;AAAA,QAC/B,YAAY,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,SAAS,MAAM;AAAA,QACjB,qCAAqC,OAAO,SAAS,CAAC;AAAA,QACtD;AAAA,QACA,EAAE,cAAc,gCAAgC;AAAA,MACpD;AAEA,aAAO;AAAA,IACX;AAAA,IACA,SAAS,CAAC,CAAC,QAAQ;AAAA,IACnB,WAAW;AAAA,EACf,CAAC;AAED,QAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,KAAK,QAAQ;AAClD,QAAM,YAAY,MAAM;AAExB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
2
2
  import { ZodError } from "zod";
3
3
  import { findAndCountWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
4
  import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
5
+ import { findEntityIdsBySearchTokens } from "@open-mercato/shared/lib/search/tokenLookup";
6
+ import { E } from "../../../../generated/entities.ids.generated.js";
5
7
  import { InboxProposal, InboxEmail, InboxProposalAction, InboxDiscrepancy } from "../../data/entities.js";
6
8
  import { proposalListQuerySchema } from "../../data/validators.js";
7
9
  import { resolveRequestContext, UnauthorizedError } from "../routeHelpers.js";
@@ -39,7 +41,20 @@ async function GET(req) {
39
41
  }
40
42
  }
41
43
  if (query.search) {
42
- where.summary = { $ilike: `%${escapeLikePattern(query.search)}%` };
44
+ const searchOr = [
45
+ { summary: { $ilike: `%${escapeLikePattern(query.search)}%` } }
46
+ ];
47
+ const tokenMatch = await findEntityIdsBySearchTokens({
48
+ db: ctx.em.getKysely(),
49
+ entityType: E.inbox_ops.inbox_proposal,
50
+ query: query.search,
51
+ fields: ["summary"],
52
+ scope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
53
+ });
54
+ if (tokenMatch.matched && tokenMatch.ids.length) {
55
+ searchOr.push({ id: { $in: tokenMatch.ids } });
56
+ }
57
+ where.$or = searchOr;
43
58
  }
44
59
  const offset = (query.page - 1) * query.pageSize;
45
60
  const [items, total] = await findAndCountWithDecryption(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/inbox_ops/api/proposals/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { ZodError } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { FilterQuery } from '@mikro-orm/postgresql'\nimport { findAndCountWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { InboxProposal, InboxEmail, InboxProposalAction, InboxDiscrepancy, type InboxProposalCategory } from '../../data/entities'\nimport { proposalListQuerySchema } from '../../data/validators'\nimport { resolveRequestContext, UnauthorizedError } from '../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'proposals' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.view'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const query = proposalListQuerySchema.parse({\n status: url.searchParams.get('status') || undefined,\n category: url.searchParams.get('category') || undefined,\n search: url.searchParams.get('search') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n })\n\n const ctx = await resolveRequestContext(req)\n\n const where: FilterQuery<InboxProposal> = {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n isActive: true,\n }\n\n if (query.status) {\n where.status = query.status\n }\n if (query.category) {\n const categories = query.category.split(',').map((c) => c.trim()).filter(Boolean) as InboxProposalCategory[]\n if (categories.length === 1) {\n where.category = categories[0]\n } else if (categories.length > 1) {\n where.category = { $in: categories }\n }\n }\n if (query.search) {\n where.summary = { $ilike: `%${escapeLikePattern(query.search)}%` }\n }\n\n const offset = (query.page - 1) * query.pageSize\n\n const [items, total] = await findAndCountWithDecryption(\n ctx.em,\n InboxProposal,\n where,\n {\n limit: query.pageSize,\n offset,\n orderBy: { createdAt: 'DESC' },\n },\n ctx.scope,\n )\n\n // Enrich proposals with email, action, and discrepancy data\n const proposalIds = items.map((p) => p.id)\n const emailIds = [...new Set(items.map((p) => p.inboxEmailId).filter(Boolean))]\n\n const [emails, allActions, allDiscrepancies] = await Promise.all([\n emailIds.length > 0\n ? findWithDecryption(ctx.em, InboxEmail, { id: { $in: emailIds }, organizationId: ctx.organizationId, tenantId: ctx.tenantId }, {}, ctx.scope)\n : Promise.resolve([] as InboxEmail[]),\n proposalIds.length > 0\n ? findWithDecryption(ctx.em, InboxProposalAction, { proposalId: { $in: proposalIds }, organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }, {}, ctx.scope)\n : Promise.resolve([] as InboxProposalAction[]),\n proposalIds.length > 0\n ? findWithDecryption(ctx.em, InboxDiscrepancy, { proposalId: { $in: proposalIds }, organizationId: ctx.organizationId, tenantId: ctx.tenantId, resolved: false }, {}, ctx.scope)\n : Promise.resolve([] as InboxDiscrepancy[]),\n ])\n\n const emailMap = new Map(emails.map((e) => [e.id, e]))\n\n const enrichedItems = items.map((proposal) => {\n const email = emailMap.get(proposal.inboxEmailId)\n const proposalActions = allActions.filter((a) => a.proposalId === proposal.id)\n const proposalDiscrepancies = allDiscrepancies.filter((d) => d.proposalId === proposal.id)\n\n return {\n ...proposal,\n actionCount: proposalActions.length,\n pendingActionCount: proposalActions.filter((a) => a.status === 'pending').length,\n discrepancyCount: proposalDiscrepancies.length,\n emailSubject: email?.subject || null,\n emailFrom: email?.forwardedByName || email?.forwardedByAddress || null,\n receivedAt: email?.receivedAt || proposal.createdAt,\n }\n })\n\n return NextResponse.json({\n items: enrichedItems,\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n if (err instanceof ZodError) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to list proposals', { err })\n return NextResponse.json({ error: 'Failed to list proposals' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Proposals',\n methods: {\n GET: {\n summary: 'List proposals',\n description: 'List inbox proposals with optional status filter and pagination',\n responses: [\n { status: 200, description: 'Paginated list of proposals' },\n { status: 401, description: 'Unauthorized' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { ZodError } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { FilterQuery } from '@mikro-orm/postgresql'\nimport { findAndCountWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { findEntityIdsBySearchTokens, type SearchTokenDatabase } from '@open-mercato/shared/lib/search/tokenLookup'\nimport { E } from '#generated/entities.ids.generated'\nimport { InboxProposal, InboxEmail, InboxProposalAction, InboxDiscrepancy, type InboxProposalCategory } from '../../data/entities'\nimport { proposalListQuerySchema } from '../../data/validators'\nimport { resolveRequestContext, UnauthorizedError } from '../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'proposals' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.view'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const query = proposalListQuerySchema.parse({\n status: url.searchParams.get('status') || undefined,\n category: url.searchParams.get('category') || undefined,\n search: url.searchParams.get('search') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n })\n\n const ctx = await resolveRequestContext(req)\n\n const where: FilterQuery<InboxProposal> = {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n isActive: true,\n }\n\n if (query.status) {\n where.status = query.status\n }\n if (query.category) {\n const categories = query.category.split(',').map((c) => c.trim()).filter(Boolean) as InboxProposalCategory[]\n if (categories.length === 1) {\n where.category = categories[0]\n } else if (categories.length > 1) {\n where.category = { $in: categories }\n }\n }\n if (query.search) {\n // `summary` is covered by the inbox_ops encryption map, so an ILIKE alone\n // compares the pattern against ciphertext and matches nothing once\n // encryption is on. Union it with the token index, which stores hashes of\n // the plaintext. Issue #2990.\n const searchOr: FilterQuery<InboxProposal>[] = [\n { summary: { $ilike: `%${escapeLikePattern(query.search)}%` } },\n ]\n const tokenMatch = await findEntityIdsBySearchTokens({\n db: ctx.em.getKysely<SearchTokenDatabase>(),\n entityType: E.inbox_ops.inbox_proposal,\n query: query.search,\n fields: ['summary'],\n scope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n })\n if (tokenMatch.matched && tokenMatch.ids.length) {\n searchOr.push({ id: { $in: tokenMatch.ids } })\n }\n where.$or = searchOr\n }\n\n const offset = (query.page - 1) * query.pageSize\n\n const [items, total] = await findAndCountWithDecryption(\n ctx.em,\n InboxProposal,\n where,\n {\n limit: query.pageSize,\n offset,\n orderBy: { createdAt: 'DESC' },\n },\n ctx.scope,\n )\n\n // Enrich proposals with email, action, and discrepancy data\n const proposalIds = items.map((p) => p.id)\n const emailIds = [...new Set(items.map((p) => p.inboxEmailId).filter(Boolean))]\n\n const [emails, allActions, allDiscrepancies] = await Promise.all([\n emailIds.length > 0\n ? findWithDecryption(ctx.em, InboxEmail, { id: { $in: emailIds }, organizationId: ctx.organizationId, tenantId: ctx.tenantId }, {}, ctx.scope)\n : Promise.resolve([] as InboxEmail[]),\n proposalIds.length > 0\n ? findWithDecryption(ctx.em, InboxProposalAction, { proposalId: { $in: proposalIds }, organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }, {}, ctx.scope)\n : Promise.resolve([] as InboxProposalAction[]),\n proposalIds.length > 0\n ? findWithDecryption(ctx.em, InboxDiscrepancy, { proposalId: { $in: proposalIds }, organizationId: ctx.organizationId, tenantId: ctx.tenantId, resolved: false }, {}, ctx.scope)\n : Promise.resolve([] as InboxDiscrepancy[]),\n ])\n\n const emailMap = new Map(emails.map((e) => [e.id, e]))\n\n const enrichedItems = items.map((proposal) => {\n const email = emailMap.get(proposal.inboxEmailId)\n const proposalActions = allActions.filter((a) => a.proposalId === proposal.id)\n const proposalDiscrepancies = allDiscrepancies.filter((d) => d.proposalId === proposal.id)\n\n return {\n ...proposal,\n actionCount: proposalActions.length,\n pendingActionCount: proposalActions.filter((a) => a.status === 'pending').length,\n discrepancyCount: proposalDiscrepancies.length,\n emailSubject: email?.subject || null,\n emailFrom: email?.forwardedByName || email?.forwardedByAddress || null,\n receivedAt: email?.receivedAt || proposal.createdAt,\n }\n })\n\n return NextResponse.json({\n items: enrichedItems,\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n if (err instanceof ZodError) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to list proposals', { err })\n return NextResponse.json({ error: 'Failed to list proposals' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Proposals',\n methods: {\n GET: {\n summary: 'List proposals',\n description: 'List inbox proposals with optional status filter and pagination',\n responses: [\n { status: 200, description: 'Paginated list of proposals' },\n { status: 401, description: 'Unauthorized' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,32 @@
1
+ const metadata = {
2
+ event: "inbox_ops.proposal.created",
3
+ persistent: false,
4
+ id: "inbox_ops:query-index-reindex-proposal-created"
5
+ };
6
+ async function handle(payload, ctx) {
7
+ const recordId = typeof payload.proposalId === "string" ? payload.proposalId : null;
8
+ const tenantId = typeof payload.tenantId === "string" ? payload.tenantId : null;
9
+ if (!recordId || !tenantId) return;
10
+ let eventBus = null;
11
+ try {
12
+ eventBus = ctx.resolve("eventBus");
13
+ } catch {
14
+ return;
15
+ }
16
+ await eventBus.emitEvent("query_index.upsert_one", {
17
+ entityType: "inbox_ops:inbox_proposal",
18
+ recordId,
19
+ organizationId: payload.organizationId ?? null,
20
+ tenantId,
21
+ crudAction: "created",
22
+ coverageBaseDelta: 1
23
+ }, {
24
+ tenantId,
25
+ organizationId: payload.organizationId ?? null
26
+ }).catch(() => void 0);
27
+ }
28
+ export {
29
+ handle as default,
30
+ metadata
31
+ };
32
+ //# sourceMappingURL=search-reindex-proposal-created.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/inbox_ops/subscribers/search-reindex-proposal-created.ts"],
4
+ "sourcesContent": ["export const metadata = {\n event: 'inbox_ops.proposal.created',\n persistent: false,\n id: 'inbox_ops:query-index-reindex-proposal-created',\n}\n\ntype Payload = {\n proposalId?: string\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype EventBus = { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> }\ntype HandlerContext = { resolve: <T = unknown>(name: string) => T }\n\nexport default async function handle(payload: Payload, ctx: HandlerContext): Promise<void> {\n const recordId = typeof payload.proposalId === 'string' ? payload.proposalId : null\n const tenantId = typeof payload.tenantId === 'string' ? payload.tenantId : null\n if (!recordId || !tenantId) return\n\n let eventBus: EventBus | null = null\n try {\n eventBus = ctx.resolve<EventBus>('eventBus')\n } catch {\n return\n }\n\n await eventBus.emitEvent('query_index.upsert_one', {\n entityType: 'inbox_ops:inbox_proposal',\n recordId,\n organizationId: payload.organizationId ?? null,\n tenantId,\n crudAction: 'created',\n coverageBaseDelta: 1,\n }, {\n tenantId,\n organizationId: payload.organizationId ?? null,\n }).catch(() => undefined)\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAWA,eAAO,OAA8B,SAAkB,KAAoC;AACzF,QAAM,WAAW,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAC/E,QAAM,WAAW,OAAO,QAAQ,aAAa,WAAW,QAAQ,WAAW;AAC3E,MAAI,CAAC,YAAY,CAAC,SAAU;AAE5B,MAAI,WAA4B;AAChC,MAAI;AACF,eAAW,IAAI,QAAkB,UAAU;AAAA,EAC7C,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,SAAS,UAAU,0BAA0B;AAAA,IACjD,YAAY;AAAA,IACZ;AAAA,IACA,gBAAgB,QAAQ,kBAAkB;AAAA,IAC1C;AAAA,IACA,YAAY;AAAA,IACZ,mBAAmB;AAAA,EACrB,GAAG;AAAA,IACD;AAAA,IACA,gBAAgB,QAAQ,kBAAkB;AAAA,EAC5C,CAAC,EAAE,MAAM,MAAM,MAAS;AAC1B;",
6
+ "names": []
7
+ }
@@ -58,11 +58,6 @@ function ExternalIdsWidget({ data }) {
58
58
  )) })
59
59
  ] });
60
60
  }
61
- ExternalIdsWidget.metadata = {
62
- id: "integrations.injection.external-ids",
63
- title: "External IDs",
64
- features: ["integrations.view"]
65
- };
66
61
  export {
67
62
  ExternalIdsWidget as default
68
63
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/integrations/widgets/injection/external-ids/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { ExternalIdMapping } from '@open-mercato/shared/modules/integrations/types'\nimport { getIntegrationTitle } from '@open-mercato/shared/modules/integrations/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype IntegrationsData = Record<string, ExternalIdMapping>\n\nconst SYNC_STATUS_STYLES: Record<ExternalIdMapping['syncStatus'], { dot: string; label: string }> = {\n synced: { dot: 'bg-status-success-icon', label: 'integrations.syncStatus.synced' },\n pending: { dot: 'bg-status-warning-icon', label: 'integrations.syncStatus.pending' },\n error: { dot: 'bg-status-error-icon', label: 'integrations.syncStatus.error' },\n not_synced: { dot: 'bg-status-neutral-icon', label: 'integrations.syncStatus.notSynced' },\n}\n\nfunction SyncStatusBadge({ status, lastSynced }: { status: ExternalIdMapping['syncStatus']; lastSynced?: string }) {\n const t = useT()\n const config = SYNC_STATUS_STYLES[status]\n const label = t(config.label, status)\n\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <span className={cn('inline-block size-1.5 rounded-full', config.dot)} aria-hidden=\"true\" />\n <span>{label}</span>\n {lastSynced && (\n <span title={lastSynced}>\n {new Date(lastSynced).toLocaleDateString()}\n </span>\n )}\n </span>\n )\n}\n\nfunction ExternalLinkIcon() {\n return (\n <svg className=\"size-3.5\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path d=\"M6.5 3.5H3a1 1 0 0 0-1 1V13a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1V9.5\" />\n <path d=\"M9.5 2h4.5v4.5M14 2 7.5 8.5\" />\n </svg>\n )\n}\n\nexport default function ExternalIdsWidget({ data }: InjectionWidgetComponentProps) {\n const t = useT()\n const integrations = (data as Record<string, unknown>)?._integrations as IntegrationsData | undefined\n\n if (!integrations || Object.keys(integrations).length === 0) return null\n\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <h3 className=\"mb-3 text-sm font-medium\">\n {t('integrations.externalIds.title', 'External IDs')}\n </h3>\n <div className=\"space-y-2\">\n {Object.entries(integrations).map(([integrationId, mapping]) => (\n <div\n key={integrationId}\n className=\"flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <span className=\"text-sm font-medium truncate\">\n {getIntegrationTitle(integrationId)}\n </span>\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground\">\n {mapping.externalId}\n </code>\n {mapping.externalUrl && (\n <a\n href={mapping.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={t('integrations.externalIds.openExternal', 'Open in external system')}\n >\n <ExternalLinkIcon />\n </a>\n )}\n </div>\n <SyncStatusBadge status={mapping.syncStatus} lastSynced={mapping.lastSyncedAt} />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\nExternalIdsWidget.metadata = {\n id: 'integrations.injection.external-ids',\n title: 'External IDs',\n features: ['integrations.view'],\n}\n"],
5
- "mappings": ";AAwBI,SACE,KADF;AAnBJ,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,UAAU;AAInB,MAAM,qBAA8F;AAAA,EAClG,QAAQ,EAAE,KAAK,0BAA0B,OAAO,iCAAiC;AAAA,EACjF,SAAS,EAAE,KAAK,0BAA0B,OAAO,kCAAkC;AAAA,EACnF,OAAO,EAAE,KAAK,wBAAwB,OAAO,gCAAgC;AAAA,EAC7E,YAAY,EAAE,KAAK,0BAA0B,OAAO,oCAAoC;AAC1F;AAEA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,GAAqE;AACjH,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,EAAE,OAAO,OAAO,MAAM;AAEpC,SACE,qBAAC,UAAK,WAAU,gEACd;AAAA,wBAAC,UAAK,WAAW,GAAG,sCAAsC,OAAO,GAAG,GAAG,eAAY,QAAO;AAAA,IAC1F,oBAAC,UAAM,iBAAM;AAAA,IACZ,cACC,oBAAC,UAAK,OAAO,YACV,cAAI,KAAK,UAAU,EAAE,mBAAmB,GAC3C;AAAA,KAEJ;AAEJ;AAEA,SAAS,mBAAmB;AAC1B,SACE,qBAAC,SAAI,WAAU,YAAW,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC1F;AAAA,wBAAC,UAAK,GAAE,mEAAkE;AAAA,IAC1E,oBAAC,UAAK,GAAE,+BAA8B;AAAA,KACxC;AAEJ;AAEe,SAAR,kBAAmC,EAAE,KAAK,GAAkC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,eAAgB,MAAkC;AAExD,MAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,EAAE,WAAW,EAAG,QAAO;AAEpE,SACE,qBAAC,SAAI,WAAU,iCACb;AAAA,wBAAC,QAAG,WAAU,4BACX,YAAE,kCAAkC,cAAc,GACrD;AAAA,IACA,oBAAC,SAAI,WAAU,aACZ,iBAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,eAAe,OAAO,MACxD;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,+BAAC,SAAI,WAAU,mCACb;AAAA,gCAAC,UAAK,WAAU,gCACb,8BAAoB,aAAa,GACpC;AAAA,YACA,oBAAC,UAAK,WAAU,0EACb,kBAAQ,YACX;AAAA,YACC,QAAQ,eACP;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,QAAQ;AAAA,gBACd,QAAO;AAAA,gBACP,KAAI;AAAA,gBACJ,WAAU;AAAA,gBACV,cAAY,EAAE,yCAAyC,yBAAyB;AAAA,gBAEhF,8BAAC,oBAAiB;AAAA;AAAA,YACpB;AAAA,aAEJ;AAAA,UACA,oBAAC,mBAAgB,QAAQ,QAAQ,YAAY,YAAY,QAAQ,cAAc;AAAA;AAAA;AAAA,MAtB1E;AAAA,IAuBP,CACD,GACH;AAAA,KACF;AAEJ;AAEA,kBAAkB,WAAW;AAAA,EAC3B,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,UAAU,CAAC,mBAAmB;AAChC;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { ExternalIdMapping } from '@open-mercato/shared/modules/integrations/types'\nimport { getIntegrationTitle } from '@open-mercato/shared/modules/integrations/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype IntegrationsData = Record<string, ExternalIdMapping>\n\nconst SYNC_STATUS_STYLES: Record<ExternalIdMapping['syncStatus'], { dot: string; label: string }> = {\n synced: { dot: 'bg-status-success-icon', label: 'integrations.syncStatus.synced' },\n pending: { dot: 'bg-status-warning-icon', label: 'integrations.syncStatus.pending' },\n error: { dot: 'bg-status-error-icon', label: 'integrations.syncStatus.error' },\n not_synced: { dot: 'bg-status-neutral-icon', label: 'integrations.syncStatus.notSynced' },\n}\n\nfunction SyncStatusBadge({ status, lastSynced }: { status: ExternalIdMapping['syncStatus']; lastSynced?: string }) {\n const t = useT()\n const config = SYNC_STATUS_STYLES[status]\n const label = t(config.label, status)\n\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <span className={cn('inline-block size-1.5 rounded-full', config.dot)} aria-hidden=\"true\" />\n <span>{label}</span>\n {lastSynced && (\n <span title={lastSynced}>\n {new Date(lastSynced).toLocaleDateString()}\n </span>\n )}\n </span>\n )\n}\n\nfunction ExternalLinkIcon() {\n return (\n <svg className=\"size-3.5\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path d=\"M6.5 3.5H3a1 1 0 0 0-1 1V13a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1V9.5\" />\n <path d=\"M9.5 2h4.5v4.5M14 2 7.5 8.5\" />\n </svg>\n )\n}\n\nexport default function ExternalIdsWidget({ data }: InjectionWidgetComponentProps) {\n const t = useT()\n const integrations = (data as Record<string, unknown>)?._integrations as IntegrationsData | undefined\n\n if (!integrations || Object.keys(integrations).length === 0) return null\n\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <h3 className=\"mb-3 text-sm font-medium\">\n {t('integrations.externalIds.title', 'External IDs')}\n </h3>\n <div className=\"space-y-2\">\n {Object.entries(integrations).map(([integrationId, mapping]) => (\n <div\n key={integrationId}\n className=\"flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <span className=\"text-sm font-medium truncate\">\n {getIntegrationTitle(integrationId)}\n </span>\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground\">\n {mapping.externalId}\n </code>\n {mapping.externalUrl && (\n <a\n href={mapping.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={t('integrations.externalIds.openExternal', 'Open in external system')}\n >\n <ExternalLinkIcon />\n </a>\n )}\n </div>\n <SyncStatusBadge status={mapping.syncStatus} lastSynced={mapping.lastSyncedAt} />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\n"],
5
+ "mappings": ";AAwBI,SACE,KADF;AAnBJ,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,UAAU;AAInB,MAAM,qBAA8F;AAAA,EAClG,QAAQ,EAAE,KAAK,0BAA0B,OAAO,iCAAiC;AAAA,EACjF,SAAS,EAAE,KAAK,0BAA0B,OAAO,kCAAkC;AAAA,EACnF,OAAO,EAAE,KAAK,wBAAwB,OAAO,gCAAgC;AAAA,EAC7E,YAAY,EAAE,KAAK,0BAA0B,OAAO,oCAAoC;AAC1F;AAEA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,GAAqE;AACjH,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,EAAE,OAAO,OAAO,MAAM;AAEpC,SACE,qBAAC,UAAK,WAAU,gEACd;AAAA,wBAAC,UAAK,WAAW,GAAG,sCAAsC,OAAO,GAAG,GAAG,eAAY,QAAO;AAAA,IAC1F,oBAAC,UAAM,iBAAM;AAAA,IACZ,cACC,oBAAC,UAAK,OAAO,YACV,cAAI,KAAK,UAAU,EAAE,mBAAmB,GAC3C;AAAA,KAEJ;AAEJ;AAEA,SAAS,mBAAmB;AAC1B,SACE,qBAAC,SAAI,WAAU,YAAW,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC1F;AAAA,wBAAC,UAAK,GAAE,mEAAkE;AAAA,IAC1E,oBAAC,UAAK,GAAE,+BAA8B;AAAA,KACxC;AAEJ;AAEe,SAAR,kBAAmC,EAAE,KAAK,GAAkC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,eAAgB,MAAkC;AAExD,MAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,EAAE,WAAW,EAAG,QAAO;AAEpE,SACE,qBAAC,SAAI,WAAU,iCACb;AAAA,wBAAC,QAAG,WAAU,4BACX,YAAE,kCAAkC,cAAc,GACrD;AAAA,IACA,oBAAC,SAAI,WAAU,aACZ,iBAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,eAAe,OAAO,MACxD;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,+BAAC,SAAI,WAAU,mCACb;AAAA,gCAAC,UAAK,WAAU,gCACb,8BAAoB,aAAa,GACpC;AAAA,YACA,oBAAC,UAAK,WAAU,0EACb,kBAAQ,YACX;AAAA,YACC,QAAQ,eACP;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,QAAQ;AAAA,gBACd,QAAO;AAAA,gBACP,KAAI;AAAA,gBACJ,WAAU;AAAA,gBACV,cAAY,EAAE,yCAAyC,yBAAyB;AAAA,gBAEhF,8BAAC,oBAAiB;AAAA;AAAA,YACpB;AAAA,aAEJ;AAAA,UACA,oBAAC,mBAAgB,QAAQ,QAAQ,YAAY,YAAY,QAAQ,cAAc;AAAA;AAAA;AAAA,MAtB1E;AAAA,IAuBP,CACD,GACH;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,14 @@
1
+ import ExternalIdsWidget from "./widget.client.js";
2
+ const widget = {
3
+ metadata: {
4
+ id: "integrations.injection.external-ids",
5
+ title: "External IDs",
6
+ features: ["integrations.view"]
7
+ },
8
+ Widget: ExternalIdsWidget
9
+ };
10
+ var widget_default = widget;
11
+ export {
12
+ widget_default as default
13
+ };
14
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/integrations/widgets/injection/external-ids/widget.ts"],
4
+ "sourcesContent": ["import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'\nimport ExternalIdsWidget from './widget.client'\n\nconst widget: InjectionWidgetModule = {\n metadata: {\n id: 'integrations.injection.external-ids',\n title: 'External IDs',\n features: ['integrations.view'],\n },\n Widget: ExternalIdsWidget,\n}\n\nexport default widget\n"],
5
+ "mappings": "AACA,OAAO,uBAAuB;AAE9B,MAAM,SAAgC;AAAA,EACpC,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,CAAC,mBAAmB;AAAA,EAChC;AAAA,EACA,QAAQ;AACV;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -1,6 +1,4 @@
1
- import { sql } from "kysely";
2
- import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
3
- import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
1
+ import { findEntityIdsBySearchTokensCompat } from "@open-mercato/shared/lib/search/tokenLookup";
4
2
  function getDb(em) {
5
3
  return em.getKysely();
6
4
  }
@@ -11,19 +9,13 @@ async function findMessageIdsBySearchTokens({
11
9
  organizationId,
12
10
  fields = ["subject", "body", "external_name"]
13
11
  }) {
14
- const trimmed = query.trim();
15
- if (!trimmed) return null;
16
- const tokens = tokenizeText(trimmed, resolveSearchConfig());
17
- if (!tokens.hashes.length) return [];
18
- const db = getDb(em);
19
- let searchQuery = db.selectFrom("search_tokens").select("entity_id").where("entity_type", "=", "messages:message").where("field", "in", fields).where("token_hash", "in", tokens.hashes).where(sql`tenant_id is not distinct from ${tenantId}`);
20
- if (organizationId) {
21
- searchQuery = searchQuery.where("organization_id", "=", organizationId);
22
- } else {
23
- searchQuery = searchQuery.where(sql`organization_id is not distinct from ${null}`);
24
- }
25
- const rows = await searchQuery.groupBy("entity_id").having(sql`count(distinct token_hash) >= ${tokens.hashes.length}`).execute();
26
- return rows.map((row) => typeof row.entity_id === "string" ? row.entity_id : null).filter((id) => typeof id === "string" && id.length > 0);
12
+ return findEntityIdsBySearchTokensCompat({
13
+ db: getDb(em),
14
+ entityType: "messages:message",
15
+ query,
16
+ fields,
17
+ scope: { tenantId, organizationId }
18
+ });
27
19
  }
28
20
  export {
29
21
  findMessageIdsBySearchTokens
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/lib/searchLookup.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'\n\nfunction getDb(em: EntityManager): Kysely<any> {\n return em.getKysely<any>()\n}\n\nexport async function findMessageIdsBySearchTokens({\n em,\n query,\n tenantId,\n organizationId,\n fields = ['subject', 'body', 'external_name'],\n}: {\n em: EntityManager\n query: string\n tenantId: string | null\n organizationId: string | null\n fields?: string[]\n}): Promise<string[] | null> {\n const trimmed = query.trim()\n if (!trimmed) return null\n\n const tokens = tokenizeText(trimmed, resolveSearchConfig())\n if (!tokens.hashes.length) return []\n\n const db = getDb(em) as any\n let searchQuery = db\n .selectFrom('search_tokens')\n .select('entity_id')\n .where('entity_type', '=', 'messages:message')\n .where('field', 'in', fields)\n .where('token_hash', 'in', tokens.hashes)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId}`)\n\n if (organizationId) {\n searchQuery = searchQuery.where('organization_id', '=', organizationId)\n } else {\n searchQuery = searchQuery.where(sql<boolean>`organization_id is not distinct from ${null}`)\n }\n\n const rows = await searchQuery\n .groupBy('entity_id')\n .having(sql<boolean>`count(distinct token_hash) >= ${tokens.hashes.length}`)\n .execute()\n\n return rows\n .map((row: { entity_id?: unknown }) => (typeof row.entity_id === 'string' ? row.entity_id : null))\n .filter((id: string | null): id is string => typeof id === 'string' && id.length > 0)\n}\n"],
5
- "mappings": "AACA,SAAsB,WAAW;AACjC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAE7B,SAAS,MAAM,IAAgC;AAC7C,SAAO,GAAG,UAAe;AAC3B;AAEA,eAAsB,6BAA6B;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC,WAAW,QAAQ,eAAe;AAC9C,GAM6B;AAC3B,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,aAAa,SAAS,oBAAoB,CAAC;AAC1D,MAAI,CAAC,OAAO,OAAO,OAAQ,QAAO,CAAC;AAEnC,QAAM,KAAK,MAAM,EAAE;AACnB,MAAI,cAAc,GACf,WAAW,eAAe,EAC1B,OAAO,WAAW,EAClB,MAAM,eAAe,KAAK,kBAAkB,EAC5C,MAAM,SAAS,MAAM,MAAM,EAC3B,MAAM,cAAc,MAAM,OAAO,MAAM,EACvC,MAAM,qCAA8C,QAAQ,EAAE;AAEjE,MAAI,gBAAgB;AAClB,kBAAc,YAAY,MAAM,mBAAmB,KAAK,cAAc;AAAA,EACxE,OAAO;AACL,kBAAc,YAAY,MAAM,2CAAoD,IAAI,EAAE;AAAA,EAC5F;AAEA,QAAM,OAAO,MAAM,YAChB,QAAQ,WAAW,EACnB,OAAO,oCAA6C,OAAO,OAAO,MAAM,EAAE,EAC1E,QAAQ;AAEX,SAAO,KACJ,IAAI,CAAC,QAAkC,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY,IAAK,EAChG,OAAO,CAAC,OAAoC,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AACxF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Kysely } from 'kysely'\nimport { findEntityIdsBySearchTokensCompat, type SearchTokenDatabase } from '@open-mercato/shared/lib/search/tokenLookup'\n\nfunction getDb(em: EntityManager): Kysely<SearchTokenDatabase> {\n return em.getKysely<SearchTokenDatabase>()\n}\n\nexport async function findMessageIdsBySearchTokens({\n em,\n query,\n tenantId,\n organizationId,\n fields = ['subject', 'body', 'external_name'],\n}: {\n em: EntityManager\n query: string\n tenantId: string | null\n organizationId: string | null\n fields?: string[]\n}): Promise<string[] | null> {\n return findEntityIdsBySearchTokensCompat({\n db: getDb(em),\n entityType: 'messages:message',\n query,\n fields,\n scope: { tenantId, organizationId },\n })\n}\n"],
5
+ "mappings": "AAEA,SAAS,yCAAmE;AAE5E,SAAS,MAAM,IAAgD;AAC7D,SAAO,GAAG,UAA+B;AAC3C;AAEA,eAAsB,6BAA6B;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC,WAAW,QAAQ,eAAe;AAC9C,GAM6B;AAC3B,SAAO,kCAAkC;AAAA,IACvC,IAAI,MAAM,EAAE;AAAA,IACZ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,OAAO,EAAE,UAAU,eAAe;AAAA,EACpC,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
2
2
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
5
+ import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
5
6
  import { createSessionSchema } from "../../data/validators.js";
6
7
  import { paymentGatewaysTag } from "../openapi.js";
7
8
  import {
@@ -86,6 +87,9 @@ async function POST(req) {
86
87
  paymentId: transaction.paymentId
87
88
  }, { status: 201 });
88
89
  } catch (err) {
90
+ if (isCrudHttpError(err)) {
91
+ return NextResponse.json(err.body, { status: err.status });
92
+ }
89
93
  const message = err instanceof Error ? err.message : "Failed to create payment session";
90
94
  const status = message.includes("No gateway adapter") ? 422 : 502;
91
95
  return NextResponse.json({ error: message }, { status });
@@ -100,6 +104,7 @@ const openApi = {
100
104
  tags: [paymentGatewaysTag],
101
105
  responses: [
102
106
  { status: 201, description: "Payment session created" },
107
+ { status: 409, description: "Amount or currency does not match the referenced order" },
103
108
  { status: 422, description: "Invalid payload or unknown provider" },
104
109
  { status: 502, description: "Gateway provider error" }
105
110
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/payment_gateways/api/sessions/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { createSessionSchema } from '../../data/validators'\nimport type { PaymentGatewayService } from '../../lib/gateway-service'\nimport { paymentGatewaysTag } from '../openapi'\nimport {\n resolveUserFeatures,\n runPaymentGatewayMutationGuardAfterSuccess,\n runPaymentGatewayMutationGuards,\n} from '../guards'\n\nconst gatewayTransactionResourceKind = 'payment_gateways.gateway_transaction'\n\nexport const metadata = {\n path: '/payment_gateways/sessions',\n POST: { requireAuth: true, requireFeatures: ['payment_gateways.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe<unknown>(req)\n const parsed = createSessionSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const guardResult = await runPaymentGatewayMutationGuards(\n container,\n {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: gatewayTransactionResourceKind,\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const service = container.resolve('paymentGatewayService') as PaymentGatewayService\n\n try {\n const { transaction, session } = await service.createPaymentSession({\n providerKey: parsed.data.providerKey,\n paymentId: crypto.randomUUID(),\n orderId: parsed.data.orderId,\n amount: parsed.data.amount,\n currencyCode: parsed.data.currencyCode,\n captureMethod: parsed.data.captureMethod,\n description: parsed.data.description,\n successUrl: parsed.data.successUrl,\n cancelUrl: parsed.data.cancelUrl,\n metadata: parsed.data.metadata,\n presentation: parsed.data.presentation,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n const redirectUrl = session.redirectUrl\n ?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null)\n\n await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: gatewayTransactionResourceKind,\n resourceId: transaction.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n\n return NextResponse.json({\n transactionId: transaction.id,\n sessionId: session.sessionId,\n providerKey: transaction.providerKey,\n clientSecret: session.clientSecret,\n redirectUrl,\n providerData: session.providerData ?? null,\n clientSession: session.clientSession ?? null,\n status: session.status,\n paymentId: transaction.paymentId,\n }, { status: 201 })\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'Failed to create payment session'\n const status = message.includes('No gateway adapter') ? 422 : 502\n return NextResponse.json({ error: message }, { status })\n }\n}\n\nexport const openApi = {\n tags: [paymentGatewaysTag],\n summary: 'Create a payment session via a gateway provider',\n methods: {\n POST: {\n summary: 'Create payment session',\n tags: [paymentGatewaysTag],\n responses: [\n { status: 201, description: 'Payment session created' },\n { status: 422, description: 'Invalid payload or unknown provider' },\n { status: 502, description: 'Gateway provider error' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createSessionSchema } from '../../data/validators'\nimport type { PaymentGatewayService } from '../../lib/gateway-service'\nimport { paymentGatewaysTag } from '../openapi'\nimport {\n resolveUserFeatures,\n runPaymentGatewayMutationGuardAfterSuccess,\n runPaymentGatewayMutationGuards,\n} from '../guards'\n\nconst gatewayTransactionResourceKind = 'payment_gateways.gateway_transaction'\n\nexport const metadata = {\n path: '/payment_gateways/sessions',\n POST: { requireAuth: true, requireFeatures: ['payment_gateways.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe<unknown>(req)\n const parsed = createSessionSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const guardResult = await runPaymentGatewayMutationGuards(\n container,\n {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: gatewayTransactionResourceKind,\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const service = container.resolve('paymentGatewayService') as PaymentGatewayService\n\n try {\n const { transaction, session } = await service.createPaymentSession({\n providerKey: parsed.data.providerKey,\n paymentId: crypto.randomUUID(),\n orderId: parsed.data.orderId,\n amount: parsed.data.amount,\n currencyCode: parsed.data.currencyCode,\n captureMethod: parsed.data.captureMethod,\n description: parsed.data.description,\n successUrl: parsed.data.successUrl,\n cancelUrl: parsed.data.cancelUrl,\n metadata: parsed.data.metadata,\n presentation: parsed.data.presentation,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n const redirectUrl = session.redirectUrl\n ?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null)\n\n await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: gatewayTransactionResourceKind,\n resourceId: transaction.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n\n return NextResponse.json({\n transactionId: transaction.id,\n sessionId: session.sessionId,\n providerKey: transaction.providerKey,\n clientSecret: session.clientSecret,\n redirectUrl,\n providerData: session.providerData ?? null,\n clientSession: session.clientSession ?? null,\n status: session.status,\n paymentId: transaction.paymentId,\n }, { status: 201 })\n } catch (err: unknown) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const message = err instanceof Error ? err.message : 'Failed to create payment session'\n const status = message.includes('No gateway adapter') ? 422 : 502\n return NextResponse.json({ error: message }, { status })\n }\n}\n\nexport const openApi = {\n tags: [paymentGatewaysTag],\n summary: 'Create a payment session via a gateway provider',\n methods: {\n POST: {\n summary: 'Create payment session',\n tags: [paymentGatewaysTag],\n responses: [\n { status: 201, description: 'Payment session created' },\n { status: 409, description: 'Amount or currency does not match the referenced order' },\n { status: 422, description: 'Invalid payload or unknown provider' },\n { status: 502, description: 'Gateway provider error' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,10 +2,26 @@ import { asFunction, asValue } from "awilix";
2
2
  import { GatewayTransaction, WebhookProcessedEvent } from "./data/entities.js";
3
3
  import { createPaymentGatewayDescriptorService } from "./lib/descriptor-service.js";
4
4
  import { createPaymentGatewayService } from "./lib/gateway-service.js";
5
+ import { isPaymentOrderTotalResolver } from "./lib/order-amount-reconciliation.js";
6
+ const ORDER_TOTAL_RESOLVER_NAME = "paymentOrderTotalResolver";
7
+ function resolveOrderTotalResolver(container, cradle) {
8
+ if (!container.hasRegistration(ORDER_TOTAL_RESOLVER_NAME)) return null;
9
+ const candidate = cradle[ORDER_TOTAL_RESOLVER_NAME];
10
+ if (!isPaymentOrderTotalResolver(candidate)) {
11
+ throw new Error(`[internal] ${ORDER_TOTAL_RESOLVER_NAME} does not implement resolveOrderTotal`);
12
+ }
13
+ return candidate;
14
+ }
5
15
  function register(container) {
6
16
  container.register({
7
17
  paymentGatewayService: asFunction(
8
- ({ em, integrationCredentialsService, integrationLogService, integrationStateService }) => createPaymentGatewayService({ em, integrationCredentialsService, integrationLogService, integrationStateService })
18
+ (cradle) => createPaymentGatewayService({
19
+ em: cradle.em,
20
+ integrationCredentialsService: cradle.integrationCredentialsService,
21
+ integrationLogService: cradle.integrationLogService,
22
+ integrationStateService: cradle.integrationStateService,
23
+ paymentOrderTotalResolver: resolveOrderTotalResolver(container, cradle)
24
+ })
9
25
  ).scoped().proxy(),
10
26
  paymentGatewayDescriptorService: asFunction(
11
27
  ({ integrationCredentialsService, integrationStateService }) => createPaymentGatewayDescriptorService({ integrationCredentialsService, integrationStateService })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/payment_gateways/di.ts"],
4
- "sourcesContent": ["import { asFunction, asValue } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CredentialsService } from '../integrations/lib/credentials-service'\nimport type { IntegrationLogService } from '../integrations/lib/log-service'\nimport type { IntegrationStateService } from '../integrations/lib/state-service'\nimport { GatewayTransaction, WebhookProcessedEvent } from './data/entities'\nimport { createPaymentGatewayDescriptorService } from './lib/descriptor-service'\nimport { createPaymentGatewayService } from './lib/gateway-service'\n\ntype Cradle = {\n em: EntityManager\n integrationCredentialsService: CredentialsService\n integrationLogService: IntegrationLogService\n integrationStateService: IntegrationStateService\n}\n\nexport function register(container: AppContainer) {\n container.register({\n paymentGatewayService: asFunction(({ em, integrationCredentialsService, integrationLogService, integrationStateService }: Cradle) =>\n createPaymentGatewayService({ em, integrationCredentialsService, integrationLogService, integrationStateService }),\n ).scoped().proxy(),\n paymentGatewayDescriptorService: asFunction(({ integrationCredentialsService, integrationStateService }: Cradle) =>\n createPaymentGatewayDescriptorService({ integrationCredentialsService, integrationStateService }),\n ).scoped().proxy(),\n\n GatewayTransaction: asValue(GatewayTransaction),\n WebhookProcessedEvent: asValue(WebhookProcessedEvent),\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY,eAAe;AAMpC,SAAS,oBAAoB,6BAA6B;AAC1D,SAAS,6CAA6C;AACtD,SAAS,mCAAmC;AASrC,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,uBAAuB;AAAA,MAAW,CAAC,EAAE,IAAI,+BAA+B,uBAAuB,wBAAwB,MACrH,4BAA4B,EAAE,IAAI,+BAA+B,uBAAuB,wBAAwB,CAAC;AAAA,IACnH,EAAE,OAAO,EAAE,MAAM;AAAA,IACjB,iCAAiC;AAAA,MAAW,CAAC,EAAE,+BAA+B,wBAAwB,MACpG,sCAAsC,EAAE,+BAA+B,wBAAwB,CAAC;AAAA,IAClG,EAAE,OAAO,EAAE,MAAM;AAAA,IAEjB,oBAAoB,QAAQ,kBAAkB;AAAA,IAC9C,uBAAuB,QAAQ,qBAAqB;AAAA,EACtD,CAAC;AACH;",
4
+ "sourcesContent": ["import { asFunction, asValue } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CredentialsService } from '../integrations/lib/credentials-service'\nimport type { IntegrationLogService } from '../integrations/lib/log-service'\nimport type { IntegrationStateService } from '../integrations/lib/state-service'\nimport type { PaymentOrderTotalResolver } from '@open-mercato/shared/modules/payment_gateways/types'\nimport { GatewayTransaction, WebhookProcessedEvent } from './data/entities'\nimport { createPaymentGatewayDescriptorService } from './lib/descriptor-service'\nimport { createPaymentGatewayService } from './lib/gateway-service'\nimport { isPaymentOrderTotalResolver } from './lib/order-amount-reconciliation'\n\ntype Cradle = {\n em: EntityManager\n integrationCredentialsService: CredentialsService\n integrationLogService: IntegrationLogService\n integrationStateService: IntegrationStateService\n}\n\nconst ORDER_TOTAL_RESOLVER_NAME = 'paymentOrderTotalResolver'\n\n/**\n * The order-total resolver is owned by whichever module owns orders (`sales`\n * registers the default one). Its absence is the only supported reason to skip\n * amount reconciliation, so this only tolerates a missing registration: a\n * registered resolver that fails to build or does not satisfy the contract\n * throws instead of silently disabling the check.\n */\nfunction resolveOrderTotalResolver(container: AppContainer, cradle: Cradle): PaymentOrderTotalResolver | null {\n if (!container.hasRegistration(ORDER_TOTAL_RESOLVER_NAME)) return null\n const candidate = (cradle as Cradle & { paymentOrderTotalResolver?: unknown })[ORDER_TOTAL_RESOLVER_NAME]\n if (!isPaymentOrderTotalResolver(candidate)) {\n throw new Error(`[internal] ${ORDER_TOTAL_RESOLVER_NAME} does not implement resolveOrderTotal`)\n }\n return candidate\n}\n\nexport function register(container: AppContainer) {\n container.register({\n paymentGatewayService: asFunction((cradle: Cradle) =>\n createPaymentGatewayService({\n em: cradle.em,\n integrationCredentialsService: cradle.integrationCredentialsService,\n integrationLogService: cradle.integrationLogService,\n integrationStateService: cradle.integrationStateService,\n paymentOrderTotalResolver: resolveOrderTotalResolver(container, cradle),\n }),\n ).scoped().proxy(),\n paymentGatewayDescriptorService: asFunction(({ integrationCredentialsService, integrationStateService }: Cradle) =>\n createPaymentGatewayDescriptorService({ integrationCredentialsService, integrationStateService }),\n ).scoped().proxy(),\n\n GatewayTransaction: asValue(GatewayTransaction),\n WebhookProcessedEvent: asValue(WebhookProcessedEvent),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY,eAAe;AAOpC,SAAS,oBAAoB,6BAA6B;AAC1D,SAAS,6CAA6C;AACtD,SAAS,mCAAmC;AAC5C,SAAS,mCAAmC;AAS5C,MAAM,4BAA4B;AASlC,SAAS,0BAA0B,WAAyB,QAAkD;AAC5G,MAAI,CAAC,UAAU,gBAAgB,yBAAyB,EAAG,QAAO;AAClE,QAAM,YAAa,OAA4D,yBAAyB;AACxG,MAAI,CAAC,4BAA4B,SAAS,GAAG;AAC3C,UAAM,IAAI,MAAM,cAAc,yBAAyB,uCAAuC;AAAA,EAChG;AACA,SAAO;AACT;AAEO,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,uBAAuB;AAAA,MAAW,CAAC,WACjC,4BAA4B;AAAA,QAC1B,IAAI,OAAO;AAAA,QACX,+BAA+B,OAAO;AAAA,QACtC,uBAAuB,OAAO;AAAA,QAC9B,yBAAyB,OAAO;AAAA,QAChC,2BAA2B,0BAA0B,WAAW,MAAM;AAAA,MACxE,CAAC;AAAA,IACH,EAAE,OAAO,EAAE,MAAM;AAAA,IACjB,iCAAiC;AAAA,MAAW,CAAC,EAAE,+BAA+B,wBAAwB,MACpG,sCAAsC,EAAE,+BAA+B,wBAAwB,CAAC;AAAA,IAClG,EAAE,OAAO,EAAE,MAAM;AAAA,IAEjB,oBAAoB,QAAQ,kBAAkB;AAAA,IAC9C,uBAAuB,QAAQ,qBAAqB;AAAA,EACtD,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ import { GatewaySessionInitialization, GatewayTransaction } from "../data/entiti
8
8
  import { canApplyManualAction, isValidTransition } from "./status-machine.js";
9
9
  import { emitPaymentGatewayEvent } from "../events.js";
10
10
  import { readGatewayMetadata, readWebhookLog } from "./transaction-fields.js";
11
+ import { reconcileSessionAmountWithOrder } from "./order-amount-reconciliation.js";
11
12
  import {
12
13
  alignCapturedAmountWithStatus,
13
14
  assertCaptureWithinRemaining,
@@ -275,6 +276,13 @@ function createPaymentGatewayService(deps) {
275
276
  return {
276
277
  async createPaymentSession(input) {
277
278
  const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
279
+ await reconcileSessionAmountWithOrder({
280
+ orderId: input.orderId,
281
+ amount: input.amount,
282
+ currencyCode: input.currencyCode,
283
+ scope,
284
+ resolver: deps.paymentOrderTotalResolver
285
+ });
278
286
  const { adapter, credentials } = await resolveAdapterAndCredentials(input.providerKey, scope);
279
287
  const sessionInput = {
280
288
  paymentId: input.paymentId,