@open-mercato/core 0.6.3-develop.3894.1.352abf4240 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/global.d.js +1 -0
- package/dist/global.d.js.map +7 -0
- package/dist/modules/catalog/commands/variants.js +11 -5
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/create/page.js +3 -61
- package/dist/modules/customers/backend/customers/deals/create/page.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +2 -0
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/create/CreateDealForm.js +233 -0
- package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsField.js +209 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsField.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsSection.js +67 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsSection.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealCreateSidebar.js +73 -0
- package/dist/modules/customers/components/detail/create/DealCreateSidebar.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealCurrencyField.js +92 -0
- package/dist/modules/customers/components/detail/create/DealCurrencyField.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealCustomAttributes.js +81 -0
- package/dist/modules/customers/components/detail/create/DealCustomAttributes.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealDetailsFields.js +171 -0
- package/dist/modules/customers/components/detail/create/DealDetailsFields.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealFormField.js +24 -0
- package/dist/modules/customers/components/detail/create/DealFormField.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealSectionCard.js +29 -0
- package/dist/modules/customers/components/detail/create/DealSectionCard.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealTipsCard.js +19 -0
- package/dist/modules/customers/components/detail/create/DealTipsCard.js.map +7 -0
- package/dist/modules/customers/components/detail/create/PipelineSelect.js +41 -0
- package/dist/modules/customers/components/detail/create/PipelineSelect.js.map +7 -0
- package/dist/modules/customers/components/detail/create/PipelineStageSelect.js +49 -0
- package/dist/modules/customers/components/detail/create/PipelineStageSelect.js.map +7 -0
- package/dist/modules/customers/components/detail/create/SuffixInput.js +21 -0
- package/dist/modules/customers/components/detail/create/SuffixInput.js.map +7 -0
- package/dist/modules/customers/components/detail/create/dealCustomFieldControl.js +270 -0
- package/dist/modules/customers/components/detail/create/dealCustomFieldControl.js.map +7 -0
- package/dist/modules/customers/components/detail/create/dealFormTypes.js +17 -0
- package/dist/modules/customers/components/detail/create/dealFormTypes.js.map +7 -0
- package/dist/modules/customers/components/detail/create/dealNumericInput.js +16 -0
- package/dist/modules/customers/components/detail/create/dealNumericInput.js.map +7 -0
- package/dist/modules/customers/components/detail/create/useDealCustomFields.js +93 -0
- package/dist/modules/customers/components/detail/create/useDealCustomFields.js.map +7 -0
- package/dist/modules/customers/components/detail/create/useDealPipelines.js +59 -0
- package/dist/modules/customers/components/detail/create/useDealPipelines.js.map +7 -0
- package/dist/modules/customers/components/formConfig.js +4 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +5 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +2 -2
- package/dist/modules/feature_toggles/lib/feature-flag-check.js +13 -5
- package/dist/modules/feature_toggles/lib/feature-flag-check.js.map +2 -2
- package/dist/modules/query_index/subscribers/coverage_refresh.js +6 -1
- package/dist/modules/query_index/subscribers/coverage_refresh.js.map +2 -2
- package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js +33 -5
- package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraph.js +29 -186
- package/dist/modules/workflows/components/WorkflowGraph.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +196 -0
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +7 -0
- package/package.json +8 -9
- package/src/global.d.ts +9 -0
- package/src/modules/catalog/commands/variants.ts +14 -5
- package/src/modules/customers/backend/customers/deals/create/page.tsx +3 -64
- package/src/modules/customers/components/detail/DealForm.tsx +2 -0
- package/src/modules/customers/components/detail/create/CreateDealForm.tsx +254 -0
- package/src/modules/customers/components/detail/create/DealAssociationsField.tsx +253 -0
- package/src/modules/customers/components/detail/create/DealAssociationsSection.tsx +72 -0
- package/src/modules/customers/components/detail/create/DealCreateSidebar.tsx +79 -0
- package/src/modules/customers/components/detail/create/DealCurrencyField.tsx +108 -0
- package/src/modules/customers/components/detail/create/DealCustomAttributes.tsx +118 -0
- package/src/modules/customers/components/detail/create/DealDetailsFields.tsx +171 -0
- package/src/modules/customers/components/detail/create/DealFormField.tsx +39 -0
- package/src/modules/customers/components/detail/create/DealSectionCard.tsx +40 -0
- package/src/modules/customers/components/detail/create/DealTipsCard.tsx +26 -0
- package/src/modules/customers/components/detail/create/PipelineSelect.tsx +55 -0
- package/src/modules/customers/components/detail/create/PipelineStageSelect.tsx +70 -0
- package/src/modules/customers/components/detail/create/SuffixInput.tsx +20 -0
- package/src/modules/customers/components/detail/create/dealCustomFieldControl.tsx +310 -0
- package/src/modules/customers/components/detail/create/dealFormTypes.ts +29 -0
- package/src/modules/customers/components/detail/create/dealNumericInput.ts +20 -0
- package/src/modules/customers/components/detail/create/useDealCustomFields.ts +118 -0
- package/src/modules/customers/components/detail/create/useDealPipelines.ts +80 -0
- package/src/modules/customers/components/formConfig.tsx +3 -0
- package/src/modules/customers/i18n/de.json +26 -0
- package/src/modules/customers/i18n/en.json +26 -0
- package/src/modules/customers/i18n/es.json +26 -0
- package/src/modules/customers/i18n/pl.json +26 -0
- package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +12 -1
- package/src/modules/feature_toggles/lib/feature-flag-check.ts +14 -4
- package/src/modules/query_index/subscribers/coverage_refresh.ts +7 -1
- package/src/modules/resources/i18n/de.json +1 -0
- package/src/modules/resources/i18n/en.json +1 -0
- package/src/modules/resources/i18n/es.json +1 -0
- package/src/modules/resources/i18n/pl.json +1 -0
- package/src/modules/sales/i18n/de.json +2 -0
- package/src/modules/sales/i18n/en.json +2 -0
- package/src/modules/sales/i18n/es.json +2 -0
- package/src/modules/sales/i18n/pl.json +2 -0
- package/src/modules/sync_excel/widgets/injection/upload-config/target-options.ts +40 -5
- package/src/modules/workflows/components/WorkflowGraph.tsx +39 -235
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +233 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dictionaries/components/DictionaryEntrySelect.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings, Save } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport { DictionaryValue, renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'\nimport { AppearanceSelector, type AppearanceSelectorLabels, useAppearanceState } from './AppearanceSelector'\n\nconst DEFAULT_APPEARANCE_LABELS: AppearanceSelectorLabels = {\n colorLabel: 'Color',\n colorHelp: 'Pick a highlight color for this entry.',\n colorClearLabel: 'Remove color',\n iconLabel: 'Icon or emoji',\n iconPlaceholder: 'Type an emoji or icon token.',\n iconPickerTriggerLabel: 'Browse icons and emoji',\n iconSearchPlaceholder: 'Search icons or emojis\u2026',\n iconSearchEmptyLabel: 'No icons match your search.',\n iconSuggestionsLabel: 'Suggestions',\n iconClearLabel: 'Remove icon',\n previewEmptyLabel: 'No appearance selected',\n}\n\nexport type DictionaryOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\nexport type DictionarySelectLabels = {\n placeholder: string\n addLabel: string\n addPrompt?: string\n dialogTitle: string\n valueLabel: string\n valuePlaceholder: string\n labelLabel: string\n labelPlaceholder: string\n emptyError: string\n cancelLabel: string\n saveLabel: string\n saveShortcutHint?: string\n successCreateLabel?: string\n errorLoad: string\n errorSave: string\n loadingLabel: string\n manageTitle: string\n}\n\nexport type DictionaryEntrySelectProps = {\n value?: string\n onChange: (value: string | undefined) => void\n fetchOptions: () => Promise<DictionaryOption[]>\n createOption?: (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => Promise<DictionaryOption | null>\n labels: DictionarySelectLabels\n manageHref?: string\n selectClassName?: string\n allowInlineCreate?: boolean\n allowAppearance?: boolean\n appearanceLabels?: AppearanceSelectorLabels\n disabled?: boolean\n showLabelInput?: boolean\n showManage?: boolean\n}\n\nexport function DictionaryEntrySelect({\n value,\n onChange,\n fetchOptions,\n createOption,\n labels,\n manageHref,\n selectClassName,\n allowInlineCreate = true,\n allowAppearance = false,\n appearanceLabels,\n disabled: disabledProp = false,\n showLabelInput = true,\n showManage = true,\n}: DictionaryEntrySelectProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const [options, setOptions] = React.useState<DictionaryOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [newValue, setNewValue] = React.useState('')\n const [newLabel, setNewLabel] = React.useState('')\n const [formError, setFormError] = React.useState<string | null>(null)\n const appearance = useAppearanceState(null, null)\n\n const loadOptions = React.useCallback(async () => {\n setLoading(true)\n try {\n const items = await fetchOptions()\n setOptions(items.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })))\n } catch (err) {\n console.error('DictionaryEntrySelect.fetchOptions failed', err)\n flash(labels.errorLoad, 'error')\n setOptions([])\n } finally {\n setLoading(false)\n }\n }, [fetchOptions, labels.errorLoad])\n\n React.useEffect(() => {\n loadOptions().catch(() => {})\n }, [loadOptions])\n\n const resetDialogState = React.useCallback(() => {\n setNewValue('')\n setNewLabel('')\n setFormError(null)\n appearance.setColor(null)\n appearance.setIcon(null)\n setSaving(false)\n }, [appearance])\n\n React.useEffect(() => {\n if (!dialogOpen) resetDialogState()\n }, [dialogOpen, resetDialogState])\n\n const activeOption = React.useMemo(\n () => options.find((option) => option.value === value) ?? null,\n [options, value],\n )\n\n const handleCreate = React.useCallback(async () => {\n if (!createOption) return\n const trimmedValue = newValue.trim()\n if (!trimmedValue.length) {\n setFormError(labels.emptyError)\n return\n }\n setSaving(true)\n try {\n const payload = await createOption({\n value: trimmedValue,\n label: showLabelInput ? newLabel.trim() || undefined : undefined,\n color: allowAppearance && appearance.color ? appearance.color : undefined,\n icon: allowAppearance && appearance.icon ? appearance.icon : undefined,\n })\n if (!payload) throw new Error('createOption did not return an entry')\n setOptions((previous) => {\n const map = new Map(previous.map((option) => [option.value, option]))\n map.set(payload.value, {\n value: payload.value,\n label: payload.label,\n color: payload.color ?? null,\n icon: payload.icon ?? null,\n })\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))\n })\n await loadOptions()\n onChange(payload.value)\n setDialogOpen(false)\n if (labels.successCreateLabel) {\n flash(labels.successCreateLabel, 'success')\n }\n } catch (err) {\n console.error('DictionaryEntrySelect.createOption failed', err)\n flash(labels.errorSave, 'error')\n } finally {\n setSaving(false)\n }\n }, [\n allowAppearance,\n appearance.color,\n appearance.icon,\n createOption,\n labels.emptyError,\n labels.errorSave,\n labels.successCreateLabel,\n loadOptions,\n newLabel,\n newValue,\n onChange,\n ])\n\n const handleDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n if (!saving) {\n setDialogOpen(false)\n }\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (!saving && newValue.trim().length) {\n handleCreate().catch(() => {})\n } else if (!saving && !newValue.trim().length) {\n setFormError(labels.emptyError)\n }\n }\n },\n [handleCreate, labels.emptyError, newValue, saving],\n )\n\n const shortcutHint = React.useMemo(() => {\n const provided = typeof labels.saveShortcutHint === 'string' ? labels.saveShortcutHint.trim() : ''\n if (provided.length) return provided\n return '\u2318/Ctrl + Enter'\n }, [labels.saveShortcutHint])\n\n const disabled = disabledProp || loading || saving\n const manageLink = manageHref ?? '/backend/config/dictionaries'\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageLinkWithReturnTo = React.useMemo(\n () => buildHrefWithReturnTo(manageLink, returnTo),\n [manageLink, returnTo],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Select\n value={value || undefined}\n onValueChange={(next) => onChange(next || undefined)}\n disabled={disabled}\n >\n <SelectTrigger\n className={selectClassName}\n title={activeOption?.label ?? undefined}\n >\n <SelectValue placeholder={labels.placeholder} />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <div className=\"flex items-center gap-1\">\n {allowInlineCreate && createOption ? (\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n disabled={disabled}\n title={labels.addLabel}\n aria-label={labels.addLabel}\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\" onKeyDown={handleDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{labels.dialogTitle}</DialogTitle>\n {labels.addPrompt ? <DialogDescription>{labels.addPrompt}</DialogDescription> : null}\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.valueLabel}</label>\n <Input\n type=\"text\"\n value={newValue}\n onChange={(event) => {\n setNewValue(event.target.value)\n if (formError) setFormError(null)\n }}\n placeholder={labels.valuePlaceholder}\n autoFocus\n disabled={saving}\n />\n </div>\n {showLabelInput ? (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.labelLabel}</label>\n <Input\n type=\"text\"\n value={newLabel}\n onChange={(event) => setNewLabel(event.target.value)}\n placeholder={labels.labelPlaceholder}\n disabled={saving}\n />\n </div>\n ) : null}\n {allowAppearance ? (\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n onIconChange={appearance.setIcon}\n onColorChange={appearance.setColor}\n labels={appearanceLabels ?? DEFAULT_APPEARANCE_LABELS}\n />\n ) : null}\n {formError ? <p className=\"text-sm text-red-600\">{formError}</p> : null}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setDialogOpen(false)} disabled={saving}>\n {labels.cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleCreate} disabled={saving || !newValue.trim()}>\n {saving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n <span className=\"flex items-center gap-2\">\n <span>{labels.saveLabel}</span>\n {!saving ? (\n <span className=\"text-xs text-muted-foreground\">{`(${shortcutHint})`}</span>\n ) : null}\n </span>\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n {showManage ? (\n <Button asChild variant=\"ghost\" size=\"icon\" title={labels.manageTitle} aria-label={labels.manageTitle}>\n <Link href={manageLinkWithReturnTo}>\n <Settings className=\"h-4 w-4\" />\n <span className=\"sr-only\">{labels.manageTitle}</span>\n </Link>\n </Button>\n ) : null}\n </div>\n </div>\n {activeOption && (activeOption.icon || activeOption.color) ? (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span className=\"inline-flex items-center gap-2 rounded border border-dashed px-2 py-1\">\n {activeOption.icon ? renderDictionaryIcon(activeOption.icon, 'h-4 w-4') : null}\n {activeOption.color ? renderDictionaryColor(activeOption.color, 'h-4 w-4 rounded-sm') : null}\n </span>\n {activeOption.color ? <span>{activeOption.color}</span> : null}\n </div>\n ) : null}\n {loading ? <div className=\"text-xs text-muted-foreground\">{labels.loadingLabel}</div> : null}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings, Save } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport { DictionaryValue, renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'\nimport { AppearanceSelector, type AppearanceSelectorLabels, useAppearanceState } from './AppearanceSelector'\n\nconst DEFAULT_APPEARANCE_LABELS: AppearanceSelectorLabels = {\n colorLabel: 'Color',\n colorHelp: 'Pick a highlight color for this entry.',\n colorClearLabel: 'Remove color',\n iconLabel: 'Icon or emoji',\n iconPlaceholder: 'Type an emoji or icon token.',\n iconPickerTriggerLabel: 'Browse icons and emoji',\n iconSearchPlaceholder: 'Search icons or emojis\u2026',\n iconSearchEmptyLabel: 'No icons match your search.',\n iconSuggestionsLabel: 'Suggestions',\n iconClearLabel: 'Remove icon',\n previewEmptyLabel: 'No appearance selected',\n}\n\nexport type DictionaryOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\nexport type DictionarySelectLabels = {\n placeholder: string\n addLabel: string\n addPrompt?: string\n dialogTitle: string\n valueLabel: string\n valuePlaceholder: string\n labelLabel: string\n labelPlaceholder: string\n emptyError: string\n cancelLabel: string\n saveLabel: string\n saveShortcutHint?: string\n successCreateLabel?: string\n errorLoad: string\n errorSave: string\n loadingLabel: string\n manageTitle: string\n}\n\nexport type DictionaryEntrySelectProps = {\n id?: string\n value?: string\n onChange: (value: string | undefined) => void\n fetchOptions: () => Promise<DictionaryOption[]>\n createOption?: (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => Promise<DictionaryOption | null>\n labels: DictionarySelectLabels\n manageHref?: string\n selectClassName?: string\n allowInlineCreate?: boolean\n allowAppearance?: boolean\n appearanceLabels?: AppearanceSelectorLabels\n disabled?: boolean\n showLabelInput?: boolean\n showManage?: boolean\n /**\n * When false, hides the read-only appearance preview (color swatch + icon + hex)\n * rendered below the trigger for the currently-selected entry. Defaults to true to\n * preserve existing behavior; set false where the host only wants a plain select\n * (e.g. a create form that shouldn't surface dictionary styling).\n */\n showActiveAppearance?: boolean\n}\n\nexport function DictionaryEntrySelect({\n id,\n value,\n onChange,\n fetchOptions,\n createOption,\n labels,\n manageHref,\n selectClassName,\n allowInlineCreate = true,\n allowAppearance = false,\n appearanceLabels,\n disabled: disabledProp = false,\n showLabelInput = true,\n showManage = true,\n showActiveAppearance = true,\n}: DictionaryEntrySelectProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const [options, setOptions] = React.useState<DictionaryOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [newValue, setNewValue] = React.useState('')\n const [newLabel, setNewLabel] = React.useState('')\n const [formError, setFormError] = React.useState<string | null>(null)\n const appearance = useAppearanceState(null, null)\n\n const loadOptions = React.useCallback(async () => {\n setLoading(true)\n try {\n const items = await fetchOptions()\n setOptions(items.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })))\n } catch (err) {\n console.error('DictionaryEntrySelect.fetchOptions failed', err)\n flash(labels.errorLoad, 'error')\n setOptions([])\n } finally {\n setLoading(false)\n }\n }, [fetchOptions, labels.errorLoad])\n\n React.useEffect(() => {\n loadOptions().catch(() => {})\n }, [loadOptions])\n\n const resetDialogState = React.useCallback(() => {\n setNewValue('')\n setNewLabel('')\n setFormError(null)\n appearance.setColor(null)\n appearance.setIcon(null)\n setSaving(false)\n }, [appearance])\n\n React.useEffect(() => {\n if (!dialogOpen) resetDialogState()\n }, [dialogOpen, resetDialogState])\n\n const activeOption = React.useMemo(\n () => options.find((option) => option.value === value) ?? null,\n [options, value],\n )\n\n const handleCreate = React.useCallback(async () => {\n if (!createOption) return\n const trimmedValue = newValue.trim()\n if (!trimmedValue.length) {\n setFormError(labels.emptyError)\n return\n }\n setSaving(true)\n try {\n const payload = await createOption({\n value: trimmedValue,\n label: showLabelInput ? newLabel.trim() || undefined : undefined,\n color: allowAppearance && appearance.color ? appearance.color : undefined,\n icon: allowAppearance && appearance.icon ? appearance.icon : undefined,\n })\n if (!payload) throw new Error('createOption did not return an entry')\n setOptions((previous) => {\n const map = new Map(previous.map((option) => [option.value, option]))\n map.set(payload.value, {\n value: payload.value,\n label: payload.label,\n color: payload.color ?? null,\n icon: payload.icon ?? null,\n })\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))\n })\n await loadOptions()\n onChange(payload.value)\n setDialogOpen(false)\n if (labels.successCreateLabel) {\n flash(labels.successCreateLabel, 'success')\n }\n } catch (err) {\n console.error('DictionaryEntrySelect.createOption failed', err)\n flash(labels.errorSave, 'error')\n } finally {\n setSaving(false)\n }\n }, [\n allowAppearance,\n appearance.color,\n appearance.icon,\n createOption,\n labels.emptyError,\n labels.errorSave,\n labels.successCreateLabel,\n loadOptions,\n newLabel,\n newValue,\n onChange,\n ])\n\n const handleDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n if (!saving) {\n setDialogOpen(false)\n }\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (!saving && newValue.trim().length) {\n handleCreate().catch(() => {})\n } else if (!saving && !newValue.trim().length) {\n setFormError(labels.emptyError)\n }\n }\n },\n [handleCreate, labels.emptyError, newValue, saving],\n )\n\n const shortcutHint = React.useMemo(() => {\n const provided = typeof labels.saveShortcutHint === 'string' ? labels.saveShortcutHint.trim() : ''\n if (provided.length) return provided\n return '\u2318/Ctrl + Enter'\n }, [labels.saveShortcutHint])\n\n const disabled = disabledProp || loading || saving\n const manageLink = manageHref ?? '/backend/config/dictionaries'\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageLinkWithReturnTo = React.useMemo(\n () => buildHrefWithReturnTo(manageLink, returnTo),\n [manageLink, returnTo],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Select\n value={value || undefined}\n onValueChange={(next) => onChange(next || undefined)}\n disabled={disabled}\n >\n <SelectTrigger\n id={id}\n className={selectClassName}\n title={activeOption?.label ?? undefined}\n >\n <SelectValue placeholder={labels.placeholder} />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <div className=\"flex items-center gap-1\">\n {allowInlineCreate && createOption ? (\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n disabled={disabled}\n title={labels.addLabel}\n aria-label={labels.addLabel}\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\" onKeyDown={handleDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{labels.dialogTitle}</DialogTitle>\n {labels.addPrompt ? <DialogDescription>{labels.addPrompt}</DialogDescription> : null}\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.valueLabel}</label>\n <Input\n type=\"text\"\n value={newValue}\n onChange={(event) => {\n setNewValue(event.target.value)\n if (formError) setFormError(null)\n }}\n placeholder={labels.valuePlaceholder}\n autoFocus\n disabled={saving}\n />\n </div>\n {showLabelInput ? (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.labelLabel}</label>\n <Input\n type=\"text\"\n value={newLabel}\n onChange={(event) => setNewLabel(event.target.value)}\n placeholder={labels.labelPlaceholder}\n disabled={saving}\n />\n </div>\n ) : null}\n {allowAppearance ? (\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n onIconChange={appearance.setIcon}\n onColorChange={appearance.setColor}\n labels={appearanceLabels ?? DEFAULT_APPEARANCE_LABELS}\n />\n ) : null}\n {formError ? <p className=\"text-sm text-red-600\">{formError}</p> : null}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setDialogOpen(false)} disabled={saving}>\n {labels.cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleCreate} disabled={saving || !newValue.trim()}>\n {saving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n <span className=\"flex items-center gap-2\">\n <span>{labels.saveLabel}</span>\n {!saving ? (\n <span className=\"text-xs text-muted-foreground\">{`(${shortcutHint})`}</span>\n ) : null}\n </span>\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n {showManage ? (\n <Button asChild variant=\"ghost\" size=\"icon\" title={labels.manageTitle} aria-label={labels.manageTitle}>\n <Link href={manageLinkWithReturnTo}>\n <Settings className=\"h-4 w-4\" />\n <span className=\"sr-only\">{labels.manageTitle}</span>\n </Link>\n </Button>\n ) : null}\n </div>\n </div>\n {showActiveAppearance && activeOption && (activeOption.icon || activeOption.color) ? (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span className=\"inline-flex items-center gap-2 rounded border border-dashed px-2 py-1\">\n {activeOption.icon ? renderDictionaryIcon(activeOption.icon, 'h-4 w-4') : null}\n {activeOption.color ? renderDictionaryColor(activeOption.color, 'h-4 w-4 rounded-sm') : null}\n </span>\n {activeOption.color ? <span>{activeOption.color}</span> : null}\n </div>\n ) : null}\n {loading ? <div className=\"text-xs text-muted-foreground\">{labels.loadingLabel}</div> : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,13 +16,19 @@ const getCacheTags = (identifier, tenantId) => {
|
|
|
16
16
|
return [getIdentifierTag(identifier), getTenantTag(tenantId)];
|
|
17
17
|
};
|
|
18
18
|
class FeatureTogglesService {
|
|
19
|
-
// 1 minute
|
|
20
19
|
constructor(cache, em) {
|
|
21
20
|
this.cache = cache;
|
|
22
21
|
this.em = em;
|
|
23
22
|
this.cacheTtlMs = 1 * 60 * 1e3;
|
|
23
|
+
// 1 minute
|
|
24
|
+
// Resolution cache can be disabled via env (e.g. integration tests that flip
|
|
25
|
+
// overrides rapidly between cases). The 1-minute TTL is a production
|
|
26
|
+
// optimization; under fast flag churn it can serve a stale value across
|
|
27
|
+
// override set/clear despite invalidation, so tests opt out for determinism.
|
|
28
|
+
this.cacheDisabled = process.env.OM_FEATURE_TOGGLES_CACHE_DISABLED === "1" || process.env.OM_FEATURE_TOGGLES_CACHE_DISABLED === "true";
|
|
24
29
|
}
|
|
25
30
|
async saveCache(identifier, tenantId, result) {
|
|
31
|
+
if (this.cacheDisabled) return;
|
|
26
32
|
const key = getIsEnabledCacheKey(identifier, tenantId);
|
|
27
33
|
await runWithCacheTenant(
|
|
28
34
|
tenantId,
|
|
@@ -31,10 +37,12 @@ class FeatureTogglesService {
|
|
|
31
37
|
}
|
|
32
38
|
async resolveToggle(identifier, tenantId) {
|
|
33
39
|
const key = getIsEnabledCacheKey(identifier, tenantId);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if (!this.cacheDisabled) {
|
|
41
|
+
const cached = await runWithCacheTenant(tenantId, () => this.cache.get(key));
|
|
42
|
+
if (cached) {
|
|
43
|
+
const parsed = toCachedResolution(cached);
|
|
44
|
+
if (parsed) return parsed;
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
let toggle = null;
|
|
40
48
|
toggle = await this.em.findOne(FeatureToggle, { identifier, deletedAt: null });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/feature_toggles/lib/feature-flag-check.ts"],
|
|
4
|
-
"sourcesContent": ["import { FeatureToggle, FeatureToggleOverride } from \"../data/entities\"\nimport { EntityManager } from \"@mikro-orm/core\"\nimport { CacheService, runWithCacheTenant } from \"@open-mercato/cache\"\n\ntype ToggleValueType = \"boolean\" | \"string\" | \"number\" | \"json\"\n\ntype ToggleResolutionSource = \"override\" | \"default\" | \"missing\"\n\ntype ToggleResolutionResult = {\n valueType: ToggleValueType\n value: boolean | string | number | unknown | null\n source: ToggleResolutionSource\n toggleId: string\n identifier: string\n tenantId: string\n}\n\ntype ToggleErrorCode = \"TYPE_MISMATCH\" | \"MISSING_TOGGLE\" | \"INVALID_VALUE\"\n\ntype ToggleError = {\n code: ToggleErrorCode\n message: string\n identifier: string\n expectedType: ToggleValueType\n actualType?: ToggleValueType\n source?: ToggleResolutionSource\n}\n\ntype ResultOk<T> = { ok: true; value: T; resolution: ToggleResolutionResult }\ntype ResultErr = { ok: false; error: ToggleError; resolution: ToggleResolutionResult }\nexport type Result<T> = ResultOk<T> | ResultErr\n\ntype ResolutionContext = {\n tenantId: string\n valueType: ToggleValueType\n}\n\nconst toCachedResolution = (value: unknown): ToggleResolutionResult | null => {\n if (typeof value !== \"object\" || value === null) return null\n const record = value as Partial<ToggleResolutionResult>\n if (\n !record.valueType ||\n typeof record.source !== \"string\" ||\n !record.toggleId ||\n !record.identifier ||\n !record.tenantId\n )\n return null\n return value as ToggleResolutionResult\n}\n\nexport const getIsEnabledCacheKey = (identifier: string, tenantId: string) => {\n return `feature_toggles:resolution:${identifier}:${tenantId}`\n}\n\nconst getIdentifierTag = (identifier: string) => `feature_toggles:identifier:${identifier}`\nconst getTenantTag = (tenantId: string) => `feature_toggles:tenant:${tenantId}`\n\nconst getCacheTags = (identifier: string, tenantId: string) => {\n return [getIdentifierTag(identifier), getTenantTag(tenantId)]\n}\n\nexport class FeatureTogglesService {\n private cacheTtlMs: number = 1 * 60 * 1000 // 1 minute\n constructor(\n private readonly cache: CacheService,\n private readonly em: EntityManager\n ) { }\n\n private async saveCache(\n identifier: string,\n tenantId: string,\n result: ToggleResolutionResult,\n ) {\n const key = getIsEnabledCacheKey(identifier, tenantId)\n await runWithCacheTenant(\n tenantId,\n () => this.cache.set(key, result, { ttl: this.cacheTtlMs, tags: getCacheTags(identifier, tenantId) }),\n )\n }\n\n private async resolveToggle(identifier: string, tenantId: string): Promise<ToggleResolutionResult> {\n const key = getIsEnabledCacheKey(identifier, tenantId)\n\n const cached = await runWithCacheTenant(tenantId, () => this.cache.get(key))\n
|
|
5
|
-
"mappings": "AAAA,SAAS,eAAe,6BAA6B;AAErD,SAAuB,0BAA0B;AAmCjD,MAAM,qBAAqB,CAAC,UAAkD;AAC5E,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,SAAS;AACf,MACE,CAAC,OAAO,aACR,OAAO,OAAO,WAAW,YACzB,CAAC,OAAO,YACR,CAAC,OAAO,cACR,CAAC,OAAO;AAER,WAAO;AACT,SAAO;AACT;AAEO,MAAM,uBAAuB,CAAC,YAAoB,aAAqB;AAC5E,SAAO,8BAA8B,UAAU,IAAI,QAAQ;AAC7D;AAEA,MAAM,mBAAmB,CAAC,eAAuB,8BAA8B,UAAU;AACzF,MAAM,eAAe,CAAC,aAAqB,0BAA0B,QAAQ;AAE7E,MAAM,eAAe,CAAC,YAAoB,aAAqB;AAC7D,SAAO,CAAC,iBAAiB,UAAU,GAAG,aAAa,QAAQ,CAAC;AAC9D;AAEO,MAAM,sBAAsB;AAAA
|
|
4
|
+
"sourcesContent": ["import { FeatureToggle, FeatureToggleOverride } from \"../data/entities\"\nimport { EntityManager } from \"@mikro-orm/core\"\nimport { CacheService, runWithCacheTenant } from \"@open-mercato/cache\"\n\ntype ToggleValueType = \"boolean\" | \"string\" | \"number\" | \"json\"\n\ntype ToggleResolutionSource = \"override\" | \"default\" | \"missing\"\n\ntype ToggleResolutionResult = {\n valueType: ToggleValueType\n value: boolean | string | number | unknown | null\n source: ToggleResolutionSource\n toggleId: string\n identifier: string\n tenantId: string\n}\n\ntype ToggleErrorCode = \"TYPE_MISMATCH\" | \"MISSING_TOGGLE\" | \"INVALID_VALUE\"\n\ntype ToggleError = {\n code: ToggleErrorCode\n message: string\n identifier: string\n expectedType: ToggleValueType\n actualType?: ToggleValueType\n source?: ToggleResolutionSource\n}\n\ntype ResultOk<T> = { ok: true; value: T; resolution: ToggleResolutionResult }\ntype ResultErr = { ok: false; error: ToggleError; resolution: ToggleResolutionResult }\nexport type Result<T> = ResultOk<T> | ResultErr\n\ntype ResolutionContext = {\n tenantId: string\n valueType: ToggleValueType\n}\n\nconst toCachedResolution = (value: unknown): ToggleResolutionResult | null => {\n if (typeof value !== \"object\" || value === null) return null\n const record = value as Partial<ToggleResolutionResult>\n if (\n !record.valueType ||\n typeof record.source !== \"string\" ||\n !record.toggleId ||\n !record.identifier ||\n !record.tenantId\n )\n return null\n return value as ToggleResolutionResult\n}\n\nexport const getIsEnabledCacheKey = (identifier: string, tenantId: string) => {\n return `feature_toggles:resolution:${identifier}:${tenantId}`\n}\n\nconst getIdentifierTag = (identifier: string) => `feature_toggles:identifier:${identifier}`\nconst getTenantTag = (tenantId: string) => `feature_toggles:tenant:${tenantId}`\n\nconst getCacheTags = (identifier: string, tenantId: string) => {\n return [getIdentifierTag(identifier), getTenantTag(tenantId)]\n}\n\nexport class FeatureTogglesService {\n private cacheTtlMs: number = 1 * 60 * 1000 // 1 minute\n // Resolution cache can be disabled via env (e.g. integration tests that flip\n // overrides rapidly between cases). The 1-minute TTL is a production\n // optimization; under fast flag churn it can serve a stale value across\n // override set/clear despite invalidation, so tests opt out for determinism.\n private readonly cacheDisabled: boolean =\n process.env.OM_FEATURE_TOGGLES_CACHE_DISABLED === '1' ||\n process.env.OM_FEATURE_TOGGLES_CACHE_DISABLED === 'true'\n constructor(\n private readonly cache: CacheService,\n private readonly em: EntityManager\n ) { }\n\n private async saveCache(\n identifier: string,\n tenantId: string,\n result: ToggleResolutionResult,\n ) {\n if (this.cacheDisabled) return\n const key = getIsEnabledCacheKey(identifier, tenantId)\n await runWithCacheTenant(\n tenantId,\n () => this.cache.set(key, result, { ttl: this.cacheTtlMs, tags: getCacheTags(identifier, tenantId) }),\n )\n }\n\n private async resolveToggle(identifier: string, tenantId: string): Promise<ToggleResolutionResult> {\n const key = getIsEnabledCacheKey(identifier, tenantId)\n\n if (!this.cacheDisabled) {\n const cached = await runWithCacheTenant(tenantId, () => this.cache.get(key))\n if (cached) {\n const parsed = toCachedResolution(cached)\n if (parsed) return parsed\n }\n }\n\n let toggle: FeatureToggle | null = null\n toggle = await this.em.findOne(FeatureToggle, { identifier, deletedAt: null })\n\n if (!toggle) {\n const result: ToggleResolutionResult = {\n valueType: \"boolean\",\n value: null,\n source: \"missing\",\n toggleId: \"\",\n identifier,\n tenantId,\n }\n return result\n }\n\n let override: FeatureToggleOverride | null = null\n override = await this.em.findOne(FeatureToggleOverride, { toggle: toggle.id, tenantId })\n\n\n const result: ToggleResolutionResult = {\n valueType: toggle.type,\n value: override ? override.value : toggle.defaultValue,\n source: override ? \"override\" : \"default\",\n toggleId: toggle.id,\n identifier: toggle.identifier,\n tenantId,\n }\n\n await this.saveCache(identifier, tenantId, result)\n return result\n }\n\n public async invalidateIsEnabledCacheByIdentifierTag(identifier: string) {\n await this.cache.deleteByTags([getIdentifierTag(identifier)])\n }\n\n public async invalidateIsEnabledCacheByKey(identifier: string, tenantId: string) {\n await runWithCacheTenant(tenantId, () => this.cache.delete(getIsEnabledCacheKey(identifier, tenantId)))\n }\n\n public async getFeatureToggleValue<T>(\n identifier: string,\n ctx: ResolutionContext\n ): Promise<Result<T>> {\n const resolution = await this.resolveToggle(identifier, ctx.tenantId)\n\n if (resolution.source === \"missing\") {\n console.warn(`[feature_toggles] Toggle \"${identifier}\" not found (missing).`)\n return {\n ok: false,\n error: {\n code: \"MISSING_TOGGLE\",\n message: `Toggle \"${identifier}\" not found (missing).`,\n identifier,\n expectedType: ctx.valueType,\n actualType: resolution.valueType,\n source: resolution.source,\n },\n resolution,\n }\n }\n\n\n\n if (resolution.valueType !== ctx.valueType) {\n console.error(\n `[feature_toggles] Toggle \"${identifier}\" has type \"${resolution.valueType}\" but \"${ctx.valueType}\" was requested.`,\n { resolution }\n )\n return {\n ok: false,\n error: {\n code: \"TYPE_MISMATCH\",\n message: `Toggle \"${identifier}\" has type \"${resolution.valueType}\" but \"${ctx.valueType}\" was requested.`,\n identifier,\n expectedType: ctx.valueType,\n actualType: resolution.valueType,\n source: resolution.source,\n },\n resolution,\n }\n }\n\n const isValueValid =\n (ctx.valueType === \"boolean\" && typeof resolution.value === \"boolean\") ||\n (ctx.valueType === \"string\" && typeof resolution.value === \"string\") ||\n (ctx.valueType === \"number\" && typeof resolution.value === \"number\") ||\n (ctx.valueType === \"json\")\n\n if (!isValueValid) {\n console.error(\n `[feature_toggles] Toggle \"${identifier}\" has invalid value for type \"${resolution.valueType}\".`,\n { resolution }\n )\n return {\n ok: false,\n error: {\n code: \"INVALID_VALUE\",\n message: `Toggle \"${identifier}\" has invalid value for type \"${resolution.valueType}\".`,\n identifier,\n expectedType: ctx.valueType,\n actualType: resolution.valueType,\n source: resolution.source,\n },\n resolution,\n }\n }\n\n return {\n ok: true,\n value: resolution.value as T,\n resolution,\n }\n }\n\n public async getBoolConfig(identifier: string, tenantId: string) {\n return this.getFeatureToggleValue<boolean>(identifier, { tenantId, valueType: \"boolean\" })\n }\n\n public async getNumberConfig(identifier: string, tenantId: string) {\n return this.getFeatureToggleValue<number>(identifier, { tenantId, valueType: \"number\" })\n }\n\n public async getStringConfig(identifier: string, tenantId: string) {\n return this.getFeatureToggleValue<string>(identifier, { tenantId, valueType: \"string\" })\n }\n\n public async getJsonConfig<T = unknown>(identifier: string, tenantId: string) {\n return this.getFeatureToggleValue<T>(identifier, { tenantId, valueType: \"json\" })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["result"]
|
|
7
7
|
}
|
|
@@ -3,6 +3,11 @@ import { refreshCoverageSnapshot } from "../lib/coverage.js";
|
|
|
3
3
|
const metadata = { event: "query_index.coverage.refresh", persistent: false };
|
|
4
4
|
const DEFAULT_DELAY_MS = 0;
|
|
5
5
|
const pending = /* @__PURE__ */ new Map();
|
|
6
|
+
function forkRefreshEntityManager(em) {
|
|
7
|
+
const fork = em.fork;
|
|
8
|
+
if (typeof fork !== "function") return em;
|
|
9
|
+
return fork.call(em, { clear: true, freshEventManager: true, useContext: false });
|
|
10
|
+
}
|
|
6
11
|
function scopeKey(input) {
|
|
7
12
|
const entity = String(input.entityType || "");
|
|
8
13
|
const tenant = input.tenantId ?? "__null__";
|
|
@@ -19,9 +24,9 @@ async function handle(payload, ctx) {
|
|
|
19
24
|
const organizationId = payload?.organizationId ?? null;
|
|
20
25
|
const withDeleted = payload?.withDeleted === true;
|
|
21
26
|
const delayMs = typeof payload?.delayMs === "number" && payload.delayMs >= 0 ? payload.delayMs : DEFAULT_DELAY_MS;
|
|
22
|
-
const em = ctx.resolve("em");
|
|
23
27
|
const key = scopeKey({ entityType, tenantId, organizationId, withDeleted });
|
|
24
28
|
const handleRefresh = async () => {
|
|
29
|
+
const em = forkRefreshEntityManager(ctx.resolve("em"));
|
|
25
30
|
try {
|
|
26
31
|
await refreshCoverageSnapshot(em, { entityType, tenantId, organizationId, withDeleted });
|
|
27
32
|
} catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/query_index/subscribers/coverage_refresh.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { refreshCoverageSnapshot } from '../lib/coverage'\n\nexport const metadata = { event: 'query_index.coverage.refresh', persistent: false }\n\ntype Payload = {\n entityType?: string\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n delayMs?: number\n}\n\nconst DEFAULT_DELAY_MS = 0\nconst pending = new Map<string, NodeJS.Timeout>()\n\nfunction scopeKey(input: Payload): string {\n const entity = String(input.entityType || '')\n const tenant = input.tenantId ?? '__null__'\n const org = input.organizationId ?? '__null__'\n const deleted = input.withDeleted ? '1' : '0'\n\n return `${entity}|${tenant}|${org}|${deleted}`\n}\n\nexport default async function handle(payload: Payload, ctx: { resolve: <T = any>(name: string) => T }) {\n const entityType = String(payload?.entityType || '')\n if (!entityType) {\n return\n }\n\n const tenantId = payload?.tenantId ?? null\n const organizationId = payload?.organizationId ?? null\n const withDeleted = payload?.withDeleted === true\n const delayMs = typeof payload?.delayMs === 'number' && payload.delayMs >= 0 ? payload.delayMs : DEFAULT_DELAY_MS\n\n const
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AAEjC,MAAM,WAAW,EAAE,OAAO,gCAAgC,YAAY,MAAM;AAUnF,MAAM,mBAAmB;AACzB,MAAM,UAAU,oBAAI,IAA4B;AAEhD,SAAS,SAAS,OAAwB;AACxC,QAAM,SAAS,OAAO,MAAM,cAAc,EAAE;AAC5C,QAAM,SAAS,MAAM,YAAY;AACjC,QAAM,MAAM,MAAM,kBAAkB;AACpC,QAAM,UAAU,MAAM,cAAc,MAAM;AAE1C,SAAO,GAAG,MAAM,IAAI,MAAM,IAAI,GAAG,IAAI,OAAO;AAC9C;AAEA,eAAO,OAA8B,SAAkB,KAAgD;AACrG,QAAM,aAAa,OAAO,SAAS,cAAc,EAAE;AACnD,MAAI,CAAC,YAAY;AACf;AAAA,EACF;AAEA,QAAM,WAAW,SAAS,YAAY;AACtC,QAAM,iBAAiB,SAAS,kBAAkB;AAClD,QAAM,cAAc,SAAS,gBAAgB;AAC7C,QAAM,UAAU,OAAO,SAAS,YAAY,YAAY,QAAQ,WAAW,IAAI,QAAQ,UAAU;AAEjG,QAAM,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { refreshCoverageSnapshot } from '../lib/coverage'\n\nexport const metadata = { event: 'query_index.coverage.refresh', persistent: false }\n\ntype Payload = {\n entityType?: string\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n delayMs?: number\n}\n\nconst DEFAULT_DELAY_MS = 0\nconst pending = new Map<string, NodeJS.Timeout>()\n\nfunction forkRefreshEntityManager(em: EntityManager): EntityManager {\n const fork = (em as unknown as { fork?: (options?: Record<string, unknown>) => EntityManager }).fork\n if (typeof fork !== 'function') return em\n return fork.call(em, { clear: true, freshEventManager: true, useContext: false })\n}\n\nfunction scopeKey(input: Payload): string {\n const entity = String(input.entityType || '')\n const tenant = input.tenantId ?? '__null__'\n const org = input.organizationId ?? '__null__'\n const deleted = input.withDeleted ? '1' : '0'\n\n return `${entity}|${tenant}|${org}|${deleted}`\n}\n\nexport default async function handle(payload: Payload, ctx: { resolve: <T = any>(name: string) => T }) {\n const entityType = String(payload?.entityType || '')\n if (!entityType) {\n return\n }\n\n const tenantId = payload?.tenantId ?? null\n const organizationId = payload?.organizationId ?? null\n const withDeleted = payload?.withDeleted === true\n const delayMs = typeof payload?.delayMs === 'number' && payload.delayMs >= 0 ? payload.delayMs : DEFAULT_DELAY_MS\n\n const key = scopeKey({ entityType, tenantId, organizationId, withDeleted })\n\n const handleRefresh = async () => {\n const em = forkRefreshEntityManager(ctx.resolve<EntityManager>('em'))\n try {\n await refreshCoverageSnapshot(em, { entityType, tenantId, organizationId, withDeleted })\n } catch (err) {\n console.warn('[query_index] Failed to refresh coverage snapshot', {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n error: err instanceof Error ? err.message : err,\n })\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.coverage.refresh',\n error: err,\n entityType,\n tenantId,\n organizationId,\n payload,\n },\n )\n }\n }\n\n if (delayMs === 0) {\n await handleRefresh()\n return\n }\n\n const existing = pending.get(key)\n if (existing) {\n clearTimeout(existing)\n }\n\n const timer = setTimeout(() => {\n pending.delete(key)\n void handleRefresh()\n }, delayMs)\n if (typeof timer.unref === 'function') {\n timer.unref()\n }\n pending.set(key, timer)\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AAEjC,MAAM,WAAW,EAAE,OAAO,gCAAgC,YAAY,MAAM;AAUnF,MAAM,mBAAmB;AACzB,MAAM,UAAU,oBAAI,IAA4B;AAEhD,SAAS,yBAAyB,IAAkC;AAClE,QAAM,OAAQ,GAAkF;AAChG,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,SAAO,KAAK,KAAK,IAAI,EAAE,OAAO,MAAM,mBAAmB,MAAM,YAAY,MAAM,CAAC;AAClF;AAEA,SAAS,SAAS,OAAwB;AACxC,QAAM,SAAS,OAAO,MAAM,cAAc,EAAE;AAC5C,QAAM,SAAS,MAAM,YAAY;AACjC,QAAM,MAAM,MAAM,kBAAkB;AACpC,QAAM,UAAU,MAAM,cAAc,MAAM;AAE1C,SAAO,GAAG,MAAM,IAAI,MAAM,IAAI,GAAG,IAAI,OAAO;AAC9C;AAEA,eAAO,OAA8B,SAAkB,KAAgD;AACrG,QAAM,aAAa,OAAO,SAAS,cAAc,EAAE;AACnD,MAAI,CAAC,YAAY;AACf;AAAA,EACF;AAEA,QAAM,WAAW,SAAS,YAAY;AACtC,QAAM,iBAAiB,SAAS,kBAAkB;AAClD,QAAM,cAAc,SAAS,gBAAgB;AAC7C,QAAM,UAAU,OAAO,SAAS,YAAY,YAAY,QAAQ,WAAW,IAAI,QAAQ,UAAU;AAEjG,QAAM,MAAM,SAAS,EAAE,YAAY,UAAU,gBAAgB,YAAY,CAAC;AAE1E,QAAM,gBAAgB,YAAY;AAChC,UAAM,KAAK,yBAAyB,IAAI,QAAuB,IAAI,CAAC;AACpE,QAAI;AACF,YAAM,wBAAwB,IAAI,EAAE,YAAY,UAAU,gBAAgB,YAAY,CAAC;AAAA,IACzF,SAAS,KAAK;AACZ,cAAQ,KAAK,qDAAqD;AAAA,QAChE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,MAC9C,CAAC;AACD,YAAM;AAAA,QACJ,EAAE,GAAG;AAAA,QACL;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,OAAO;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,GAAG;AACjB,UAAM,cAAc;AACpB;AAAA,EACF;AAEA,QAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,MAAI,UAAU;AACZ,iBAAa,QAAQ;AAAA,EACvB;AAEA,QAAM,QAAQ,WAAW,MAAM;AAC7B,YAAQ,OAAO,GAAG;AAClB,SAAK,cAAc;AAAA,EACrB,GAAG,OAAO;AACV,MAAI,OAAO,MAAM,UAAU,YAAY;AACrC,UAAM,MAAM;AAAA,EACd;AACA,UAAQ,IAAI,KAAK,KAAK;AACxB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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:
|
|
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
|
}
|