@open-mercato/core 0.6.6 → 0.6.7-develop.6553.1.2ec7246e04

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 (271) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
  3. package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +12 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/api_keys/api/keys/route.js +30 -13
  9. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  10. package/dist/modules/api_keys/cli.js +2 -1
  11. package/dist/modules/api_keys/cli.js.map +2 -2
  12. package/dist/modules/attachments/api/file/[id]/route.js +9 -7
  13. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  14. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
  15. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  16. package/dist/modules/attachments/api/route.js +21 -17
  17. package/dist/modules/attachments/api/route.js.map +2 -2
  18. package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
  19. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  20. package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
  21. package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
  22. package/dist/modules/attachments/lib/requestScope.js +10 -0
  23. package/dist/modules/attachments/lib/requestScope.js.map +7 -0
  24. package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
  25. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  26. package/dist/modules/auth/api/autologin.js +85 -0
  27. package/dist/modules/auth/api/autologin.js.map +7 -0
  28. package/dist/modules/auth/api/users/route.js +6 -2
  29. package/dist/modules/auth/api/users/route.js.map +2 -2
  30. package/dist/modules/auth/cli.js +3 -2
  31. package/dist/modules/auth/cli.js.map +2 -2
  32. package/dist/modules/auth/lib/autologin.js +19 -0
  33. package/dist/modules/auth/lib/autologin.js.map +7 -0
  34. package/dist/modules/configs/lib/upgrade-actions.js +22 -0
  35. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  36. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
  37. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
  39. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  40. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
  41. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
  43. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  44. package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
  45. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  46. package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
  47. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  48. package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
  49. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  50. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
  51. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  52. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  53. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  54. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
  55. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  56. package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
  57. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  58. package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
  59. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  60. package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
  61. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  62. package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
  63. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  64. package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
  65. package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
  66. package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
  67. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  68. package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
  69. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  70. package/dist/modules/dashboards/cli.js +4 -3
  71. package/dist/modules/dashboards/cli.js.map +2 -2
  72. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
  73. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  74. package/dist/modules/dictionaries/commands/entries.js +7 -14
  75. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  76. package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
  77. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  78. package/dist/modules/dictionaries/commands/factory.js +9 -3
  79. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  80. package/dist/modules/dictionaries/fields/dictionary.js +56 -41
  81. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  82. package/dist/modules/directory/utils/organizationScope.js +10 -3
  83. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  84. package/dist/modules/entities/api/records.js +5 -4
  85. package/dist/modules/entities/api/records.js.map +2 -2
  86. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
  87. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  88. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  89. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  90. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
  91. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  92. package/dist/modules/entities/cli.js +2 -1
  93. package/dist/modules/entities/cli.js.map +2 -2
  94. package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
  95. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  96. package/dist/modules/feature_toggles/defaults.json +8 -0
  97. package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
  98. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  99. package/dist/modules/payment_gateways/data/entities.js +45 -0
  100. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  101. package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
  102. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  103. package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
  104. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
  105. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
  106. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
  107. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
  108. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  109. package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
  110. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  111. package/dist/modules/query_index/api/status.js +2 -8
  112. package/dist/modules/query_index/api/status.js.map +2 -2
  113. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  114. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  115. package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
  116. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  117. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
  118. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  119. package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
  120. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  121. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
  122. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  123. package/dist/modules/sales/backend/sales/channels/page.js +6 -0
  124. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  125. package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
  126. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  127. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
  128. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  129. package/dist/modules/sales/commands/documents.js +44 -0
  130. package/dist/modules/sales/commands/documents.js.map +2 -2
  131. package/dist/modules/sales/commands/returns.js +2 -27
  132. package/dist/modules/sales/commands/returns.js.map +2 -2
  133. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
  134. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
  135. package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
  136. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  137. package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
  138. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  139. package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
  140. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  141. package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
  142. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  143. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
  144. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  145. package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
  146. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  147. package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
  148. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
  149. package/dist/modules/sales/lib/calculations.js +6 -0
  150. package/dist/modules/sales/lib/calculations.js.map +2 -2
  151. package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
  152. package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
  153. package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
  154. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
  155. package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
  156. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  157. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
  158. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  159. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
  160. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  161. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
  162. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  163. package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
  164. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  165. package/dist/modules/sync_excel/lib/parser.js +185 -0
  166. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  167. package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
  168. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  169. package/generated/entities/gateway_session_initialization/index.ts +10 -0
  170. package/generated/entities.ids.generated.ts +1 -0
  171. package/generated/entity-fields-registry.ts +12 -0
  172. package/package.json +9 -8
  173. package/src/modules/api_keys/api/keys/route.ts +36 -13
  174. package/src/modules/api_keys/cli.ts +2 -3
  175. package/src/modules/attachments/api/file/[id]/route.ts +11 -7
  176. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
  177. package/src/modules/attachments/api/route.ts +26 -21
  178. package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
  179. package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
  180. package/src/modules/attachments/lib/requestScope.ts +27 -0
  181. package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
  182. package/src/modules/auth/api/autologin.ts +100 -0
  183. package/src/modules/auth/api/users/route.ts +11 -2
  184. package/src/modules/auth/cli.ts +3 -2
  185. package/src/modules/auth/lib/autologin.ts +36 -0
  186. package/src/modules/configs/i18n/de.json +5 -1
  187. package/src/modules/configs/i18n/en.json +5 -1
  188. package/src/modules/configs/i18n/es.json +5 -1
  189. package/src/modules/configs/i18n/pl.json +5 -1
  190. package/src/modules/configs/lib/upgrade-actions.ts +24 -0
  191. package/src/modules/customer_accounts/AGENTS.md +8 -6
  192. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
  193. package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
  194. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
  195. package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
  196. package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
  197. package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
  198. package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
  199. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
  200. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
  201. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
  202. package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
  203. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
  204. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
  205. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
  206. package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
  207. package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
  208. package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
  209. package/src/modules/dashboards/cli.ts +4 -9
  210. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
  211. package/src/modules/dictionaries/commands/entries.ts +7 -16
  212. package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
  213. package/src/modules/dictionaries/commands/factory.ts +8 -2
  214. package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
  215. package/src/modules/dictionaries/i18n/de.json +2 -0
  216. package/src/modules/dictionaries/i18n/en.json +2 -0
  217. package/src/modules/dictionaries/i18n/es.json +2 -0
  218. package/src/modules/dictionaries/i18n/pl.json +2 -0
  219. package/src/modules/directory/utils/organizationScope.ts +36 -2
  220. package/src/modules/entities/api/records.ts +5 -7
  221. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
  222. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
  223. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
  224. package/src/modules/entities/cli.ts +2 -1
  225. package/src/modules/entities/i18n/de.json +34 -0
  226. package/src/modules/entities/i18n/en.json +34 -0
  227. package/src/modules/entities/i18n/es.json +34 -0
  228. package/src/modules/entities/i18n/pl.json +34 -0
  229. package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
  230. package/src/modules/feature_toggles/defaults.json +8 -0
  231. package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
  232. package/src/modules/payment_gateways/data/entities.ts +39 -0
  233. package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
  234. package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
  235. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
  236. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
  237. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
  238. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
  239. package/src/modules/query_index/api/status.ts +2 -8
  240. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
  241. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
  242. package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
  243. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
  244. package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
  245. package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
  246. package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
  247. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
  248. package/src/modules/sales/commands/documents.ts +65 -0
  249. package/src/modules/sales/commands/returns.ts +2 -32
  250. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
  251. package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
  252. package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
  253. package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
  254. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
  255. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
  256. package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
  257. package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
  258. package/src/modules/sales/i18n/de.json +5 -0
  259. package/src/modules/sales/i18n/en.json +5 -0
  260. package/src/modules/sales/i18n/es.json +5 -0
  261. package/src/modules/sales/i18n/pl.json +5 -0
  262. package/src/modules/sales/lib/calculations.ts +15 -0
  263. package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
  264. package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
  265. package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
  266. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
  267. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
  268. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
  269. package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
  270. package/src/modules/sync_excel/lib/parser.ts +245 -0
  271. package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
@@ -17,8 +17,8 @@ import { CustomerAddress, CustomerEntity } from '../../../customers/data/entitie
17
17
  import { Attachment } from '../../../attachments/data/entities'
18
18
  import { CustomFieldDef } from '../../../entities/data/entities'
19
19
  import { SyncExcelUpload } from '../../data/entities'
20
- import { parseCsvDocument, type CsvPreviewRow } from '../parser'
21
- import { readSyncExcelUploadBuffer } from '../upload-storage'
20
+ import { parseCsvDocumentBatches, parseCsvStreamMetadata, type CsvPreviewRow } from '../parser'
21
+ import { createSyncExcelUploadReadStream } from '../upload-storage'
22
22
  import { E } from '#generated/entities.ids.generated'
23
23
 
24
24
  type SyncExcelCursor = {
@@ -785,20 +785,16 @@ async function resolveExistingPersonId(params: {
785
785
  return params.emailDedupeIndex.get(params.email) ?? null
786
786
  }
787
787
 
788
+ function mappingHasEmailField(mapping: DataMapping): boolean {
789
+ return mapping.fields.some((field) => field.localField === 'person.primaryEmail')
790
+ }
791
+
788
792
  async function buildEmailDedupeIndex(params: {
789
793
  em: EntityManager
790
- rows: CsvPreviewRow[]
791
794
  mapping: DataMapping
792
795
  scope: TenantScope
793
796
  }): Promise<EmailDedupeIndex> {
794
- const emails = new Set<string>()
795
- for (const row of params.rows) {
796
- const rowValues = mapRowValues(row, params.mapping.fields)
797
- const email = rowValues.values.primaryEmail
798
- if (email) emails.add(email)
799
- }
800
-
801
- if (emails.size === 0) return new Map()
797
+ if (!mappingHasEmailField(params.mapping)) return new Map()
802
798
 
803
799
  const candidates = await findWithDecryption(
804
800
  params.em,
@@ -821,7 +817,7 @@ async function buildEmailDedupeIndex(params: {
821
817
  const index: EmailDedupeIndex = new Map()
822
818
  for (const candidate of candidates) {
823
819
  const email = normalizeEmail(candidate.primaryEmail)
824
- if (!email || !emails.has(email) || index.has(email)) continue
820
+ if (!email || index.has(email)) continue
825
821
  index.set(email, candidate.id)
826
822
  }
827
823
  return index
@@ -1043,26 +1039,28 @@ export const syncExcelCustomersAdapter: DataSyncAdapter = {
1043
1039
  throw new Error('CSV upload attachment could not be found.')
1044
1040
  }
1045
1041
 
1046
- const fileBuffer = await readSyncExcelUploadBuffer(attachment)
1047
- const document = parseCsvDocument(fileBuffer)
1042
+ const document = await parseCsvStreamMetadata(await createSyncExcelUploadReadStream(attachment))
1048
1043
  const startOffset = cursor?.uploadId === upload.id ? cursor.offset : 0
1049
1044
  const commandContext = buildCommandContext(container, input.scope)
1050
1045
  const customFieldDefinitions = await loadImportCustomFieldDefinitions(em, input.scope)
1051
1046
  const emailDedupeIndex = await buildEmailDedupeIndex({
1052
1047
  em,
1053
- rows: document.rows.slice(startOffset),
1054
1048
  mapping: input.mapping,
1055
1049
  scope: input.scope,
1056
1050
  })
1051
+ let batchIndex = 0
1057
1052
 
1058
- for (let offset = startOffset, batchIndex = 0; offset < document.rows.length; offset += input.batchSize, batchIndex += 1) {
1059
- const batchRows = document.rows.slice(offset, offset + input.batchSize)
1053
+ for await (const batch of parseCsvDocumentBatches(await createSyncExcelUploadReadStream(attachment), {
1054
+ batchSize: input.batchSize,
1055
+ startOffset,
1056
+ })) {
1057
+ const batchRows = batch.rows
1060
1058
  const items: ImportItem[] = []
1061
1059
 
1062
1060
  for (let index = 0; index < batchRows.length; index += 1) {
1063
1061
  items.push(await processRow({
1064
1062
  row: batchRows[index],
1065
- rowNumber: offset + index + 1,
1063
+ rowNumber: batch.rowStart + index + 1,
1066
1064
  mapping: input.mapping,
1067
1065
  scope: input.scope,
1068
1066
  commandBus,
@@ -1074,17 +1072,17 @@ export const syncExcelCustomersAdapter: DataSyncAdapter = {
1074
1072
  }))
1075
1073
  }
1076
1074
 
1077
- const nextOffset = offset + batchRows.length
1078
1075
  yield {
1079
1076
  items,
1080
- cursor: createCursor(upload.id, nextOffset),
1081
- hasMore: nextOffset < document.rows.length,
1077
+ cursor: createCursor(upload.id, batch.nextOffset),
1078
+ hasMore: batch.nextOffset < document.totalRows,
1082
1079
  totalEstimate: document.totalRows,
1083
1080
  processedCount: batchRows.length,
1084
1081
  refreshCoverageEntityTypes: CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES,
1085
1082
  batchIndex,
1086
- message: `Processed ${nextOffset} of ${document.totalRows} CSV rows`,
1083
+ message: `Processed ${batch.nextOffset} of ${document.totalRows} CSV rows`,
1087
1084
  }
1085
+ batchIndex += 1
1088
1086
  }
1089
1087
 
1090
1088
  upload.status = 'completed'
@@ -1,3 +1,5 @@
1
+ import { TextDecoder } from 'util'
2
+
1
3
  export type CsvPreviewRow = Record<string, string | null>
2
4
 
3
5
  export type CsvPreview = {
@@ -16,10 +18,36 @@ export type CsvDocument = {
16
18
  encoding: 'utf-8'
17
19
  }
18
20
 
21
+ export type CsvStreamMetadata = {
22
+ headers: string[]
23
+ totalRows: number
24
+ delimiter: ',' | ';'
25
+ encoding: 'utf-8'
26
+ }
27
+
28
+ export type CsvDocumentBatch = {
29
+ headers: string[]
30
+ rows: CsvPreviewRow[]
31
+ rowStart: number
32
+ nextOffset: number
33
+ delimiter: ',' | ';'
34
+ encoding: 'utf-8'
35
+ }
36
+
19
37
  type ParseCsvPreviewOptions = {
20
38
  maxRows?: number
21
39
  }
22
40
 
41
+ type ParseCsvBatchOptions = {
42
+ batchSize: number
43
+ startOffset?: number
44
+ }
45
+
46
+ type CsvInputChunk = Buffer | Uint8Array | string
47
+
48
+ const CSV_DELIMITER_PROBE_ROWS = 5
49
+ const CSV_DELIMITER_PROBE_MAX_CHARS = 64 * 1024
50
+
23
51
  function stripUtf8Bom(value: string): string {
24
52
  return value.charCodeAt(0) === 0xfeff ? value.slice(1) : value
25
53
  }
@@ -131,6 +159,223 @@ function rowsToObjects(headers: string[], rows: string[][], maxRows: number): Cs
131
159
  })
132
160
  }
133
161
 
162
+ async function createCsvRowStream(input: AsyncIterable<CsvInputChunk>): Promise<{
163
+ delimiter: ',' | ';'
164
+ rows: AsyncIterable<string[]>
165
+ }> {
166
+ const iterator = input[Symbol.asyncIterator]()
167
+ const decoder = new TextDecoder('utf-8')
168
+ const prefixTexts: string[] = []
169
+ let probeText = ''
170
+
171
+ while (probeText.length < CSV_DELIMITER_PROBE_MAX_CHARS) {
172
+ const next = await iterator.next()
173
+ if (next.done) break
174
+
175
+ const text = typeof next.value === 'string' ? next.value : decoder.decode(next.value, { stream: true })
176
+ prefixTexts.push(text)
177
+ probeText += text
178
+
179
+ if (parseCsvText(probeText, ',').length >= CSV_DELIMITER_PROBE_ROWS) {
180
+ break
181
+ }
182
+ }
183
+
184
+ const delimiter = detectCsvDelimiter(probeText)
185
+
186
+ async function* decodedChunks(): AsyncIterable<string> {
187
+ for (const text of prefixTexts) {
188
+ yield text
189
+ }
190
+ while (true) {
191
+ const next = await iterator.next()
192
+ if (next.done) break
193
+ yield typeof next.value === 'string' ? next.value : decoder.decode(next.value, { stream: true })
194
+ }
195
+ const finalChunk = decoder.decode()
196
+ if (finalChunk.length > 0) {
197
+ yield finalChunk
198
+ }
199
+ }
200
+
201
+ return {
202
+ delimiter,
203
+ rows: parseCsvRowsFromTextChunks(decodedChunks(), delimiter),
204
+ }
205
+ }
206
+
207
+ async function* parseCsvRowsFromTextChunks(
208
+ chunks: AsyncIterable<string>,
209
+ delimiter: ',' | ';',
210
+ ): AsyncIterable<string[]> {
211
+ let currentRow: string[] = []
212
+ let currentValue = ''
213
+ let inQuotes = false
214
+ let isFirstChunk = true
215
+ let previousWasCarriageReturn = false
216
+ let pendingQuote = false
217
+ let pendingQuoteWasInQuotes = false
218
+
219
+ const completeRow = (): string[] | null => {
220
+ currentRow.push(currentValue)
221
+ const completedRow = currentRow
222
+ currentRow = []
223
+ currentValue = ''
224
+ return completedRow.some((value) => value.trim().length > 0) ? completedRow : null
225
+ }
226
+
227
+ for await (const chunk of chunks) {
228
+ const text = isFirstChunk ? stripUtf8Bom(chunk) : chunk
229
+ isFirstChunk = false
230
+
231
+ for (let index = 0; index < text.length; index += 1) {
232
+ const character = text[index]
233
+
234
+ if (pendingQuote) {
235
+ pendingQuote = false
236
+ if (pendingQuoteWasInQuotes && character === '"') {
237
+ currentValue += '"'
238
+ continue
239
+ }
240
+ inQuotes = !pendingQuoteWasInQuotes
241
+ }
242
+
243
+ if (previousWasCarriageReturn) {
244
+ previousWasCarriageReturn = false
245
+ if (character === '\n') {
246
+ continue
247
+ }
248
+ }
249
+
250
+ if (character === '"') {
251
+ if (index + 1 >= text.length) {
252
+ pendingQuote = true
253
+ pendingQuoteWasInQuotes = inQuotes
254
+ continue
255
+ }
256
+ if (inQuotes && text[index + 1] === '"') {
257
+ currentValue += '"'
258
+ index += 1
259
+ continue
260
+ }
261
+ inQuotes = !inQuotes
262
+ continue
263
+ }
264
+
265
+ if (!inQuotes && character === delimiter) {
266
+ currentRow.push(currentValue)
267
+ currentValue = ''
268
+ continue
269
+ }
270
+
271
+ if (character === '\r') {
272
+ previousWasCarriageReturn = true
273
+ if (inQuotes) {
274
+ currentValue += '\n'
275
+ } else {
276
+ const row = completeRow()
277
+ if (row) yield row
278
+ }
279
+ continue
280
+ }
281
+
282
+ if (character === '\n') {
283
+ if (inQuotes) {
284
+ currentValue += '\n'
285
+ } else {
286
+ const row = completeRow()
287
+ if (row) yield row
288
+ }
289
+ continue
290
+ }
291
+
292
+ currentValue += character
293
+ }
294
+ }
295
+
296
+ if (currentValue.length > 0 || currentRow.length > 0) {
297
+ const row = completeRow()
298
+ if (row) yield row
299
+ }
300
+ }
301
+
302
+ export async function parseCsvStreamMetadata(input: AsyncIterable<CsvInputChunk>): Promise<CsvStreamMetadata> {
303
+ const stream = await createCsvRowStream(input)
304
+ let headers: string[] = []
305
+ let hasHeaders = false
306
+ let totalRows = 0
307
+
308
+ for await (const row of stream.rows) {
309
+ if (!hasHeaders) {
310
+ headers = normalizeHeaders(row)
311
+ hasHeaders = true
312
+ continue
313
+ }
314
+ totalRows += 1
315
+ }
316
+
317
+ return {
318
+ headers,
319
+ totalRows,
320
+ delimiter: stream.delimiter,
321
+ encoding: 'utf-8',
322
+ }
323
+ }
324
+
325
+ export async function* parseCsvDocumentBatches(
326
+ input: AsyncIterable<CsvInputChunk>,
327
+ options: ParseCsvBatchOptions,
328
+ ): AsyncIterable<CsvDocumentBatch> {
329
+ const stream = await createCsvRowStream(input)
330
+ const batchSize = Math.max(1, Math.floor(options.batchSize))
331
+ const startOffset = Math.max(0, Math.floor(options.startOffset ?? 0))
332
+ let headers: string[] | null = null
333
+ let rowOffset = 0
334
+ let batchRows: string[][] = []
335
+ let batchStart = startOffset
336
+
337
+ for await (const row of stream.rows) {
338
+ if (!headers) {
339
+ headers = normalizeHeaders(row)
340
+ continue
341
+ }
342
+
343
+ if (rowOffset < startOffset) {
344
+ rowOffset += 1
345
+ continue
346
+ }
347
+
348
+ if (batchRows.length === 0) {
349
+ batchStart = rowOffset
350
+ }
351
+ batchRows.push(row)
352
+ rowOffset += 1
353
+
354
+ if (batchRows.length >= batchSize) {
355
+ yield {
356
+ headers,
357
+ rows: rowsToObjects(headers, batchRows, batchRows.length),
358
+ rowStart: batchStart,
359
+ nextOffset: rowOffset,
360
+ delimiter: stream.delimiter,
361
+ encoding: 'utf-8',
362
+ }
363
+ batchRows = []
364
+ }
365
+ }
366
+
367
+ if (headers && batchRows.length > 0) {
368
+ yield {
369
+ headers,
370
+ rows: rowsToObjects(headers, batchRows, batchRows.length),
371
+ rowStart: batchStart,
372
+ nextOffset: rowOffset,
373
+ delimiter: stream.delimiter,
374
+ encoding: 'utf-8',
375
+ }
376
+ }
377
+ }
378
+
134
379
  export function parseCsvPreview(buffer: Buffer, options: ParseCsvPreviewOptions = {}): CsvPreview {
135
380
  const text = buffer.toString('utf-8')
136
381
  const delimiter = detectCsvDelimiter(text)
@@ -1,4 +1,5 @@
1
- import { promises as fs } from 'fs'
1
+ import { createReadStream, promises as fs } from 'fs'
2
+ import { Readable } from 'stream'
2
3
  import { randomUUID } from 'crypto'
3
4
  import type { EntityManager } from '@mikro-orm/postgresql'
4
5
  import { Attachment, AttachmentPartition } from '../../attachments/data/entities'
@@ -79,3 +80,24 @@ export async function readSyncExcelUploadBuffer(
79
80
  throw error
80
81
  }
81
82
  }
83
+
84
+ export async function createSyncExcelUploadReadStream(
85
+ attachment: Pick<Attachment, 'partitionCode' | 'storagePath' | 'storageDriver' | 'storageMetadata'>,
86
+ ): Promise<Readable> {
87
+ const inlineCsvBase64 = attachment.storageMetadata?.inlineCsvBase64
88
+
89
+ try {
90
+ const absolutePath = resolveAttachmentAbsolutePath(
91
+ attachment.partitionCode,
92
+ attachment.storagePath,
93
+ attachment.storageDriver,
94
+ )
95
+ await fs.access(absolutePath)
96
+ return createReadStream(absolutePath)
97
+ } catch (error) {
98
+ if (typeof inlineCsvBase64 === 'string' && inlineCsvBase64.length > 0) {
99
+ return Readable.from([Buffer.from(inlineCsvBase64, 'base64')])
100
+ }
101
+ throw error
102
+ }
103
+ }