Importer update add support for meta data and multiple inserts for some entities like addresses and phones, updated other things
This commit is contained in:
@@ -130,6 +130,24 @@ function evaluateMappingSaved() {
|
||||
persistedSignature.value = computeMappingSignature(mappingRows.value);
|
||||
}
|
||||
|
||||
function normalizeOptions(val) {
|
||||
if (!val) {
|
||||
return {};
|
||||
}
|
||||
if (typeof val === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(val);
|
||||
return parsed && typeof parsed === "object" ? parsed : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (typeof val === "object") {
|
||||
return val;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function computeMappingSignature(rows) {
|
||||
return rows
|
||||
.filter((r) => r && r.source_column)
|
||||
@@ -270,6 +288,7 @@ function defaultEntityDefs() {
|
||||
"description",
|
||||
"type_id",
|
||||
"client_case_id",
|
||||
"meta",
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -359,6 +378,7 @@ const displayRows = computed(() => {
|
||||
skip: false,
|
||||
transform: "trim",
|
||||
apply_mode: "both",
|
||||
options: {},
|
||||
position: idx,
|
||||
}));
|
||||
});
|
||||
@@ -570,6 +590,7 @@ async function fetchColumns() {
|
||||
skip: false,
|
||||
transform: "trim",
|
||||
apply_mode: "both",
|
||||
options: {},
|
||||
position: idx,
|
||||
}));
|
||||
suppressMappingWatch = false;
|
||||
@@ -592,6 +613,7 @@ async function fetchColumns() {
|
||||
skip: false,
|
||||
transform: m.transform || "trim",
|
||||
apply_mode: m.apply_mode || "both",
|
||||
options: normalizeOptions(m.options),
|
||||
position: idx,
|
||||
};
|
||||
});
|
||||
@@ -685,6 +707,7 @@ async function loadImportMappings() {
|
||||
field,
|
||||
transform: m.transform || "",
|
||||
apply_mode: m.apply_mode || "both",
|
||||
options: normalizeOptions(m.options) || r.options || {},
|
||||
skip: false,
|
||||
position: idx,
|
||||
};
|
||||
@@ -738,7 +761,13 @@ async function saveMappings() {
|
||||
target_field: `${entityKeyToRecord(r.entity)}.${r.field}`,
|
||||
transform: r.transform || null,
|
||||
apply_mode: r.apply_mode || "both",
|
||||
options: null,
|
||||
options:
|
||||
r.field === "meta"
|
||||
? {
|
||||
key: r.options?.key ?? null,
|
||||
type: r.options?.type ?? null,
|
||||
}
|
||||
: null,
|
||||
}));
|
||||
if (!mappings.length) {
|
||||
mappingSaved.value = false;
|
||||
@@ -820,6 +849,7 @@ onMounted(async () => {
|
||||
skip: false,
|
||||
transform: "trim",
|
||||
apply_mode: "both",
|
||||
options: {},
|
||||
position: idx,
|
||||
};
|
||||
});
|
||||
@@ -877,6 +907,7 @@ watch(
|
||||
skip: false,
|
||||
transform: "trim",
|
||||
apply_mode: "both",
|
||||
options: {},
|
||||
position: idx,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -35,6 +35,8 @@ function duplicateTarget(row){
|
||||
<th class="p-2 border">Source column</th>
|
||||
<th class="p-2 border">Entity</th>
|
||||
<th class="p-2 border">Field</th>
|
||||
<th class="p-2 border">Meta key</th>
|
||||
<th class="p-2 border">Meta type</th>
|
||||
<th class="p-2 border">Transform</th>
|
||||
<th class="p-2 border">Apply mode</th>
|
||||
<th class="p-2 border">Skip</th>
|
||||
@@ -55,6 +57,32 @@ function duplicateTarget(row){
|
||||
<option v-for="f in fieldsForEntity(row.entity)" :key="f" :value="f">{{ f }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="p-2 border">
|
||||
<input
|
||||
v-if="row.field === 'meta'"
|
||||
v-model="(row.options ||= {}).key"
|
||||
type="text"
|
||||
class="border rounded p-1 w-full"
|
||||
placeholder="e.g. monthly_rent"
|
||||
:disabled="isCompleted"
|
||||
/>
|
||||
<span v-else class="text-gray-400 text-xs">—</span>
|
||||
</td>
|
||||
<td class="p-2 border">
|
||||
<select
|
||||
v-if="row.field === 'meta'"
|
||||
v-model="(row.options ||= {}).type"
|
||||
class="border rounded p-1 w-full"
|
||||
:disabled="isCompleted"
|
||||
>
|
||||
<option :value="null">Default (string)</option>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
<option value="date">date</option>
|
||||
<option value="boolean">boolean</option>
|
||||
</select>
|
||||
<span v-else class="text-gray-400 text-xs">—</span>
|
||||
</td>
|
||||
<td class="p-2 border">
|
||||
<select v-model="row.transform" class="border rounded p-1 w-full" :disabled="isCompleted">
|
||||
<option value="">None</option>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
const props = defineProps({ mappings: Array })
|
||||
const props = defineProps({ mappings: Array });
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="mappings?.length" class="pt-4">
|
||||
@@ -12,14 +12,30 @@ const props = defineProps({ mappings: Array })
|
||||
<th class="p-2 border">Target field</th>
|
||||
<th class="p-2 border">Transform</th>
|
||||
<th class="p-2 border">Mode</th>
|
||||
<th class="p-2 border">Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in mappings" :key="m.id || (m.source_column + m.target_field)" class="border-t">
|
||||
<tr
|
||||
v-for="m in mappings"
|
||||
:key="m.id || m.source_column + m.target_field"
|
||||
class="border-t"
|
||||
>
|
||||
<td class="p-2 border">{{ m.source_column }}</td>
|
||||
<td class="p-2 border">{{ m.target_field }}</td>
|
||||
<td class="p-2 border">{{ m.transform || '—' }}</td>
|
||||
<td class="p-2 border">{{ m.apply_mode || 'both' }}</td>
|
||||
<td class="p-2 border">{{ m.transform || "—" }}</td>
|
||||
<td class="p-2 border">{{ m.apply_mode || "both" }}</td>
|
||||
<td class="p-2 border">
|
||||
<template v-if="m.options">
|
||||
<span v-if="m.options.key" class="inline-block mr-2"
|
||||
>key: <strong>{{ m.options.key }}</strong></span
|
||||
>
|
||||
<span v-if="m.options.type" class="inline-block"
|
||||
>type: <strong>{{ m.options.type }}</strong></span
|
||||
>
|
||||
</template>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -99,23 +99,45 @@ const entityStats = computed(() => {
|
||||
if (!r.entities) continue;
|
||||
for (const [k, ent] of Object.entries(r.entities)) {
|
||||
if (!stats[k]) continue;
|
||||
// Count one row per entity root
|
||||
stats[k].total_rows++;
|
||||
switch (ent.action) {
|
||||
case "create":
|
||||
stats[k].create++;
|
||||
break;
|
||||
case "update":
|
||||
stats[k].update++;
|
||||
break;
|
||||
case "missing_ref":
|
||||
stats[k].missing_ref++;
|
||||
break;
|
||||
case "invalid":
|
||||
stats[k].invalid++;
|
||||
break;
|
||||
if (Array.isArray(ent)) {
|
||||
for (const item of ent) {
|
||||
switch (item.action) {
|
||||
case "create":
|
||||
stats[k].create++;
|
||||
break;
|
||||
case "update":
|
||||
stats[k].update++;
|
||||
break;
|
||||
case "missing_ref":
|
||||
stats[k].missing_ref++;
|
||||
break;
|
||||
case "invalid":
|
||||
stats[k].invalid++;
|
||||
break;
|
||||
}
|
||||
if (item.duplicate) stats[k].duplicate++;
|
||||
if (item.duplicate_db) stats[k].duplicate_db++;
|
||||
}
|
||||
} else {
|
||||
switch (ent.action) {
|
||||
case "create":
|
||||
stats[k].create++;
|
||||
break;
|
||||
case "update":
|
||||
stats[k].update++;
|
||||
break;
|
||||
case "missing_ref":
|
||||
stats[k].missing_ref++;
|
||||
break;
|
||||
case "invalid":
|
||||
stats[k].invalid++;
|
||||
break;
|
||||
}
|
||||
if (ent.duplicate) stats[k].duplicate++;
|
||||
if (ent.duplicate_db) stats[k].duplicate_db++;
|
||||
}
|
||||
if (ent.duplicate) stats[k].duplicate++;
|
||||
if (ent.duplicate_db) stats[k].duplicate_db++;
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
@@ -134,7 +156,9 @@ const visibleRows = computed(() => {
|
||||
.filter((r) => {
|
||||
if (!r.entities || !r.entities[activeEntity.value]) return false;
|
||||
const ent = r.entities[activeEntity.value];
|
||||
if (hideChain.value && ent.existing_chain) return false;
|
||||
if (!Array.isArray(ent)) {
|
||||
if (hideChain.value && ent.existing_chain) return false;
|
||||
}
|
||||
if (showOnlyChanged.value) {
|
||||
// Define change criteria per entity
|
||||
if (activeEntity.value === "account") {
|
||||
@@ -148,6 +172,9 @@ const visibleRows = computed(() => {
|
||||
return ent.amount !== null && ent.amount !== undefined;
|
||||
}
|
||||
// Generic entities: any create/update considered change
|
||||
if (Array.isArray(ent)) {
|
||||
return ent.some((i) => i && (i.action === "create" || i.action === "update"));
|
||||
}
|
||||
if (ent.action === "create" || ent.action === "update") return true;
|
||||
return false;
|
||||
}
|
||||
@@ -371,36 +398,45 @@ function referenceOf(entityName, ent) {
|
||||
class="font-semibold uppercase tracking-wide text-gray-600 mb-1 flex items-center justify-between"
|
||||
>
|
||||
<span>{{ activeEntity }}</span>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].action_label"
|
||||
:class="[
|
||||
'text-[10px] px-1 py-0.5 rounded',
|
||||
r.entities[activeEntity].action === 'create' && 'bg-emerald-100 text-emerald-700',
|
||||
r.entities[activeEntity].action === 'update' && 'bg-blue-100 text-blue-700',
|
||||
r.entities[activeEntity].action === 'reactivate' && 'bg-purple-100 text-purple-700 font-semibold',
|
||||
r.entities[activeEntity].action === 'skip' && 'bg-gray-100 text-gray-600',
|
||||
r.entities[activeEntity].action === 'implicit' && 'bg-teal-100 text-teal-700'
|
||||
].filter(Boolean)"
|
||||
>{{ r.entities[activeEntity].action_label }}</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].existing_chain"
|
||||
class="ml-1 text-[9px] px-1 py-0.5 rounded bg-indigo-100 text-indigo-700"
|
||||
title="Iz obstoječe verige (contract → client_case → person)"
|
||||
>chain</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].inherited_reference"
|
||||
class="ml-1 text-[9px] px-1 py-0.5 rounded bg-amber-100 text-amber-700"
|
||||
title="Referenca podedovana"
|
||||
>inh</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].action === 'implicit'"
|
||||
class="ml-1 text-[9px] px-1 py-0.5 rounded bg-teal-100 text-teal-700"
|
||||
title="Implicitno"
|
||||
>impl</span
|
||||
>
|
||||
<template v-if="!Array.isArray(r.entities[activeEntity])">
|
||||
<span
|
||||
v-if="r.entities[activeEntity].action_label"
|
||||
:class="
|
||||
[
|
||||
'text-[10px] px-1 py-0.5 rounded',
|
||||
r.entities[activeEntity].action === 'create' &&
|
||||
'bg-emerald-100 text-emerald-700',
|
||||
r.entities[activeEntity].action === 'update' &&
|
||||
'bg-blue-100 text-blue-700',
|
||||
r.entities[activeEntity].action === 'reactivate' &&
|
||||
'bg-purple-100 text-purple-700 font-semibold',
|
||||
r.entities[activeEntity].action === 'skip' &&
|
||||
'bg-gray-100 text-gray-600',
|
||||
r.entities[activeEntity].action === 'implicit' &&
|
||||
'bg-teal-100 text-teal-700',
|
||||
].filter(Boolean)
|
||||
"
|
||||
>{{ r.entities[activeEntity].action_label }}</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].existing_chain"
|
||||
class="ml-1 text-[9px] px-1 py-0.5 rounded bg-indigo-100 text-indigo-700"
|
||||
title="Iz obstoječe verige (contract → client_case → person)"
|
||||
>chain</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].inherited_reference"
|
||||
class="ml-1 text-[9px] px-1 py-0.5 rounded bg-amber-100 text-amber-700"
|
||||
title="Referenca podedovana"
|
||||
>inh</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].action === 'implicit'"
|
||||
class="ml-1 text-[9px] px-1 py-0.5 rounded bg-teal-100 text-teal-700"
|
||||
title="Implicitno"
|
||||
>impl</span
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template v-if="activeEntity === 'account'">
|
||||
@@ -510,10 +546,13 @@ function referenceOf(entityName, ent) {
|
||||
<div>
|
||||
Akcija:
|
||||
<span
|
||||
:class="[
|
||||
'font-medium inline-flex items-center gap-1',
|
||||
r.entities[activeEntity].action === 'reactivate' && 'text-purple-700'
|
||||
].filter(Boolean)"
|
||||
:class="
|
||||
[
|
||||
'font-medium inline-flex items-center gap-1',
|
||||
r.entities[activeEntity].action === 'reactivate' &&
|
||||
'text-purple-700',
|
||||
].filter(Boolean)
|
||||
"
|
||||
>{{
|
||||
r.entities[activeEntity].action_label ||
|
||||
r.entities[activeEntity].action
|
||||
@@ -526,179 +565,319 @@ function referenceOf(entityName, ent) {
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].original_action === 'update' && r.entities[activeEntity].action === 'reactivate'" class="text-[10px] text-purple-600 mt-0.5">
|
||||
<div
|
||||
v-if="
|
||||
r.entities[activeEntity].original_action === 'update' &&
|
||||
r.entities[activeEntity].action === 'reactivate'
|
||||
"
|
||||
class="text-[10px] text-purple-600 mt-0.5"
|
||||
>
|
||||
(iz neaktivnega → aktivno)
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].meta"
|
||||
class="mt-1 text-[10px] text-gray-700"
|
||||
>
|
||||
<div class="font-semibold text-gray-600">Meta</div>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="(entries, grp) in r.entities[activeEntity].meta"
|
||||
:key="grp"
|
||||
class="border rounded p-1 bg-white"
|
||||
>
|
||||
<div class="text-[9px] text-gray-500 mb-0.5">
|
||||
skupina: {{ grp }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(entry, key) in entries"
|
||||
:key="key"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<span class="text-gray-500">{{ key }}:</span>
|
||||
<span class="text-gray-800">{{ entry?.value ?? "—" }}</span>
|
||||
<span class="text-gray-400">(iz: {{ entry?.title }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex flex-wrap gap-1 mb-1">
|
||||
<span
|
||||
v-if="r.entities[activeEntity].identity_used"
|
||||
class="px-1 py-0.5 rounded bg-indigo-50 text-indigo-700 text-[10px]"
|
||||
title="Uporabljena identiteta"
|
||||
>{{ r.entities[activeEntity].identity_used }}</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].duplicate"
|
||||
class="px-1 py-0.5 rounded bg-amber-100 text-amber-700 text-[10px]"
|
||||
title="Podvojen v tej seriji"
|
||||
>duplikat</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].duplicate_db"
|
||||
class="px-1 py-0.5 rounded bg-amber-200 text-amber-800 text-[10px]"
|
||||
title="Že obstaja v bazi"
|
||||
>obstaja v bazi</span
|
||||
>
|
||||
</div>
|
||||
<template v-if="activeEntity === 'person'">
|
||||
<div class="grid grid-cols-1 gap-0.5">
|
||||
<!-- Multi-item rendering for grouped roots (email/phone/address) -->
|
||||
<template v-if="Array.isArray(r.entities[activeEntity])">
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-if="
|
||||
referenceOf(activeEntity, r.entities[activeEntity]) !== '—'
|
||||
"
|
||||
class="text-[10px] text-gray-600"
|
||||
v-for="(item, idx) in r.entities[activeEntity]"
|
||||
:key="idx"
|
||||
class="border rounded p-2 bg-white"
|
||||
>
|
||||
Ref:
|
||||
<span class="font-medium text-gray-800">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].full_name"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Ime:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].full_name
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
r.entities[activeEntity].first_name ||
|
||||
r.entities[activeEntity].last_name
|
||||
"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Ime:
|
||||
<span class="font-medium">{{
|
||||
[
|
||||
r.entities[activeEntity].first_name,
|
||||
r.entities[activeEntity].last_name,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].birthday"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Rojstvo:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].birthday
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].description"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Opis:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].description
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].identity_candidates?.length"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Identitete:
|
||||
{{ r.entities[activeEntity].identity_candidates.join(", ") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeEntity === 'email'"
|
||||
><div class="text-[10px] text-gray-600">
|
||||
Email:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div></template
|
||||
>
|
||||
<template v-else-if="activeEntity === 'phone'"
|
||||
><div class="text-[10px] text-gray-600">
|
||||
Telefon:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div></template
|
||||
>
|
||||
<template v-else-if="activeEntity === 'address'">
|
||||
<div class="text-[10px] text-gray-600 space-y-0.5">
|
||||
<div
|
||||
v-if="
|
||||
referenceOf(activeEntity, r.entities[activeEntity]) !== '—'
|
||||
"
|
||||
>
|
||||
Ref:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].address">
|
||||
Naslov:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].address
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
r.entities[activeEntity].postal_code ||
|
||||
r.entities[activeEntity].country
|
||||
"
|
||||
>
|
||||
Lokacija:
|
||||
<span class="font-medium">{{
|
||||
[
|
||||
r.entities[activeEntity].postal_code,
|
||||
r.entities[activeEntity].country,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeEntity === 'client_case'">
|
||||
<div class="text-[10px] text-gray-600 space-y-0.5">
|
||||
<div
|
||||
v-if="
|
||||
referenceOf(activeEntity, r.entities[activeEntity]) !== '—'
|
||||
"
|
||||
>
|
||||
Ref:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].title">
|
||||
Naslov:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].title
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].status">
|
||||
Status:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].status
|
||||
}}</span>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="
|
||||
item.group !== undefined &&
|
||||
item.group !== null &&
|
||||
String(item.group) !== ''
|
||||
"
|
||||
class="text-[10px] px-1 py-0.5 rounded bg-gray-100 text-gray-700"
|
||||
>skupina: {{ item.group }}</span
|
||||
>
|
||||
<span
|
||||
v-if="item.identity_used"
|
||||
class="px-1 py-0.5 rounded bg-indigo-50 text-indigo-700 text-[10px]"
|
||||
title="Uporabljena identiteta"
|
||||
>{{ item.identity_used }}</span
|
||||
>
|
||||
<span
|
||||
v-if="item.duplicate"
|
||||
class="px-1 py-0.5 rounded bg-amber-100 text-amber-700 text-[10px]"
|
||||
title="Podvojen v tej seriji"
|
||||
>duplikat</span
|
||||
>
|
||||
<span
|
||||
v-if="item.duplicate_db"
|
||||
class="px-1 py-0.5 rounded bg-amber-200 text-amber-800 text-[10px]"
|
||||
title="Že obstaja v bazi"
|
||||
>obstaja v bazi</span
|
||||
>
|
||||
</div>
|
||||
<span
|
||||
v-if="item.action_label"
|
||||
:class="
|
||||
[
|
||||
'text-[10px] px-1 py-0.5 rounded',
|
||||
item.action === 'create' &&
|
||||
'bg-emerald-100 text-emerald-700',
|
||||
item.action === 'update' &&
|
||||
'bg-blue-100 text-blue-700',
|
||||
item.action === 'skip' && 'bg-gray-100 text-gray-600',
|
||||
].filter(Boolean)
|
||||
"
|
||||
>{{ item.action_label || item.action }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<template v-if="activeEntity === 'email'">
|
||||
<div class="text-[10px] text-gray-600">
|
||||
Email:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, item)
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeEntity === 'phone'">
|
||||
<div class="text-[10px] text-gray-600">
|
||||
Telefon:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, item)
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeEntity === 'address'">
|
||||
<div class="text-[10px] text-gray-600 space-y-0.5">
|
||||
<div v-if="referenceOf(activeEntity, item) !== '—'">
|
||||
Ref:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, item)
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="item.address">
|
||||
Naslov:
|
||||
<span class="font-medium">{{ item.address }}</span>
|
||||
</div>
|
||||
<div v-if="item.postal_code || item.country">
|
||||
Lokacija:
|
||||
<span class="font-medium">{{
|
||||
[item.postal_code, item.country]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<pre class="text-[10px] whitespace-pre-wrap">{{
|
||||
item
|
||||
}}</pre>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Single-item generic rendering (existing) -->
|
||||
<template v-else>
|
||||
<pre class="text-[10px] whitespace-pre-wrap">{{
|
||||
r.entities[activeEntity]
|
||||
}}</pre>
|
||||
<div class="flex flex-wrap gap-1 mb-1">
|
||||
<span
|
||||
v-if="r.entities[activeEntity].identity_used"
|
||||
class="px-1 py-0.5 rounded bg-indigo-50 text-indigo-700 text-[10px]"
|
||||
title="Uporabljena identiteta"
|
||||
>{{ r.entities[activeEntity].identity_used }}</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].duplicate"
|
||||
class="px-1 py-0.5 rounded bg-amber-100 text-amber-700 text-[10px]"
|
||||
title="Podvojen v tej seriji"
|
||||
>duplikat</span
|
||||
>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].duplicate_db"
|
||||
class="px-1 py-0.5 rounded bg-amber-200 text-amber-800 text-[10px]"
|
||||
title="Že obstaja v bazi"
|
||||
>obstaja v bazi</span
|
||||
>
|
||||
</div>
|
||||
<template v-if="activeEntity === 'person'">
|
||||
<div class="grid grid-cols-1 gap-0.5">
|
||||
<div
|
||||
v-if="
|
||||
referenceOf(activeEntity, r.entities[activeEntity]) !==
|
||||
'—'
|
||||
"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Ref:
|
||||
<span class="font-medium text-gray-800">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].full_name"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Ime:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].full_name
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
r.entities[activeEntity].first_name ||
|
||||
r.entities[activeEntity].last_name
|
||||
"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Ime:
|
||||
<span class="font-medium">{{
|
||||
[
|
||||
r.entities[activeEntity].first_name,
|
||||
r.entities[activeEntity].last_name,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].birthday"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Rojstvo:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].birthday
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].description"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Opis:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].description
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="r.entities[activeEntity].identity_candidates?.length"
|
||||
class="text-[10px] text-gray-600"
|
||||
>
|
||||
Identitete:
|
||||
{{
|
||||
r.entities[activeEntity].identity_candidates.join(", ")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeEntity === 'email'"
|
||||
><div class="text-[10px] text-gray-600">
|
||||
Email:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div></template
|
||||
>
|
||||
<template v-else-if="activeEntity === 'phone'"
|
||||
><div class="text-[10px] text-gray-600">
|
||||
Telefon:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div></template
|
||||
>
|
||||
<template v-else-if="activeEntity === 'address'">
|
||||
<div class="text-[10px] text-gray-600 space-y-0.5">
|
||||
<div
|
||||
v-if="
|
||||
referenceOf(activeEntity, r.entities[activeEntity]) !==
|
||||
'—'
|
||||
"
|
||||
>
|
||||
Ref:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].address">
|
||||
Naslov:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].address
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
r.entities[activeEntity].postal_code ||
|
||||
r.entities[activeEntity].country
|
||||
"
|
||||
>
|
||||
Lokacija:
|
||||
<span class="font-medium">{{
|
||||
[
|
||||
r.entities[activeEntity].postal_code,
|
||||
r.entities[activeEntity].country,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeEntity === 'client_case'">
|
||||
<div class="text-[10px] text-gray-600 space-y-0.5">
|
||||
<div
|
||||
v-if="
|
||||
referenceOf(activeEntity, r.entities[activeEntity]) !==
|
||||
'—'
|
||||
"
|
||||
>
|
||||
Ref:
|
||||
<span class="font-medium">{{
|
||||
referenceOf(activeEntity, r.entities[activeEntity])
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].title">
|
||||
Naslov:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].title
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].status">
|
||||
Status:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].status
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<pre class="text-[10px] whitespace-pre-wrap">{{
|
||||
r.entities[activeEntity]
|
||||
}}</pre>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -55,6 +55,7 @@ const bulkGlobal = ref({
|
||||
default_field: "",
|
||||
transform: "",
|
||||
apply_mode: "both",
|
||||
group: "",
|
||||
});
|
||||
const unassigned = computed(() =>
|
||||
(props.template.mappings || []).filter((m) => !m.target_field)
|
||||
@@ -104,6 +105,21 @@ function saveUnassigned(m) {
|
||||
} else {
|
||||
m.target_field = null;
|
||||
}
|
||||
if (st.group) {
|
||||
m.options = m.options && typeof m.options === "object" ? m.options : {};
|
||||
m.options.group = st.group;
|
||||
}
|
||||
// If targeting any .meta field, allow setting options.key via UI
|
||||
if (st.field === "meta") {
|
||||
if (st.metaKey && String(st.metaKey).trim() !== "") {
|
||||
m.options = m.options && typeof m.options === "object" ? m.options : {};
|
||||
m.options.key = String(st.metaKey).trim();
|
||||
}
|
||||
if (st.metaType && String(st.metaType).trim() !== "") {
|
||||
m.options = m.options && typeof m.options === "object" ? m.options : {};
|
||||
m.options.type = String(st.metaType).trim();
|
||||
}
|
||||
}
|
||||
updateMapping(m);
|
||||
}
|
||||
|
||||
@@ -141,13 +157,22 @@ function addRow(entity) {
|
||||
const row = newRows.value[entity];
|
||||
if (!row || !row.source || !row.field) return;
|
||||
const target_field = `${entity}.${row.field}`;
|
||||
const opts = {};
|
||||
if (row.group) opts.group = row.group;
|
||||
if (entity === "contract" && row.field === "meta" && row.metaKey) {
|
||||
opts.key = String(row.metaKey).trim();
|
||||
}
|
||||
const payload = {
|
||||
source_column: row.source,
|
||||
target_field,
|
||||
transform: row.transform || null,
|
||||
apply_mode: row.apply_mode || "both",
|
||||
options: Object.keys(opts).length ? opts : null,
|
||||
position: (props.template.mappings?.length || 0) + 1,
|
||||
};
|
||||
if (row.field === "meta" && row.metaType) {
|
||||
opts.type = String(row.metaType).trim();
|
||||
}
|
||||
useForm(payload).post(
|
||||
route("importTemplates.mappings.add", { template: props.template.uuid }),
|
||||
{
|
||||
@@ -165,6 +190,7 @@ function updateMapping(m) {
|
||||
target_field: m.target_field,
|
||||
transform: m.transform,
|
||||
apply_mode: m.apply_mode,
|
||||
options: m.options || null,
|
||||
position: m.position,
|
||||
};
|
||||
useForm(payload).put(
|
||||
@@ -602,6 +628,15 @@ watch(
|
||||
<option value="update">update</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-indigo-900">Group (za vse)</label>
|
||||
<input
|
||||
v-model="bulkGlobal.group"
|
||||
type="text"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
placeholder="1, 2, home, work"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<button
|
||||
@@ -614,6 +649,7 @@ watch(
|
||||
default_field: bulkGlobal.default_field || null,
|
||||
transform: bulkGlobal.transform || null,
|
||||
apply_mode: bulkGlobal.apply_mode || 'both',
|
||||
group: bulkGlobal.group || '',
|
||||
}).post(
|
||||
route('importTemplates.mappings.bulk', {
|
||||
template: props.template.uuid,
|
||||
@@ -626,6 +662,7 @@ watch(
|
||||
bulkGlobal.default_field = '';
|
||||
bulkGlobal.transform = '';
|
||||
bulkGlobal.apply_mode = 'both';
|
||||
bulkGlobal.group = '';
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -710,6 +747,39 @@ watch(
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600">Group</label>
|
||||
<input
|
||||
v-model="(unassignedState[m.id] ||= {}).group"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
type="text"
|
||||
placeholder="1, 2, home, work"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="(unassignedState[m.id] || {}).field === 'meta'">
|
||||
<label class="block text-xs text-gray-600">Meta key</label>
|
||||
<input
|
||||
v-model="(unassignedState[m.id] ||= {}).metaKey"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
type="text"
|
||||
placeholder="npr.: note, category"
|
||||
/>
|
||||
<label class="block text-xs text-gray-600 mt-2">Meta type</label>
|
||||
<select
|
||||
v-model="(unassignedState[m.id] ||= {}).metaType"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
>
|
||||
<option value="">(auto/string)</option>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
<option value="date">date</option>
|
||||
<option value="boolean">boolean</option>
|
||||
</select>
|
||||
<p class="text-[11px] text-gray-500 mt-1">
|
||||
Če ne določiš, lahko uporabiš tudi zapis cilja kot
|
||||
<code>contract.meta[key]</code>.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600">Transform</label>
|
||||
<select v-model="m.transform" class="mt-1 w-full border rounded p-2">
|
||||
@@ -800,7 +870,7 @@ watch(
|
||||
class="flex items-center justify-between p-2 border rounded gap-3"
|
||||
>
|
||||
<div
|
||||
class="grid grid-cols-1 sm:grid-cols-5 gap-2 flex-1 items-center"
|
||||
class="grid grid-cols-1 sm:grid-cols-6 gap-2 flex-1 items-center"
|
||||
>
|
||||
<input
|
||||
v-model="m.source_column"
|
||||
@@ -822,6 +892,28 @@ watch(
|
||||
<option value="update">update</option>
|
||||
<option value="keyref">keyref (use as lookup key)</option>
|
||||
</select>
|
||||
<input
|
||||
v-model="(m.options ||= {}).group"
|
||||
class="border rounded p-2 text-sm"
|
||||
placeholder="Group"
|
||||
/>
|
||||
<input
|
||||
v-if="/^(contracts?\.meta)(\.|\[|$)/.test(m.target_field || '')"
|
||||
v-model="(m.options ||= {}).key"
|
||||
class="border rounded p-2 text-sm"
|
||||
placeholder="Meta key"
|
||||
/>
|
||||
<select
|
||||
v-if="/^(contracts?\.meta)(\.|\[|$)/.test(m.target_field || '')"
|
||||
v-model="(m.options ||= {}).type"
|
||||
class="border rounded p-2 text-sm"
|
||||
>
|
||||
<option value="">(auto/string)</option>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
<option value="date">date</option>
|
||||
<option value="boolean">boolean</option>
|
||||
</select>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="px-2 py-1 text-xs border rounded"
|
||||
@@ -859,7 +951,7 @@ watch(
|
||||
|
||||
<!-- Add new mapping row -->
|
||||
<div class="p-3 bg-gray-50 rounded border">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-5 gap-2 items-end">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-6 gap-2 items-end">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600"
|
||||
>Source column (ne-dodeljene)</label
|
||||
@@ -919,6 +1011,35 @@ watch(
|
||||
<option value="keyref">keyref (use as lookup key)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600">Group</label>
|
||||
<input
|
||||
v-model="(newRows[entity] ||= {}).group"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
type="text"
|
||||
placeholder="1, 2, home, work"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="(newRows[entity] || {}).field === 'meta'">
|
||||
<label class="block text-xs text-gray-600">Meta key</label>
|
||||
<input
|
||||
v-model="(newRows[entity] ||= {}).metaKey"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
type="text"
|
||||
placeholder="npr.: note, category"
|
||||
/>
|
||||
<label class="block text-xs text-gray-600 mt-2">Meta type</label>
|
||||
<select
|
||||
v-model="(newRows[entity] ||= {}).metaType"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
>
|
||||
<option value="">(auto/string)</option>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
<option value="date">date</option>
|
||||
<option value="boolean">boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-1">
|
||||
<button
|
||||
@click.prevent="addRow(entity)"
|
||||
@@ -992,6 +1113,15 @@ watch(
|
||||
<option value="keyref">keyref (use as lookup key)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600">Group (za vse)</label>
|
||||
<input
|
||||
v-model="(bulkRows[entity] ||= {}).group"
|
||||
class="mt-1 w-full border rounded p-2"
|
||||
type="text"
|
||||
placeholder="1, 2, home, work"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button
|
||||
@@ -1006,6 +1136,7 @@ watch(
|
||||
default_field: b.default_field || null,
|
||||
transform: b.transform || null,
|
||||
apply_mode: b.apply_mode || 'both',
|
||||
group: b.group || '',
|
||||
}).post(
|
||||
route('importTemplates.mappings.bulk', {
|
||||
template: props.template.uuid,
|
||||
@@ -1051,6 +1182,7 @@ watch(
|
||||
target_field: `${s.entity}.${s.field}`,
|
||||
transform: b.transform || null,
|
||||
apply_mode: b.apply_mode || 'both',
|
||||
options: b.group ? { group: b.group } : null,
|
||||
position: (props.template.mappings?.length || 0) + 1,
|
||||
};
|
||||
useForm(payload).post(
|
||||
|
||||
Reference in New Issue
Block a user