@open-mercato/core 0.6.2-develop.3330.1.8ad2b3b84c → 0.6.2-develop.3366.1.4692954e44

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 (102) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/sync_excel_upload/index.js +37 -0
  3. package/dist/generated/entities/sync_excel_upload/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +4 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +19 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/customers/search.js +3 -2
  9. package/dist/modules/customers/search.js.map +2 -2
  10. package/dist/modules/data_sync/api/runs/[id]/cancel.js +16 -0
  11. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  12. package/dist/modules/data_sync/di.js +2 -1
  13. package/dist/modules/data_sync/di.js.map +2 -2
  14. package/dist/modules/data_sync/lib/sync-engine.js +178 -54
  15. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  16. package/dist/modules/data_sync/lib/sync-run-service.js +23 -0
  17. package/dist/modules/data_sync/lib/sync-run-service.js.map +3 -3
  18. package/dist/modules/integrations/backend/integrations/[id]/page.js +241 -55
  19. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +3 -3
  20. package/dist/modules/sync_excel/acl.js +10 -0
  21. package/dist/modules/sync_excel/acl.js.map +7 -0
  22. package/dist/modules/sync_excel/api/import/route.js +164 -0
  23. package/dist/modules/sync_excel/api/import/route.js.map +7 -0
  24. package/dist/modules/sync_excel/api/preview/route.js +84 -0
  25. package/dist/modules/sync_excel/api/preview/route.js.map +7 -0
  26. package/dist/modules/sync_excel/api/upload/route.js +131 -0
  27. package/dist/modules/sync_excel/api/upload/route.js.map +7 -0
  28. package/dist/modules/sync_excel/data/entities.js +79 -0
  29. package/dist/modules/sync_excel/data/entities.js.map +7 -0
  30. package/dist/modules/sync_excel/data/validators.js +58 -0
  31. package/dist/modules/sync_excel/data/validators.js.map +7 -0
  32. package/dist/modules/sync_excel/di.js +11 -0
  33. package/dist/modules/sync_excel/di.js.map +7 -0
  34. package/dist/modules/sync_excel/index.js +11 -0
  35. package/dist/modules/sync_excel/index.js.map +7 -0
  36. package/dist/modules/sync_excel/integration.js +36 -0
  37. package/dist/modules/sync_excel/integration.js.map +7 -0
  38. package/dist/modules/sync_excel/lib/adapters/customers.js +852 -0
  39. package/dist/modules/sync_excel/lib/adapters/customers.js.map +7 -0
  40. package/dist/modules/sync_excel/lib/column-detector.js +77 -0
  41. package/dist/modules/sync_excel/lib/column-detector.js.map +7 -0
  42. package/dist/modules/sync_excel/lib/mapping.js +16 -0
  43. package/dist/modules/sync_excel/lib/mapping.js.map +7 -0
  44. package/dist/modules/sync_excel/lib/parser.js +125 -0
  45. package/dist/modules/sync_excel/lib/parser.js.map +7 -0
  46. package/dist/modules/sync_excel/lib/scope.js +44 -0
  47. package/dist/modules/sync_excel/lib/scope.js.map +7 -0
  48. package/dist/modules/sync_excel/lib/upload-storage.js +63 -0
  49. package/dist/modules/sync_excel/lib/upload-storage.js.map +7 -0
  50. package/dist/modules/sync_excel/migrations/Migration20260329181120_sync_excel.js +11 -0
  51. package/dist/modules/sync_excel/migrations/Migration20260329181120_sync_excel.js.map +7 -0
  52. package/dist/modules/sync_excel/setup.js +19 -0
  53. package/dist/modules/sync_excel/setup.js.map +7 -0
  54. package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js +293 -0
  55. package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js.map +7 -0
  56. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js +934 -0
  57. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js.map +7 -0
  58. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.js +15 -0
  59. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.js.map +7 -0
  60. package/dist/modules/sync_excel/widgets/injection-table.js +17 -0
  61. package/dist/modules/sync_excel/widgets/injection-table.js.map +7 -0
  62. package/generated/entities/sync_excel_upload/index.ts +17 -0
  63. package/generated/entities.ids.generated.ts +4 -0
  64. package/generated/entity-fields-registry.ts +19 -0
  65. package/package.json +7 -7
  66. package/src/modules/customers/search.ts +3 -2
  67. package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -0
  68. package/src/modules/data_sync/di.ts +4 -1
  69. package/src/modules/data_sync/lib/adapter.ts +15 -0
  70. package/src/modules/data_sync/lib/sync-engine.ts +205 -52
  71. package/src/modules/data_sync/lib/sync-run-service.ts +24 -0
  72. package/src/modules/integrations/backend/integrations/[id]/page.tsx +220 -15
  73. package/src/modules/integrations/i18n/de.json +9 -0
  74. package/src/modules/integrations/i18n/en.json +9 -0
  75. package/src/modules/integrations/i18n/es.json +9 -0
  76. package/src/modules/integrations/i18n/pl.json +9 -0
  77. package/src/modules/sync_excel/acl.ts +6 -0
  78. package/src/modules/sync_excel/api/import/route.ts +182 -0
  79. package/src/modules/sync_excel/api/preview/route.ts +89 -0
  80. package/src/modules/sync_excel/api/upload/route.ts +144 -0
  81. package/src/modules/sync_excel/data/entities.ts +61 -0
  82. package/src/modules/sync_excel/data/validators.ts +58 -0
  83. package/src/modules/sync_excel/di.ts +9 -0
  84. package/src/modules/sync_excel/i18n/de.json +107 -0
  85. package/src/modules/sync_excel/i18n/en.json +107 -0
  86. package/src/modules/sync_excel/i18n/es.json +107 -0
  87. package/src/modules/sync_excel/i18n/pl.json +107 -0
  88. package/src/modules/sync_excel/index.ts +7 -0
  89. package/src/modules/sync_excel/integration.ts +31 -0
  90. package/src/modules/sync_excel/lib/adapters/customers.ts +1097 -0
  91. package/src/modules/sync_excel/lib/column-detector.ts +91 -0
  92. package/src/modules/sync_excel/lib/mapping.ts +19 -0
  93. package/src/modules/sync_excel/lib/parser.ts +165 -0
  94. package/src/modules/sync_excel/lib/scope.ts +63 -0
  95. package/src/modules/sync_excel/lib/upload-storage.ts +79 -0
  96. package/src/modules/sync_excel/migrations/.snapshot-open-mercato.json +199 -0
  97. package/src/modules/sync_excel/migrations/Migration20260329181120_sync_excel.ts +10 -0
  98. package/src/modules/sync_excel/setup.ts +20 -0
  99. package/src/modules/sync_excel/widgets/injection/upload-config/target-options.ts +351 -0
  100. package/src/modules/sync_excel/widgets/injection/upload-config/widget.client.tsx +1224 -0
  101. package/src/modules/sync_excel/widgets/injection/upload-config/widget.ts +30 -0
  102. package/src/modules/sync_excel/widgets/injection-table.ts +15 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sync_excel/lib/adapters/customers.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { extractPhoneDigits, validatePhoneNumber } from '@open-mercato/shared/lib/phone'\nimport type {\n DataMapping,\n DataSyncAdapter,\n FieldMapping,\n ImportBatch,\n ImportItem,\n TenantScope,\n} from '../../../data_sync/lib/adapter'\nimport type { ExternalIdMappingService } from '../../../data_sync/lib/id-mapping'\nimport { SyncMapping } from '../../../data_sync/data/entities'\nimport { CustomerAddress, CustomerEntity } from '../../../customers/data/entities'\nimport { Attachment } from '../../../attachments/data/entities'\nimport { CustomFieldDef } from '../../../entities/data/entities'\nimport { SyncExcelUpload } from '../../data/entities'\nimport { parseCsvDocument, type CsvPreviewRow } from '../parser'\nimport { readSyncExcelUploadBuffer } from '../upload-storage'\nimport { E } from '#generated/entities.ids.generated'\n\ntype SyncExcelCursor = {\n uploadId: string\n offset: number\n}\n\ntype Container = Awaited<ReturnType<typeof createRequestContainer>>\n\nconst CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES = [\n E.customers.customer_entity,\n E.customers.customer_person_profile,\n E.customers.customer_address,\n]\n\ntype PersonFieldValues = {\n externalId?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n primaryEmail?: string | null\n primaryPhone?: string | null\n jobTitle?: string | null\n status?: string | null\n source?: string | null\n description?: string | null\n}\n\ntype AddressFieldValues = {\n name?: string | null\n purpose?: string | null\n companyName?: string | null\n addressLine1?: string | null\n addressLine2?: string | null\n buildingNumber?: string | null\n flatNumber?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n country?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype PersonRowValues = {\n values: PersonFieldValues\n customFields: Record<string, unknown>\n addressValues: AddressFieldValues\n}\n\ntype ImportCustomFieldDefinition = {\n key: string\n kind: string\n entityId: string\n organizationId?: string | null\n tenantId?: string | null\n updatedAt?: Date | string | null\n}\n\ntype EmailDedupeIndex = Map<string, string>\n\ntype BuiltPersonPayload = {\n values: PersonFieldValues\n customFields: Record<string, unknown>\n addressValues: AddressFieldValues\n createInput: {\n organizationId: string\n tenantId: string\n firstName: string\n lastName: string\n displayName: string\n primaryEmail?: string\n primaryPhone?: string\n jobTitle?: string\n status?: string\n source?: string\n description?: string\n customFields?: Record<string, unknown>\n } | null\n updateInput: {\n organizationId: string\n tenantId: string\n primaryEmail?: string\n primaryPhone?: string\n jobTitle?: string\n status?: string\n source?: string\n description?: string\n firstName?: string\n lastName?: string\n displayName?: string\n customFields?: Record<string, unknown>\n }\n sourceIdentifier: string | null\n}\n\nfunction normalizeOptionalString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeEmail(value: unknown): string | null {\n const normalized = normalizeOptionalString(value)\n return normalized ? normalized.toLowerCase() : null\n}\n\nconst COUNTRY_DIAL_CODES: Record<string, string> = {\n canada: '1',\n england: '44',\n greatbritain: '44',\n poland: '48',\n scotland: '44',\n uk: '44',\n unitedkingdom: '44',\n unitedstates: '1',\n unitedstatesofamerica: '1',\n usa: '1',\n wales: '44',\n}\n\nfunction normalizeCountryKey(value: unknown): string | null {\n const normalized = normalizeOptionalString(value)\n if (!normalized) return null\n return normalized.toLowerCase().replace(/[^a-z]/g, '')\n}\n\nfunction resolveCountryDialCode(row: CsvPreviewRow): string | null {\n const countryCandidates = [\n row.Country,\n row.country,\n row['Country/Region'],\n row['country/region'],\n ]\n\n for (const candidate of countryCandidates) {\n const countryKey = normalizeCountryKey(candidate)\n if (!countryKey) continue\n const dialCode = COUNTRY_DIAL_CODES[countryKey]\n if (dialCode) return dialCode\n }\n\n return null\n}\n\nfunction stripPhoneExtension(value: string): string {\n return value\n .replace(/\\s*(ext\\.?|extension|x)\\s*\\d+$/i, '')\n .replace(/^\\+\\s+/, '+')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction normalizeImportedPhone(value: unknown, row: CsvPreviewRow): string | null {\n const normalized = normalizeOptionalString(value)\n if (!normalized) return null\n\n const directValidation = validatePhoneNumber(normalized)\n if (directValidation.valid) {\n return directValidation.normalized\n }\n\n const sanitized = stripPhoneExtension(normalized)\n const sanitizedValidation = validatePhoneNumber(sanitized)\n if (sanitizedValidation.valid) {\n return sanitizedValidation.normalized\n }\n\n const digits = extractPhoneDigits(sanitized)\n if (!digits) return null\n\n if (digits.length >= 9 && digits.length <= 15 && sanitized.startsWith('00')) {\n const internationalCandidate = `+${digits.slice(2)}`\n const internationalValidation = validatePhoneNumber(internationalCandidate)\n if (internationalValidation.valid) {\n return internationalValidation.normalized\n }\n }\n\n const dialCode = resolveCountryDialCode(row)\n if (!dialCode) return null\n\n let nationalDigits = digits\n if (nationalDigits.startsWith(dialCode)) {\n nationalDigits = nationalDigits.slice(dialCode.length)\n } else {\n nationalDigits = nationalDigits.replace(/^0+/, '')\n }\n\n if (!nationalDigits) return null\n\n const localizedCandidate = `+${dialCode}${nationalDigits}`\n const localizedValidation = validatePhoneNumber(localizedCandidate)\n return localizedValidation.valid ? localizedValidation.normalized : null\n}\n\nfunction normalizeOptionalNumber(value: unknown): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : null\n }\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (trimmed.length === 0) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\n}\n\nfunction normalizeCustomFieldDate(value: string): string | null {\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString().slice(0, 10)\n}\n\nfunction normalizeCustomFieldBoolean(value: string): boolean | null {\n const normalized = value.trim().toLowerCase()\n if (['true', '1', 'yes', 'y', 't', 'tak'].includes(normalized)) return true\n if (['false', '0', 'no', 'n', 'f', 'nie'].includes(normalized)) return false\n return null\n}\n\nfunction coerceCustomFieldValue(\n key: string,\n value: unknown,\n definitions: Map<string, ImportCustomFieldDefinition>,\n): { ok: true; value: unknown } | { ok: false; error: string } {\n if (typeof value === 'string' && value.trim().length === 0) {\n return { ok: true, value: null }\n }\n\n const definition = definitions.get(key)\n if (!definition) {\n return { ok: true, value: typeof value === 'string' ? value.trim() : value }\n }\n\n if (value === null || value === undefined) {\n return { ok: true, value: null }\n }\n\n const text = typeof value === 'string' ? value.trim() : String(value)\n if (text.length === 0) {\n return { ok: true, value: null }\n }\n\n const kind = definition.kind.toLowerCase()\n if (kind === 'boolean') {\n const booleanValue = normalizeCustomFieldBoolean(text)\n if (booleanValue === null) {\n return { ok: false, error: `Custom field \"${key}\" expects a boolean value.` }\n }\n return { ok: true, value: booleanValue }\n }\n\n if (kind === 'integer') {\n const numberValue = Number(text)\n if (!Number.isInteger(numberValue)) {\n return { ok: false, error: `Custom field \"${key}\" expects an integer value.` }\n }\n return { ok: true, value: numberValue }\n }\n\n if (kind === 'float' || kind === 'currency' || kind === 'number') {\n const numberValue = Number(text)\n if (!Number.isFinite(numberValue)) {\n return { ok: false, error: `Custom field \"${key}\" expects a number value.` }\n }\n return { ok: true, value: numberValue }\n }\n\n if (kind === 'date' || kind === 'datetime') {\n const dateValue = normalizeCustomFieldDate(text)\n if (!dateValue) {\n return { ok: false, error: `Custom field \"${key}\" expects a date value.` }\n }\n return { ok: true, value: dateValue }\n }\n\n return { ok: true, value: text }\n}\n\nfunction coerceCustomFields(\n values: Record<string, unknown>,\n definitions: Map<string, ImportCustomFieldDefinition>,\n): { ok: true; values: Record<string, unknown> } | { ok: false; error: string } {\n const coerced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n const result = coerceCustomFieldValue(key, value, definitions)\n if (!result.ok) return result\n coerced[key] = result.value\n }\n return { ok: true, values: coerced }\n}\n\nfunction customFieldDefinitionScopeScore(definition: ImportCustomFieldDefinition): number {\n return (definition.tenantId ? 2 : 0) + (definition.organizationId ? 1 : 0)\n}\n\nfunction customFieldDefinitionUpdatedAt(definition: ImportCustomFieldDefinition): number {\n if (!definition.updatedAt) return 0\n const value = definition.updatedAt instanceof Date\n ? definition.updatedAt.getTime()\n : new Date(definition.updatedAt).getTime()\n return Number.isFinite(value) ? value : 0\n}\n\nfunction preferCustomFieldDefinition(\n current: ImportCustomFieldDefinition | undefined,\n candidate: ImportCustomFieldDefinition,\n): boolean {\n if (!current) return true\n const candidateScore = customFieldDefinitionScopeScore(candidate)\n const currentScore = customFieldDefinitionScopeScore(current)\n if (candidateScore !== currentScore) return candidateScore > currentScore\n return customFieldDefinitionUpdatedAt(candidate) >= customFieldDefinitionUpdatedAt(current)\n}\n\nasync function loadImportCustomFieldDefinitions(\n em: EntityManager,\n scope: TenantScope,\n): Promise<Map<string, ImportCustomFieldDefinition>> {\n const definitions = await findWithDecryption(\n em,\n CustomFieldDef,\n {\n entityId: { $in: [E.customers.customer_entity, E.customers.customer_person_profile] as string[] },\n deletedAt: null,\n isActive: true,\n $and: [\n { $or: [{ tenantId: scope.tenantId }, { tenantId: null }] },\n { $or: [{ organizationId: scope.organizationId }, { organizationId: null }] },\n ],\n },\n undefined,\n scope,\n )\n\n const byKey = new Map<string, ImportCustomFieldDefinition>()\n for (const definition of definitions) {\n const candidate: ImportCustomFieldDefinition = {\n key: definition.key,\n kind: definition.kind,\n entityId: definition.entityId,\n organizationId: definition.organizationId ?? null,\n tenantId: definition.tenantId ?? null,\n updatedAt: definition.updatedAt ?? null,\n }\n if (preferCustomFieldDefinition(byKey.get(candidate.key), candidate)) {\n byKey.set(candidate.key, candidate)\n }\n }\n return byKey\n}\n\nfunction buildCommandContext(container: Container, scope: TenantScope): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: {\n selectedId: scope.organizationId,\n filterIds: [scope.organizationId],\n allowedIds: [scope.organizationId],\n tenantId: scope.tenantId,\n },\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nexport function createCursor(uploadId: string, offset: number): string {\n return JSON.stringify({\n uploadId,\n offset,\n })\n}\n\nexport function parseCursor(value: string | null | undefined): SyncExcelCursor | null {\n if (!value) return null\n try {\n const parsed = JSON.parse(value) as Partial<SyncExcelCursor> | null\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.uploadId !== 'string' || parsed.uploadId.trim().length === 0) return null\n if (typeof parsed.offset !== 'number' || !Number.isFinite(parsed.offset) || parsed.offset < 0) return null\n return {\n uploadId: parsed.uploadId,\n offset: parsed.offset,\n }\n } catch {\n return null\n }\n}\n\nfunction mapRowValues(row: CsvPreviewRow, fields: FieldMapping[]): PersonRowValues {\n const values: PersonFieldValues = {}\n const customFields: Record<string, unknown> = {}\n const addressValues: AddressFieldValues = {}\n\n for (const field of fields) {\n if (field.mappingKind === 'ignore') continue\n const rawValue = row[field.externalField]\n if (rawValue === undefined || rawValue === null) continue\n if (field.mappingKind === 'custom_field' || field.localField.startsWith('cf:')) {\n const customFieldKey = field.localField.startsWith('cf:') ? field.localField.slice(3) : field.localField\n if (customFieldKey.trim().length > 0) {\n customFields[customFieldKey] = rawValue\n }\n continue\n }\n\n if (field.localField === 'person.externalId') {\n values.externalId = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.firstName') {\n values.firstName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.lastName') {\n values.lastName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.displayName') {\n values.displayName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.primaryEmail') {\n values.primaryEmail = normalizeEmail(rawValue)\n continue\n }\n if (field.localField === 'person.primaryPhone') {\n values.primaryPhone = normalizeImportedPhone(rawValue, row)\n continue\n }\n if (field.localField === 'person.jobTitle') {\n values.jobTitle = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.status') {\n values.status = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.source') {\n values.source = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.description') {\n values.description = normalizeOptionalString(rawValue)\n continue\n }\n\n if (field.localField === 'address.name') {\n addressValues.name = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.purpose') {\n addressValues.purpose = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.companyName') {\n addressValues.companyName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.addressLine1') {\n addressValues.addressLine1 = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.addressLine2') {\n addressValues.addressLine2 = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.buildingNumber') {\n addressValues.buildingNumber = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.flatNumber') {\n addressValues.flatNumber = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.city') {\n addressValues.city = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.region') {\n addressValues.region = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.postalCode') {\n addressValues.postalCode = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.country') {\n addressValues.country = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.latitude') {\n addressValues.latitude = normalizeOptionalNumber(rawValue)\n continue\n }\n if (field.localField === 'address.longitude') {\n addressValues.longitude = normalizeOptionalNumber(rawValue)\n }\n }\n\n return {\n values,\n customFields,\n addressValues,\n }\n}\n\nfunction derivePersonNames(values: PersonFieldValues): { firstName: string; lastName: string; displayName: string } | null {\n const firstName = values.firstName ?? null\n const lastName = values.lastName ?? null\n const explicitDisplayName = values.displayName ?? null\n\n if (firstName && lastName) {\n return {\n firstName,\n lastName,\n displayName: explicitDisplayName ?? `${firstName} ${lastName}`.trim(),\n }\n }\n\n if (explicitDisplayName) {\n const parts = explicitDisplayName.split(/\\s+/).filter((part) => part.length > 0)\n if (parts.length >= 2) {\n return {\n firstName: firstName ?? parts.slice(0, -1).join(' '),\n lastName: lastName ?? parts.at(-1) ?? explicitDisplayName,\n displayName: explicitDisplayName,\n }\n }\n return {\n firstName: firstName ?? explicitDisplayName,\n lastName: lastName ?? explicitDisplayName,\n displayName: explicitDisplayName,\n }\n }\n\n return null\n}\n\nexport function buildPersonPayload(row: CsvPreviewRow, mapping: DataMapping, scope: TenantScope): BuiltPersonPayload {\n const { values, customFields, addressValues } = mapRowValues(row, mapping.fields)\n const derivedNames = derivePersonNames(values)\n const sourceIdentifier = values.externalId ?? values.primaryEmail ?? values.displayName ?? null\n\n const updateInput: BuiltPersonPayload['updateInput'] = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n\n if (values.primaryEmail) updateInput.primaryEmail = values.primaryEmail\n if (values.primaryPhone) updateInput.primaryPhone = values.primaryPhone\n if (values.jobTitle) updateInput.jobTitle = values.jobTitle\n if (values.status) updateInput.status = values.status\n if (values.source) updateInput.source = values.source\n if (values.description) updateInput.description = values.description\n if (derivedNames?.firstName) updateInput.firstName = derivedNames.firstName\n if (derivedNames?.lastName) updateInput.lastName = derivedNames.lastName\n if (derivedNames?.displayName) updateInput.displayName = derivedNames.displayName\n\n if (!derivedNames) {\n return {\n values,\n customFields,\n addressValues,\n createInput: null,\n updateInput,\n sourceIdentifier,\n }\n }\n\n return {\n values,\n customFields,\n addressValues,\n createInput: {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n firstName: derivedNames.firstName,\n lastName: derivedNames.lastName,\n displayName: derivedNames.displayName,\n ...(values.primaryEmail ? { primaryEmail: values.primaryEmail } : {}),\n ...(values.primaryPhone ? { primaryPhone: values.primaryPhone } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.status ? { status: values.status } : {}),\n ...(values.source ? { source: values.source } : {}),\n ...(values.description ? { description: values.description } : {}),\n },\n updateInput,\n sourceIdentifier,\n }\n}\n\nfunction hasAddressValues(addressValues: AddressFieldValues): boolean {\n return Object.values(addressValues).some((value) => value !== null && value !== undefined)\n}\n\nfunction buildAddressCreateInput(addressValues: AddressFieldValues, entityId: string, scope: TenantScope) {\n if (!addressValues.addressLine1) return null\n\n return {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n entityId,\n addressLine1: addressValues.addressLine1,\n isPrimary: true,\n ...(addressValues.name ? { name: addressValues.name } : {}),\n ...(addressValues.purpose ? { purpose: addressValues.purpose } : {}),\n ...(addressValues.companyName ? { companyName: addressValues.companyName } : {}),\n ...(addressValues.addressLine2 ? { addressLine2: addressValues.addressLine2 } : {}),\n ...(addressValues.buildingNumber ? { buildingNumber: addressValues.buildingNumber } : {}),\n ...(addressValues.flatNumber ? { flatNumber: addressValues.flatNumber } : {}),\n ...(addressValues.city ? { city: addressValues.city } : {}),\n ...(addressValues.region ? { region: addressValues.region } : {}),\n ...(addressValues.postalCode ? { postalCode: addressValues.postalCode } : {}),\n ...(addressValues.country ? { country: addressValues.country } : {}),\n ...(addressValues.latitude !== null && addressValues.latitude !== undefined ? { latitude: addressValues.latitude } : {}),\n ...(addressValues.longitude !== null && addressValues.longitude !== undefined ? { longitude: addressValues.longitude } : {}),\n }\n}\n\nasync function upsertPrimaryAddress(params: {\n entityId: string\n addressValues: AddressFieldValues\n scope: TenantScope\n commandBus: CommandBus\n commandContext: CommandRuntimeContext\n em: EntityManager\n}): Promise<void> {\n if (!hasAddressValues(params.addressValues)) return\n if (!params.addressValues.addressLine1) return\n\n const [existingPrimaryAddress] = await findWithDecryption(\n params.em,\n CustomerAddress,\n {\n entity: params.entityId,\n organizationId: params.scope.organizationId,\n tenantId: params.scope.tenantId,\n isPrimary: true,\n },\n {\n orderBy: {\n createdAt: 'asc',\n },\n limit: 1,\n },\n params.scope,\n )\n\n if (existingPrimaryAddress) {\n await params.commandBus.execute('customers.addresses.update', {\n input: {\n id: existingPrimaryAddress.id,\n isPrimary: true,\n ...(params.addressValues.name ? { name: params.addressValues.name } : {}),\n ...(params.addressValues.purpose ? { purpose: params.addressValues.purpose } : {}),\n ...(params.addressValues.companyName ? { companyName: params.addressValues.companyName } : {}),\n ...(params.addressValues.addressLine1 ? { addressLine1: params.addressValues.addressLine1 } : {}),\n ...(params.addressValues.addressLine2 ? { addressLine2: params.addressValues.addressLine2 } : {}),\n ...(params.addressValues.buildingNumber ? { buildingNumber: params.addressValues.buildingNumber } : {}),\n ...(params.addressValues.flatNumber ? { flatNumber: params.addressValues.flatNumber } : {}),\n ...(params.addressValues.city ? { city: params.addressValues.city } : {}),\n ...(params.addressValues.region ? { region: params.addressValues.region } : {}),\n ...(params.addressValues.postalCode ? { postalCode: params.addressValues.postalCode } : {}),\n ...(params.addressValues.country ? { country: params.addressValues.country } : {}),\n ...(params.addressValues.latitude !== null && params.addressValues.latitude !== undefined\n ? { latitude: params.addressValues.latitude }\n : {}),\n ...(params.addressValues.longitude !== null && params.addressValues.longitude !== undefined\n ? { longitude: params.addressValues.longitude }\n : {}),\n },\n ctx: params.commandContext,\n })\n return\n }\n\n const createInput = buildAddressCreateInput(params.addressValues, params.entityId, params.scope)\n if (!createInput) return\n\n await params.commandBus.execute('customers.addresses.create', {\n input: createInput,\n ctx: params.commandContext,\n })\n}\n\nasync function loadStoredMapping(em: EntityManager, entityType: string, scope: TenantScope): Promise<DataMapping> {\n const stored = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n integrationId: 'sync_excel',\n entityType,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (stored?.mapping && typeof stored.mapping === 'object') {\n return stored.mapping as unknown as DataMapping\n }\n\n return {\n entityType,\n fields: [],\n matchStrategy: 'custom',\n }\n}\n\nasync function resolveUpload(em: EntityManager, runId: string | undefined, cursor: SyncExcelCursor | null, scope: TenantScope): Promise<SyncExcelUpload | null> {\n if (runId) {\n const uploadByRun = await findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n syncRunId: runId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n if (uploadByRun) return uploadByRun\n }\n\n if (cursor?.uploadId) {\n return findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n id: cursor.uploadId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n }\n\n return null\n}\n\nasync function resolveExistingPersonId(params: {\n externalIdMappingService: ExternalIdMappingService\n externalId: string | null | undefined\n email: string | null | undefined\n emailDedupeIndex: EmailDedupeIndex\n scope: TenantScope\n}): Promise<string | null> {\n if (params.externalId) {\n const mappedLocalId = await params.externalIdMappingService.lookupLocalId(\n 'sync_excel',\n 'customers.person',\n params.externalId,\n params.scope,\n )\n if (mappedLocalId) return mappedLocalId\n }\n\n if (!params.email) return null\n return params.emailDedupeIndex.get(params.email) ?? null\n}\n\nasync function buildEmailDedupeIndex(params: {\n em: EntityManager\n rows: CsvPreviewRow[]\n mapping: DataMapping\n scope: TenantScope\n}): Promise<EmailDedupeIndex> {\n const emails = new Set<string>()\n for (const row of params.rows) {\n const rowValues = mapRowValues(row, params.mapping.fields)\n const email = rowValues.values.primaryEmail\n if (email) emails.add(email)\n }\n\n if (emails.size === 0) return new Map()\n\n const candidates = await findWithDecryption(\n params.em,\n CustomerEntity,\n {\n kind: 'person',\n organizationId: params.scope.organizationId,\n tenantId: params.scope.tenantId,\n deletedAt: null,\n isActive: true,\n },\n {\n orderBy: {\n createdAt: 'asc',\n },\n },\n params.scope,\n )\n\n const index: EmailDedupeIndex = new Map()\n for (const candidate of candidates) {\n const email = normalizeEmail(candidate.primaryEmail)\n if (!email || !emails.has(email) || index.has(email)) continue\n index.set(email, candidate.id)\n }\n return index\n}\n\nfunction isEmptyRow(row: CsvPreviewRow): boolean {\n return !Object.values(row).some((value) => normalizeOptionalString(value))\n}\n\nasync function processRow(params: {\n row: CsvPreviewRow\n rowNumber: number\n mapping: DataMapping\n scope: TenantScope\n commandBus: CommandBus\n commandContext: CommandRuntimeContext\n externalIdMappingService: ExternalIdMappingService\n emailDedupeIndex: EmailDedupeIndex\n customFieldDefinitions: Map<string, ImportCustomFieldDefinition>\n em: EntityManager\n}): Promise<ImportItem> {\n if (isEmptyRow(params.row)) {\n return {\n externalId: `row:${params.rowNumber}`,\n action: 'skip',\n data: {\n rowNumber: params.rowNumber,\n reason: 'empty_row',\n },\n }\n }\n\n const payload = buildPersonPayload(params.row, params.mapping, params.scope)\n const externalId = payload.values.externalId ?? null\n const sourceIdentifier = payload.sourceIdentifier ?? `row:${params.rowNumber}`\n const customFieldResult = coerceCustomFields(payload.customFields, params.customFieldDefinitions)\n if (!customFieldResult.ok) {\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage: customFieldResult.error,\n },\n }\n }\n const existingId = await resolveExistingPersonId({\n externalIdMappingService: params.externalIdMappingService,\n externalId,\n email: payload.values.primaryEmail,\n emailDedupeIndex: params.emailDedupeIndex,\n scope: params.scope,\n })\n\n if (!existingId && !payload.createInput) {\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage: 'Import row is missing a usable person name mapping.',\n },\n }\n }\n\n try {\n if (existingId) {\n const updateInput = {\n id: existingId,\n ...payload.updateInput,\n ...(Object.keys(customFieldResult.values).length > 0 ? { customFields: customFieldResult.values } : {}),\n }\n await params.commandBus.execute('customers.people.update', {\n input: updateInput,\n ctx: params.commandContext,\n })\n\n await upsertPrimaryAddress({\n entityId: existingId,\n addressValues: payload.addressValues,\n scope: params.scope,\n commandBus: params.commandBus,\n commandContext: params.commandContext,\n em: params.em,\n })\n\n if (externalId) {\n await params.externalIdMappingService.storeExternalIdMapping(\n 'sync_excel',\n 'customers.person',\n existingId,\n externalId,\n params.scope,\n )\n }\n if (payload.values.primaryEmail && !params.emailDedupeIndex.has(payload.values.primaryEmail)) {\n params.emailDedupeIndex.set(payload.values.primaryEmail, existingId)\n }\n\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'update',\n data: {\n localId: existingId,\n rowNumber: params.rowNumber,\n sourceIdentifier,\n },\n }\n }\n\n const commandResult = await params.commandBus.execute<\n NonNullable<BuiltPersonPayload['createInput']>,\n { entityId: string; personId: string }\n >('customers.people.create', {\n input: {\n ...payload.createInput!,\n ...(Object.keys(customFieldResult.values).length > 0 ? { customFields: customFieldResult.values } : {}),\n },\n ctx: params.commandContext,\n })\n\n await upsertPrimaryAddress({\n entityId: commandResult.result.entityId,\n addressValues: payload.addressValues,\n scope: params.scope,\n commandBus: params.commandBus,\n commandContext: params.commandContext,\n em: params.em,\n })\n\n if (externalId) {\n await params.externalIdMappingService.storeExternalIdMapping(\n 'sync_excel',\n 'customers.person',\n commandResult.result.entityId,\n externalId,\n params.scope,\n )\n }\n if (payload.values.primaryEmail && !params.emailDedupeIndex.has(payload.values.primaryEmail)) {\n params.emailDedupeIndex.set(payload.values.primaryEmail, commandResult.result.entityId)\n }\n\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'create',\n data: {\n localId: commandResult.result.entityId,\n personId: commandResult.result.personId,\n rowNumber: params.rowNumber,\n sourceIdentifier,\n },\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Import row failed'\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage,\n },\n }\n }\n}\n\nexport const syncExcelCustomersAdapter: DataSyncAdapter = {\n providerKey: 'excel',\n operationalTelemetry: true,\n direction: 'import',\n supportedEntities: ['customers.person'],\n\n async getMapping(input): Promise<DataMapping> {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return loadStoredMapping(em, input.entityType, input.scope)\n },\n\n async *streamImport(input): AsyncIterable<ImportBatch> {\n if (input.entityType !== 'customers.person') {\n throw new Error(`Unsupported sync_excel entity type: ${input.entityType}`)\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandBus = container.resolve('commandBus') as CommandBus\n const externalIdMappingService = container.resolve('externalIdMappingService') as ExternalIdMappingService\n const cursor = parseCursor(input.cursor)\n const upload = await resolveUpload(em, input.runId, cursor, input.scope)\n\n if (!upload) {\n throw new Error('CSV upload session was not found for this sync run.')\n }\n\n if (input.runId) {\n upload.syncRunId = input.runId\n }\n upload.status = 'importing'\n await em.flush()\n\n try {\n const attachment = await findOneWithDecryption(\n em,\n Attachment,\n {\n id: upload.attachmentId,\n organizationId: input.scope.organizationId,\n tenantId: input.scope.tenantId,\n },\n undefined,\n input.scope,\n )\n\n if (!attachment) {\n throw new Error('CSV upload attachment could not be found.')\n }\n\n const fileBuffer = await readSyncExcelUploadBuffer(attachment)\n const document = parseCsvDocument(fileBuffer)\n const startOffset = cursor?.uploadId === upload.id ? cursor.offset : 0\n const commandContext = buildCommandContext(container, input.scope)\n const customFieldDefinitions = await loadImportCustomFieldDefinitions(em, input.scope)\n\n for (let offset = startOffset, batchIndex = 0; offset < document.rows.length; offset += input.batchSize, batchIndex += 1) {\n const batchRows = document.rows.slice(offset, offset + input.batchSize)\n const emailDedupeIndex = await buildEmailDedupeIndex({\n em,\n rows: batchRows,\n mapping: input.mapping,\n scope: input.scope,\n })\n const items: ImportItem[] = []\n\n for (let index = 0; index < batchRows.length; index += 1) {\n items.push(await processRow({\n row: batchRows[index],\n rowNumber: offset + index + 1,\n mapping: input.mapping,\n scope: input.scope,\n commandBus,\n commandContext,\n externalIdMappingService,\n emailDedupeIndex,\n customFieldDefinitions,\n em,\n }))\n }\n\n const nextOffset = offset + batchRows.length\n yield {\n items,\n cursor: createCursor(upload.id, nextOffset),\n hasMore: nextOffset < document.rows.length,\n totalEstimate: document.totalRows,\n processedCount: batchRows.length,\n refreshCoverageEntityTypes: CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES,\n batchIndex,\n message: `Processed ${nextOffset} of ${document.totalRows} CSV rows`,\n }\n }\n\n upload.status = 'completed'\n await em.flush()\n } catch (error) {\n upload.status = 'failed'\n await em.flush()\n throw error\n }\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,77 @@
1
+ const PERSON_FIELD_ALIASES = {
2
+ "person.externalId": ["record id", "external id", "lead id"],
3
+ "person.firstName": ["first name", "firstname", "given name"],
4
+ "person.lastName": ["last name", "lastname", "surname", "family name"],
5
+ "person.displayName": ["lead name", "display name", "full name", "name"],
6
+ "person.primaryEmail": ["email", "primary email", "email address"],
7
+ "person.primaryPhone": ["mobile", "phone", "mobile phone", "telephone"],
8
+ "person.jobTitle": ["title", "job title", "position"],
9
+ "person.status": ["lead status", "status"],
10
+ "person.source": ["lead source", "source"],
11
+ "person.description": ["description", "notes", "comment"],
12
+ "address.name": ["address label", "address name", "label"],
13
+ "address.purpose": ["address purpose", "address type"],
14
+ "address.companyName": ["address company", "address company name"],
15
+ "address.addressLine1": ["address line 1", "street address", "street", "street 1"],
16
+ "address.addressLine2": ["address line 2", "street 2"],
17
+ "address.buildingNumber": ["building number", "building no", "house number"],
18
+ "address.flatNumber": ["flat number", "apartment number", "unit number"],
19
+ "address.city": ["city", "town"],
20
+ "address.region": ["region", "state", "province"],
21
+ "address.postalCode": ["postal code", "zip code", "postcode"],
22
+ "address.country": ["country"],
23
+ "address.latitude": ["latitude", "lat"],
24
+ "address.longitude": ["longitude", "lng", "lon"]
25
+ };
26
+ function normalizeLabel(value) {
27
+ return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
28
+ }
29
+ function buildFieldMapping(externalField, localField) {
30
+ if (localField === "person.externalId") {
31
+ return {
32
+ externalField,
33
+ localField,
34
+ mappingKind: "external_id",
35
+ dedupeRole: "primary"
36
+ };
37
+ }
38
+ if (localField === "person.primaryEmail") {
39
+ return {
40
+ externalField,
41
+ localField,
42
+ mappingKind: "core",
43
+ dedupeRole: "secondary"
44
+ };
45
+ }
46
+ return {
47
+ externalField,
48
+ localField,
49
+ mappingKind: "core"
50
+ };
51
+ }
52
+ function detectCustomersPersonMapping(columns) {
53
+ const fields = [];
54
+ const unmappedColumns = [];
55
+ for (const column of columns) {
56
+ const normalizedColumn = normalizeLabel(column);
57
+ const matchingField = Object.entries(PERSON_FIELD_ALIASES).find(([, aliases]) => aliases.includes(normalizedColumn));
58
+ if (!matchingField) {
59
+ unmappedColumns.push(column);
60
+ continue;
61
+ }
62
+ fields.push(buildFieldMapping(column, matchingField[0]));
63
+ }
64
+ const hasExternalId = fields.some((field) => field.localField === "person.externalId");
65
+ const hasEmail = fields.some((field) => field.localField === "person.primaryEmail");
66
+ return {
67
+ entityType: "customers.person",
68
+ matchStrategy: hasExternalId ? "externalId" : hasEmail ? "email" : "custom",
69
+ matchField: hasExternalId ? "person.externalId" : hasEmail ? "person.primaryEmail" : void 0,
70
+ fields,
71
+ unmappedColumns
72
+ };
73
+ }
74
+ export {
75
+ detectCustomersPersonMapping
76
+ };
77
+ //# sourceMappingURL=column-detector.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sync_excel/lib/column-detector.ts"],
4
+ "sourcesContent": ["import type { DataMapping, FieldMapping } from '../../data_sync/lib/adapter'\n\nconst PERSON_FIELD_ALIASES: Record<string, string[]> = {\n 'person.externalId': ['record id', 'external id', 'lead id'],\n 'person.firstName': ['first name', 'firstname', 'given name'],\n 'person.lastName': ['last name', 'lastname', 'surname', 'family name'],\n 'person.displayName': ['lead name', 'display name', 'full name', 'name'],\n 'person.primaryEmail': ['email', 'primary email', 'email address'],\n 'person.primaryPhone': ['mobile', 'phone', 'mobile phone', 'telephone'],\n 'person.jobTitle': ['title', 'job title', 'position'],\n 'person.status': ['lead status', 'status'],\n 'person.source': ['lead source', 'source'],\n 'person.description': ['description', 'notes', 'comment'],\n 'address.name': ['address label', 'address name', 'label'],\n 'address.purpose': ['address purpose', 'address type'],\n 'address.companyName': ['address company', 'address company name'],\n 'address.addressLine1': ['address line 1', 'street address', 'street', 'street 1'],\n 'address.addressLine2': ['address line 2', 'street 2'],\n 'address.buildingNumber': ['building number', 'building no', 'house number'],\n 'address.flatNumber': ['flat number', 'apartment number', 'unit number'],\n 'address.city': ['city', 'town'],\n 'address.region': ['region', 'state', 'province'],\n 'address.postalCode': ['postal code', 'zip code', 'postcode'],\n 'address.country': ['country'],\n 'address.latitude': ['latitude', 'lat'],\n 'address.longitude': ['longitude', 'lng', 'lon'],\n}\n\nfunction normalizeLabel(value: string): string {\n return value\n .trim()\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .toLowerCase()\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction buildFieldMapping(externalField: string, localField: string): FieldMapping {\n if (localField === 'person.externalId') {\n return {\n externalField,\n localField,\n mappingKind: 'external_id',\n dedupeRole: 'primary',\n }\n }\n\n if (localField === 'person.primaryEmail') {\n return {\n externalField,\n localField,\n mappingKind: 'core',\n dedupeRole: 'secondary',\n }\n }\n\n return {\n externalField,\n localField,\n mappingKind: 'core',\n }\n}\n\nexport function detectCustomersPersonMapping(columns: string[]): DataMapping & { unmappedColumns: string[] } {\n const fields: FieldMapping[] = []\n const unmappedColumns: string[] = []\n\n for (const column of columns) {\n const normalizedColumn = normalizeLabel(column)\n const matchingField = Object.entries(PERSON_FIELD_ALIASES).find(([, aliases]) => aliases.includes(normalizedColumn))\n\n if (!matchingField) {\n unmappedColumns.push(column)\n continue\n }\n\n fields.push(buildFieldMapping(column, matchingField[0]))\n }\n\n const hasExternalId = fields.some((field) => field.localField === 'person.externalId')\n const hasEmail = fields.some((field) => field.localField === 'person.primaryEmail')\n\n return {\n entityType: 'customers.person',\n matchStrategy: hasExternalId ? 'externalId' : hasEmail ? 'email' : 'custom',\n matchField: hasExternalId ? 'person.externalId' : hasEmail ? 'person.primaryEmail' : undefined,\n fields,\n unmappedColumns,\n }\n}\n"],
5
+ "mappings": "AAEA,MAAM,uBAAiD;AAAA,EACrD,qBAAqB,CAAC,aAAa,eAAe,SAAS;AAAA,EAC3D,oBAAoB,CAAC,cAAc,aAAa,YAAY;AAAA,EAC5D,mBAAmB,CAAC,aAAa,YAAY,WAAW,aAAa;AAAA,EACrE,sBAAsB,CAAC,aAAa,gBAAgB,aAAa,MAAM;AAAA,EACvE,uBAAuB,CAAC,SAAS,iBAAiB,eAAe;AAAA,EACjE,uBAAuB,CAAC,UAAU,SAAS,gBAAgB,WAAW;AAAA,EACtE,mBAAmB,CAAC,SAAS,aAAa,UAAU;AAAA,EACpD,iBAAiB,CAAC,eAAe,QAAQ;AAAA,EACzC,iBAAiB,CAAC,eAAe,QAAQ;AAAA,EACzC,sBAAsB,CAAC,eAAe,SAAS,SAAS;AAAA,EACxD,gBAAgB,CAAC,iBAAiB,gBAAgB,OAAO;AAAA,EACzD,mBAAmB,CAAC,mBAAmB,cAAc;AAAA,EACrD,uBAAuB,CAAC,mBAAmB,sBAAsB;AAAA,EACjE,wBAAwB,CAAC,kBAAkB,kBAAkB,UAAU,UAAU;AAAA,EACjF,wBAAwB,CAAC,kBAAkB,UAAU;AAAA,EACrD,0BAA0B,CAAC,mBAAmB,eAAe,cAAc;AAAA,EAC3E,sBAAsB,CAAC,eAAe,oBAAoB,aAAa;AAAA,EACvE,gBAAgB,CAAC,QAAQ,MAAM;AAAA,EAC/B,kBAAkB,CAAC,UAAU,SAAS,UAAU;AAAA,EAChD,sBAAsB,CAAC,eAAe,YAAY,UAAU;AAAA,EAC5D,mBAAmB,CAAC,SAAS;AAAA,EAC7B,oBAAoB,CAAC,YAAY,KAAK;AAAA,EACtC,qBAAqB,CAAC,aAAa,OAAO,KAAK;AACjD;AAEA,SAAS,eAAe,OAAuB;AAC7C,SAAO,MACJ,KAAK,EACL,QAAQ,sBAAsB,OAAO,EACrC,YAAY,EACZ,QAAQ,UAAU,GAAG,EACrB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEA,SAAS,kBAAkB,eAAuB,YAAkC;AAClF,MAAI,eAAe,qBAAqB;AACtC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,YAAY;AAAA,IACd;AAAA,EACF;AAEA,MAAI,eAAe,uBAAuB;AACxC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,YAAY;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,aAAa;AAAA,EACf;AACF;AAEO,SAAS,6BAA6B,SAAgE;AAC3G,QAAM,SAAyB,CAAC;AAChC,QAAM,kBAA4B,CAAC;AAEnC,aAAW,UAAU,SAAS;AAC5B,UAAM,mBAAmB,eAAe,MAAM;AAC9C,UAAM,gBAAgB,OAAO,QAAQ,oBAAoB,EAAE,KAAK,CAAC,CAAC,EAAE,OAAO,MAAM,QAAQ,SAAS,gBAAgB,CAAC;AAEnH,QAAI,CAAC,eAAe;AAClB,sBAAgB,KAAK,MAAM;AAC3B;AAAA,IACF;AAEA,WAAO,KAAK,kBAAkB,QAAQ,cAAc,CAAC,CAAC,CAAC;AAAA,EACzD;AAEA,QAAM,gBAAgB,OAAO,KAAK,CAAC,UAAU,MAAM,eAAe,mBAAmB;AACrF,QAAM,WAAW,OAAO,KAAK,CAAC,UAAU,MAAM,eAAe,qBAAqB;AAElF,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,eAAe,gBAAgB,eAAe,WAAW,UAAU;AAAA,IACnE,YAAY,gBAAgB,sBAAsB,WAAW,wBAAwB;AAAA,IACrF;AAAA,IACA;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import { detectCustomersPersonMapping } from "./column-detector.js";
2
+ function buildSuggestedMapping(entityType, headers) {
3
+ if (entityType === "customers.person") {
4
+ return detectCustomersPersonMapping(headers);
5
+ }
6
+ return {
7
+ entityType,
8
+ matchStrategy: "custom",
9
+ fields: [],
10
+ unmappedColumns: headers.slice()
11
+ };
12
+ }
13
+ export {
14
+ buildSuggestedMapping
15
+ };
16
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sync_excel/lib/mapping.ts"],
4
+ "sourcesContent": ["import type { DataMapping, FieldMapping } from '../../data_sync/lib/adapter'\nimport { detectCustomersPersonMapping } from './column-detector'\n\nexport type SuggestedMapping = DataMapping & {\n unmappedColumns: string[]\n}\n\nexport function buildSuggestedMapping(entityType: string, headers: string[]): SuggestedMapping {\n if (entityType === 'customers.person') {\n return detectCustomersPersonMapping(headers)\n }\n\n return {\n entityType,\n matchStrategy: 'custom',\n fields: [] as FieldMapping[],\n unmappedColumns: headers.slice(),\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,oCAAoC;AAMtC,SAAS,sBAAsB,YAAoB,SAAqC;AAC7F,MAAI,eAAe,oBAAoB;AACrC,WAAO,6BAA6B,OAAO;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,QAAQ,CAAC;AAAA,IACT,iBAAiB,QAAQ,MAAM;AAAA,EACjC;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,125 @@
1
+ function stripUtf8Bom(value) {
2
+ return value.charCodeAt(0) === 65279 ? value.slice(1) : value;
3
+ }
4
+ function countFields(line, delimiter) {
5
+ let count = 1;
6
+ let inQuotes = false;
7
+ for (let index = 0; index < line.length; index += 1) {
8
+ const character = line[index];
9
+ if (character === '"') {
10
+ if (inQuotes && line[index + 1] === '"') {
11
+ index += 1;
12
+ continue;
13
+ }
14
+ inQuotes = !inQuotes;
15
+ continue;
16
+ }
17
+ if (!inQuotes && character === delimiter) {
18
+ count += 1;
19
+ }
20
+ }
21
+ return count;
22
+ }
23
+ function detectCsvDelimiter(text) {
24
+ const normalized = stripUtf8Bom(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
25
+ const lines = normalized.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).slice(0, 5);
26
+ if (lines.length === 0) return ",";
27
+ const commaScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ","), 0);
28
+ const semicolonScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ";"), 0);
29
+ return semicolonScore > commaScore ? ";" : ",";
30
+ }
31
+ function parseCsvText(text, delimiter) {
32
+ const normalized = stripUtf8Bom(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
33
+ const rows = [];
34
+ let currentRow = [];
35
+ let currentValue = "";
36
+ let inQuotes = false;
37
+ for (let index = 0; index < normalized.length; index += 1) {
38
+ const character = normalized[index];
39
+ if (character === '"') {
40
+ if (inQuotes && normalized[index + 1] === '"') {
41
+ currentValue += '"';
42
+ index += 1;
43
+ continue;
44
+ }
45
+ inQuotes = !inQuotes;
46
+ continue;
47
+ }
48
+ if (!inQuotes && character === delimiter) {
49
+ currentRow.push(currentValue);
50
+ currentValue = "";
51
+ continue;
52
+ }
53
+ if (!inQuotes && character === "\n") {
54
+ currentRow.push(currentValue);
55
+ const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0);
56
+ if (isMeaningfulRow) {
57
+ rows.push(currentRow);
58
+ }
59
+ currentRow = [];
60
+ currentValue = "";
61
+ continue;
62
+ }
63
+ currentValue += character;
64
+ }
65
+ if (currentValue.length > 0 || currentRow.length > 0) {
66
+ currentRow.push(currentValue);
67
+ const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0);
68
+ if (isMeaningfulRow) {
69
+ rows.push(currentRow);
70
+ }
71
+ }
72
+ return rows;
73
+ }
74
+ function normalizeHeaders(headers) {
75
+ return headers.map((header, index) => {
76
+ const trimmed = header.trim();
77
+ return trimmed.length > 0 ? trimmed : `Column ${index + 1}`;
78
+ });
79
+ }
80
+ function rowsToObjects(headers, rows, maxRows) {
81
+ return rows.slice(0, maxRows).map((row) => {
82
+ const record = {};
83
+ headers.forEach((header, index) => {
84
+ const value = row[index];
85
+ record[header] = typeof value === "string" && value.length > 0 ? value : null;
86
+ });
87
+ return record;
88
+ });
89
+ }
90
+ function parseCsvPreview(buffer, options = {}) {
91
+ const text = buffer.toString("utf-8");
92
+ const delimiter = detectCsvDelimiter(text);
93
+ const rows = parseCsvText(text, delimiter);
94
+ const [rawHeaders = [], ...dataRows] = rows;
95
+ const headers = normalizeHeaders(rawHeaders);
96
+ const maxRows = options.maxRows ?? 5;
97
+ return {
98
+ headers,
99
+ sampleRows: rowsToObjects(headers, dataRows, maxRows),
100
+ totalRows: dataRows.length,
101
+ delimiter,
102
+ encoding: "utf-8"
103
+ };
104
+ }
105
+ function parseCsvDocument(buffer) {
106
+ const text = buffer.toString("utf-8");
107
+ const delimiter = detectCsvDelimiter(text);
108
+ const rows = parseCsvText(text, delimiter);
109
+ const [rawHeaders = [], ...dataRows] = rows;
110
+ const headers = normalizeHeaders(rawHeaders);
111
+ return {
112
+ headers,
113
+ rows: rowsToObjects(headers, dataRows, dataRows.length),
114
+ totalRows: dataRows.length,
115
+ delimiter,
116
+ encoding: "utf-8"
117
+ };
118
+ }
119
+ export {
120
+ detectCsvDelimiter,
121
+ parseCsvDocument,
122
+ parseCsvPreview,
123
+ parseCsvText
124
+ };
125
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sync_excel/lib/parser.ts"],
4
+ "sourcesContent": ["export type CsvPreviewRow = Record<string, string | null>\n\nexport type CsvPreview = {\n headers: string[]\n sampleRows: CsvPreviewRow[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\nexport type CsvDocument = {\n headers: string[]\n rows: CsvPreviewRow[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\ntype ParseCsvPreviewOptions = {\n maxRows?: number\n}\n\nfunction stripUtf8Bom(value: string): string {\n return value.charCodeAt(0) === 0xfeff ? value.slice(1) : value\n}\n\nfunction countFields(line: string, delimiter: ',' | ';'): number {\n let count = 1\n let inQuotes = false\n\n for (let index = 0; index < line.length; index += 1) {\n const character = line[index]\n if (character === '\"') {\n if (inQuotes && line[index + 1] === '\"') {\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n if (!inQuotes && character === delimiter) {\n count += 1\n }\n }\n\n return count\n}\n\nexport function detectCsvDelimiter(text: string): ',' | ';' {\n const normalized = stripUtf8Bom(text).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n const lines = normalized\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .slice(0, 5)\n\n if (lines.length === 0) return ','\n\n const commaScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ','), 0)\n const semicolonScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ';'), 0)\n\n return semicolonScore > commaScore ? ';' : ','\n}\n\nexport function parseCsvText(text: string, delimiter: ',' | ';'): string[][] {\n const normalized = stripUtf8Bom(text).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n const rows: string[][] = []\n let currentRow: string[] = []\n let currentValue = ''\n let inQuotes = false\n\n for (let index = 0; index < normalized.length; index += 1) {\n const character = normalized[index]\n\n if (character === '\"') {\n if (inQuotes && normalized[index + 1] === '\"') {\n currentValue += '\"'\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n\n if (!inQuotes && character === delimiter) {\n currentRow.push(currentValue)\n currentValue = ''\n continue\n }\n\n if (!inQuotes && character === '\\n') {\n currentRow.push(currentValue)\n const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0)\n if (isMeaningfulRow) {\n rows.push(currentRow)\n }\n currentRow = []\n currentValue = ''\n continue\n }\n\n currentValue += character\n }\n\n if (currentValue.length > 0 || currentRow.length > 0) {\n currentRow.push(currentValue)\n const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0)\n if (isMeaningfulRow) {\n rows.push(currentRow)\n }\n }\n\n return rows\n}\n\nfunction normalizeHeaders(headers: string[]): string[] {\n return headers.map((header, index) => {\n const trimmed = header.trim()\n return trimmed.length > 0 ? trimmed : `Column ${index + 1}`\n })\n}\n\nfunction rowsToObjects(headers: string[], rows: string[][], maxRows: number): CsvPreviewRow[] {\n return rows.slice(0, maxRows).map((row) => {\n const record: CsvPreviewRow = {}\n headers.forEach((header, index) => {\n const value = row[index]\n record[header] = typeof value === 'string' && value.length > 0 ? value : null\n })\n return record\n })\n}\n\nexport function parseCsvPreview(buffer: Buffer, options: ParseCsvPreviewOptions = {}): CsvPreview {\n const text = buffer.toString('utf-8')\n const delimiter = detectCsvDelimiter(text)\n const rows = parseCsvText(text, delimiter)\n const [rawHeaders = [], ...dataRows] = rows\n const headers = normalizeHeaders(rawHeaders)\n const maxRows = options.maxRows ?? 5\n\n return {\n headers,\n sampleRows: rowsToObjects(headers, dataRows, maxRows),\n totalRows: dataRows.length,\n delimiter,\n encoding: 'utf-8',\n }\n}\n\nexport function parseCsvDocument(buffer: Buffer): CsvDocument {\n const text = buffer.toString('utf-8')\n const delimiter = detectCsvDelimiter(text)\n const rows = parseCsvText(text, delimiter)\n const [rawHeaders = [], ...dataRows] = rows\n const headers = normalizeHeaders(rawHeaders)\n\n return {\n headers,\n rows: rowsToObjects(headers, dataRows, dataRows.length),\n totalRows: dataRows.length,\n delimiter,\n encoding: 'utf-8',\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,44 @@
1
+ import { isAllOrganizationsSelection } from "@open-mercato/core/modules/directory/constants";
2
+ import { getSelectedOrganizationFromRequest, resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
3
+ const CONCRETE_ORGANIZATION_REQUIRED_ERROR = "Select a concrete organization before importing CSV.";
4
+ function normalizeSelectedOrganizationId(value) {
5
+ if (typeof value !== "string") return null;
6
+ const trimmed = value.trim();
7
+ return trimmed.length > 0 ? trimmed : null;
8
+ }
9
+ async function resolveSyncExcelConcreteScope(params) {
10
+ const { auth, container, request } = params;
11
+ if (!auth?.tenantId) {
12
+ return { ok: false, status: 401, error: "Unauthorized" };
13
+ }
14
+ const requestedSelectedId = normalizeSelectedOrganizationId(getSelectedOrganizationFromRequest(request));
15
+ if (!requestedSelectedId || isAllOrganizationsSelection(requestedSelectedId)) {
16
+ return { ok: false, status: 422, error: CONCRETE_ORGANIZATION_REQUIRED_ERROR };
17
+ }
18
+ const organizationScope = await resolveOrganizationScopeForRequest({
19
+ container,
20
+ auth,
21
+ request,
22
+ selectedId: requestedSelectedId
23
+ });
24
+ const selectedId = organizationScope.selectedId;
25
+ const tenantId = organizationScope.tenantId ?? auth.tenantId;
26
+ const filterIds = organizationScope.filterIds;
27
+ const selectedIsResolvable = Boolean(
28
+ selectedId && tenantId && Array.isArray(filterIds) && selectedId === requestedSelectedId && filterIds.includes(selectedId)
29
+ );
30
+ if (!selectedIsResolvable || !selectedId || !tenantId) {
31
+ return { ok: false, status: 422, error: CONCRETE_ORGANIZATION_REQUIRED_ERROR };
32
+ }
33
+ return {
34
+ ok: true,
35
+ scope: {
36
+ organizationId: selectedId,
37
+ tenantId
38
+ }
39
+ };
40
+ }
41
+ export {
42
+ resolveSyncExcelConcreteScope
43
+ };
44
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sync_excel/lib/scope.ts"],
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { getSelectedOrganizationFromRequest, resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { TenantScope } from '../../data_sync/lib/adapter'\n\nexport type SyncExcelConcreteScopeResult =\n | { ok: true; scope: TenantScope }\n | { ok: false; status: 401 | 422; error: string }\n\nconst CONCRETE_ORGANIZATION_REQUIRED_ERROR = 'Select a concrete organization before importing CSV.'\n\nfunction normalizeSelectedOrganizationId(value: string | null | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport async function resolveSyncExcelConcreteScope(params: {\n auth: AuthContext\n container: AwilixContainer\n request: Request\n}): Promise<SyncExcelConcreteScopeResult> {\n const { auth, container, request } = params\n if (!auth?.tenantId) {\n return { ok: false, status: 401, error: 'Unauthorized' }\n }\n\n const requestedSelectedId = normalizeSelectedOrganizationId(getSelectedOrganizationFromRequest(request))\n if (!requestedSelectedId || isAllOrganizationsSelection(requestedSelectedId)) {\n return { ok: false, status: 422, error: CONCRETE_ORGANIZATION_REQUIRED_ERROR }\n }\n\n const organizationScope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request,\n selectedId: requestedSelectedId,\n })\n\n const selectedId = organizationScope.selectedId\n const tenantId = organizationScope.tenantId ?? auth.tenantId\n const filterIds = organizationScope.filterIds\n const selectedIsResolvable = Boolean(\n selectedId\n && tenantId\n && Array.isArray(filterIds)\n && selectedId === requestedSelectedId\n && filterIds.includes(selectedId),\n )\n\n if (!selectedIsResolvable || !selectedId || !tenantId) {\n return { ok: false, status: 422, error: CONCRETE_ORGANIZATION_REQUIRED_ERROR }\n }\n\n return {\n ok: true,\n scope: {\n organizationId: selectedId,\n tenantId,\n },\n }\n}\n"],
5
+ "mappings": "AAEA,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC,0CAA0C;AAOvF,MAAM,uCAAuC;AAE7C,SAAS,gCAAgC,OAAiD;AACxF,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAEA,eAAsB,8BAA8B,QAIV;AACxC,QAAM,EAAE,MAAM,WAAW,QAAQ,IAAI;AACrC,MAAI,CAAC,MAAM,UAAU;AACnB,WAAO,EAAE,IAAI,OAAO,QAAQ,KAAK,OAAO,eAAe;AAAA,EACzD;AAEA,QAAM,sBAAsB,gCAAgC,mCAAmC,OAAO,CAAC;AACvG,MAAI,CAAC,uBAAuB,4BAA4B,mBAAmB,GAAG;AAC5E,WAAO,EAAE,IAAI,OAAO,QAAQ,KAAK,OAAO,qCAAqC;AAAA,EAC/E;AAEA,QAAM,oBAAoB,MAAM,mCAAmC;AAAA,IACjE;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,EACd,CAAC;AAED,QAAM,aAAa,kBAAkB;AACrC,QAAM,WAAW,kBAAkB,YAAY,KAAK;AACpD,QAAM,YAAY,kBAAkB;AACpC,QAAM,uBAAuB;AAAA,IAC3B,cACG,YACA,MAAM,QAAQ,SAAS,KACvB,eAAe,uBACf,UAAU,SAAS,UAAU;AAAA,EAClC;AAEA,MAAI,CAAC,wBAAwB,CAAC,cAAc,CAAC,UAAU;AACrD,WAAO,EAAE,IAAI,OAAO,QAAQ,KAAK,OAAO,qCAAqC;AAAA,EAC/E;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,MACL,gBAAgB;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,63 @@
1
+ import { promises as fs } from "fs";
2
+ import { randomUUID } from "crypto";
3
+ import { Attachment, AttachmentPartition } from "../../attachments/data/entities.js";
4
+ import { buildAttachmentFileUrl } from "../../attachments/lib/imageUrls.js";
5
+ import { ensureDefaultPartitions } from "../../attachments/lib/partitions.js";
6
+ import { resolveAttachmentAbsolutePath, storePartitionFile } from "../../attachments/lib/storage.js";
7
+ const SYNC_EXCEL_ATTACHMENT_ENTITY_ID = "sync_excel:upload";
8
+ const SYNC_EXCEL_PARTITION_CODE = "privateAttachments";
9
+ async function createSyncExcelUploadAttachment(input) {
10
+ await ensureDefaultPartitions(input.em);
11
+ const partition = await input.em.findOne(AttachmentPartition, { code: SYNC_EXCEL_PARTITION_CODE });
12
+ if (!partition) {
13
+ throw new Error("Storage partition is not configured.");
14
+ }
15
+ const stored = await storePartitionFile({
16
+ partitionCode: partition.code,
17
+ orgId: input.organizationId,
18
+ tenantId: input.tenantId,
19
+ fileName: input.fileName,
20
+ buffer: input.buffer
21
+ });
22
+ const attachmentId = randomUUID();
23
+ const attachment = input.em.create(Attachment, {
24
+ id: attachmentId,
25
+ entityId: SYNC_EXCEL_ATTACHMENT_ENTITY_ID,
26
+ recordId: input.uploadId,
27
+ organizationId: input.organizationId,
28
+ tenantId: input.tenantId,
29
+ partitionCode: partition.code,
30
+ fileName: input.fileName,
31
+ mimeType: input.mimeType,
32
+ fileSize: input.buffer.length,
33
+ storageDriver: partition.storageDriver || "local",
34
+ storagePath: stored.storagePath,
35
+ url: buildAttachmentFileUrl(attachmentId),
36
+ storageMetadata: {
37
+ module: "sync_excel",
38
+ temporary: true,
39
+ uploadId: input.uploadId,
40
+ inlineCsvBase64: input.buffer.toString("base64")
41
+ }
42
+ });
43
+ input.em.persist(attachment);
44
+ await input.em.flush();
45
+ return attachment;
46
+ }
47
+ async function readSyncExcelUploadBuffer(attachment) {
48
+ const inlineCsvBase64 = attachment.storageMetadata?.inlineCsvBase64;
49
+ if (typeof inlineCsvBase64 === "string" && inlineCsvBase64.length > 0) {
50
+ return Buffer.from(inlineCsvBase64, "base64");
51
+ }
52
+ const absolutePath = resolveAttachmentAbsolutePath(
53
+ attachment.partitionCode,
54
+ attachment.storagePath,
55
+ attachment.storageDriver
56
+ );
57
+ return fs.readFile(absolutePath);
58
+ }
59
+ export {
60
+ createSyncExcelUploadAttachment,
61
+ readSyncExcelUploadBuffer
62
+ };
63
+ //# sourceMappingURL=upload-storage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sync_excel/lib/upload-storage.ts"],
4
+ "sourcesContent": ["import { promises as fs } from 'fs'\nimport { randomUUID } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Attachment, AttachmentPartition } from '../../attachments/data/entities'\nimport { buildAttachmentFileUrl } from '../../attachments/lib/imageUrls'\nimport { ensureDefaultPartitions } from '../../attachments/lib/partitions'\nimport { resolveAttachmentAbsolutePath, storePartitionFile } from '../../attachments/lib/storage'\n\nconst SYNC_EXCEL_ATTACHMENT_ENTITY_ID = 'sync_excel:upload'\nconst SYNC_EXCEL_PARTITION_CODE = 'privateAttachments'\n\nexport async function createSyncExcelUploadAttachment(input: {\n em: EntityManager\n uploadId: string\n organizationId: string\n tenantId: string\n fileName: string\n mimeType: string\n buffer: Buffer\n}): Promise<Attachment> {\n await ensureDefaultPartitions(input.em)\n\n // Attachment partitions are global storage configuration, not tenant-scoped payload data.\n const partition = await input.em.findOne(AttachmentPartition, { code: SYNC_EXCEL_PARTITION_CODE })\n if (!partition) {\n throw new Error('Storage partition is not configured.')\n }\n\n const stored = await storePartitionFile({\n partitionCode: partition.code,\n orgId: input.organizationId,\n tenantId: input.tenantId,\n fileName: input.fileName,\n buffer: input.buffer,\n })\n\n const attachmentId = randomUUID()\n const attachment = input.em.create(Attachment, {\n id: attachmentId,\n entityId: SYNC_EXCEL_ATTACHMENT_ENTITY_ID,\n recordId: input.uploadId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n partitionCode: partition.code,\n fileName: input.fileName,\n mimeType: input.mimeType,\n fileSize: input.buffer.length,\n storageDriver: partition.storageDriver || 'local',\n storagePath: stored.storagePath,\n url: buildAttachmentFileUrl(attachmentId),\n storageMetadata: {\n module: 'sync_excel',\n temporary: true,\n uploadId: input.uploadId,\n inlineCsvBase64: input.buffer.toString('base64'),\n },\n })\n\n input.em.persist(attachment)\n await input.em.flush()\n\n return attachment\n}\n\nexport async function readSyncExcelUploadBuffer(\n attachment: Pick<Attachment, 'partitionCode' | 'storagePath' | 'storageDriver' | 'storageMetadata'>,\n): Promise<Buffer> {\n const inlineCsvBase64 = attachment.storageMetadata?.inlineCsvBase64\n if (typeof inlineCsvBase64 === 'string' && inlineCsvBase64.length > 0) {\n return Buffer.from(inlineCsvBase64, 'base64')\n }\n\n const absolutePath = resolveAttachmentAbsolutePath(\n attachment.partitionCode,\n attachment.storagePath,\n attachment.storageDriver,\n )\n return fs.readFile(absolutePath)\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,SAAS,kBAAkB;AAE3B,SAAS,YAAY,2BAA2B;AAChD,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,+BAA+B,0BAA0B;AAElE,MAAM,kCAAkC;AACxC,MAAM,4BAA4B;AAElC,eAAsB,gCAAgC,OAQ9B;AACtB,QAAM,wBAAwB,MAAM,EAAE;AAGtC,QAAM,YAAY,MAAM,MAAM,GAAG,QAAQ,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjG,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AAEA,QAAM,SAAS,MAAM,mBAAmB;AAAA,IACtC,eAAe,UAAU;AAAA,IACzB,OAAO,MAAM;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,QAAQ,MAAM;AAAA,EAChB,CAAC;AAED,QAAM,eAAe,WAAW;AAChC,QAAM,aAAa,MAAM,GAAG,OAAO,YAAY;AAAA,IAC7C,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,IAChB,eAAe,UAAU;AAAA,IACzB,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM,OAAO;AAAA,IACvB,eAAe,UAAU,iBAAiB;AAAA,IAC1C,aAAa,OAAO;AAAA,IACpB,KAAK,uBAAuB,YAAY;AAAA,IACxC,iBAAiB;AAAA,MACf,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,iBAAiB,MAAM,OAAO,SAAS,QAAQ;AAAA,IACjD;AAAA,EACF,CAAC;AAED,QAAM,GAAG,QAAQ,UAAU;AAC3B,QAAM,MAAM,GAAG,MAAM;AAErB,SAAO;AACT;AAEA,eAAsB,0BACpB,YACiB;AACjB,QAAM,kBAAkB,WAAW,iBAAiB;AACpD,MAAI,OAAO,oBAAoB,YAAY,gBAAgB,SAAS,GAAG;AACrE,WAAO,OAAO,KAAK,iBAAiB,QAAQ;AAAA,EAC9C;AAEA,QAAM,eAAe;AAAA,IACnB,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACA,SAAO,GAAG,SAAS,YAAY;AACjC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260329181120_sync_excel extends Migration {
3
+ async up() {
4
+ this.addSql(`create table "sync_excel_uploads" ("id" uuid not null default gen_random_uuid(), "attachment_id" uuid not null, "filename" text not null, "mime_type" text not null, "file_size" int not null, "entity_type" text not null, "delimiter" text null, "encoding" text null, "headers" jsonb not null, "sample_rows" jsonb not null, "total_rows" int not null, "status" text not null default 'uploaded', "sync_run_id" uuid null, "organization_id" uuid not null, "tenant_id" uuid not null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "sync_excel_uploads_pkey" primary key ("id"));`);
5
+ this.addSql(`create index "sync_excel_uploads_organization_id_tenant_id_status_index" on "sync_excel_uploads" ("organization_id", "tenant_id", "status");`);
6
+ }
7
+ }
8
+ export {
9
+ Migration20260329181120_sync_excel
10
+ };
11
+ //# sourceMappingURL=Migration20260329181120_sync_excel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sync_excel/migrations/Migration20260329181120_sync_excel.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260329181120_sync_excel extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`create table \"sync_excel_uploads\" (\"id\" uuid not null default gen_random_uuid(), \"attachment_id\" uuid not null, \"filename\" text not null, \"mime_type\" text not null, \"file_size\" int not null, \"entity_type\" text not null, \"delimiter\" text null, \"encoding\" text null, \"headers\" jsonb not null, \"sample_rows\" jsonb not null, \"total_rows\" int not null, \"status\" text not null default 'uploaded', \"sync_run_id\" uuid null, \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, constraint \"sync_excel_uploads_pkey\" primary key (\"id\"));`);\n this.addSql(`create index \"sync_excel_uploads_organization_id_tenant_id_status_index\" on \"sync_excel_uploads\" (\"organization_id\", \"tenant_id\", \"status\");`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,2CAA2C,UAAU;AAAA,EAEhE,MAAe,KAAoB;AACjC,SAAK,OAAO,6lBAA6lB;AACzmB,SAAK,OAAO,8IAA8I;AAAA,EAC5J;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ const setup = {
2
+ defaultRoleFeatures: {
3
+ superadmin: ["sync_excel.view", "sync_excel.run"],
4
+ admin: ["sync_excel.view", "sync_excel.run"]
5
+ },
6
+ async seedDefaults({ tenantId, organizationId, container }) {
7
+ const credentialsService = container.resolve("integrationCredentialsService");
8
+ const integrationStateService = container.resolve("integrationStateService");
9
+ const scope = { tenantId, organizationId };
10
+ await credentialsService.save("sync_excel", {}, scope);
11
+ await integrationStateService.upsert("sync_excel", { isEnabled: true }, scope);
12
+ }
13
+ };
14
+ var setup_default = setup;
15
+ export {
16
+ setup_default as default,
17
+ setup
18
+ };
19
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/sync_excel/setup.ts"],
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport type { CredentialsService } from '../integrations/lib/credentials-service'\nimport type { IntegrationStateService } from '../integrations/lib/state-service'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n superadmin: ['sync_excel.view', 'sync_excel.run'],\n admin: ['sync_excel.view', 'sync_excel.run'],\n },\n async seedDefaults({ tenantId, organizationId, container }) {\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n const scope = { tenantId, organizationId }\n\n await credentialsService.save('sync_excel', {}, scope)\n await integrationStateService.upsert('sync_excel', { isEnabled: true }, scope)\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AAIO,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,YAAY,CAAC,mBAAmB,gBAAgB;AAAA,IAChD,OAAO,CAAC,mBAAmB,gBAAgB;AAAA,EAC7C;AAAA,EACA,MAAM,aAAa,EAAE,UAAU,gBAAgB,UAAU,GAAG;AAC1D,UAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,UAAM,0BAA0B,UAAU,QAAQ,yBAAyB;AAC3E,UAAM,QAAQ,EAAE,UAAU,eAAe;AAEzC,UAAM,mBAAmB,KAAK,cAAc,CAAC,GAAG,KAAK;AACrD,UAAM,wBAAwB,OAAO,cAAc,EAAE,WAAW,KAAK,GAAG,KAAK;AAAA,EAC/E;AACF;AAEA,IAAO,gBAAQ;",
6
+ "names": []
7
+ }