@open-mercato/core 0.6.3-develop.3881.1.0b590ac4eb → 0.6.3-develop.3901.1.ddad60693a

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 (37) hide show
  1. package/dist/modules/auth/backend/auth/profile/page.js +1 -1
  2. package/dist/modules/auth/backend/auth/profile/page.js.map +2 -2
  3. package/dist/modules/auth/backend/profile/change-password/page.js +1 -1
  4. package/dist/modules/auth/backend/profile/change-password/page.js.map +2 -2
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js +1 -1
  6. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  7. package/dist/modules/auth/backend/users/create/page.js +6 -1
  8. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  9. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +8 -1
  10. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  11. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +3 -2
  12. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  13. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +3 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  15. package/dist/modules/configs/cli.js +27 -14
  16. package/dist/modules/configs/cli.js.map +2 -2
  17. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
  18. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  19. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +1 -1
  20. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  21. package/dist/modules/sales/components/channels/ChannelOfferForm.js +1 -1
  22. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  23. package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js +33 -5
  24. package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js.map +2 -2
  25. package/package.json +7 -7
  26. package/src/modules/auth/backend/auth/profile/page.tsx +1 -1
  27. package/src/modules/auth/backend/profile/change-password/page.tsx +1 -1
  28. package/src/modules/auth/backend/users/[id]/edit/page.tsx +1 -1
  29. package/src/modules/auth/backend/users/create/page.tsx +6 -1
  30. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +8 -1
  31. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +3 -2
  32. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +3 -2
  33. package/src/modules/configs/cli.ts +34 -13
  34. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
  35. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +1 -1
  36. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +1 -1
  37. package/src/modules/sync_excel/widgets/injection/upload-config/target-options.ts +40 -5
@@ -172,6 +172,38 @@ const ADDRESS_TARGET_OPTIONS = [
172
172
  function normalizeMatchToken(value) {
173
173
  return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
174
174
  }
175
+ const TRAILING_IMPORT_QUALIFIERS = /* @__PURE__ */ new Set(["external", "imported", "crm"]);
176
+ function addNormalizedMatchToken(tokens, value) {
177
+ const normalized = normalizeMatchToken(value);
178
+ if (normalized.length > 0) {
179
+ tokens.add(normalized);
180
+ }
181
+ }
182
+ function stripParentheticalText(value) {
183
+ return value.replace(/\s*\([^)]*\)\s*/g, " ");
184
+ }
185
+ function stripTrailingImportQualifier(value) {
186
+ const parts = normalizeMatchToken(value).split(" ").filter((part) => part.length > 0);
187
+ while (parts.length > 1 && TRAILING_IMPORT_QUALIFIERS.has(parts[parts.length - 1])) {
188
+ parts.pop();
189
+ }
190
+ return parts.join(" ");
191
+ }
192
+ function buildCustomFieldMatchTokens(def, fallback) {
193
+ const tokens = /* @__PURE__ */ new Set();
194
+ const candidates = [
195
+ def.key,
196
+ fallback,
197
+ stripParentheticalText(fallback),
198
+ stripTrailingImportQualifier(def.key),
199
+ stripTrailingImportQualifier(fallback),
200
+ stripTrailingImportQualifier(stripParentheticalText(fallback))
201
+ ];
202
+ for (const candidate of candidates) {
203
+ addNormalizedMatchToken(tokens, candidate);
204
+ }
205
+ return Array.from(tokens);
206
+ }
175
207
  function titleizeKey(key) {
176
208
  return key.split("_").map((part) => part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part).join(" ");
177
209
  }
@@ -207,15 +239,11 @@ function scoreCustomFieldDefinition(def, entityIndex) {
207
239
  }
208
240
  function buildCustomFieldOption(def) {
209
241
  const fallback = typeof def.label === "string" && def.label.trim().length > 0 ? def.label.trim() : titleizeKey(def.key);
210
- const normalizedTokens = Array.from(new Set([
211
- normalizeMatchToken(def.key),
212
- normalizeMatchToken(fallback)
213
- ].filter((value) => value.length > 0)));
214
242
  return {
215
243
  value: `cf:${def.key}`,
216
244
  fallback,
217
245
  mappingKind: "custom_field",
218
- matchTokens: normalizedTokens
246
+ matchTokens: buildCustomFieldMatchTokens(def, fallback)
219
247
  };
220
248
  }
221
249
  function selectPreferredCustomFieldDefs(customFieldDefs) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/sync_excel/widgets/injection/upload-config/target-options.ts"],
4
- "sourcesContent": ["import type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport type { FieldMapping, FieldMappingDedupeRole, FieldMappingKind } from '../../../../data_sync/lib/adapter'\n\nexport const SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS = [\n 'customers:customer_entity',\n 'customers:customer_person_profile',\n] as const\n\nexport type SuggestedMapping = {\n entityType: 'customers.person'\n matchStrategy: 'externalId' | 'email' | 'custom'\n matchField?: string\n fields: FieldMapping[]\n unmappedColumns: string[]\n}\n\nexport type MappingTargetOption = {\n value: string\n labelKey?: string\n fallback: string\n mappingKind: FieldMappingKind\n dedupeRole?: FieldMappingDedupeRole\n matchTokens: string[]\n}\n\nconst CORE_TARGET_OPTIONS: MappingTargetOption[] = [\n {\n value: 'person.externalId',\n labelKey: 'sync_excel.mapping.targets.externalId',\n fallback: 'External ID',\n mappingKind: 'external_id',\n dedupeRole: 'primary',\n matchTokens: ['external id', 'record id', 'lead id'],\n },\n {\n value: 'person.firstName',\n labelKey: 'sync_excel.mapping.targets.firstName',\n fallback: 'First name',\n mappingKind: 'core',\n matchTokens: ['first name', 'firstname', 'given name'],\n },\n {\n value: 'person.lastName',\n labelKey: 'sync_excel.mapping.targets.lastName',\n fallback: 'Last name',\n mappingKind: 'core',\n matchTokens: ['last name', 'lastname', 'surname', 'family name'],\n },\n {\n value: 'person.displayName',\n labelKey: 'sync_excel.mapping.targets.displayName',\n fallback: 'Display name',\n mappingKind: 'core',\n matchTokens: ['display name', 'lead name', 'full name', 'name'],\n },\n {\n value: 'person.primaryEmail',\n labelKey: 'sync_excel.mapping.targets.primaryEmail',\n fallback: 'Primary email',\n mappingKind: 'core',\n dedupeRole: 'secondary',\n matchTokens: ['email', 'primary email', 'email address'],\n },\n {\n value: 'person.primaryPhone',\n labelKey: 'sync_excel.mapping.targets.primaryPhone',\n fallback: 'Primary phone',\n mappingKind: 'core',\n matchTokens: ['phone', 'primary phone', 'mobile', 'mobile phone', 'telephone'],\n },\n {\n value: 'person.jobTitle',\n labelKey: 'sync_excel.mapping.targets.jobTitle',\n fallback: 'Job title',\n mappingKind: 'core',\n matchTokens: ['job title', 'title', 'position'],\n },\n {\n value: 'person.status',\n labelKey: 'sync_excel.mapping.targets.status',\n fallback: 'Status',\n mappingKind: 'core',\n matchTokens: ['status', 'lead status'],\n },\n {\n value: 'person.source',\n labelKey: 'sync_excel.mapping.targets.source',\n fallback: 'Source',\n mappingKind: 'core',\n matchTokens: ['source', 'lead source'],\n },\n {\n value: 'person.description',\n labelKey: 'sync_excel.mapping.targets.description',\n fallback: 'Description',\n mappingKind: 'core',\n matchTokens: ['description', 'notes', 'comment'],\n },\n]\n\nconst ADDRESS_TARGET_OPTIONS: MappingTargetOption[] = [\n {\n value: 'address.name',\n labelKey: 'sync_excel.mapping.targets.addressName',\n fallback: 'Address label',\n mappingKind: 'core',\n matchTokens: ['address label', 'address name', 'label'],\n },\n {\n value: 'address.purpose',\n labelKey: 'sync_excel.mapping.targets.addressPurpose',\n fallback: 'Address purpose',\n mappingKind: 'core',\n matchTokens: ['address purpose', 'address type'],\n },\n {\n value: 'address.companyName',\n labelKey: 'sync_excel.mapping.targets.addressCompanyName',\n fallback: 'Address company',\n mappingKind: 'core',\n matchTokens: ['address company', 'address company name'],\n },\n {\n value: 'address.addressLine1',\n labelKey: 'sync_excel.mapping.targets.addressLine1',\n fallback: 'Address line 1',\n mappingKind: 'core',\n matchTokens: ['address line 1', 'street address', 'street', 'street 1'],\n },\n {\n value: 'address.addressLine2',\n labelKey: 'sync_excel.mapping.targets.addressLine2',\n fallback: 'Address line 2',\n mappingKind: 'core',\n matchTokens: ['address line 2', 'street 2'],\n },\n {\n value: 'address.buildingNumber',\n labelKey: 'sync_excel.mapping.targets.buildingNumber',\n fallback: 'Building number',\n mappingKind: 'core',\n matchTokens: ['building number', 'building no', 'house number'],\n },\n {\n value: 'address.flatNumber',\n labelKey: 'sync_excel.mapping.targets.flatNumber',\n fallback: 'Flat number',\n mappingKind: 'core',\n matchTokens: ['flat number', 'apartment number', 'unit number'],\n },\n {\n value: 'address.city',\n labelKey: 'sync_excel.mapping.targets.city',\n fallback: 'City',\n mappingKind: 'core',\n matchTokens: ['city', 'town'],\n },\n {\n value: 'address.region',\n labelKey: 'sync_excel.mapping.targets.region',\n fallback: 'Region / State',\n mappingKind: 'core',\n matchTokens: ['region', 'state', 'province'],\n },\n {\n value: 'address.postalCode',\n labelKey: 'sync_excel.mapping.targets.postalCode',\n fallback: 'Postal code',\n mappingKind: 'core',\n matchTokens: ['postal code', 'zip code', 'postcode'],\n },\n {\n value: 'address.country',\n labelKey: 'sync_excel.mapping.targets.country',\n fallback: 'Country',\n mappingKind: 'core',\n matchTokens: ['country'],\n },\n {\n value: 'address.latitude',\n labelKey: 'sync_excel.mapping.targets.latitude',\n fallback: 'Latitude',\n mappingKind: 'core',\n matchTokens: ['latitude', 'lat'],\n },\n {\n value: 'address.longitude',\n labelKey: 'sync_excel.mapping.targets.longitude',\n fallback: 'Longitude',\n mappingKind: 'core',\n matchTokens: ['longitude', 'lng', 'lon'],\n },\n]\n\nfunction normalizeMatchToken(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 titleizeKey(key: string): string {\n return key\n .split('_')\n .map((part) => (part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part))\n .join(' ')\n}\n\nfunction scoreCustomFieldDefinition(def: CustomFieldDefDto, entityIndex: number): {\n base: number\n penalty: number\n entityIndex: number\n} {\n const listVisibleScore = def.listVisible === false ? 0 : 1\n const formEditableScore = def.formEditable === false ? 0 : 1\n const filterableScore = def.filterable ? 1 : 0\n const kindScore = (() => {\n switch (def.kind) {\n case 'dictionary':\n return 8\n case 'relation':\n return 6\n case 'select':\n return 4\n case 'multiline':\n return 3\n case 'boolean':\n case 'integer':\n case 'float':\n return 2\n default:\n return 1\n }\n })()\n const optionsBonus = Array.isArray(def.options) && def.options.length > 0 ? 2 : 0\n const dictionaryBonus = typeof def.dictionaryId === 'string' && def.dictionaryId.trim().length > 0 ? 5 : 0\n return {\n base: (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus,\n penalty: typeof def.priority === 'number' ? def.priority : 0,\n entityIndex,\n }\n}\n\nfunction buildCustomFieldOption(def: CustomFieldDefDto): MappingTargetOption {\n const fallback = typeof def.label === 'string' && def.label.trim().length > 0\n ? def.label.trim()\n : titleizeKey(def.key)\n const normalizedTokens = Array.from(new Set([\n normalizeMatchToken(def.key),\n normalizeMatchToken(fallback),\n ].filter((value) => value.length > 0)))\n\n return {\n value: `cf:${def.key}`,\n fallback,\n mappingKind: 'custom_field',\n matchTokens: normalizedTokens,\n }\n}\n\nfunction selectPreferredCustomFieldDefs(customFieldDefs: CustomFieldDefDto[]): CustomFieldDefDto[] {\n const entityOrder = new Map<string, number>()\n SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS.forEach((entityId, index) => entityOrder.set(entityId, index))\n const bestByKey = new Map<string, { def: CustomFieldDefDto; score: { base: number; penalty: number; entityIndex: number } }>()\n\n for (const def of customFieldDefs) {\n if (typeof def.key !== 'string' || def.key.trim().length === 0) continue\n const score = scoreCustomFieldDefinition(def, entityOrder.get(def.entityId ?? '') ?? Number.MAX_SAFE_INTEGER)\n const existing = bestByKey.get(def.key)\n const isBetter = !existing\n || score.base > existing.score.base\n || (\n score.base === existing.score.base\n && (score.penalty < existing.score.penalty\n || (score.penalty === existing.score.penalty && score.entityIndex < existing.score.entityIndex))\n )\n if (isBetter) {\n bestByKey.set(def.key, { def, score })\n }\n }\n\n return Array.from(bestByKey.values()).map((entry) => entry.def)\n}\n\nexport function buildPeopleTargetOptions(customFieldDefs: CustomFieldDefDto[]): MappingTargetOption[] {\n const customOptions = selectPreferredCustomFieldDefs(customFieldDefs).map(buildCustomFieldOption)\n return [...CORE_TARGET_OPTIONS, ...ADDRESS_TARGET_OPTIONS, ...customOptions]\n}\n\nexport function buildPeopleSuggestedMapping(\n headers: string[],\n suggestedMapping: SuggestedMapping,\n customFieldDefs: CustomFieldDefDto[],\n): SuggestedMapping {\n const fields = [...suggestedMapping.fields]\n const usedExternalFields = new Set(fields.map((field) => field.externalField))\n const usedTargetFields = new Set(fields.map((field) => field.localField))\n const supplementalTargetOptions = buildPeopleTargetOptions(customFieldDefs).filter(\n (option) => option.mappingKind === 'custom_field' || option.value.startsWith('address.'),\n )\n\n for (const header of headers) {\n if (usedExternalFields.has(header)) continue\n const normalizedHeader = normalizeMatchToken(header)\n const matchedOption = supplementalTargetOptions.find((option) => {\n if (usedTargetFields.has(option.value)) return false\n return option.matchTokens.includes(normalizedHeader)\n })\n if (!matchedOption) continue\n\n fields.push({\n externalField: header,\n localField: matchedOption.value,\n mappingKind: matchedOption.mappingKind,\n })\n usedExternalFields.add(header)\n usedTargetFields.add(matchedOption.value)\n }\n\n return {\n ...suggestedMapping,\n fields,\n unmappedColumns: headers.filter((header) => !usedExternalFields.has(header)),\n }\n}\n\nexport function buildSuggestedMappingSignature(headers: string[], suggestedMapping: SuggestedMapping): string {\n return JSON.stringify({\n headers,\n matchStrategy: suggestedMapping.matchStrategy,\n matchField: suggestedMapping.matchField ?? null,\n fields: suggestedMapping.fields.map((field) => ({\n externalField: field.externalField,\n localField: field.localField,\n mappingKind: field.mappingKind ?? null,\n dedupeRole: field.dedupeRole ?? null,\n })),\n })\n}\n\nexport function findMappingTargetOption(\n targetOptions: MappingTargetOption[],\n targetField: string,\n): MappingTargetOption | undefined {\n return targetOptions.find((option) => option.value === targetField)\n}\n\nexport { normalizeMatchToken }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport type { FieldMapping, FieldMappingDedupeRole, FieldMappingKind } from '../../../../data_sync/lib/adapter'\n\nexport const SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS = [\n 'customers:customer_entity',\n 'customers:customer_person_profile',\n] as const\n\nexport type SuggestedMapping = {\n entityType: 'customers.person'\n matchStrategy: 'externalId' | 'email' | 'custom'\n matchField?: string\n fields: FieldMapping[]\n unmappedColumns: string[]\n}\n\nexport type MappingTargetOption = {\n value: string\n labelKey?: string\n fallback: string\n mappingKind: FieldMappingKind\n dedupeRole?: FieldMappingDedupeRole\n matchTokens: string[]\n}\n\nconst CORE_TARGET_OPTIONS: MappingTargetOption[] = [\n {\n value: 'person.externalId',\n labelKey: 'sync_excel.mapping.targets.externalId',\n fallback: 'External ID',\n mappingKind: 'external_id',\n dedupeRole: 'primary',\n matchTokens: ['external id', 'record id', 'lead id'],\n },\n {\n value: 'person.firstName',\n labelKey: 'sync_excel.mapping.targets.firstName',\n fallback: 'First name',\n mappingKind: 'core',\n matchTokens: ['first name', 'firstname', 'given name'],\n },\n {\n value: 'person.lastName',\n labelKey: 'sync_excel.mapping.targets.lastName',\n fallback: 'Last name',\n mappingKind: 'core',\n matchTokens: ['last name', 'lastname', 'surname', 'family name'],\n },\n {\n value: 'person.displayName',\n labelKey: 'sync_excel.mapping.targets.displayName',\n fallback: 'Display name',\n mappingKind: 'core',\n matchTokens: ['display name', 'lead name', 'full name', 'name'],\n },\n {\n value: 'person.primaryEmail',\n labelKey: 'sync_excel.mapping.targets.primaryEmail',\n fallback: 'Primary email',\n mappingKind: 'core',\n dedupeRole: 'secondary',\n matchTokens: ['email', 'primary email', 'email address'],\n },\n {\n value: 'person.primaryPhone',\n labelKey: 'sync_excel.mapping.targets.primaryPhone',\n fallback: 'Primary phone',\n mappingKind: 'core',\n matchTokens: ['phone', 'primary phone', 'mobile', 'mobile phone', 'telephone'],\n },\n {\n value: 'person.jobTitle',\n labelKey: 'sync_excel.mapping.targets.jobTitle',\n fallback: 'Job title',\n mappingKind: 'core',\n matchTokens: ['job title', 'title', 'position'],\n },\n {\n value: 'person.status',\n labelKey: 'sync_excel.mapping.targets.status',\n fallback: 'Status',\n mappingKind: 'core',\n matchTokens: ['status', 'lead status'],\n },\n {\n value: 'person.source',\n labelKey: 'sync_excel.mapping.targets.source',\n fallback: 'Source',\n mappingKind: 'core',\n matchTokens: ['source', 'lead source'],\n },\n {\n value: 'person.description',\n labelKey: 'sync_excel.mapping.targets.description',\n fallback: 'Description',\n mappingKind: 'core',\n matchTokens: ['description', 'notes', 'comment'],\n },\n]\n\nconst ADDRESS_TARGET_OPTIONS: MappingTargetOption[] = [\n {\n value: 'address.name',\n labelKey: 'sync_excel.mapping.targets.addressName',\n fallback: 'Address label',\n mappingKind: 'core',\n matchTokens: ['address label', 'address name', 'label'],\n },\n {\n value: 'address.purpose',\n labelKey: 'sync_excel.mapping.targets.addressPurpose',\n fallback: 'Address purpose',\n mappingKind: 'core',\n matchTokens: ['address purpose', 'address type'],\n },\n {\n value: 'address.companyName',\n labelKey: 'sync_excel.mapping.targets.addressCompanyName',\n fallback: 'Address company',\n mappingKind: 'core',\n matchTokens: ['address company', 'address company name'],\n },\n {\n value: 'address.addressLine1',\n labelKey: 'sync_excel.mapping.targets.addressLine1',\n fallback: 'Address line 1',\n mappingKind: 'core',\n matchTokens: ['address line 1', 'street address', 'street', 'street 1'],\n },\n {\n value: 'address.addressLine2',\n labelKey: 'sync_excel.mapping.targets.addressLine2',\n fallback: 'Address line 2',\n mappingKind: 'core',\n matchTokens: ['address line 2', 'street 2'],\n },\n {\n value: 'address.buildingNumber',\n labelKey: 'sync_excel.mapping.targets.buildingNumber',\n fallback: 'Building number',\n mappingKind: 'core',\n matchTokens: ['building number', 'building no', 'house number'],\n },\n {\n value: 'address.flatNumber',\n labelKey: 'sync_excel.mapping.targets.flatNumber',\n fallback: 'Flat number',\n mappingKind: 'core',\n matchTokens: ['flat number', 'apartment number', 'unit number'],\n },\n {\n value: 'address.city',\n labelKey: 'sync_excel.mapping.targets.city',\n fallback: 'City',\n mappingKind: 'core',\n matchTokens: ['city', 'town'],\n },\n {\n value: 'address.region',\n labelKey: 'sync_excel.mapping.targets.region',\n fallback: 'Region / State',\n mappingKind: 'core',\n matchTokens: ['region', 'state', 'province'],\n },\n {\n value: 'address.postalCode',\n labelKey: 'sync_excel.mapping.targets.postalCode',\n fallback: 'Postal code',\n mappingKind: 'core',\n matchTokens: ['postal code', 'zip code', 'postcode'],\n },\n {\n value: 'address.country',\n labelKey: 'sync_excel.mapping.targets.country',\n fallback: 'Country',\n mappingKind: 'core',\n matchTokens: ['country'],\n },\n {\n value: 'address.latitude',\n labelKey: 'sync_excel.mapping.targets.latitude',\n fallback: 'Latitude',\n mappingKind: 'core',\n matchTokens: ['latitude', 'lat'],\n },\n {\n value: 'address.longitude',\n labelKey: 'sync_excel.mapping.targets.longitude',\n fallback: 'Longitude',\n mappingKind: 'core',\n matchTokens: ['longitude', 'lng', 'lon'],\n },\n]\n\nfunction normalizeMatchToken(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\nconst TRAILING_IMPORT_QUALIFIERS = new Set(['external', 'imported', 'crm'])\n\nfunction addNormalizedMatchToken(tokens: Set<string>, value: string): void {\n const normalized = normalizeMatchToken(value)\n if (normalized.length > 0) {\n tokens.add(normalized)\n }\n}\n\nfunction stripParentheticalText(value: string): string {\n return value.replace(/\\s*\\([^)]*\\)\\s*/g, ' ')\n}\n\nfunction stripTrailingImportQualifier(value: string): string {\n const parts = normalizeMatchToken(value).split(' ').filter((part) => part.length > 0)\n while (parts.length > 1 && TRAILING_IMPORT_QUALIFIERS.has(parts[parts.length - 1])) {\n parts.pop()\n }\n return parts.join(' ')\n}\n\nfunction buildCustomFieldMatchTokens(def: CustomFieldDefDto, fallback: string): string[] {\n const tokens = new Set<string>()\n const candidates = [\n def.key,\n fallback,\n stripParentheticalText(fallback),\n stripTrailingImportQualifier(def.key),\n stripTrailingImportQualifier(fallback),\n stripTrailingImportQualifier(stripParentheticalText(fallback)),\n ]\n\n for (const candidate of candidates) {\n addNormalizedMatchToken(tokens, candidate)\n }\n\n return Array.from(tokens)\n}\n\nfunction titleizeKey(key: string): string {\n return key\n .split('_')\n .map((part) => (part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part))\n .join(' ')\n}\n\nfunction scoreCustomFieldDefinition(def: CustomFieldDefDto, entityIndex: number): {\n base: number\n penalty: number\n entityIndex: number\n} {\n const listVisibleScore = def.listVisible === false ? 0 : 1\n const formEditableScore = def.formEditable === false ? 0 : 1\n const filterableScore = def.filterable ? 1 : 0\n const kindScore = (() => {\n switch (def.kind) {\n case 'dictionary':\n return 8\n case 'relation':\n return 6\n case 'select':\n return 4\n case 'multiline':\n return 3\n case 'boolean':\n case 'integer':\n case 'float':\n return 2\n default:\n return 1\n }\n })()\n const optionsBonus = Array.isArray(def.options) && def.options.length > 0 ? 2 : 0\n const dictionaryBonus = typeof def.dictionaryId === 'string' && def.dictionaryId.trim().length > 0 ? 5 : 0\n return {\n base: (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus,\n penalty: typeof def.priority === 'number' ? def.priority : 0,\n entityIndex,\n }\n}\n\nfunction buildCustomFieldOption(def: CustomFieldDefDto): MappingTargetOption {\n const fallback = typeof def.label === 'string' && def.label.trim().length > 0\n ? def.label.trim()\n : titleizeKey(def.key)\n\n return {\n value: `cf:${def.key}`,\n fallback,\n mappingKind: 'custom_field',\n matchTokens: buildCustomFieldMatchTokens(def, fallback),\n }\n}\n\nfunction selectPreferredCustomFieldDefs(customFieldDefs: CustomFieldDefDto[]): CustomFieldDefDto[] {\n const entityOrder = new Map<string, number>()\n SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS.forEach((entityId, index) => entityOrder.set(entityId, index))\n const bestByKey = new Map<string, { def: CustomFieldDefDto; score: { base: number; penalty: number; entityIndex: number } }>()\n\n for (const def of customFieldDefs) {\n if (typeof def.key !== 'string' || def.key.trim().length === 0) continue\n const score = scoreCustomFieldDefinition(def, entityOrder.get(def.entityId ?? '') ?? Number.MAX_SAFE_INTEGER)\n const existing = bestByKey.get(def.key)\n const isBetter = !existing\n || score.base > existing.score.base\n || (\n score.base === existing.score.base\n && (score.penalty < existing.score.penalty\n || (score.penalty === existing.score.penalty && score.entityIndex < existing.score.entityIndex))\n )\n if (isBetter) {\n bestByKey.set(def.key, { def, score })\n }\n }\n\n return Array.from(bestByKey.values()).map((entry) => entry.def)\n}\n\nexport function buildPeopleTargetOptions(customFieldDefs: CustomFieldDefDto[]): MappingTargetOption[] {\n const customOptions = selectPreferredCustomFieldDefs(customFieldDefs).map(buildCustomFieldOption)\n return [...CORE_TARGET_OPTIONS, ...ADDRESS_TARGET_OPTIONS, ...customOptions]\n}\n\nexport function buildPeopleSuggestedMapping(\n headers: string[],\n suggestedMapping: SuggestedMapping,\n customFieldDefs: CustomFieldDefDto[],\n): SuggestedMapping {\n const fields = [...suggestedMapping.fields]\n const usedExternalFields = new Set(fields.map((field) => field.externalField))\n const usedTargetFields = new Set(fields.map((field) => field.localField))\n const supplementalTargetOptions = buildPeopleTargetOptions(customFieldDefs).filter(\n (option) => option.mappingKind === 'custom_field' || option.value.startsWith('address.'),\n )\n\n for (const header of headers) {\n if (usedExternalFields.has(header)) continue\n const normalizedHeader = normalizeMatchToken(header)\n const matchedOption = supplementalTargetOptions.find((option) => {\n if (usedTargetFields.has(option.value)) return false\n return option.matchTokens.includes(normalizedHeader)\n })\n if (!matchedOption) continue\n\n fields.push({\n externalField: header,\n localField: matchedOption.value,\n mappingKind: matchedOption.mappingKind,\n })\n usedExternalFields.add(header)\n usedTargetFields.add(matchedOption.value)\n }\n\n return {\n ...suggestedMapping,\n fields,\n unmappedColumns: headers.filter((header) => !usedExternalFields.has(header)),\n }\n}\n\nexport function buildSuggestedMappingSignature(headers: string[], suggestedMapping: SuggestedMapping): string {\n return JSON.stringify({\n headers,\n matchStrategy: suggestedMapping.matchStrategy,\n matchField: suggestedMapping.matchField ?? null,\n fields: suggestedMapping.fields.map((field) => ({\n externalField: field.externalField,\n localField: field.localField,\n mappingKind: field.mappingKind ?? null,\n dedupeRole: field.dedupeRole ?? null,\n })),\n })\n}\n\nexport function findMappingTargetOption(\n targetOptions: MappingTargetOption[],\n targetField: string,\n): MappingTargetOption | undefined {\n return targetOptions.find((option) => option.value === targetField)\n}\n\nexport { normalizeMatchToken }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.3-develop.3881.1.0b590ac4eb",
3
+ "version": "0.6.3-develop.3901.1.ddad60693a",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -243,16 +243,16 @@
243
243
  "zod": "^4.4.3"
244
244
  },
245
245
  "peerDependencies": {
246
- "@open-mercato/ai-assistant": "0.6.3-develop.3881.1.0b590ac4eb",
247
- "@open-mercato/shared": "0.6.3-develop.3881.1.0b590ac4eb",
248
- "@open-mercato/ui": "0.6.3-develop.3881.1.0b590ac4eb",
246
+ "@open-mercato/ai-assistant": "0.6.3-develop.3901.1.ddad60693a",
247
+ "@open-mercato/shared": "0.6.3-develop.3901.1.ddad60693a",
248
+ "@open-mercato/ui": "0.6.3-develop.3901.1.ddad60693a",
249
249
  "react": "^19.0.0",
250
250
  "react-dom": "^19.0.0"
251
251
  },
252
252
  "devDependencies": {
253
- "@open-mercato/ai-assistant": "0.6.3-develop.3881.1.0b590ac4eb",
254
- "@open-mercato/shared": "0.6.3-develop.3881.1.0b590ac4eb",
255
- "@open-mercato/ui": "0.6.3-develop.3881.1.0b590ac4eb",
253
+ "@open-mercato/ai-assistant": "0.6.3-develop.3901.1.ddad60693a",
254
+ "@open-mercato/shared": "0.6.3-develop.3901.1.ddad60693a",
255
+ "@open-mercato/ui": "0.6.3-develop.3901.1.ddad60693a",
256
256
  "@testing-library/dom": "^10.4.1",
257
257
  "@testing-library/jest-dom": "^6.9.1",
258
258
  "@testing-library/react": "^16.3.1",
@@ -55,7 +55,7 @@ export default function AuthProfilePage() {
55
55
  setError(null)
56
56
  try {
57
57
  const { ok, result } = await apiCall<ProfileResponse>('/api/auth/profile')
58
- if (!ok) throw new Error('load_failed')
58
+ if (!ok) throw new Error(t('auth.profile.form.errors.load', 'Failed to load profile.'))
59
59
  const resolvedEmail = typeof result?.email === 'string' ? result.email : ''
60
60
  if (!cancelled) setEmail(resolvedEmail)
61
61
  } catch (err) {
@@ -54,7 +54,7 @@ export default function ProfileChangePasswordPage() {
54
54
  setError(null)
55
55
  try {
56
56
  const { ok, result } = await apiCall<ProfileResponse>('/api/auth/profile')
57
- if (!ok) throw new Error('load_failed')
57
+ if (!ok) throw new Error(t('auth.profile.form.errors.load', 'Failed to load profile.'))
58
58
  const resolvedEmail = typeof result?.email === 'string' ? result.email : ''
59
59
  if (!cancelled) setEmail(resolvedEmail)
60
60
  } catch (err) {
@@ -176,7 +176,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
176
176
  const { ok, result } = await apiCall<UserListResponse>(
177
177
  `/api/auth/users?id=${encodeURIComponent(String(id))}&page=1&pageSize=1`,
178
178
  )
179
- if (!ok) throw new Error('load_failed')
179
+ if (!ok) throw new Error(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))
180
180
  const item = Array.isArray(result?.items) ? result?.items?.[0] : undefined
181
181
  if (!cancelled) {
182
182
  setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))
@@ -109,7 +109,12 @@ export default function CreateUserPage() {
109
109
  setWidgetError(null)
110
110
  try {
111
111
  const { ok, result } = await apiCall<WidgetCatalogResponse>('/api/dashboards/widgets/catalog')
112
- if (!ok) throw new Error('request_failed')
112
+ if (!ok) {
113
+ throw new Error(t(
114
+ 'auth.users.widgets.errors.load',
115
+ 'Unable to load dashboard widgets. You can configure them later from the user page.',
116
+ ))
117
+ }
113
118
  if (!cancelled) {
114
119
  const rawItems: unknown[] = Array.isArray(result?.items) ? result?.items ?? [] : []
115
120
  const normalized = rawItems
@@ -556,7 +556,14 @@ export default function EditCatalogProductPage({
556
556
  const productRes = await apiCall<ProductResponse>(
557
557
  `/api/catalog/products?id=${encodeURIComponent(productId!)}&page=1&pageSize=1&withDeleted=false`,
558
558
  );
559
- if (!productRes.ok) throw new Error("load_failed");
559
+ if (!productRes.ok) {
560
+ throw new Error(
561
+ t(
562
+ "catalog.products.edit.errors.load",
563
+ "Failed to load product details.",
564
+ ),
565
+ );
566
+ }
560
567
  const record = Array.isArray(productRes.result?.items)
561
568
  ? productRes.result?.items?.[0]
562
569
  : undefined;
@@ -9,6 +9,7 @@ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors
9
9
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
10
10
  import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
11
11
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
+ import { ErrorMessage } from '@open-mercato/ui/backend/detail'
12
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
13
14
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
14
15
  import { E } from '#generated/entities.ids.generated'
@@ -165,7 +166,7 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
165
166
  const variantRes = await apiCall<VariantResponse>(
166
167
  `/api/catalog/variants?id=${encodeURIComponent(variantId!)}&page=1&pageSize=1`,
167
168
  )
168
- if (!variantRes.ok) throw new Error('load_variant_failed')
169
+ if (!variantRes.ok) throw new Error(t('catalog.variants.form.errors.load', 'Failed to load variant.'))
169
170
  const record = Array.isArray(variantRes.result?.items) ? variantRes.result?.items?.[0] : undefined
170
171
  if (!record) throw new Error(t('catalog.variants.form.errors.notFound', 'Variant not found.'))
171
172
  const resolvedProductId =
@@ -421,7 +422,7 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
421
422
  <Page>
422
423
  <PageBody>
423
424
  {error ? (
424
- <div className="mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">{error}</div>
425
+ <ErrorMessage label={error} className="mb-4" />
425
426
  ) : null}
426
427
  <CrudForm<VariantFormValues>
427
428
  title={formTitle}
@@ -9,6 +9,7 @@ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors
9
9
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
10
10
  import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
11
11
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
+ import { ErrorMessage } from '@open-mercato/ui/backend/detail'
12
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
13
14
  import { E } from '#generated/entities.ids.generated'
14
15
  import {
@@ -136,7 +137,7 @@ export default function CreateVariantPage({ params }: { params?: { productId?: s
136
137
  const res = await apiCall<ProductResponse>(
137
138
  `/api/catalog/products?id=${encodeURIComponent(productId!)}&page=1&pageSize=1`,
138
139
  )
139
- if (!res.ok) throw new Error('load_failed')
140
+ if (!res.ok) throw new Error(t('catalog.variants.form.errors.load', 'Failed to load product context.'))
140
141
  const record = Array.isArray(res.result?.items) ? res.result?.items?.[0] : undefined
141
142
  if (!record) throw new Error(t('catalog.products.edit.errors.notFound', 'Product not found.'))
142
143
  const metadata = (record.metadata ?? {}) as Record<string, unknown>
@@ -298,7 +299,7 @@ export default function CreateVariantPage({ params }: { params?: { productId?: s
298
299
  <Page>
299
300
  <PageBody>
300
301
  {error ? (
301
- <div className="mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">{error}</div>
302
+ <ErrorMessage label={error} className="mb-4" />
302
303
  ) : null}
303
304
  <CrudForm<VariantFormValues>
304
305
  title={formTitle}
@@ -16,6 +16,12 @@ import { touchGeneratedBarrels } from './lib/touchGeneratedBarrels'
16
16
 
17
17
  type ParsedArgs = Record<string, string | boolean>
18
18
 
19
+ export const STRUCTURAL_CACHE_REQUESTS: CachePurgeRequest[] = [
20
+ { kind: 'pattern', pattern: 'nav:*' },
21
+ { kind: 'segment', segment: 'admin-nav' },
22
+ { kind: 'segment', segment: 'portal-nav' },
23
+ ]
24
+
19
25
  type CacheScope = {
20
26
  label: string
21
27
  tenantId: string | null
@@ -157,7 +163,7 @@ function printCacheHelp() {
157
163
  console.log('ℹ️ Notes:')
158
164
  console.log(' `stats` mirrors the cache admin page segment overview for CRUD/widget caches.')
159
165
  console.log(' `purge --id` removes every key whose name contains the provided token (for example a user id or entity id).')
160
- console.log(' `structural` targets navigation caches (`nav:*`) and is the recommended post-step after module/sidebar structure changes.')
166
+ console.log(' `structural` targets navigation/sidebar caches and is the recommended post-step after module/sidebar structure changes.')
161
167
  console.log(' When no scope flag is supplied, this command uses the global cache scope only.')
162
168
  }
163
169
 
@@ -201,11 +207,14 @@ async function runCacheStats(args: ParsedArgs) {
201
207
  }
202
208
  }
203
209
 
204
- async function runCachePurge(args: ParsedArgs) {
210
+ async function runCachePurgeRequest(
211
+ args: ParsedArgs,
212
+ request: CachePurgeRequest,
213
+ emitOutput = true,
214
+ ) {
205
215
  const json = flagEnabled(args, 'json')
206
216
  const quiet = flagEnabled(args, 'quiet')
207
217
  const dryRun = flagEnabled(args, 'dry-run', 'dryRun')
208
- const request = resolveCachePurgeRequest(args)
209
218
  const container = await createRequestContainer()
210
219
  try {
211
220
  const em = container.resolve('em') as EntityManager
@@ -228,13 +237,13 @@ async function runCachePurge(args: ParsedArgs) {
228
237
  })
229
238
  }
230
239
 
231
- if (json) {
240
+ if (json && emitOutput) {
232
241
  console.log(JSON.stringify(results, null, 2))
233
- return
242
+ return results
234
243
  }
235
244
 
236
- if (quiet) {
237
- return
245
+ if (quiet || !emitOutput) {
246
+ return results
238
247
  }
239
248
 
240
249
  for (const result of results) {
@@ -246,22 +255,34 @@ async function runCachePurge(args: ParsedArgs) {
246
255
  }
247
256
  }
248
257
  }
258
+ return results
249
259
  } finally {
250
260
  await disposeContainer(container)
251
261
  }
252
262
  }
253
263
 
264
+ async function runCachePurge(args: ParsedArgs) {
265
+ await runCachePurgeRequest(args, resolveCachePurgeRequest(args))
266
+ }
267
+
254
268
  async function runStructuralCachePurge(args: ParsedArgs) {
255
- const nextArgs: ParsedArgs = {
256
- ...args,
257
- pattern: 'nav:*',
269
+ const json = flagEnabled(args, 'json')
270
+ const structuralResults: Array<{
271
+ request: CachePurgeRequest
272
+ results: Awaited<ReturnType<typeof runCachePurgeRequest>>
273
+ }> = []
274
+ for (const request of STRUCTURAL_CACHE_REQUESTS) {
275
+ const results = await runCachePurgeRequest(args, request, !json)
276
+ structuralResults.push({ request, results })
277
+ }
278
+ if (json) {
279
+ console.log(JSON.stringify(structuralResults, null, 2))
258
280
  }
259
- await runCachePurge(nextArgs)
260
281
  const quiet = flagEnabled(args, 'quiet')
261
282
  try {
262
- touchGeneratedBarrels({ quiet })
283
+ touchGeneratedBarrels({ quiet: quiet || json })
263
284
  } catch (err) {
264
- if (!quiet) {
285
+ if (!quiet && !json) {
265
286
  console.warn(
266
287
  `[structural] failed to touch generated barrels: ${(err as Error).message ?? err}`,
267
288
  )
@@ -37,7 +37,7 @@ export default function ResourcesResourceTypeEditPage({ params }: { params?: { i
37
37
  { errorMessage: t('resources.resourceTypes.errors.load', 'Failed to load resource types.') },
38
38
  )
39
39
  const item = Array.isArray(payload.items) ? payload.items[0] : null
40
- if (!item) throw new Error('not_found')
40
+ if (!item) throw new Error(t('resources.resourceTypes.errors.notFound', 'Resource type not found.'))
41
41
  if (!cancelled) {
42
42
  const customValues = extractCustomFieldValues(item)
43
43
  setInitialValues({
@@ -53,7 +53,7 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
53
53
  )
54
54
  const item = Array.isArray(payload.items) ? payload.items[0] : null
55
55
  if (!item) {
56
- throw new Error('not_found')
56
+ throw new Error(t('sales.channels.form.errors.notFound', 'Channel not found.'))
57
57
  }
58
58
  if (!cancelled) {
59
59
  setInitialValues(mapChannelToFormValues(item))
@@ -291,7 +291,7 @@ export function ChannelOfferForm({ channelId: lockedChannelId, offerId, mode }:
291
291
  { errorMessage: t('sales.channels.offers.errors.loadOffer', 'Failed to load offer.') },
292
292
  )
293
293
  const offer = Array.isArray(payload.items) ? payload.items[0] : null
294
- if (!offer) throw new Error('not_found')
294
+ if (!offer) throw new Error(t('sales.channels.offers.errors.notFound', 'Offer not found.'))
295
295
  const values = mapOfferToFormValues(offer, lockedChannelId)
296
296
  const pricePayload = await readApiResultOrThrow<PriceResponse>(
297
297
  `/api/catalog/prices?offerId=${encodeURIComponent(offer.id as string)}&pageSize=${MAX_LIST_PAGE_SIZE}`,
@@ -202,6 +202,45 @@ function normalizeMatchToken(value: string): string {
202
202
  .trim()
203
203
  }
204
204
 
205
+ const TRAILING_IMPORT_QUALIFIERS = new Set(['external', 'imported', 'crm'])
206
+
207
+ function addNormalizedMatchToken(tokens: Set<string>, value: string): void {
208
+ const normalized = normalizeMatchToken(value)
209
+ if (normalized.length > 0) {
210
+ tokens.add(normalized)
211
+ }
212
+ }
213
+
214
+ function stripParentheticalText(value: string): string {
215
+ return value.replace(/\s*\([^)]*\)\s*/g, ' ')
216
+ }
217
+
218
+ function stripTrailingImportQualifier(value: string): string {
219
+ const parts = normalizeMatchToken(value).split(' ').filter((part) => part.length > 0)
220
+ while (parts.length > 1 && TRAILING_IMPORT_QUALIFIERS.has(parts[parts.length - 1])) {
221
+ parts.pop()
222
+ }
223
+ return parts.join(' ')
224
+ }
225
+
226
+ function buildCustomFieldMatchTokens(def: CustomFieldDefDto, fallback: string): string[] {
227
+ const tokens = new Set<string>()
228
+ const candidates = [
229
+ def.key,
230
+ fallback,
231
+ stripParentheticalText(fallback),
232
+ stripTrailingImportQualifier(def.key),
233
+ stripTrailingImportQualifier(fallback),
234
+ stripTrailingImportQualifier(stripParentheticalText(fallback)),
235
+ ]
236
+
237
+ for (const candidate of candidates) {
238
+ addNormalizedMatchToken(tokens, candidate)
239
+ }
240
+
241
+ return Array.from(tokens)
242
+ }
243
+
205
244
  function titleizeKey(key: string): string {
206
245
  return key
207
246
  .split('_')
@@ -248,16 +287,12 @@ function buildCustomFieldOption(def: CustomFieldDefDto): MappingTargetOption {
248
287
  const fallback = typeof def.label === 'string' && def.label.trim().length > 0
249
288
  ? def.label.trim()
250
289
  : titleizeKey(def.key)
251
- const normalizedTokens = Array.from(new Set([
252
- normalizeMatchToken(def.key),
253
- normalizeMatchToken(fallback),
254
- ].filter((value) => value.length > 0)))
255
290
 
256
291
  return {
257
292
  value: `cf:${def.key}`,
258
293
  fallback,
259
294
  mappingKind: 'custom_field',
260
- matchTokens: normalizedTokens,
295
+ matchTokens: buildCustomFieldMatchTokens(def, fallback),
261
296
  }
262
297
  }
263
298