@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
- package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +12 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +30 -13
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +2 -1
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +9 -7
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +21 -17
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
- package/dist/modules/attachments/lib/requestScope.js +10 -0
- package/dist/modules/attachments/lib/requestScope.js.map +7 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/auth/api/autologin.js +85 -0
- package/dist/modules/auth/api/autologin.js.map +7 -0
- package/dist/modules/auth/api/users/route.js +6 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +3 -2
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/autologin.js +19 -0
- package/dist/modules/auth/lib/autologin.js.map +7 -0
- package/dist/modules/configs/lib/upgrade-actions.js +22 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
- package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
- package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +4 -3
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +7 -14
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +9 -3
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +56 -41
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +10 -3
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +5 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +2 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
- package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +8 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/data/entities.js +45 -0
- package/dist/modules/payment_gateways/data/entities.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
- package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
- package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/status.js +2 -8
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +44 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +2 -27
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
- package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
- package/dist/modules/sales/lib/calculations.js +6 -0
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
- package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/sync_excel/lib/parser.js +185 -0
- package/dist/modules/sync_excel/lib/parser.js.map +2 -2
- package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
- package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
- package/generated/entities/gateway_session_initialization/index.ts +10 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +12 -0
- package/package.json +9 -8
- package/src/modules/api_keys/api/keys/route.ts +36 -13
- package/src/modules/api_keys/cli.ts +2 -3
- package/src/modules/attachments/api/file/[id]/route.ts +11 -7
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
- package/src/modules/attachments/api/route.ts +26 -21
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
- package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
- package/src/modules/attachments/lib/requestScope.ts +27 -0
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
- package/src/modules/auth/api/autologin.ts +100 -0
- package/src/modules/auth/api/users/route.ts +11 -2
- package/src/modules/auth/cli.ts +3 -2
- package/src/modules/auth/lib/autologin.ts +36 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +24 -0
- package/src/modules/customer_accounts/AGENTS.md +8 -6
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
- package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
- package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
- package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
- package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
- package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
- package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
- package/src/modules/dashboards/cli.ts +4 -9
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
- package/src/modules/dictionaries/commands/entries.ts +7 -16
- package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
- package/src/modules/dictionaries/commands/factory.ts +8 -2
- package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
- package/src/modules/dictionaries/i18n/de.json +2 -0
- package/src/modules/dictionaries/i18n/en.json +2 -0
- package/src/modules/dictionaries/i18n/es.json +2 -0
- package/src/modules/dictionaries/i18n/pl.json +2 -0
- package/src/modules/directory/utils/organizationScope.ts +36 -2
- package/src/modules/entities/api/records.ts +5 -7
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
- package/src/modules/entities/cli.ts +2 -1
- package/src/modules/entities/i18n/de.json +34 -0
- package/src/modules/entities/i18n/en.json +34 -0
- package/src/modules/entities/i18n/es.json +34 -0
- package/src/modules/entities/i18n/pl.json +34 -0
- package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
- package/src/modules/feature_toggles/defaults.json +8 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
- package/src/modules/payment_gateways/data/entities.ts +39 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
- package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
- package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
- package/src/modules/query_index/api/status.ts +2 -8
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
- package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/returns.ts +2 -32
- package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
- package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
- package/src/modules/sales/i18n/de.json +5 -0
- package/src/modules/sales/i18n/en.json +5 -0
- package/src/modules/sales/i18n/es.json +5 -0
- package/src/modules/sales/i18n/pl.json +5 -0
- package/src/modules/sales/lib/calculations.ts +15 -0
- package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
- package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
- package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
- package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
- package/src/modules/sync_excel/lib/parser.ts +245 -0
- 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 {
|
|
21
|
-
import {
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
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 (
|
|
1059
|
-
|
|
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:
|
|
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.
|
|
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
|
+
}
|