@open-mercato/core 0.9.0 → 0.9.1-develop.7462.1.94c09f158f

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 (276) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
  3. package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
  4. package/dist/generated/entities/customer_interaction/index.js +2 -0
  5. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/attachments/api/route.js +29 -3
  9. package/dist/modules/attachments/api/route.js.map +2 -2
  10. package/dist/modules/attachments/lib/ocrLimits.js +49 -0
  11. package/dist/modules/attachments/lib/ocrLimits.js.map +7 -0
  12. package/dist/modules/attachments/lib/ocrQueue.js +76 -2
  13. package/dist/modules/attachments/lib/ocrQueue.js.map +2 -2
  14. package/dist/modules/attachments/lib/ocrService.js +4 -0
  15. package/dist/modules/attachments/lib/ocrService.js.map +2 -2
  16. package/dist/modules/attachments/lib/pdfProcessing.js +14 -2
  17. package/dist/modules/attachments/lib/pdfProcessing.js.map +2 -2
  18. package/dist/modules/attachments/lib/scoped-upload-service.js +4 -2
  19. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  20. package/dist/modules/attachments/lib/textExtraction.js +13 -1
  21. package/dist/modules/attachments/lib/textExtraction.js.map +2 -2
  22. package/dist/modules/catalog/commands/categories.js +1 -1
  23. package/dist/modules/catalog/commands/categories.js.map +2 -2
  24. package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
  25. package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
  26. package/dist/modules/catalog/commands/products.js +52 -17
  27. package/dist/modules/catalog/commands/products.js.map +2 -2
  28. package/dist/modules/catalog/search.js +3 -1
  29. package/dist/modules/catalog/search.js.map +2 -2
  30. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +2 -1
  31. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +2 -2
  32. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
  33. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  34. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
  35. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
  36. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
  37. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
  38. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +10 -7
  39. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
  40. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
  41. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  42. package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
  43. package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
  44. package/dist/modules/communication_channels/lib/oauth-result.js +12 -0
  45. package/dist/modules/communication_channels/lib/oauth-result.js.map +7 -0
  46. package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
  47. package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
  48. package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
  49. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
  50. package/dist/modules/customers/api/activity-types/route.js +151 -0
  51. package/dist/modules/customers/api/activity-types/route.js.map +7 -0
  52. package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
  53. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  54. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
  55. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  56. package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
  57. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  58. package/dist/modules/customers/api/interactions/route.js +3 -0
  59. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  60. package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
  61. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  62. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
  63. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
  64. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
  65. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  66. package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
  67. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  68. package/dist/modules/customers/calendar-event-types.js +548 -0
  69. package/dist/modules/customers/calendar-event-types.js.map +7 -0
  70. package/dist/modules/customers/commands/dictionaries.js +70 -10
  71. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  72. package/dist/modules/customers/commands/interactions.js +187 -22
  73. package/dist/modules/customers/commands/interactions.js.map +3 -3
  74. package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
  75. package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
  76. package/dist/modules/customers/components/DictionarySettings.js +3 -1
  77. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  78. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
  79. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
  80. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
  81. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  82. package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
  83. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  84. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
  85. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
  87. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
  88. package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
  89. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  90. package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
  91. package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
  92. package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
  93. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
  94. package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
  95. package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
  96. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
  97. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  98. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
  99. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
  100. package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
  101. package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
  102. package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
  103. package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
  104. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
  105. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
  107. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/types.js +1 -0
  109. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  110. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
  111. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  112. package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
  113. package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
  114. package/dist/modules/customers/components/detail/DealForm.js +7 -3
  115. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  116. package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
  117. package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
  118. package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
  119. package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
  120. package/dist/modules/customers/components/detail/PersonCard.js +5 -2
  121. package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
  122. package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
  123. package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
  124. package/dist/modules/customers/data/entities.js +6 -0
  125. package/dist/modules/customers/data/entities.js.map +2 -2
  126. package/dist/modules/customers/data/validators.js +39 -10
  127. package/dist/modules/customers/data/validators.js.map +2 -2
  128. package/dist/modules/customers/di.js +20 -1
  129. package/dist/modules/customers/di.js.map +2 -2
  130. package/dist/modules/customers/extension-points.js +13 -0
  131. package/dist/modules/customers/extension-points.js.map +2 -2
  132. package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
  133. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  134. package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
  135. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  136. package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
  137. package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
  138. package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
  139. package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
  140. package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
  141. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  142. package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
  143. package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
  144. package/dist/modules/customers/lib/calendar/timezone.js +111 -0
  145. package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
  146. package/dist/modules/customers/lib/interactionReadModel.js +1 -0
  147. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  148. package/dist/modules/customers/lib/todoCompatibility.js +1 -0
  149. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  150. package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
  151. package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
  152. package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
  153. package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
  154. package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
  155. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  156. package/dist/modules/messages/subscribers/message-notification.js +13 -1
  157. package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
  158. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
  159. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  160. package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
  161. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  162. package/dist/modules/planner/lib/availabilityMerge.js +101 -38
  163. package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
  164. package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
  165. package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
  166. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  167. package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
  168. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  169. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
  170. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  171. package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
  172. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  173. package/generated/entities/customer_dictionary_entry/index.ts +1 -0
  174. package/generated/entities/customer_interaction/index.ts +1 -0
  175. package/generated/entity-fields-registry.ts +2 -0
  176. package/package.json +9 -8
  177. package/src/modules/attachments/api/route.ts +36 -3
  178. package/src/modules/attachments/lib/ocrLimits.ts +62 -0
  179. package/src/modules/attachments/lib/ocrQueue.ts +95 -2
  180. package/src/modules/attachments/lib/ocrService.ts +4 -0
  181. package/src/modules/attachments/lib/pdfProcessing.ts +14 -2
  182. package/src/modules/attachments/lib/scoped-upload-service.ts +4 -2
  183. package/src/modules/attachments/lib/textExtraction.ts +14 -1
  184. package/src/modules/auth/i18n/de.json +3 -0
  185. package/src/modules/auth/i18n/en.json +3 -0
  186. package/src/modules/auth/i18n/es.json +3 -0
  187. package/src/modules/auth/i18n/ko.json +3 -0
  188. package/src/modules/auth/i18n/pl.json +3 -0
  189. package/src/modules/catalog/commands/categories.ts +1 -1
  190. package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
  191. package/src/modules/catalog/commands/products.ts +58 -17
  192. package/src/modules/catalog/search.ts +5 -1
  193. package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +2 -1
  194. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
  195. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
  196. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
  197. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +12 -9
  198. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
  199. package/src/modules/communication_channels/i18n/de.json +4 -1
  200. package/src/modules/communication_channels/i18n/en.json +4 -1
  201. package/src/modules/communication_channels/i18n/es.json +4 -1
  202. package/src/modules/communication_channels/i18n/ko.json +4 -1
  203. package/src/modules/communication_channels/i18n/pl.json +4 -1
  204. package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
  205. package/src/modules/communication_channels/lib/oauth-result.ts +13 -0
  206. package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
  207. package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
  208. package/src/modules/customers/api/activity-types/route.ts +154 -0
  209. package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
  210. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
  211. package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
  212. package/src/modules/customers/api/interactions/route.ts +4 -0
  213. package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
  214. package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
  215. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
  216. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
  217. package/src/modules/customers/calendar-event-types.ts +674 -0
  218. package/src/modules/customers/commands/dictionaries.ts +84 -8
  219. package/src/modules/customers/commands/interactions.ts +244 -21
  220. package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
  221. package/src/modules/customers/components/DictionarySettings.tsx +5 -3
  222. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
  223. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
  224. package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
  225. package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
  226. package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
  227. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
  228. package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
  229. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
  230. package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
  231. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
  232. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
  233. package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
  234. package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
  235. package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
  236. package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
  237. package/src/modules/customers/components/calendar/types.ts +1 -0
  238. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
  239. package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
  240. package/src/modules/customers/components/detail/DealForm.tsx +7 -3
  241. package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
  242. package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
  243. package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
  244. package/src/modules/customers/components/detail/types.ts +1 -0
  245. package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
  246. package/src/modules/customers/data/entities.ts +8 -1
  247. package/src/modules/customers/data/validators.ts +42 -7
  248. package/src/modules/customers/di.ts +21 -0
  249. package/src/modules/customers/extension-points.ts +13 -0
  250. package/src/modules/customers/i18n/de.json +104 -8
  251. package/src/modules/customers/i18n/en.json +104 -8
  252. package/src/modules/customers/i18n/es.json +104 -8
  253. package/src/modules/customers/i18n/ko.json +104 -8
  254. package/src/modules/customers/i18n/pl.json +104 -8
  255. package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
  256. package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
  257. package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
  258. package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
  259. package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
  260. package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
  261. package/src/modules/customers/lib/calendar/timezone.ts +117 -0
  262. package/src/modules/customers/lib/interactionReadModel.ts +1 -0
  263. package/src/modules/customers/lib/todoCompatibility.ts +2 -0
  264. package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
  265. package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
  266. package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
  267. package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
  268. package/src/modules/messages/subscribers/message-notification.ts +13 -3
  269. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
  270. package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
  271. package/src/modules/planner/lib/availabilityMerge.ts +106 -41
  272. package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
  273. package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
  274. package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
  275. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
  276. package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
@@ -3,6 +3,8 @@ import { Attachment, AttachmentPartition } from '../data/entities'
3
3
  import { OcrService } from './ocrService'
4
4
  import type { StorageDriver } from './drivers/types'
5
5
  import { createLogger } from '@open-mercato/shared/lib/logger'
6
+ import { resolveOcrMaxConcurrency, resolveOcrMaxWaitQueue } from './ocrLimits'
7
+ import { extractAttachmentContent } from './textExtraction'
6
8
 
7
9
  const logger = createLogger('attachments').child({ component: 'ocr' })
8
10
 
@@ -15,6 +17,45 @@ export type OcrRequestedEvent = {
15
17
  tenantId: string | null
16
18
  }
17
19
 
20
+ /** Outcome of `requestOcrProcessing` — background LLM OCR vs inline text fallback. */
21
+ export type OcrProcessingDispatch = 'queued' | 'inline_fallback'
22
+
23
+ let activeOcrJobs = 0
24
+ const ocrWaitQueue: Array<() => void> = []
25
+ // Jobs scheduled with setImmediate that have not yet entered a slot or the wait queue.
26
+ // Counted toward the wait-queue cap so a burst in one event-loop turn cannot pass the check together.
27
+ let pendingOcrJobs = 0
28
+
29
+ /** Test-only: reset in-process OCR concurrency bookkeeping. */
30
+ export function resetOcrConcurrencyStateForTests(): void {
31
+ activeOcrJobs = 0
32
+ ocrWaitQueue.length = 0
33
+ pendingOcrJobs = 0
34
+ }
35
+
36
+ /** Test-only: inspect in-process OCR concurrency counters. */
37
+ export function getOcrConcurrencyStateForTests(): { active: number; waiting: number } {
38
+ return { active: activeOcrJobs, waiting: ocrWaitQueue.length }
39
+ }
40
+
41
+ export async function withOcrConcurrencySlot<T>(run: () => Promise<T>): Promise<T> {
42
+ const maxConcurrency = resolveOcrMaxConcurrency()
43
+ // Re-check after wake: multiple waiters can resume when one slot frees (barging).
44
+ while (activeOcrJobs >= maxConcurrency) {
45
+ await new Promise<void>((resolve) => {
46
+ ocrWaitQueue.push(resolve)
47
+ })
48
+ }
49
+ activeOcrJobs += 1
50
+ try {
51
+ return await run()
52
+ } finally {
53
+ activeOcrJobs -= 1
54
+ const next = ocrWaitQueue.shift()
55
+ if (next) next()
56
+ }
57
+ }
58
+
18
59
  export async function processAttachmentOcr(
19
60
  em: EntityManager,
20
61
  payload: OcrRequestedEvent,
@@ -73,12 +114,49 @@ export async function processAttachmentOcr(
73
114
  }
74
115
  }
75
116
 
117
+ /**
118
+ * When the in-process OCR wait queue is full, extract plain text/PDF/DOCX content
119
+ * inline (no LLM) so `attachment.content` is not left NULL forever.
120
+ */
121
+ async function persistInlineTextExtractionFallback(
122
+ em: EntityManager,
123
+ payload: OcrRequestedEvent,
124
+ driver: StorageDriver,
125
+ ): Promise<void> {
126
+ const { attachmentId, storagePath, mimeType, partitionCode } = payload
127
+ const { filePath, cleanup } = await driver.toLocalPath(partitionCode, storagePath)
128
+ try {
129
+ const content = await extractAttachmentContent({ filePath, mimeType })
130
+ if (!content) {
131
+ logger.info('OCR wait queue full; inline extraction produced no content', { attachmentId })
132
+ return
133
+ }
134
+ const row = await em.findOne(Attachment, { id: attachmentId })
135
+ if (!row) {
136
+ logger.error('Attachment not found during OCR overflow fallback', { attachmentId })
137
+ return
138
+ }
139
+ row.content = content
140
+ await em.persist(row).flush()
141
+ logger.info('OCR wait queue full; stored inline text extraction', {
142
+ attachmentId,
143
+ contentLength: content.length,
144
+ })
145
+ } catch (error) {
146
+ logger.error('OCR wait queue overflow inline extraction failed', { attachmentId, err: error })
147
+ } finally {
148
+ await cleanup().catch((cleanupError) => {
149
+ logger.warn('Temp file cleanup failed after OCR overflow fallback', { err: cleanupError })
150
+ })
151
+ }
152
+ }
153
+
76
154
  export async function requestOcrProcessing(
77
155
  em: EntityManager,
78
156
  attachment: Attachment,
79
157
  driver: StorageDriver,
80
158
  storagePath: string,
81
- ): Promise<void> {
159
+ ): Promise<OcrProcessingDispatch> {
82
160
  const payload: OcrRequestedEvent = {
83
161
  attachmentId: attachment.id,
84
162
  storagePath,
@@ -97,9 +175,24 @@ export async function requestOcrProcessing(
97
175
 
98
176
  const workerEm = em.fork()
99
177
 
178
+ const maxWaitQueue = resolveOcrMaxWaitQueue()
179
+ if (ocrWaitQueue.length + pendingOcrJobs >= maxWaitQueue) {
180
+ logger.warn('OCR wait queue full; falling back to inline text extraction', {
181
+ attachmentId: attachment.id,
182
+ waiting: ocrWaitQueue.length,
183
+ pending: pendingOcrJobs,
184
+ maxWaitQueue,
185
+ })
186
+ await persistInlineTextExtractionFallback(workerEm, payload, driver)
187
+ return 'inline_fallback'
188
+ }
189
+
190
+ pendingOcrJobs += 1
100
191
  setImmediate(() => {
101
- processAttachmentOcr(workerEm, payload, driver).catch((error) => {
192
+ pendingOcrJobs -= 1
193
+ withOcrConcurrencySlot(() => processAttachmentOcr(workerEm, payload, driver)).catch((error) => {
102
194
  logger.error('Background processing error', { err: error })
103
195
  })
104
196
  })
197
+ return 'queued'
105
198
  }
@@ -3,6 +3,7 @@ import { createOpenAI } from '@ai-sdk/openai'
3
3
  import fs from 'fs/promises'
4
4
  import path from 'path'
5
5
  import { createLogger } from '@open-mercato/shared/lib/logger'
6
+ import { resolveOcrMaxOutputTokens, resolveOcrPageTimeoutMs } from './ocrLimits'
6
7
 
7
8
  const logger = createLogger('attachments').child({ component: 'ocr-service' })
8
9
 
@@ -102,6 +103,9 @@ export class OcrService {
102
103
  try {
103
104
  const result = await generateText({
104
105
  model: client(model),
106
+ abortSignal: AbortSignal.timeout(resolveOcrPageTimeoutMs()),
107
+ maxRetries: 0,
108
+ maxOutputTokens: resolveOcrMaxOutputTokens(),
105
109
  messages: [
106
110
  {
107
111
  role: 'user',
@@ -1,11 +1,14 @@
1
1
  import fs from 'fs/promises'
2
2
  import path from 'path'
3
3
  import { createRequire } from 'module'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+ import { resolveMaxOcrPages, resolvePdfPageIterationLimit } from './ocrLimits'
4
6
 
5
7
  const moduleRequire = createRequire(path.join(process.cwd(), 'package.json'))
6
8
  const pdfJsPackageRoot = path.dirname(moduleRequire.resolve('pdfjs-dist/package.json'))
7
9
  const cMapUrl = `${path.join(pdfJsPackageRoot, 'cmaps')}${path.sep}`
8
10
  const standardFontDataUrl = `${path.join(pdfJsPackageRoot, 'standard_fonts')}${path.sep}`
11
+ const logger = createLogger('attachments').child({ component: 'pdf-processing' })
9
12
 
10
13
  const MIN_RENDER_SCALE = 2
11
14
  const MAX_RENDER_SCALE = 4
@@ -113,8 +116,17 @@ export async function preparePdfPagesForOcr(filePath: string): Promise<PdfOcrPre
113
116
 
114
117
  try {
115
118
  const pages: PdfPageOcrInput[] = []
119
+ const maxPages = resolveMaxOcrPages()
120
+ const pageLimit = resolvePdfPageIterationLimit(pdfDocument.numPages, maxPages)
121
+ if (pdfDocument.numPages > pageLimit) {
122
+ logger.warn('PDF page count exceeds OCR cap; truncating', {
123
+ numPages: pdfDocument.numPages,
124
+ maxPages: pageLimit,
125
+ filePath,
126
+ })
127
+ }
116
128
 
117
- for (let pageNumber = 1; pageNumber <= pdfDocument.numPages; pageNumber += 1) {
129
+ for (let pageNumber = 1; pageNumber <= pageLimit; pageNumber += 1) {
118
130
  const page = await pdfDocument.getPage(pageNumber)
119
131
 
120
132
  try {
@@ -141,7 +153,7 @@ export async function preparePdfPagesForOcr(filePath: string): Promise<PdfOcrPre
141
153
  }
142
154
 
143
155
  return {
144
- pageCount: pdfDocument.numPages,
156
+ pageCount: pages.length,
145
157
  pages,
146
158
  }
147
159
  } finally {
@@ -265,9 +265,11 @@ export class ScopedAttachmentUploadService {
265
265
  }
266
266
 
267
267
  if (useLlmOcr) {
268
- requestOcrProcessing(em, attachment, driver, storedPath).catch((error) => {
268
+ try {
269
+ await requestOcrProcessing(em, attachment, driver, storedPath)
270
+ } catch (error) {
269
271
  logger.error('Scoped attachment OCR scheduling failed', { err: error })
270
- })
272
+ }
271
273
  }
272
274
  if (this.deps.dataEngine) {
273
275
  await emitCrudSideEffects({
@@ -1,6 +1,10 @@
1
1
  import fs from 'fs/promises'
2
2
  import path from 'path'
3
3
  import { createRequire } from 'module'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+ import { resolveMaxOcrPages, resolvePdfPageIterationLimit } from './ocrLimits'
6
+
7
+ const logger = createLogger('attachments').child({ component: 'text-extraction' })
4
8
 
5
9
  // NOTE: child_process is intentionally NOT imported here.
6
10
  // This module MUST NOT shell out to any external binary for content extraction.
@@ -68,8 +72,17 @@ async function extractPdfText(filePath: string): Promise<string | null> {
68
72
  })
69
73
  const pdfDocument = await loadingTask.promise
70
74
  const textParts: string[] = []
75
+ const maxPages = resolveMaxOcrPages()
76
+ const pageLimit = resolvePdfPageIterationLimit(pdfDocument.numPages, maxPages)
77
+ if (pdfDocument.numPages > pageLimit) {
78
+ logger.warn('PDF page count exceeds text-extraction cap; truncating', {
79
+ numPages: pdfDocument.numPages,
80
+ maxPages: pageLimit,
81
+ filePath,
82
+ })
83
+ }
71
84
  try {
72
- for (let pageNumber = 1; pageNumber <= pdfDocument.numPages; pageNumber += 1) {
85
+ for (let pageNumber = 1; pageNumber <= pageLimit; pageNumber += 1) {
73
86
  const page = await pdfDocument.getPage(pageNumber)
74
87
  try {
75
88
  const textContent = await page.getTextContent()
@@ -212,6 +212,8 @@
212
212
  "auth.acl.features.forms.view": "Formulare und Einreichungen ansehen",
213
213
  "auth.acl.features.gateway_stripe.configure": "Configure Stripe gateway settings",
214
214
  "auth.acl.features.gateway_stripe.view": "View Stripe gateway configuration",
215
+ "auth.acl.features.gateway_tpay.configure": "Tpay-Gateway-Einstellungen konfigurieren",
216
+ "auth.acl.features.gateway_tpay.view": "Tpay-Gateway-Konfiguration anzeigen",
215
217
  "auth.acl.features.inbox_ops.log.view": "View processing log",
216
218
  "auth.acl.features.inbox_ops.proposals.manage": "Manage proposals",
217
219
  "auth.acl.features.inbox_ops.proposals.view": "View proposals",
@@ -428,6 +430,7 @@
428
430
  "auth.acl.modules.example_customers_sync": "Example Customers Sync",
429
431
  "auth.acl.modules.feature_toggles": "Feature Toggles",
430
432
  "auth.acl.modules.gateway_stripe": "Stripe Payment Gateway",
433
+ "auth.acl.modules.gateway_tpay": "Tpay-Zahlungsgateway",
431
434
  "auth.acl.modules.inbox_ops": "InboxOps — Email-to-ERP Agent",
432
435
  "auth.acl.modules.integrations": "Integrations",
433
436
  "auth.acl.modules.messages": "Messages",
@@ -212,6 +212,8 @@
212
212
  "auth.acl.features.forms.view": "View forms and submissions",
213
213
  "auth.acl.features.gateway_stripe.configure": "Configure Stripe gateway settings",
214
214
  "auth.acl.features.gateway_stripe.view": "View Stripe gateway configuration",
215
+ "auth.acl.features.gateway_tpay.configure": "Configure Tpay gateway settings",
216
+ "auth.acl.features.gateway_tpay.view": "View Tpay gateway configuration",
215
217
  "auth.acl.features.inbox_ops.log.view": "View processing log",
216
218
  "auth.acl.features.inbox_ops.proposals.manage": "Manage proposals",
217
219
  "auth.acl.features.inbox_ops.proposals.view": "View proposals",
@@ -428,6 +430,7 @@
428
430
  "auth.acl.modules.example_customers_sync": "Example Customers Sync",
429
431
  "auth.acl.modules.feature_toggles": "Feature Toggles",
430
432
  "auth.acl.modules.gateway_stripe": "Stripe Payment Gateway",
433
+ "auth.acl.modules.gateway_tpay": "Tpay Payment Gateway",
431
434
  "auth.acl.modules.inbox_ops": "InboxOps — Email-to-ERP Agent",
432
435
  "auth.acl.modules.integrations": "Integrations",
433
436
  "auth.acl.modules.messages": "Messages",
@@ -212,6 +212,8 @@
212
212
  "auth.acl.features.forms.view": "Ver formularios y envíos",
213
213
  "auth.acl.features.gateway_stripe.configure": "Configure Stripe gateway settings",
214
214
  "auth.acl.features.gateway_stripe.view": "View Stripe gateway configuration",
215
+ "auth.acl.features.gateway_tpay.configure": "Configurar los ajustes de la pasarela Tpay",
216
+ "auth.acl.features.gateway_tpay.view": "Ver la configuración de la pasarela Tpay",
215
217
  "auth.acl.features.inbox_ops.log.view": "View processing log",
216
218
  "auth.acl.features.inbox_ops.proposals.manage": "Manage proposals",
217
219
  "auth.acl.features.inbox_ops.proposals.view": "View proposals",
@@ -428,6 +430,7 @@
428
430
  "auth.acl.modules.example_customers_sync": "Example Customers Sync",
429
431
  "auth.acl.modules.feature_toggles": "Feature Toggles",
430
432
  "auth.acl.modules.gateway_stripe": "Stripe Payment Gateway",
433
+ "auth.acl.modules.gateway_tpay": "Pasarela de pago Tpay",
431
434
  "auth.acl.modules.inbox_ops": "InboxOps — Email-to-ERP Agent",
432
435
  "auth.acl.modules.integrations": "Integrations",
433
436
  "auth.acl.modules.messages": "Messages",
@@ -212,6 +212,8 @@
212
212
  "auth.acl.features.forms.view": "View forms and submissions",
213
213
  "auth.acl.features.gateway_stripe.configure": "Configure Stripe gateway settings",
214
214
  "auth.acl.features.gateway_stripe.view": "View Stripe gateway configuration",
215
+ "auth.acl.features.gateway_tpay.configure": "Tpay 게이트웨이 설정 구성",
216
+ "auth.acl.features.gateway_tpay.view": "Tpay 게이트웨이 구성 보기",
215
217
  "auth.acl.features.inbox_ops.log.view": "View processing log",
216
218
  "auth.acl.features.inbox_ops.proposals.manage": "Manage proposals",
217
219
  "auth.acl.features.inbox_ops.proposals.view": "View proposals",
@@ -428,6 +430,7 @@
428
430
  "auth.acl.modules.example_customers_sync": "Example Customers Sync",
429
431
  "auth.acl.modules.feature_toggles": "Feature Toggles",
430
432
  "auth.acl.modules.gateway_stripe": "Stripe Payment Gateway",
433
+ "auth.acl.modules.gateway_tpay": "Tpay 결제 게이트웨이",
431
434
  "auth.acl.modules.inbox_ops": "InboxOps — Email-to-ERP Agent",
432
435
  "auth.acl.modules.integrations": "Integrations",
433
436
  "auth.acl.modules.messages": "Messages",
@@ -212,6 +212,8 @@
212
212
  "auth.acl.features.forms.view": "Przeglądanie formularzy i zgłoszeń",
213
213
  "auth.acl.features.gateway_stripe.configure": "Konfiguruj ustawienia bramki Stripe",
214
214
  "auth.acl.features.gateway_stripe.view": "Wyświetl konfigurację bramki Stripe",
215
+ "auth.acl.features.gateway_tpay.configure": "Konfiguruj ustawienia bramki Tpay",
216
+ "auth.acl.features.gateway_tpay.view": "Wyświetl konfigurację bramki Tpay",
215
217
  "auth.acl.features.inbox_ops.log.view": "Wyświetl dziennik przetwarzania",
216
218
  "auth.acl.features.inbox_ops.proposals.manage": "Zarządzaj propozycjami",
217
219
  "auth.acl.features.inbox_ops.proposals.view": "Wyświetl propozycje",
@@ -428,6 +430,7 @@
428
430
  "auth.acl.modules.example_customers_sync": "Przykładowa synchronizacja klientów",
429
431
  "auth.acl.modules.feature_toggles": "Przełączniki funkcji",
430
432
  "auth.acl.modules.gateway_stripe": "Bramka płatnicza Stripe",
433
+ "auth.acl.modules.gateway_tpay": "Bramka płatnicza Tpay",
431
434
  "auth.acl.modules.inbox_ops": "InboxOps — agent e-mail do ERP",
432
435
  "auth.acl.modules.integrations": "Integracje",
433
436
  "auth.acl.modules.messages": "Wiadomości",
@@ -455,7 +455,7 @@ const updateCategoryCommand: CommandHandler<CategoryUpdateInput, { categoryId: s
455
455
  ],
456
456
  { transaction: true }
457
457
  )
458
- const resetValues = buildCustomFieldResetMap(payload?.after?.custom ?? undefined, before.custom ?? undefined)
458
+ const resetValues = buildCustomFieldResetMap(before.custom ?? undefined, payload?.after?.custom ?? undefined)
459
459
  if (Object.keys(resetValues).length) {
460
460
  await setCustomFieldsIfAny({
461
461
  dataEngine: ctx.container.resolve('dataEngine'),