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:
Simon Pocrnjič
2025-10-09 22:28:48 +02:00
parent c8029c9eb0
commit 0598261cdc
27 changed files with 2517 additions and 375 deletions
@@ -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>