@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,144 @@
1
+ import { randomUUID } from 'crypto'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { NextResponse } from 'next/server'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { z } from 'zod'
7
+ import { syncExcelUploadResponseSchema, syncExcelEntityTypeSchema } from '../../data/validators'
8
+ import { SyncExcelUpload } from '../../data/entities'
9
+ import { buildSuggestedMapping } from '../../lib/mapping'
10
+ import { parseCsvPreview } from '../../lib/parser'
11
+ import { createSyncExcelUploadAttachment } from '../../lib/upload-storage'
12
+ import { resolveSyncExcelConcreteScope } from '../../lib/scope'
13
+ import { isMultipartRequestWithinUploadLimit, resolveDefaultAttachmentMaxUploadBytes } from '../../../attachments/lib/upload-limits'
14
+
15
+ export const metadata = {
16
+ POST: { requireAuth: true, requireFeatures: ['sync_excel.run'] },
17
+ }
18
+
19
+ const multipartSchema = z.object({
20
+ entityType: syncExcelEntityTypeSchema.default('customers.person'),
21
+ })
22
+
23
+ const errorSchema = z.object({
24
+ error: z.string(),
25
+ })
26
+
27
+ export const openApi = {
28
+ tags: ['SyncExcel'],
29
+ summary: 'Upload CSV file for sync_excel preview',
30
+ methods: {
31
+ POST: {
32
+ summary: 'Upload CSV file',
33
+ requestBody: {
34
+ contentType: 'multipart/form-data',
35
+ },
36
+ responses: [
37
+ { status: 200, description: 'CSV uploaded and preview parsed', schema: syncExcelUploadResponseSchema },
38
+ ],
39
+ errors: [
40
+ { status: 400, description: 'Invalid multipart payload', schema: errorSchema },
41
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
42
+ { status: 413, description: 'CSV upload exceeds the maximum upload size', schema: errorSchema },
43
+ { status: 422, description: 'Unsupported entity type or file type', schema: errorSchema },
44
+ ],
45
+ },
46
+ },
47
+ }
48
+
49
+ export async function POST(request: Request) {
50
+ const auth = await getAuthFromRequest(request)
51
+ if (!auth?.tenantId) {
52
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
53
+ }
54
+
55
+ const container = await createRequestContainer()
56
+ const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request })
57
+ if (!scopeResult.ok) {
58
+ return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status })
59
+ }
60
+ const { scope } = scopeResult
61
+
62
+ const contentType = request.headers.get('content-type') || ''
63
+ if (!contentType.toLowerCase().includes('multipart/form-data')) {
64
+ return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 })
65
+ }
66
+ if (!isMultipartRequestWithinUploadLimit(request.headers.get('content-length'))) {
67
+ return NextResponse.json({ error: 'CSV upload exceeds the maximum upload size.' }, { status: 413 })
68
+ }
69
+
70
+ const formData = await request.formData()
71
+ const parsedPayload = multipartSchema.safeParse({
72
+ entityType: typeof formData.get('entityType') === 'string' ? formData.get('entityType') : undefined,
73
+ })
74
+
75
+ if (!parsedPayload.success) {
76
+ return NextResponse.json({ error: 'Invalid upload payload.' }, { status: 422 })
77
+ }
78
+
79
+ const file = formData.get('file')
80
+ if (!(file instanceof File)) {
81
+ return NextResponse.json({ error: 'Select a CSV file to upload.' }, { status: 400 })
82
+ }
83
+
84
+ const supportedMimeTypes = new Set(['text/csv', 'application/vnd.ms-excel', 'application/csv'])
85
+ const isCsvByMime = supportedMimeTypes.has(file.type)
86
+ const isCsvByName = file.name.toLowerCase().endsWith('.csv')
87
+
88
+ if (!isCsvByMime && !isCsvByName) {
89
+ return NextResponse.json({ error: 'Only CSV uploads are supported in this foundation slice.' }, { status: 422 })
90
+ }
91
+
92
+ const maxUploadBytes = resolveDefaultAttachmentMaxUploadBytes()
93
+ if (file.size > maxUploadBytes) {
94
+ return NextResponse.json({ error: 'CSV upload exceeds the maximum upload size.' }, { status: 413 })
95
+ }
96
+
97
+ const fileBuffer = Buffer.from(await file.arrayBuffer())
98
+ const preview = parseCsvPreview(fileBuffer, { maxRows: 5 })
99
+ const suggestedMapping = buildSuggestedMapping(parsedPayload.data.entityType, preview.headers)
100
+
101
+ const em = container.resolve('em') as EntityManager
102
+ const uploadId = randomUUID()
103
+ const attachment = await createSyncExcelUploadAttachment({
104
+ em,
105
+ uploadId,
106
+ organizationId: scope.organizationId,
107
+ tenantId: scope.tenantId,
108
+ fileName: file.name,
109
+ mimeType: file.type || 'text/csv',
110
+ buffer: fileBuffer,
111
+ })
112
+
113
+ const upload = em.create(SyncExcelUpload, {
114
+ id: uploadId,
115
+ attachmentId: attachment.id,
116
+ filename: file.name,
117
+ mimeType: file.type || 'text/csv',
118
+ fileSize: fileBuffer.length,
119
+ entityType: parsedPayload.data.entityType,
120
+ delimiter: preview.delimiter,
121
+ encoding: preview.encoding,
122
+ headers: preview.headers,
123
+ sampleRows: preview.sampleRows,
124
+ totalRows: preview.totalRows,
125
+ status: 'uploaded',
126
+ organizationId: scope.organizationId,
127
+ tenantId: scope.tenantId,
128
+ })
129
+
130
+ em.persist(upload)
131
+ await em.flush()
132
+
133
+ return NextResponse.json(syncExcelUploadResponseSchema.parse({
134
+ uploadId: upload.id,
135
+ filename: upload.filename,
136
+ mimeType: upload.mimeType,
137
+ fileSize: upload.fileSize,
138
+ entityType: upload.entityType,
139
+ headers: upload.headers,
140
+ sampleRows: upload.sampleRows,
141
+ totalRows: upload.totalRows,
142
+ suggestedMapping,
143
+ }))
144
+ }
@@ -0,0 +1,61 @@
1
+ import { OptionalProps } from '@mikro-orm/core'
2
+ import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'
3
+
4
+ export type SyncExcelUploadStatus = 'uploaded' | 'previewed' | 'importing' | 'completed' | 'failed'
5
+
6
+ @Entity({ tableName: 'sync_excel_uploads' })
7
+ @Index({ properties: ['organizationId', 'tenantId', 'status'] })
8
+ export class SyncExcelUpload {
9
+ [OptionalProps]?: 'delimiter' | 'encoding' | 'status' | 'syncRunId' | 'createdAt' | 'updatedAt'
10
+
11
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
12
+ id!: string
13
+
14
+ @Property({ name: 'attachment_id', type: 'uuid' })
15
+ attachmentId!: string
16
+
17
+ @Property({ name: 'filename', type: 'text' })
18
+ filename!: string
19
+
20
+ @Property({ name: 'mime_type', type: 'text' })
21
+ mimeType!: string
22
+
23
+ @Property({ name: 'file_size', type: 'int' })
24
+ fileSize!: number
25
+
26
+ @Property({ name: 'entity_type', type: 'text' })
27
+ entityType!: string
28
+
29
+ @Property({ name: 'delimiter', type: 'text', nullable: true })
30
+ delimiter?: string | null
31
+
32
+ @Property({ name: 'encoding', type: 'text', nullable: true })
33
+ encoding?: string | null
34
+
35
+ @Property({ name: 'headers', type: 'json' })
36
+ headers!: string[]
37
+
38
+ @Property({ name: 'sample_rows', type: 'json' })
39
+ sampleRows!: Array<Record<string, string | null>>
40
+
41
+ @Property({ name: 'total_rows', type: 'int' })
42
+ totalRows!: number
43
+
44
+ @Property({ name: 'status', type: 'text', default: 'uploaded' })
45
+ status: SyncExcelUploadStatus = 'uploaded'
46
+
47
+ @Property({ name: 'sync_run_id', type: 'uuid', nullable: true })
48
+ syncRunId?: string | null
49
+
50
+ @Property({ name: 'organization_id', type: 'uuid' })
51
+ organizationId!: string
52
+
53
+ @Property({ name: 'tenant_id', type: 'uuid' })
54
+ tenantId!: string
55
+
56
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
57
+ createdAt: Date = new Date()
58
+
59
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
60
+ updatedAt: Date = new Date()
61
+ }
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod'
2
+
3
+ export const syncExcelEntityTypes = ['customers.person'] as const
4
+
5
+ export const syncExcelEntityTypeSchema = z.enum(syncExcelEntityTypes)
6
+
7
+ export const syncExcelPreviewRowSchema = z.record(z.string(), z.string().nullable())
8
+
9
+ export const syncExcelSuggestedFieldSchema = z.object({
10
+ externalField: z.string().min(1),
11
+ localField: z.string().min(1),
12
+ transform: z.string().optional(),
13
+ required: z.boolean().optional(),
14
+ defaultValue: z.unknown().optional(),
15
+ mappingKind: z.enum(['core', 'relation', 'external_id', 'custom_field', 'metadata', 'ignore']).optional(),
16
+ dedupeRole: z.enum(['primary', 'secondary']).optional(),
17
+ })
18
+
19
+ export const syncExcelSuggestedMappingSchema = z.object({
20
+ entityType: syncExcelEntityTypeSchema,
21
+ matchStrategy: z.enum(['externalId', 'email', 'custom']),
22
+ matchField: z.string().optional(),
23
+ fields: z.array(syncExcelSuggestedFieldSchema),
24
+ unmappedColumns: z.array(z.string()),
25
+ })
26
+
27
+ export const syncExcelUploadResponseSchema = z.object({
28
+ uploadId: z.string().uuid(),
29
+ filename: z.string().min(1),
30
+ mimeType: z.string().min(1),
31
+ fileSize: z.number().int().nonnegative(),
32
+ entityType: syncExcelEntityTypeSchema,
33
+ headers: z.array(z.string()),
34
+ sampleRows: z.array(syncExcelPreviewRowSchema),
35
+ totalRows: z.number().int().nonnegative(),
36
+ suggestedMapping: syncExcelSuggestedMappingSchema,
37
+ })
38
+
39
+ export const syncExcelPreviewQuerySchema = z.object({
40
+ uploadId: z.string().uuid(),
41
+ entityType: syncExcelEntityTypeSchema.optional(),
42
+ })
43
+
44
+ export const syncExcelImportRequestSchema = z.object({
45
+ uploadId: z.string().uuid(),
46
+ entityType: syncExcelEntityTypeSchema,
47
+ mapping: syncExcelSuggestedMappingSchema,
48
+ batchSize: z.number().int().min(1).max(1000).default(100).optional(),
49
+ })
50
+
51
+ export const syncExcelImportResponseSchema = z.object({
52
+ runId: z.string().uuid(),
53
+ progressJobId: z.string().uuid().nullable(),
54
+ status: z.enum(['pending', 'running', 'completed', 'failed', 'cancelled']),
55
+ })
56
+
57
+ export type SyncExcelPreviewQuery = z.infer<typeof syncExcelPreviewQuerySchema>
58
+ export type SyncExcelImportRequest = z.infer<typeof syncExcelImportRequestSchema>
@@ -0,0 +1,9 @@
1
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
2
+ import { registerDataSyncAdapter } from '../data_sync/lib/adapter-registry'
3
+ import { syncExcelCustomersAdapter } from './lib/adapters/customers'
4
+
5
+ export function register(_container: AppContainer) {
6
+ registerDataSyncAdapter(syncExcelCustomersAdapter)
7
+ }
8
+
9
+ export default register
@@ -0,0 +1,107 @@
1
+ {
2
+ "sync_excel.integration.description": "Upload CSV files, preview mappings, and run file-based imports through Data Sync.",
3
+ "sync_excel.integration.title": "Excel / CSV Import",
4
+ "sync_excel.mapping.targets.addressCompanyName": "Address company",
5
+ "sync_excel.mapping.targets.addressLine1": "Address line 1",
6
+ "sync_excel.mapping.targets.addressLine2": "Address line 2",
7
+ "sync_excel.mapping.targets.addressName": "Address label",
8
+ "sync_excel.mapping.targets.addressPurpose": "Address purpose",
9
+ "sync_excel.mapping.targets.buildingNumber": "Building number",
10
+ "sync_excel.mapping.targets.city": "City",
11
+ "sync_excel.mapping.targets.country": "Country",
12
+ "sync_excel.mapping.targets.description": "Description",
13
+ "sync_excel.mapping.targets.displayName": "Display name",
14
+ "sync_excel.mapping.targets.externalId": "External ID",
15
+ "sync_excel.mapping.targets.firstName": "First name",
16
+ "sync_excel.mapping.targets.flatNumber": "Flat number",
17
+ "sync_excel.mapping.targets.ignore": "Leave unmapped",
18
+ "sync_excel.mapping.targets.jobTitle": "Job title",
19
+ "sync_excel.mapping.targets.lastName": "Last name",
20
+ "sync_excel.mapping.targets.latitude": "Latitude",
21
+ "sync_excel.mapping.targets.longitude": "Longitude",
22
+ "sync_excel.mapping.targets.postalCode": "Postal code",
23
+ "sync_excel.mapping.targets.primaryEmail": "Primary email",
24
+ "sync_excel.mapping.targets.primaryPhone": "Primary phone",
25
+ "sync_excel.mapping.targets.region": "Region / State",
26
+ "sync_excel.mapping.targets.source": "Source",
27
+ "sync_excel.mapping.targets.status": "Status",
28
+ "sync_excel.preview.errors.notFound": "Upload preview not found.",
29
+ "sync_excel.tabs.import": "Import CSV",
30
+ "sync_excel.upload.errors.fileRequired": "Select a CSV file to upload.",
31
+ "sync_excel.upload.errors.invalidPayload": "Invalid upload payload.",
32
+ "sync_excel.upload.errors.invalidType": "Only CSV uploads are supported in this foundation slice.",
33
+ "sync_excel.widget.actions.cancel": "Cancel run",
34
+ "sync_excel.widget.actions.cancelPending": "Cancelling...",
35
+ "sync_excel.widget.actions.refresh": "Refresh preview",
36
+ "sync_excel.widget.actions.refreshPending": "Refreshing...",
37
+ "sync_excel.widget.actions.refreshRun": "Refresh run status",
38
+ "sync_excel.widget.actions.reset": "Reset to suggestion",
39
+ "sync_excel.widget.actions.start": "Start import",
40
+ "sync_excel.widget.actions.startPending": "Starting...",
41
+ "sync_excel.widget.actions.upload": "Upload and preview",
42
+ "sync_excel.widget.actions.uploadPending": "Uploading...",
43
+ "sync_excel.widget.disabled.message": "Enable this integration to upload CSV files and start imports.",
44
+ "sync_excel.widget.disabled.title": "Integration disabled",
45
+ "sync_excel.widget.mapping.sampleValue": "Sample value",
46
+ "sync_excel.widget.mapping.sourceColumn": "Source column",
47
+ "sync_excel.widget.mapping.targetField": "Target field",
48
+ "sync_excel.widget.matchStrategy.custom": "Custom matching",
49
+ "sync_excel.widget.matchStrategy.customDescription": "Advanced option. Use only if you know how existing records should be identified in this import.",
50
+ "sync_excel.widget.matchStrategy.customWarningMessage": "Custom matching may create duplicates if the mapped fields do not uniquely identify a person.",
51
+ "sync_excel.widget.matchStrategy.customWarningTitle": "Use custom matching with care",
52
+ "sync_excel.widget.matchStrategy.email": "Email address",
53
+ "sync_excel.widget.matchStrategy.emailDescription": "Matches people by email. Works best when each person has one reliable, unique email address.",
54
+ "sync_excel.widget.matchStrategy.externalId": "Source record ID (Recommended)",
55
+ "sync_excel.widget.matchStrategy.externalIdDescription": "Best for repeated imports from the same source. Future imports update the same people instead of creating duplicates.",
56
+ "sync_excel.widget.matchStrategy.help": "This decides whether imported rows update existing people or create new ones.",
57
+ "sync_excel.widget.matchStrategy.label": "How to match existing people",
58
+ "sync_excel.widget.messages.cancelError": "Failed to cancel import run.",
59
+ "sync_excel.widget.messages.cancelSuccess": "Import run cancelled.",
60
+ "sync_excel.widget.messages.fileRequired": "Select a CSV file first.",
61
+ "sync_excel.widget.messages.importError": "Failed to start import run.",
62
+ "sync_excel.widget.messages.importStarted": "Import run started.",
63
+ "sync_excel.widget.messages.mappingReset": "Suggested mapping restored.",
64
+ "sync_excel.widget.messages.previewError": "Failed to refresh preview.",
65
+ "sync_excel.widget.messages.previewRefreshed": "Preview refreshed.",
66
+ "sync_excel.widget.messages.restoringSession": "Letzte CSV-Sitzung wird wiederhergestellt...",
67
+ "sync_excel.widget.messages.uploadError": "Failed to upload CSV file.",
68
+ "sync_excel.widget.messages.uploadSuccess": "CSV preview is ready.",
69
+ "sync_excel.widget.metrics.entity": "Target entity",
70
+ "sync_excel.widget.metrics.identity": "Identity strategy",
71
+ "sync_excel.widget.metrics.mapped": "Mapped columns",
72
+ "sync_excel.widget.metrics.unmapped": "Unmapped columns",
73
+ "sync_excel.widget.preview.description": "Every source column stays visible here. Leave a target empty to keep the column unmapped for now.",
74
+ "sync_excel.widget.preview.rows": "{count} rows",
75
+ "sync_excel.widget.preview.title": "Preview and mapping",
76
+ "sync_excel.widget.run.description": "The import runs in the background through Data Sync and updates this view as progress changes.",
77
+ "sync_excel.widget.run.errorTitle": "Run error",
78
+ "sync_excel.widget.run.metrics.created": "Created",
79
+ "sync_excel.widget.run.metrics.failed": "Failed",
80
+ "sync_excel.widget.run.metrics.processed": "Processed",
81
+ "sync_excel.widget.run.metrics.skipped": "Skipped",
82
+ "sync_excel.widget.run.metrics.updated": "Updated",
83
+ "sync_excel.widget.run.progressLabel": "Progress",
84
+ "sync_excel.widget.run.status.cancelled": "Cancelled",
85
+ "sync_excel.widget.run.status.completed": "Completed",
86
+ "sync_excel.widget.run.status.failed": "Failed",
87
+ "sync_excel.widget.run.status.idle": "Idle",
88
+ "sync_excel.widget.run.status.pending": "Pending",
89
+ "sync_excel.widget.run.status.running": "Running",
90
+ "sync_excel.widget.run.title": "Import run status",
91
+ "sync_excel.widget.upload.description": "Upload a CSV file, review the suggested people mapping, and start a background import run.",
92
+ "sync_excel.widget.upload.fileLabel": "CSV file",
93
+ "sync_excel.widget.upload.selectedFile": "Selected file",
94
+ "sync_excel.widget.upload.title": "Upload CSV",
95
+ "sync_excel.widget.validation.duplicateRiskMessage": "Diese Zuordnung verwendet nicht die empfohlene Strategie Externe ID oder E-Mail. Ein erneuter Import desselben Datensatzes kann doppelte Personen erzeugen.",
96
+ "sync_excel.widget.validation.duplicateRiskTitle": "Risiko von Duplikaten",
97
+ "sync_excel.widget.validation.duplicateTargetsMessage": "Each target field can only be mapped once in this foundation slice: {targets}",
98
+ "sync_excel.widget.validation.duplicateTargetsTitle": "Duplicate target fields",
99
+ "sync_excel.widget.validation.identityMessage": "Map either External ID or Primary email so reimports can match existing people.",
100
+ "sync_excel.widget.validation.identityTitle": "Map an identity field",
101
+ "sync_excel.widget.validation.nameMessage": "Map Last name or Display name so the importer can build person records.",
102
+ "sync_excel.widget.validation.nameTitle": "Map a name field",
103
+ "sync_excel.widget.validation.readyMessage": "The current mapping is valid for the first customers.person slice.",
104
+ "sync_excel.widget.validation.readyTitle": "Ready to import",
105
+ "sync_excel.widget.validation.reimportMessage": "Ein weiterer Import aus derselben hochgeladenen CSV kann Duplikate erzeugen, wenn die Abgleichstrategie bestehende Personen nicht zuverlässig erkennt.",
106
+ "sync_excel.widget.validation.reimportTitle": "Für diesen Upload gibt es bereits einen Lauf"
107
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "sync_excel.integration.description": "Upload CSV files, preview mappings, and run file-based imports through Data Sync.",
3
+ "sync_excel.integration.title": "Excel / CSV Import",
4
+ "sync_excel.mapping.targets.addressCompanyName": "Address company",
5
+ "sync_excel.mapping.targets.addressLine1": "Address line 1",
6
+ "sync_excel.mapping.targets.addressLine2": "Address line 2",
7
+ "sync_excel.mapping.targets.addressName": "Address label",
8
+ "sync_excel.mapping.targets.addressPurpose": "Address purpose",
9
+ "sync_excel.mapping.targets.buildingNumber": "Building number",
10
+ "sync_excel.mapping.targets.city": "City",
11
+ "sync_excel.mapping.targets.country": "Country",
12
+ "sync_excel.mapping.targets.description": "Description",
13
+ "sync_excel.mapping.targets.displayName": "Display name",
14
+ "sync_excel.mapping.targets.externalId": "External ID",
15
+ "sync_excel.mapping.targets.firstName": "First name",
16
+ "sync_excel.mapping.targets.flatNumber": "Flat number",
17
+ "sync_excel.mapping.targets.ignore": "Leave unmapped",
18
+ "sync_excel.mapping.targets.jobTitle": "Job title",
19
+ "sync_excel.mapping.targets.lastName": "Last name",
20
+ "sync_excel.mapping.targets.latitude": "Latitude",
21
+ "sync_excel.mapping.targets.longitude": "Longitude",
22
+ "sync_excel.mapping.targets.postalCode": "Postal code",
23
+ "sync_excel.mapping.targets.primaryEmail": "Primary email",
24
+ "sync_excel.mapping.targets.primaryPhone": "Primary phone",
25
+ "sync_excel.mapping.targets.region": "Region / State",
26
+ "sync_excel.mapping.targets.source": "Source",
27
+ "sync_excel.mapping.targets.status": "Status",
28
+ "sync_excel.preview.errors.notFound": "Upload preview not found.",
29
+ "sync_excel.tabs.import": "Import CSV",
30
+ "sync_excel.upload.errors.fileRequired": "Select a CSV file to upload.",
31
+ "sync_excel.upload.errors.invalidPayload": "Invalid upload payload.",
32
+ "sync_excel.upload.errors.invalidType": "Only CSV uploads are supported in this foundation slice.",
33
+ "sync_excel.widget.actions.cancel": "Cancel run",
34
+ "sync_excel.widget.actions.cancelPending": "Cancelling...",
35
+ "sync_excel.widget.actions.refresh": "Refresh preview",
36
+ "sync_excel.widget.actions.refreshPending": "Refreshing...",
37
+ "sync_excel.widget.actions.refreshRun": "Refresh run status",
38
+ "sync_excel.widget.actions.reset": "Reset to suggestion",
39
+ "sync_excel.widget.actions.start": "Start import",
40
+ "sync_excel.widget.actions.startPending": "Starting...",
41
+ "sync_excel.widget.actions.upload": "Upload and preview",
42
+ "sync_excel.widget.actions.uploadPending": "Uploading...",
43
+ "sync_excel.widget.disabled.message": "Enable this integration to upload CSV files and start imports.",
44
+ "sync_excel.widget.disabled.title": "Integration disabled",
45
+ "sync_excel.widget.mapping.sampleValue": "Sample value",
46
+ "sync_excel.widget.mapping.sourceColumn": "Source column",
47
+ "sync_excel.widget.mapping.targetField": "Target field",
48
+ "sync_excel.widget.matchStrategy.custom": "Custom matching",
49
+ "sync_excel.widget.matchStrategy.customDescription": "Advanced option. Use only if you know how existing records should be identified in this import.",
50
+ "sync_excel.widget.matchStrategy.customWarningMessage": "Custom matching may create duplicates if the mapped fields do not uniquely identify a person.",
51
+ "sync_excel.widget.matchStrategy.customWarningTitle": "Use custom matching with care",
52
+ "sync_excel.widget.matchStrategy.email": "Email address",
53
+ "sync_excel.widget.matchStrategy.emailDescription": "Matches people by email. Works best when each person has one reliable, unique email address.",
54
+ "sync_excel.widget.matchStrategy.externalId": "Source record ID (Recommended)",
55
+ "sync_excel.widget.matchStrategy.externalIdDescription": "Best for repeated imports from the same source. Future imports update the same people instead of creating duplicates.",
56
+ "sync_excel.widget.matchStrategy.help": "This decides whether imported rows update existing people or create new ones.",
57
+ "sync_excel.widget.matchStrategy.label": "How to match existing people",
58
+ "sync_excel.widget.messages.cancelError": "Failed to cancel import run.",
59
+ "sync_excel.widget.messages.cancelSuccess": "Import run cancelled.",
60
+ "sync_excel.widget.messages.fileRequired": "Select a CSV file first.",
61
+ "sync_excel.widget.messages.importError": "Failed to start import run.",
62
+ "sync_excel.widget.messages.importStarted": "Import run started.",
63
+ "sync_excel.widget.messages.mappingReset": "Suggested mapping restored.",
64
+ "sync_excel.widget.messages.previewError": "Failed to refresh preview.",
65
+ "sync_excel.widget.messages.previewRefreshed": "Preview refreshed.",
66
+ "sync_excel.widget.messages.restoringSession": "Restoring the last CSV session...",
67
+ "sync_excel.widget.messages.uploadError": "Failed to upload CSV file.",
68
+ "sync_excel.widget.messages.uploadSuccess": "CSV preview is ready.",
69
+ "sync_excel.widget.metrics.entity": "Target entity",
70
+ "sync_excel.widget.metrics.identity": "Identity strategy",
71
+ "sync_excel.widget.metrics.mapped": "Mapped columns",
72
+ "sync_excel.widget.metrics.unmapped": "Unmapped columns",
73
+ "sync_excel.widget.preview.description": "Every source column stays visible here. Leave a target empty to keep the column unmapped for now.",
74
+ "sync_excel.widget.preview.rows": "{count} rows",
75
+ "sync_excel.widget.preview.title": "Preview and mapping",
76
+ "sync_excel.widget.run.description": "The import runs in the background through Data Sync and updates this view as progress changes.",
77
+ "sync_excel.widget.run.errorTitle": "Run error",
78
+ "sync_excel.widget.run.metrics.created": "Created",
79
+ "sync_excel.widget.run.metrics.failed": "Failed",
80
+ "sync_excel.widget.run.metrics.processed": "Processed",
81
+ "sync_excel.widget.run.metrics.skipped": "Skipped",
82
+ "sync_excel.widget.run.metrics.updated": "Updated",
83
+ "sync_excel.widget.run.progressLabel": "Progress",
84
+ "sync_excel.widget.run.status.cancelled": "Cancelled",
85
+ "sync_excel.widget.run.status.completed": "Completed",
86
+ "sync_excel.widget.run.status.failed": "Failed",
87
+ "sync_excel.widget.run.status.idle": "Idle",
88
+ "sync_excel.widget.run.status.pending": "Pending",
89
+ "sync_excel.widget.run.status.running": "Running",
90
+ "sync_excel.widget.run.title": "Import run status",
91
+ "sync_excel.widget.upload.description": "Upload a CSV file, review the suggested people mapping, and start a background import run.",
92
+ "sync_excel.widget.upload.fileLabel": "CSV file",
93
+ "sync_excel.widget.upload.selectedFile": "Selected file",
94
+ "sync_excel.widget.upload.title": "Upload CSV",
95
+ "sync_excel.widget.validation.duplicateRiskMessage": "This mapping does not use the recommended External ID or Email matching strategy. Re-importing the same dataset may create duplicate people.",
96
+ "sync_excel.widget.validation.duplicateRiskTitle": "Duplicate risk",
97
+ "sync_excel.widget.validation.duplicateTargetsMessage": "Each target field can only be mapped once in this foundation slice: {targets}",
98
+ "sync_excel.widget.validation.duplicateTargetsTitle": "Duplicate target fields",
99
+ "sync_excel.widget.validation.identityMessage": "Map either External ID or Primary email so reimports can match existing people.",
100
+ "sync_excel.widget.validation.identityTitle": "Map an identity field",
101
+ "sync_excel.widget.validation.nameMessage": "Map Last name or Display name so the importer can build person records.",
102
+ "sync_excel.widget.validation.nameTitle": "Map a name field",
103
+ "sync_excel.widget.validation.readyMessage": "The current mapping is valid for the first customers.person slice.",
104
+ "sync_excel.widget.validation.readyTitle": "Ready to import",
105
+ "sync_excel.widget.validation.reimportMessage": "Starting another import from the same uploaded CSV may create duplicates unless your matching strategy reliably identifies existing people.",
106
+ "sync_excel.widget.validation.reimportTitle": "This upload already has a run"
107
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "sync_excel.integration.description": "Upload CSV files, preview mappings, and run file-based imports through Data Sync.",
3
+ "sync_excel.integration.title": "Excel / CSV Import",
4
+ "sync_excel.mapping.targets.addressCompanyName": "Address company",
5
+ "sync_excel.mapping.targets.addressLine1": "Address line 1",
6
+ "sync_excel.mapping.targets.addressLine2": "Address line 2",
7
+ "sync_excel.mapping.targets.addressName": "Address label",
8
+ "sync_excel.mapping.targets.addressPurpose": "Address purpose",
9
+ "sync_excel.mapping.targets.buildingNumber": "Building number",
10
+ "sync_excel.mapping.targets.city": "City",
11
+ "sync_excel.mapping.targets.country": "Country",
12
+ "sync_excel.mapping.targets.description": "Description",
13
+ "sync_excel.mapping.targets.displayName": "Display name",
14
+ "sync_excel.mapping.targets.externalId": "External ID",
15
+ "sync_excel.mapping.targets.firstName": "First name",
16
+ "sync_excel.mapping.targets.flatNumber": "Flat number",
17
+ "sync_excel.mapping.targets.ignore": "Leave unmapped",
18
+ "sync_excel.mapping.targets.jobTitle": "Job title",
19
+ "sync_excel.mapping.targets.lastName": "Last name",
20
+ "sync_excel.mapping.targets.latitude": "Latitude",
21
+ "sync_excel.mapping.targets.longitude": "Longitude",
22
+ "sync_excel.mapping.targets.postalCode": "Postal code",
23
+ "sync_excel.mapping.targets.primaryEmail": "Primary email",
24
+ "sync_excel.mapping.targets.primaryPhone": "Primary phone",
25
+ "sync_excel.mapping.targets.region": "Region / State",
26
+ "sync_excel.mapping.targets.source": "Source",
27
+ "sync_excel.mapping.targets.status": "Status",
28
+ "sync_excel.preview.errors.notFound": "Upload preview not found.",
29
+ "sync_excel.tabs.import": "Import CSV",
30
+ "sync_excel.upload.errors.fileRequired": "Select a CSV file to upload.",
31
+ "sync_excel.upload.errors.invalidPayload": "Invalid upload payload.",
32
+ "sync_excel.upload.errors.invalidType": "Only CSV uploads are supported in this foundation slice.",
33
+ "sync_excel.widget.actions.cancel": "Cancel run",
34
+ "sync_excel.widget.actions.cancelPending": "Cancelling...",
35
+ "sync_excel.widget.actions.refresh": "Refresh preview",
36
+ "sync_excel.widget.actions.refreshPending": "Refreshing...",
37
+ "sync_excel.widget.actions.refreshRun": "Refresh run status",
38
+ "sync_excel.widget.actions.reset": "Reset to suggestion",
39
+ "sync_excel.widget.actions.start": "Start import",
40
+ "sync_excel.widget.actions.startPending": "Starting...",
41
+ "sync_excel.widget.actions.upload": "Upload and preview",
42
+ "sync_excel.widget.actions.uploadPending": "Uploading...",
43
+ "sync_excel.widget.disabled.message": "Enable this integration to upload CSV files and start imports.",
44
+ "sync_excel.widget.disabled.title": "Integration disabled",
45
+ "sync_excel.widget.mapping.sampleValue": "Sample value",
46
+ "sync_excel.widget.mapping.sourceColumn": "Source column",
47
+ "sync_excel.widget.mapping.targetField": "Target field",
48
+ "sync_excel.widget.matchStrategy.custom": "Custom matching",
49
+ "sync_excel.widget.matchStrategy.customDescription": "Advanced option. Use only if you know how existing records should be identified in this import.",
50
+ "sync_excel.widget.matchStrategy.customWarningMessage": "Custom matching may create duplicates if the mapped fields do not uniquely identify a person.",
51
+ "sync_excel.widget.matchStrategy.customWarningTitle": "Use custom matching with care",
52
+ "sync_excel.widget.matchStrategy.email": "Email address",
53
+ "sync_excel.widget.matchStrategy.emailDescription": "Matches people by email. Works best when each person has one reliable, unique email address.",
54
+ "sync_excel.widget.matchStrategy.externalId": "Source record ID (Recommended)",
55
+ "sync_excel.widget.matchStrategy.externalIdDescription": "Best for repeated imports from the same source. Future imports update the same people instead of creating duplicates.",
56
+ "sync_excel.widget.matchStrategy.help": "This decides whether imported rows update existing people or create new ones.",
57
+ "sync_excel.widget.matchStrategy.label": "How to match existing people",
58
+ "sync_excel.widget.messages.cancelError": "Failed to cancel import run.",
59
+ "sync_excel.widget.messages.cancelSuccess": "Import run cancelled.",
60
+ "sync_excel.widget.messages.fileRequired": "Select a CSV file first.",
61
+ "sync_excel.widget.messages.importError": "Failed to start import run.",
62
+ "sync_excel.widget.messages.importStarted": "Import run started.",
63
+ "sync_excel.widget.messages.mappingReset": "Suggested mapping restored.",
64
+ "sync_excel.widget.messages.previewError": "Failed to refresh preview.",
65
+ "sync_excel.widget.messages.previewRefreshed": "Preview refreshed.",
66
+ "sync_excel.widget.messages.restoringSession": "Restaurando la última sesión de CSV...",
67
+ "sync_excel.widget.messages.uploadError": "Failed to upload CSV file.",
68
+ "sync_excel.widget.messages.uploadSuccess": "CSV preview is ready.",
69
+ "sync_excel.widget.metrics.entity": "Target entity",
70
+ "sync_excel.widget.metrics.identity": "Identity strategy",
71
+ "sync_excel.widget.metrics.mapped": "Mapped columns",
72
+ "sync_excel.widget.metrics.unmapped": "Unmapped columns",
73
+ "sync_excel.widget.preview.description": "Every source column stays visible here. Leave a target empty to keep the column unmapped for now.",
74
+ "sync_excel.widget.preview.rows": "{count} rows",
75
+ "sync_excel.widget.preview.title": "Preview and mapping",
76
+ "sync_excel.widget.run.description": "The import runs in the background through Data Sync and updates this view as progress changes.",
77
+ "sync_excel.widget.run.errorTitle": "Run error",
78
+ "sync_excel.widget.run.metrics.created": "Created",
79
+ "sync_excel.widget.run.metrics.failed": "Failed",
80
+ "sync_excel.widget.run.metrics.processed": "Processed",
81
+ "sync_excel.widget.run.metrics.skipped": "Skipped",
82
+ "sync_excel.widget.run.metrics.updated": "Updated",
83
+ "sync_excel.widget.run.progressLabel": "Progress",
84
+ "sync_excel.widget.run.status.cancelled": "Cancelled",
85
+ "sync_excel.widget.run.status.completed": "Completed",
86
+ "sync_excel.widget.run.status.failed": "Failed",
87
+ "sync_excel.widget.run.status.idle": "Idle",
88
+ "sync_excel.widget.run.status.pending": "Pending",
89
+ "sync_excel.widget.run.status.running": "Running",
90
+ "sync_excel.widget.run.title": "Import run status",
91
+ "sync_excel.widget.upload.description": "Upload a CSV file, review the suggested people mapping, and start a background import run.",
92
+ "sync_excel.widget.upload.fileLabel": "CSV file",
93
+ "sync_excel.widget.upload.selectedFile": "Selected file",
94
+ "sync_excel.widget.upload.title": "Upload CSV",
95
+ "sync_excel.widget.validation.duplicateRiskMessage": "Este mapeo no usa la estrategia recomendada de ID externo o correo electrónico. Reimportar el mismo conjunto de datos puede crear personas duplicadas.",
96
+ "sync_excel.widget.validation.duplicateRiskTitle": "Riesgo de duplicados",
97
+ "sync_excel.widget.validation.duplicateTargetsMessage": "Each target field can only be mapped once in this foundation slice: {targets}",
98
+ "sync_excel.widget.validation.duplicateTargetsTitle": "Duplicate target fields",
99
+ "sync_excel.widget.validation.identityMessage": "Map either External ID or Primary email so reimports can match existing people.",
100
+ "sync_excel.widget.validation.identityTitle": "Map an identity field",
101
+ "sync_excel.widget.validation.nameMessage": "Map Last name or Display name so the importer can build person records.",
102
+ "sync_excel.widget.validation.nameTitle": "Map a name field",
103
+ "sync_excel.widget.validation.readyMessage": "The current mapping is valid for the first customers.person slice.",
104
+ "sync_excel.widget.validation.readyTitle": "Ready to import",
105
+ "sync_excel.widget.validation.reimportMessage": "Iniciar otra importación desde el mismo CSV cargado puede crear duplicados si la estrategia de coincidencia no identifica de forma fiable a las personas existentes.",
106
+ "sync_excel.widget.validation.reimportTitle": "Esta carga ya tiene una ejecución"
107
+ }