@open-mercato/core 0.6.2-develop.3467.1.2a1818709d → 0.6.2

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 (158) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/queue-runner.js +15 -3
  3. package/dist/helpers/integration/queue-runner.js.map +2 -2
  4. package/dist/helpers/integration/queue.js +1 -1
  5. package/dist/helpers/integration/queue.js.map +2 -2
  6. package/dist/helpers/integration/salesUi.js +24 -1
  7. package/dist/helpers/integration/salesUi.js.map +2 -2
  8. package/dist/modules/auth/api/roles/acl/route.js +37 -1
  9. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  10. package/dist/modules/auth/api/roles/route.js +36 -2
  11. package/dist/modules/auth/api/roles/route.js.map +2 -2
  12. package/dist/modules/auth/api/users/acl/route.js +36 -1
  13. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  14. package/dist/modules/auth/api/users/route.js +101 -13
  15. package/dist/modules/auth/api/users/route.js.map +2 -2
  16. package/dist/modules/auth/backend/roles/[id]/edit/page.js +32 -14
  17. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  18. package/dist/modules/auth/backend/users/[id]/edit/page.js +1 -1
  19. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  20. package/dist/modules/auth/backend/users/create/page.js +1 -1
  21. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  22. package/dist/modules/auth/backend/users/roleOptions.js +1 -1
  23. package/dist/modules/auth/backend/users/roleOptions.js.map +2 -2
  24. package/dist/modules/auth/commands/roles.js +8 -0
  25. package/dist/modules/auth/commands/roles.js.map +2 -2
  26. package/dist/modules/auth/commands/users.js +3 -0
  27. package/dist/modules/auth/commands/users.js.map +2 -2
  28. package/dist/modules/auth/components/AclEditor.js +53 -26
  29. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  30. package/dist/modules/auth/lib/grantChecks.js +97 -2
  31. package/dist/modules/auth/lib/grantChecks.js.map +2 -2
  32. package/dist/modules/business_rules/lib/expression-evaluator.js +9 -7
  33. package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
  34. package/dist/modules/customers/ai-agents.js +10 -1
  35. package/dist/modules/customers/ai-agents.js.map +2 -2
  36. package/dist/modules/customers/ai-tools/settings-pack.js +55 -1
  37. package/dist/modules/customers/ai-tools/settings-pack.js.map +2 -2
  38. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +13 -5
  39. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  40. package/dist/modules/customers/lib/personCompanyLinkTable.js +4 -30
  41. package/dist/modules/customers/lib/personCompanyLinkTable.js.map +2 -2
  42. package/dist/modules/customers/migrations/Migration20260521175027.js +12 -0
  43. package/dist/modules/customers/migrations/Migration20260521175027.js.map +7 -0
  44. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +41 -12
  45. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  46. package/dist/modules/data_sync/api/options.js +2 -0
  47. package/dist/modules/data_sync/api/options.js.map +2 -2
  48. package/dist/modules/data_sync/api/run.js +77 -58
  49. package/dist/modules/data_sync/api/run.js.map +2 -2
  50. package/dist/modules/data_sync/backend/data-sync/page.js +3 -2
  51. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  52. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +8 -3
  53. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  54. package/dist/modules/integrations/backend/integrations/[id]/page.js +15 -13
  55. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  56. package/dist/modules/integrations/data/validators.js +9 -6
  57. package/dist/modules/integrations/data/validators.js.map +2 -2
  58. package/dist/modules/messages/api/route.js +2 -2
  59. package/dist/modules/messages/api/route.js.map +2 -2
  60. package/dist/modules/messages/commands/messages.js +42 -2
  61. package/dist/modules/messages/commands/messages.js.map +2 -2
  62. package/dist/modules/messages/components/MessagesInboxPageClient.js +66 -87
  63. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  64. package/dist/modules/messages/components/inboxFilters.js +133 -0
  65. package/dist/modules/messages/components/inboxFilters.js.map +7 -0
  66. package/dist/modules/messages/data/validators.js +8 -6
  67. package/dist/modules/messages/data/validators.js.map +2 -2
  68. package/dist/modules/progress/api/jobs/route.js +26 -16
  69. package/dist/modules/progress/api/jobs/route.js.map +2 -2
  70. package/dist/modules/query_index/di.js +8 -6
  71. package/dist/modules/query_index/di.js.map +2 -2
  72. package/dist/modules/query_index/lib/indexer.js +12 -10
  73. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  74. package/dist/modules/query_index/subscribers/upsert_one.js +7 -1
  75. package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
  76. package/dist/modules/sales/api/order-adjustments/route.js.map +2 -2
  77. package/dist/modules/sales/api/quote-adjustments/route.js.map +2 -2
  78. package/dist/modules/sales/commands/documents.js +104 -0
  79. package/dist/modules/sales/commands/documents.js.map +2 -2
  80. package/dist/modules/sales/data/validators.js +20 -1
  81. package/dist/modules/sales/data/validators.js.map +2 -2
  82. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
  83. package/dist/modules/sync_excel/api/import/route.js +126 -116
  84. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  85. package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
  86. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  87. package/dist/modules/translations/components/TranslationManager.js +9 -4
  88. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  89. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  90. package/dist/modules/workflows/lib/event-trigger-service.js +23 -11
  91. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  92. package/jest.config.cjs +4 -1
  93. package/package.json +15 -16
  94. package/src/helpers/integration/queue-runner.ts +21 -2
  95. package/src/helpers/integration/queue.ts +1 -1
  96. package/src/helpers/integration/salesUi.ts +26 -1
  97. package/src/modules/auth/api/roles/acl/route.ts +39 -1
  98. package/src/modules/auth/api/roles/route.ts +38 -2
  99. package/src/modules/auth/api/users/acl/route.ts +41 -1
  100. package/src/modules/auth/api/users/route.ts +105 -12
  101. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +35 -13
  102. package/src/modules/auth/backend/users/[id]/edit/page.tsx +1 -1
  103. package/src/modules/auth/backend/users/create/page.tsx +1 -1
  104. package/src/modules/auth/backend/users/roleOptions.ts +2 -1
  105. package/src/modules/auth/commands/roles.ts +8 -0
  106. package/src/modules/auth/commands/users.ts +3 -0
  107. package/src/modules/auth/components/AclEditor.tsx +50 -24
  108. package/src/modules/auth/lib/grantChecks.ts +116 -2
  109. package/src/modules/business_rules/lib/expression-evaluator.ts +9 -13
  110. package/src/modules/customers/ai-agents.ts +10 -1
  111. package/src/modules/customers/ai-tools/settings-pack.ts +62 -1
  112. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +18 -7
  113. package/src/modules/customers/lib/personCompanyLinkTable.ts +33 -41
  114. package/src/modules/customers/migrations/Migration20260521175027.ts +39 -0
  115. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +38 -12
  116. package/src/modules/data_sync/api/options.ts +2 -0
  117. package/src/modules/data_sync/api/run.ts +80 -61
  118. package/src/modules/data_sync/backend/data-sync/page.tsx +10 -0
  119. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +17 -3
  120. package/src/modules/data_sync/i18n/de.json +3 -0
  121. package/src/modules/data_sync/i18n/en.json +3 -0
  122. package/src/modules/data_sync/i18n/es.json +3 -0
  123. package/src/modules/data_sync/i18n/pl.json +3 -0
  124. package/src/modules/data_sync/lib/adapter.ts +9 -0
  125. package/src/modules/integrations/backend/integrations/[id]/page.tsx +15 -13
  126. package/src/modules/integrations/data/validators.ts +5 -4
  127. package/src/modules/messages/api/route.ts +2 -2
  128. package/src/modules/messages/commands/messages.ts +54 -2
  129. package/src/modules/messages/components/MessagesInboxPageClient.tsx +75 -90
  130. package/src/modules/messages/components/inboxFilters.ts +162 -0
  131. package/src/modules/messages/data/validators.ts +7 -5
  132. package/src/modules/messages/i18n/de.json +5 -4
  133. package/src/modules/messages/i18n/en.json +5 -4
  134. package/src/modules/messages/i18n/es.json +5 -4
  135. package/src/modules/messages/i18n/pl.json +5 -4
  136. package/src/modules/progress/api/jobs/route.ts +30 -20
  137. package/src/modules/query_index/di.ts +9 -7
  138. package/src/modules/query_index/lib/indexer.ts +13 -11
  139. package/src/modules/query_index/subscribers/upsert_one.ts +9 -1
  140. package/src/modules/sales/api/order-adjustments/route.ts +2 -1
  141. package/src/modules/sales/api/quote-adjustments/route.ts +2 -1
  142. package/src/modules/sales/commands/documents.ts +116 -0
  143. package/src/modules/sales/data/validators.ts +38 -0
  144. package/src/modules/sales/i18n/de.json +2 -0
  145. package/src/modules/sales/i18n/en.json +2 -0
  146. package/src/modules/sales/i18n/es.json +2 -0
  147. package/src/modules/sales/i18n/pl.json +2 -0
  148. package/src/modules/sales/lib/makeSalesLineRoute.ts +2 -1
  149. package/src/modules/staff/i18n/de.json +1 -0
  150. package/src/modules/staff/i18n/en.json +1 -0
  151. package/src/modules/staff/i18n/es.json +1 -0
  152. package/src/modules/staff/i18n/pl.json +1 -0
  153. package/src/modules/sync_excel/api/import/route.ts +141 -131
  154. package/src/modules/sync_excel/lib/adapters/customers.ts +1 -0
  155. package/src/modules/translations/components/TranslationManager.tsx +9 -4
  156. package/src/modules/workflows/lib/activity-executor.ts +4 -3
  157. package/src/modules/workflows/lib/event-trigger-service.ts +24 -11
  158. package/tsconfig.json +1 -0
@@ -4,6 +4,8 @@
4
4
  "audit_logs.resource_kind.sales.payment": "Payment",
5
5
  "audit_logs.resource_kind.sales.shipment": "Shipment",
6
6
  "customers~sales.nav.group": "Sales",
7
+ "sales.adjustments.error.returnExceedsRemainingGrandTotalGross": "Return adjustment amountGross exceeds the remaining grand total. Reduce the amount or remove existing returns first.",
8
+ "sales.adjustments.error.returnExceedsRemainingGrandTotalNet": "Return adjustment amountNet exceeds the remaining grand total. Reduce the amount or remove existing returns first.",
7
9
  "sales.audit.channels.create": "Create sales channel",
8
10
  "sales.audit.channels.delete": "Delete sales channel",
9
11
  "sales.audit.channels.update": "Update sales channel",
@@ -4,6 +4,8 @@
4
4
  "audit_logs.resource_kind.sales.payment": "Pago",
5
5
  "audit_logs.resource_kind.sales.shipment": "Envío",
6
6
  "customers~sales.nav.group": "Ventas",
7
+ "sales.adjustments.error.returnExceedsRemainingGrandTotalGross": "El importe bruto del ajuste de devolución supera el total restante. Reduzca el importe o elimine las devoluciones existentes.",
8
+ "sales.adjustments.error.returnExceedsRemainingGrandTotalNet": "El importe neto del ajuste de devolución supera el total restante. Reduzca el importe o elimine las devoluciones existentes.",
7
9
  "sales.audit.channels.create": "Crear canal de ventas",
8
10
  "sales.audit.channels.delete": "Eliminar canal de ventas",
9
11
  "sales.audit.channels.update": "Actualizar canal de ventas",
@@ -4,6 +4,8 @@
4
4
  "audit_logs.resource_kind.sales.payment": "Płatność",
5
5
  "audit_logs.resource_kind.sales.shipment": "Wysyłka",
6
6
  "customers~sales.nav.group": "Sprzedaż",
7
+ "sales.adjustments.error.returnExceedsRemainingGrandTotalGross": "Kwota brutto korekty zwrotu przekracza pozostałą wartość zamówienia. Zmniejsz kwotę lub usuń istniejące zwroty.",
8
+ "sales.adjustments.error.returnExceedsRemainingGrandTotalNet": "Kwota netto korekty zwrotu przekracza pozostałą wartość zamówienia. Zmniejsz kwotę lub usuń istniejące zwroty.",
7
9
  "sales.audit.channels.create": "Utwórz kanał sprzedaży",
8
10
  "sales.audit.channels.delete": "Usuń kanał sprzedaży",
9
11
  "sales.audit.channels.update": "Zaktualizuj kanał sprzedaży",
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod'
2
+ import type { EntityManager } from '@mikro-orm/core'
2
3
  import { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'
3
4
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
5
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
@@ -210,7 +211,7 @@ export function makeSalesLineRoute(config: SalesLineRouteConfig) {
210
211
  if (query.id) filters.id = { $eq: query.id }
211
212
  if (query[parentFkParam]) filters[parentFkColumn] = { $eq: query[parentFkParam] }
212
213
  try {
213
- const em = ctx.container.resolve('em')
214
+ const em = ctx.container.resolve<EntityManager>('em')
214
215
  const cfFilters = await buildCustomFieldFiltersFromQuery({
215
216
  entityId,
216
217
  query,
@@ -280,6 +280,7 @@
280
280
  "staff.messageObjects.notFound": "Nicht gefunden",
281
281
  "staff.messageObjects.team.title": "Team",
282
282
  "staff.messageObjects.teamMember.title": "Teammitglied",
283
+ "staff.messages.leaveRequestApproval": "Urlaubsantrag – Genehmigung",
283
284
  "staff.myAvailability.empty.profileRequired": "Erstelle dein Teamprofil, um Verfügbarkeit zu verwalten.",
284
285
  "staff.myAvailability.errors.load": "Verfügbarkeit konnte nicht geladen werden.",
285
286
  "staff.myAvailability.loading": "Verfügbarkeit wird geladen...",
@@ -280,6 +280,7 @@
280
280
  "staff.messageObjects.notFound": "Not found",
281
281
  "staff.messageObjects.team.title": "Team",
282
282
  "staff.messageObjects.teamMember.title": "Team member",
283
+ "staff.messages.leaveRequestApproval": "Leave request approval",
283
284
  "staff.myAvailability.empty.profileRequired": "Create your team member profile to manage availability.",
284
285
  "staff.myAvailability.errors.load": "Failed to load availability.",
285
286
  "staff.myAvailability.loading": "Loading availability...",
@@ -280,6 +280,7 @@
280
280
  "staff.messageObjects.notFound": "No encontrado",
281
281
  "staff.messageObjects.team.title": "Equipo",
282
282
  "staff.messageObjects.teamMember.title": "Miembro del equipo",
283
+ "staff.messages.leaveRequestApproval": "Solicitud de permiso: aprobación",
283
284
  "staff.myAvailability.empty.profileRequired": "Crea tu perfil de miembro para gestionar la disponibilidad.",
284
285
  "staff.myAvailability.errors.load": "No se pudo cargar la disponibilidad.",
285
286
  "staff.myAvailability.loading": "Cargando disponibilidad...",
@@ -280,6 +280,7 @@
280
280
  "staff.messageObjects.notFound": "Nie znaleziono",
281
281
  "staff.messageObjects.team.title": "Zespół",
282
282
  "staff.messageObjects.teamMember.title": "Członek zespołu",
283
+ "staff.messages.leaveRequestApproval": "Wniosek urlopowy – zatwierdzenie",
283
284
  "staff.myAvailability.empty.profileRequired": "Utwórz profil pracownika, aby zarządzać dostępnością.",
284
285
  "staff.myAvailability.errors.load": "Nie udało się załadować dostępności.",
285
286
  "staff.myAvailability.loading": "Ładowanie dostępności...",
@@ -46,137 +46,147 @@ export const openApi = {
46
46
  }
47
47
 
48
48
  export async function POST(request: Request) {
49
- const auth = await getAuthFromRequest(request)
50
- if (!auth?.tenantId) {
51
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
52
- }
53
-
54
- const payload = await readJsonSafe(request)
55
- const parsedPayload = syncExcelImportRequestSchema.safeParse(payload)
56
- if (!parsedPayload.success) {
57
- return NextResponse.json({ error: 'Invalid import payload.' }, { status: 422 })
58
- }
59
-
60
- const container = await createRequestContainer()
61
- const em = container.resolve('em') as EntityManager
62
- const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
63
- const progressService = container.resolve('progressService') as ProgressService
64
- const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
65
- const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService
66
- const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request })
67
- if (!scopeResult.ok) {
68
- return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status })
69
- }
70
- const { scope } = scopeResult
71
-
72
- const upload = await findOneWithDecryption(
73
- em,
74
- SyncExcelUpload,
75
- {
76
- id: parsedPayload.data.uploadId,
77
- organizationId: scope.organizationId,
78
- tenantId: scope.tenantId,
79
- },
80
- undefined,
81
- scope,
82
- )
83
-
84
- if (!upload) {
85
- return NextResponse.json({ error: 'Upload preview not found.' }, { status: 404 })
86
- }
87
-
88
- const attachment = await findOneWithDecryption(
89
- em,
90
- Attachment,
91
- {
92
- id: upload.attachmentId,
93
- organizationId: scope.organizationId,
94
- tenantId: scope.tenantId,
95
- },
96
- undefined,
97
- scope,
98
- )
99
-
100
- if (!attachment) {
101
- return NextResponse.json({ error: 'Upload attachment not found.' }, { status: 404 })
102
- }
103
-
104
- if (upload.entityType !== parsedPayload.data.entityType) {
105
- return NextResponse.json({ error: 'Upload entity type does not match requested import target.' }, { status: 422 })
106
- }
107
-
108
- if (parsedPayload.data.mapping.entityType !== parsedPayload.data.entityType) {
109
- return NextResponse.json({ error: 'Mapping entity type does not match requested import target.' }, { status: 422 })
110
- }
111
-
112
- const overlap = await syncRunService.findRunningOverlap('sync_excel', parsedPayload.data.entityType, 'import', scope)
113
- if (overlap) {
114
- return NextResponse.json({ error: 'A sync_excel import is already in progress for this entity type.' }, { status: 409 })
115
- }
116
-
117
- const existingMapping = await findOneWithDecryption(
118
- em,
119
- SyncMapping,
120
- {
121
- integrationId: 'sync_excel',
122
- entityType: parsedPayload.data.entityType,
123
- organizationId: scope.organizationId,
124
- tenantId: scope.tenantId,
125
- },
126
- undefined,
127
- scope,
128
- )
129
-
130
- if (existingMapping) {
131
- existingMapping.mapping = parsedPayload.data.mapping
132
- } else {
133
- em.persist(em.create(SyncMapping, {
134
- integrationId: 'sync_excel',
135
- entityType: parsedPayload.data.entityType,
136
- mapping: parsedPayload.data.mapping,
137
- organizationId: scope.organizationId,
138
- tenantId: scope.tenantId,
139
- }))
140
- }
141
-
142
- await credentialsService.save('sync_excel', {}, scope)
143
- await integrationStateService.upsert('sync_excel', { isEnabled: true }, scope)
144
- await em.flush()
145
-
146
- const { run, progressJob } = await startDataSyncRun({
147
- syncRunService,
148
- progressService,
149
- scope: {
150
- ...scope,
151
- userId: auth.sub,
152
- },
153
- input: {
154
- integrationId: 'sync_excel',
155
- entityType: parsedPayload.data.entityType,
156
- direction: 'import',
157
- cursor: createCursor(upload.id, 0),
158
- triggeredBy: auth.sub,
159
- batchSize: parsedPayload.data.batchSize ?? 100,
160
- progressJob: {
161
- jobType: 'sync_excel:import',
162
- name: `CSV import — ${parsedPayload.data.entityType}`,
163
- description: upload.filename,
164
- meta: {
165
- integrationId: 'sync_excel',
166
- uploadId: upload.id,
167
- hiddenFromTopBar: false,
49
+ try {
50
+ const auth = await getAuthFromRequest(request)
51
+ if (!auth?.tenantId) {
52
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
53
+ }
54
+
55
+ const payload = await readJsonSafe(request)
56
+ const parsedPayload = syncExcelImportRequestSchema.safeParse(payload)
57
+ if (!parsedPayload.success) {
58
+ return NextResponse.json({ error: 'Invalid import payload.' }, { status: 422 })
59
+ }
60
+
61
+ const container = await createRequestContainer()
62
+ const em = container.resolve('em') as EntityManager
63
+ const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
64
+ const progressService = container.resolve('progressService') as ProgressService
65
+ const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
66
+ const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService
67
+ const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request })
68
+ if (!scopeResult.ok) {
69
+ return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status })
70
+ }
71
+ const { scope } = scopeResult
72
+
73
+ const upload = await findOneWithDecryption(
74
+ em,
75
+ SyncExcelUpload,
76
+ {
77
+ id: parsedPayload.data.uploadId,
78
+ organizationId: scope.organizationId,
79
+ tenantId: scope.tenantId,
80
+ },
81
+ undefined,
82
+ scope,
83
+ )
84
+
85
+ if (!upload) {
86
+ return NextResponse.json({ error: 'Upload preview not found.' }, { status: 404 })
87
+ }
88
+
89
+ const attachment = await findOneWithDecryption(
90
+ em,
91
+ Attachment,
92
+ {
93
+ id: upload.attachmentId,
94
+ organizationId: scope.organizationId,
95
+ tenantId: scope.tenantId,
96
+ },
97
+ undefined,
98
+ scope,
99
+ )
100
+
101
+ if (!attachment) {
102
+ return NextResponse.json({ error: 'Upload attachment not found.' }, { status: 404 })
103
+ }
104
+
105
+ if (upload.entityType !== parsedPayload.data.entityType) {
106
+ return NextResponse.json({ error: 'Upload entity type does not match requested import target.' }, { status: 422 })
107
+ }
108
+
109
+ if (parsedPayload.data.mapping.entityType !== parsedPayload.data.entityType) {
110
+ return NextResponse.json({ error: 'Mapping entity type does not match requested import target.' }, { status: 422 })
111
+ }
112
+
113
+ const overlap = await syncRunService.findRunningOverlap('sync_excel', parsedPayload.data.entityType, 'import', scope)
114
+ if (overlap) {
115
+ return NextResponse.json({ error: 'A sync_excel import is already in progress for this entity type.' }, { status: 409 })
116
+ }
117
+
118
+ const existingMapping = await findOneWithDecryption(
119
+ em,
120
+ SyncMapping,
121
+ {
122
+ integrationId: 'sync_excel',
123
+ entityType: parsedPayload.data.entityType,
124
+ organizationId: scope.organizationId,
125
+ tenantId: scope.tenantId,
126
+ },
127
+ undefined,
128
+ scope,
129
+ )
130
+
131
+ if (existingMapping) {
132
+ existingMapping.mapping = parsedPayload.data.mapping
133
+ } else {
134
+ em.persist(em.create(SyncMapping, {
135
+ integrationId: 'sync_excel',
136
+ entityType: parsedPayload.data.entityType,
137
+ mapping: parsedPayload.data.mapping,
138
+ organizationId: scope.organizationId,
139
+ tenantId: scope.tenantId,
140
+ }))
141
+ }
142
+
143
+ await credentialsService.save('sync_excel', {}, scope)
144
+ await integrationStateService.upsert('sync_excel', { isEnabled: true }, scope)
145
+ await em.flush()
146
+
147
+ const { run, progressJob } = await startDataSyncRun({
148
+ syncRunService,
149
+ progressService,
150
+ scope: {
151
+ ...scope,
152
+ userId: auth.sub,
153
+ },
154
+ input: {
155
+ integrationId: 'sync_excel',
156
+ entityType: parsedPayload.data.entityType,
157
+ direction: 'import',
158
+ cursor: createCursor(upload.id, 0),
159
+ triggeredBy: auth.sub,
160
+ batchSize: parsedPayload.data.batchSize ?? 100,
161
+ progressJob: {
162
+ jobType: 'sync_excel:import',
163
+ name: `CSV import — ${parsedPayload.data.entityType}`,
164
+ description: upload.filename,
165
+ meta: {
166
+ integrationId: 'sync_excel',
167
+ uploadId: upload.id,
168
+ hiddenFromTopBar: false,
169
+ },
168
170
  },
169
171
  },
170
- },
171
- })
172
-
173
- upload.syncRunId = run.id
174
- upload.status = 'importing'
175
- await em.flush()
176
-
177
- return NextResponse.json(syncExcelImportResponseSchema.parse({
178
- runId: run.id,
179
- progressJobId: progressJob?.id ?? null,
180
- status: run.status,
181
- }), { status: 201 })
172
+ })
173
+
174
+ upload.syncRunId = run.id
175
+ upload.status = 'importing'
176
+ await em.flush()
177
+
178
+ return NextResponse.json(syncExcelImportResponseSchema.parse({
179
+ runId: run.id,
180
+ progressJobId: progressJob?.id ?? null,
181
+ status: run.status,
182
+ }), { status: 201 })
183
+ } catch (error) {
184
+ const message = error instanceof Error ? error.message : String(error)
185
+ const stack = error instanceof Error ? error.stack : undefined
186
+ console.error('[sync_excel.import] unhandled error', { message, stack })
187
+ return NextResponse.json(
188
+ { error: 'Failed to start sync_excel import.', message, stack },
189
+ { status: 500 },
190
+ )
191
+ }
182
192
  }
@@ -993,6 +993,7 @@ async function processRow(params: {
993
993
 
994
994
  export const syncExcelCustomersAdapter: DataSyncAdapter = {
995
995
  providerKey: 'excel',
996
+ runMode: 'provider',
996
997
  operationalTelemetry: true,
997
998
  direction: 'import',
998
999
  supportedEntities: ['customers.person'],
@@ -69,6 +69,7 @@ export function TranslationManager({
69
69
  const [editedTranslations, setEditedTranslations] = React.useState<Record<string, Record<string, string>>>({})
70
70
  const editedTranslationsRef = React.useRef<Record<string, Record<string, string>>>({})
71
71
  const [hasUserEdited, setHasUserEdited] = React.useState(false)
72
+ const hasUserEditedRef = React.useRef(false)
72
73
 
73
74
  const entityType = isEmbedded ? (propEntityType ?? '') : selectedEntityType
74
75
  const recordId = isEmbedded ? (propRecordId ?? '') : selectedRecordId
@@ -191,11 +192,11 @@ export function TranslationManager({
191
192
 
192
193
  React.useEffect(() => {
193
194
  const sig = translationSignature
194
- if (sig === lastTranslationSignatureRef.current && hasUserEdited) return
195
+ if (sig === lastTranslationSignatureRef.current && hasUserEditedRef.current) return
195
196
  lastTranslationSignatureRef.current = sig
196
197
 
197
198
  if (!translationData?.translations) {
198
- if (!hasUserEdited) {
199
+ if (!hasUserEditedRef.current) {
199
200
  editedTranslationsRef.current = {}
200
201
  setEditedTranslations({})
201
202
  }
@@ -210,11 +211,11 @@ export function TranslationManager({
210
211
  parsed[locale][key] = typeof val === 'string' ? val : ''
211
212
  }
212
213
  }
213
- if (!hasUserEdited) {
214
+ if (!hasUserEditedRef.current) {
214
215
  editedTranslationsRef.current = parsed
215
216
  setEditedTranslations(parsed)
216
217
  }
217
- }, [translationSignature, translationData, hasUserEdited])
218
+ }, [translationSignature, translationData])
218
219
 
219
220
  const mutation = useMutation({
220
221
  mutationFn: async () => {
@@ -252,6 +253,7 @@ export function TranslationManager({
252
253
  },
253
254
  onSuccess: () => {
254
255
  flash(t('translations.manager.flash.saved', 'Translations saved'), 'success')
256
+ hasUserEditedRef.current = false
255
257
  setHasUserEdited(false)
256
258
  void refetchTranslation()
257
259
  },
@@ -262,6 +264,7 @@ export function TranslationManager({
262
264
  })
263
265
 
264
266
  const updateFieldValue = (locale: string, fieldKey: string, value: string) => {
267
+ hasUserEditedRef.current = true
265
268
  setHasUserEdited(true)
266
269
  const next = {
267
270
  ...editedTranslationsRef.current,
@@ -288,6 +291,7 @@ export function TranslationManager({
288
291
  value={selectedRecordId}
289
292
  onChange={(next) => {
290
293
  setSelectedRecordId(next)
294
+ hasUserEditedRef.current = false
291
295
  setHasUserEdited(false)
292
296
  }}
293
297
  placeholder={t('translations.manager.searchRecords', 'Search records...')}
@@ -482,6 +486,7 @@ export function TranslationManager({
482
486
  onChange={(next) => {
483
487
  setSelectedEntityType(next)
484
488
  setSelectedRecordId('')
489
+ hasUserEditedRef.current = false
485
490
  setHasUserEdited(false)
486
491
  }}
487
492
  placeholder={t('translations.manager.placeholder', 'Select an entity')}
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { EntityManager } from '@mikro-orm/core'
14
+ import type { EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'
14
15
  import type { AwilixContainer } from 'awilix'
15
16
  import { WorkflowInstance } from '../data/entities'
16
17
  import { createModuleQueue, Queue } from '@open-mercato/queue'
@@ -406,7 +407,7 @@ export async function executeSendEmail(
406
407
 
407
408
  // Check if email service is available in container
408
409
  try {
409
- const emailService = container.resolve('emailService')
410
+ const emailService = container.resolve<{ send: (input: unknown) => Promise<unknown> | unknown }>('emailService')
410
411
  if (emailService && typeof emailService.send === 'function') {
411
412
  await emailService.send({
412
413
  to,
@@ -441,7 +442,7 @@ export async function executeEmitEvent(
441
442
  }
442
443
 
443
444
  // Get event bus from container
444
- const eventBus = container.resolve('eventBus')
445
+ const eventBus = container.resolve<{ emitEvent: (event: string, payload: unknown, options?: unknown) => Promise<unknown> | unknown }>('eventBus')
445
446
 
446
447
  if (!eventBus || typeof eventBus.emitEvent !== 'function') {
447
448
  throw new Error('Event bus not available in container')
@@ -789,7 +790,7 @@ export async function executeCallApi(
789
790
  const { withOnetimeApiKey } = await import('../../api_keys/services/apiKeyService')
790
791
 
791
792
  // 4. Get EntityManager from container (for correct type)
792
- const apiKeyEm = container.resolve('em')
793
+ const apiKeyEm = container.resolve<PostgreSqlEntityManager>('em')
793
794
 
794
795
  // 5. Resolve the roles that the one-time API key will inherit.
795
796
  //
@@ -6,9 +6,12 @@
6
6
  */
7
7
 
8
8
  import type { EntityManager } from '@mikro-orm/core'
9
+ import type { EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'
9
10
  import type { AwilixContainer } from 'awilix'
10
11
  import type { CacheService } from '@open-mercato/cache'
11
12
  import { matchEventPattern } from '@open-mercato/shared/lib/events/patterns'
13
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
14
+ import { testLinearRegex } from '@open-mercato/shared/lib/regex/linear'
12
15
  import {
13
16
  WorkflowEventTrigger,
14
17
  WorkflowDefinition,
@@ -257,11 +260,12 @@ function evaluateCondition(condition: TriggerFilterCondition, payload: Record<st
257
260
  if (typeof value !== 'string' || typeof expected !== 'string') return false
258
261
  if (value.length > MAX_WORKFLOW_REGEX_INPUT_LENGTH) return false
259
262
  if (!isSafeWorkflowRegexPattern(expected)) return false
260
- try {
261
- const regex = new RegExp(expected)
262
- return regex.test(value)
263
- } catch {
264
- return false
263
+ {
264
+ const result = testLinearRegex(expected, value, {
265
+ maxPatternLength: MAX_WORKFLOW_REGEX_PATTERN_LENGTH,
266
+ maxInputLength: MAX_WORKFLOW_REGEX_INPUT_LENGTH,
267
+ })
268
+ return result.ok ? result.matched : false
265
269
  }
266
270
 
267
271
  default:
@@ -325,7 +329,9 @@ async function loadLegacyTriggers(
325
329
  tenantId: string,
326
330
  organizationId: string
327
331
  ): Promise<UnifiedTrigger[]> {
328
- const legacyTriggers = await em.find(
332
+ const postgresEm = em as unknown as PostgreSqlEntityManager
333
+ const legacyTriggers = await findWithDecryption(
334
+ postgresEm,
329
335
  WorkflowEventTrigger,
330
336
  {
331
337
  tenantId,
@@ -335,16 +341,19 @@ async function loadLegacyTriggers(
335
341
  },
336
342
  {
337
343
  orderBy: { priority: 'DESC', createdAt: 'ASC' },
338
- }
344
+ },
345
+ { tenantId, organizationId },
339
346
  )
340
347
 
341
348
  // Get definitions for these triggers to get workflowId
342
349
  const definitionIds = [...new Set(legacyTriggers.map(t => t.workflowDefinitionId))]
343
- const definitions = definitionIds.length > 0 ? await em.find(WorkflowDefinition, {
350
+ const definitions = definitionIds.length > 0 ? await findWithDecryption(postgresEm, WorkflowDefinition, {
344
351
  id: { $in: definitionIds },
352
+ tenantId,
353
+ organizationId,
345
354
  enabled: true,
346
355
  deletedAt: null,
347
- }) : []
356
+ }, {}, { tenantId, organizationId }) : []
348
357
  const definitionMap = new Map(definitions.map(d => [d.id, d]))
349
358
 
350
359
  return legacyTriggers
@@ -379,15 +388,19 @@ async function loadEmbeddedTriggers(
379
388
  tenantId: string,
380
389
  organizationId: string
381
390
  ): Promise<UnifiedTrigger[]> {
391
+ const postgresEm = em as unknown as PostgreSqlEntityManager
382
392
  // Load all enabled definitions that may have triggers
383
- const definitions = await em.find(
393
+ const definitions = await findWithDecryption(
394
+ postgresEm,
384
395
  WorkflowDefinition,
385
396
  {
386
397
  tenantId,
387
398
  organizationId,
388
399
  enabled: true,
389
400
  deletedAt: null,
390
- }
401
+ },
402
+ {},
403
+ { tenantId, organizationId },
391
404
  )
392
405
 
393
406
  const triggers: UnifiedTrigger[] = []
package/tsconfig.json CHANGED
@@ -4,6 +4,7 @@
4
4
  "compilerOptions": {
5
5
  "noEmit": true,
6
6
  "baseUrl": ".",
7
+ "ignoreDeprecations": "6.0",
7
8
  "paths": {
8
9
  "@open-mercato/core/modules/core/__integration__/helpers/*": [
9
10
  "./src/modules/core/__integration__/helpers/*"