This commit is contained in:
Simon Pocrnjič
2025-12-26 22:39:58 +01:00
parent f8623a6071
commit dea7432deb
55 changed files with 7977 additions and 1983 deletions
+112 -198
View File
@@ -9,13 +9,19 @@ import LogsTable from "./Partials/LogsTable.vue";
import ProcessResult from "./Partials/ProcessResult.vue";
import { ref, computed, onMounted, watch } from "vue";
import { router } from "@inertiajs/vue3";
import Multiselect from "vue-multiselect";
import axios from "axios";
import Modal from "@/Components/Modal.vue"; // still potentially used elsewhere
import Modal from "@/Components/Modal.vue";
import CsvPreviewModal from "./Partials/CsvPreviewModal.vue";
import SimulationModal from "./Partials/SimulationModal.vue";
import MissingContractsModal from "./Partials/MissingContractsModal.vue";
import FoundContractsModal from "./Partials/FoundContractsModal.vue";
import UnresolvedRowsModal from "./Partials/UnresolvedRowsModal.vue";
import { useCurrencyFormat } from "./useCurrencyFormat.js";
import DialogModal from "@/Components/DialogModal.vue";
import { Switch } from "@/Components/ui/switch";
import { Label } from "@/Components/ui/label";
import { Button } from "@/Components/ui/button";
import { Badge } from "@/Components/ui/badge";
import { Checkbox } from "@/Components/ui/checkbox";
// Reintroduce props definition lost during earlier edits
const props = defineProps({
@@ -180,11 +186,6 @@ async function openUnresolved() {
unresolvedLoading.value = false;
}
}
function downloadUnresolvedCsv() {
if (!importId.value) return;
// Direct download
window.location.href = route("imports.missing-keyref-csv", { import: importId.value });
}
// History import: list of contracts that already existed in DB and were matched
const isHistoryImport = computed(() => {
@@ -592,32 +593,39 @@ const statusInfo = computed(() => {
completed: {
label: "Zaključeno",
classes: "bg-emerald-100 text-emerald-700 border border-emerald-300",
variant: "default",
},
processing: {
label: "Obdelava",
classes: "bg-indigo-100 text-indigo-700 border border-indigo-300",
variant: "default",
},
validating: {
label: "Preverjanje",
classes: "bg-indigo-100 text-indigo-700 border border-indigo-300",
variant: "default",
},
failed: {
label: "Neuspešno",
classes: "bg-red-100 text-red-700 border border-red-300",
variant: "destructive",
},
parsed: {
label: "Razčlenjeno",
classes: "bg-slate-100 text-slate-700 border border-slate-300",
variant: "secondary",
},
uploaded: {
label: "Naloženo",
classes: "bg-slate-100 text-slate-700 border border-slate-300",
variant: "secondary",
},
};
return (
map[raw] || {
label: raw || "Status",
classes: "bg-gray-100 text-gray-700 border border-gray-300",
variant: "outline",
}
);
});
@@ -1117,11 +1125,19 @@ async function fetchSimulation() {
headers: { Accept: "application/json" },
withCredentials: true,
});
// V2 format
paymentSimRows.value = Array.isArray(data?.rows) ? data.rows : [];
paymentSimEntities.value = Array.isArray(data?.entities) ? data.entities : [];
// Summaries keys vary (payment, contract, account, etc.). Keep existing behaviour for payment summary exposure.
paymentSimSummary.value = data?.summaries?.payment || null;
paymentSimSummarySl.value = data?.povzetki?.payment || null;
paymentSimSummary.value = data?.summaries || null;
// Extract unique entity types from rows for SimulationModal
const entitySet = new Set();
for (const row of data?.rows || []) {
if (row.entities && typeof row.entities === 'object') {
Object.keys(row.entities).forEach(key => entitySet.add(key));
}
}
paymentSimEntities.value = Array.from(entitySet);
} catch (e) {
console.error("Simulation failed", e.response?.status || "", e.response?.data || e);
} finally {
@@ -1142,20 +1158,20 @@ async function fetchSimulation() {
selectedClientOption?.name || selectedClientOption?.uuid || "—"
}}</strong>
</span>
<span
v-if="templateApplied"
class="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600 align-middle"
>uporabljena</span
<Badge v-if="templateApplied" variant="secondary" class="text-[10px]"
>uporabljena</Badge
>
<span
<Badge
v-if="props.import?.status"
:class="['px-2 py-0.5 rounded-full text-xs font-medium', statusInfo.classes]"
>{{ statusInfo.label }}</span
:variant="statusInfo.variant || 'default'"
class="text-xs"
>{{ statusInfo.label }}</Badge
>
<span
<Badge
v-if="showMissingEnabled"
class="text-[10px] px-1 py-0.5 rounded bg-amber-100 text-amber-700 align-middle"
>seznam manjkajočih</span
variant="outline"
class="text-[10px] bg-amber-50 text-amber-700 border-amber-200"
>seznam manjkajočih</Badge
>
</div>
</div>
@@ -1167,13 +1183,15 @@ async function fetchSimulation() {
v-if="isHistoryImport || historyFoundContracts.length"
class="flex flex-wrap items-center gap-2 text-sm"
>
<button
class="px-3 py-1.5 bg-emerald-700 text-white text-xs rounded"
<Button
variant="default"
size="sm"
class="bg-emerald-700 hover:bg-emerald-800 text-xs"
@click.prevent="showFoundContracts = true"
title="Prikaži pogodbe, ki so bile najdene in že obstajajo v bazi"
>
Najdene pogodbe
</button>
</Button>
<span v-if="historyFoundContracts.length" class="text-xs text-gray-600">
{{ historyFoundContracts.length }} že obstoječih
</span>
@@ -1210,28 +1228,34 @@ async function fetchSimulation() {
</div>
</div>
<div class="mt-3 flex items-center gap-2">
<button
class="px-3 py-1.5 bg-gray-700 text-white text-xs rounded"
<Button
variant="secondary"
size="sm"
class="text-xs"
@click.prevent="openPreview"
>
Ogled CSV
</button>
<button
</Button>
<Button
v-if="canShowMissingButton"
class="px-3 py-1.5 bg-indigo-600 text-white text-xs rounded"
variant="default"
size="sm"
class="bg-indigo-600 hover:bg-indigo-700 text-xs"
@click.prevent="openMissingContracts"
title="Prikaži aktivne pogodbe, ki niso bile prisotne v uvozu (samo keyref)"
>
Ogled manjkajoče
</button>
<button
</Button>
<Button
v-if="isCompleted && contractRefIsKeyref"
class="px-3 py-1.5 bg-amber-600 text-white text-xs rounded"
variant="default"
size="sm"
class="bg-amber-600 hover:bg-amber-700 text-xs"
@click.prevent="openUnresolved"
title="Prikaži vrstice, kjer pogodba (keyref) ni bila najdena"
>
Neobstoječi
</button>
</Button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
@@ -1265,22 +1289,34 @@ async function fetchSimulation() {
@apply-template="applyTemplateToImport"
/>
<!-- Import options -->
<div v-if="!isCompleted" class="mt-2 p-3 rounded border bg-gray-50">
<div class="flex items-center gap-3">
<label class="inline-flex items-center text-sm text-gray-700">
<input
type="checkbox"
class="rounded mr-2"
v-model="showMissingEnabled"
@change="saveImportOptions"
/>
<span>Seznam manjkajočih (po končanem uvozu)</span>
</label>
<div
v-if="!isCompleted"
class="mt-2 p-4 rounded-lg border bg-linear-to-br from-gray-50 to-gray-100"
>
<div class="flex items-start gap-3">
<Checkbox
:id="'show-missing-checkbox'"
:checked="showMissingEnabled"
@update:checked="
(val) => {
showMissingEnabled = val;
saveImportOptions();
}
"
/>
<div class="flex-1">
<Label
:for="'show-missing-checkbox'"
class="text-sm font-medium text-gray-700 cursor-pointer"
>
Seznam manjkajočih (po končanem uvozu)
</Label>
<p class="mt-1 text-xs text-gray-500">
Ko je omogočeno in je "contract.reference" nastavljen na keyref, bo po
končanem uvozu na voljo gumb za ogled pogodb, ki jih ni v datoteki.
</p>
</div>
</div>
<p class="mt-1 text-xs text-gray-500">
Ko je omogočeno in je "contract.reference" nastavljen na keyref, bo po
končanem uvozu na voljo gumb za ogled pogodb, ki jih ni v datoteki.
</p>
</div>
<ChecklistSteps :steps="stepStates" :missing-critical="missingCritical" />
</div>
@@ -1356,160 +1392,37 @@ async function fetchSimulation() {
:truncated="previewTruncated"
:has-header="detected.has_header"
@close="showPreview = false"
@change-limit="(val) => (previewLimit = val)"
@change-limit="
async (val) => {
previewLimit = val;
await fetchPreview();
}
"
@refresh="fetchPreview"
/>
<!-- Missing contracts modal -->
<Modal
<MissingContractsModal
:show="showMissingContracts"
max-width="2xl"
:loading="missingContractsLoading"
:contracts="missingContracts"
:format-money="formatMoney"
@close="showMissingContracts = false"
>
<div class="p-4 max-h-[70vh] overflow-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg">Manjkajoče pogodbe (aktivne, ne-arhivirane)</h3>
<button
class="text-gray-500 hover:text-gray-700"
@click.prevent="showMissingContracts = false"
>
Zapri
</button>
</div>
<div v-if="missingContractsLoading" class="py-8 text-center text-sm text-gray-500">
Nalagam …
</div>
<div v-else>
<div v-if="!missingContracts.length" class="py-6 text-sm text-gray-600">
Ni zadetkov.
</div>
<ul v-else class="divide-y divide-gray-200">
<li
v-for="row in missingContracts"
:key="row.uuid"
class="py-2 text-sm flex items-center justify-between"
>
<div class="min-w-0">
<div class="font-mono text-gray-800">{{ row.reference }}</div>
<div class="text-xs text-gray-500 truncate">
<span class="font-medium text-gray-600">Primer: </span>
<span>{{ row.full_name || "" }}</span>
<span v-if="row.balance_amount != null" class="ml-2"
>• {{ formatMoney(row.balance_amount) }}</span
>
</div>
</div>
<div class="flex-shrink-0">
<a
:href="route('clientCase.show', { client_case: row.case_uuid })"
class="text-blue-600 hover:underline text-xs"
>Odpri primer</a
>
</div>
</li>
</ul>
</div>
</div>
</Modal>
/>
<!-- History import: existing contracts found -->
<DialogModal :show="showFoundContracts" max-width="3xl" @close="showFoundContracts = false">
<template #title>Obstoječe pogodbe najdene v zgodovinskem uvozu</template>
<template #content>
<div v-if="!historyFoundContracts.length" class="text-sm text-gray-600">Ni zadetkov.</div>
<ul v-else class="divide-y divide-gray-200 max-h-[70vh] overflow-auto">
<li
v-for="item in historyFoundContracts"
:key="item.contract_uuid || item.reference"
class="py-3 flex items-center justify-between gap-4"
>
<div class="min-w-0">
<div class="font-mono text-sm text-gray-900">{{ item.reference }}</div>
<div class="text-xs text-gray-600 truncate">
<span>{{ item.full_name || "" }}</span>
</div>
</div>
<div class="flex-shrink-0">
<a
v-if="item.case_uuid"
:href="route('clientCase.show', { client_case: item.case_uuid })"
class="text-blue-600 hover:underline text-xs"
>
Odpri primer
</a>
</div>
</li>
</ul>
</template>
<template #footer>
<button
class="px-3 py-1.5 bg-gray-700 text-white text-xs rounded"
@click.prevent="showFoundContracts = false"
>
Zapri
</button>
</template>
</DialogModal>
<FoundContractsModal
:show="showFoundContracts"
:contracts="historyFoundContracts"
@close="showFoundContracts = false"
/>
<!-- Unresolved keyref rows modal -->
<Modal :show="showUnresolved" max-width="5xl" @close="showUnresolved = false">
<div class="p-4 max-h-[75vh] overflow-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg">
Vrstice z neobstoječim contract.reference (KEYREF)
</h3>
<div class="flex items-center gap-2">
<button
class="px-3 py-1.5 bg-green-600 text-white text-xs rounded"
@click.prevent="downloadUnresolvedCsv"
>
Prenesi CSV
</button>
<button
class="text-gray-500 hover:text-gray-700"
@click.prevent="showUnresolved = false"
>
Zapri
</button>
</div>
</div>
<div v-if="unresolvedLoading" class="py-8 text-center text-sm text-gray-500">
Nalagam …
</div>
<div v-else>
<div v-if="!unresolvedRows.length" class="py-6 text-sm text-gray-600">
Ni zadetkov.
</div>
<div v-else class="overflow-auto border border-gray-200 rounded">
<table class="min-w-full text-sm">
<thead class="bg-gray-50 text-gray-700">
<tr>
<th class="px-3 py-2 text-left w-24"># vrstica</th>
<th
v-for="(c, i) in unresolvedColumns"
:key="i"
class="px-3 py-2 text-left"
>
{{ c }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="r in unresolvedRows" :key="r.id" class="border-t">
<td class="px-3 py-2 text-gray-500">{{ r.row_number }}</td>
<td
v-for="(c, i) in unresolvedColumns"
:key="i"
class="px-3 py-2 whitespace-pre-wrap break-words"
>
{{ r.values?.[i] ?? "" }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</Modal>
<UnresolvedRowsModal
:show="showUnresolved"
:loading="unresolvedLoading"
:columns="unresolvedColumns"
:rows="unresolvedRows"
:import-id="importId"
@close="showUnresolved = false"
/>
<SimulationModal
:show="showPaymentSim"
:rows="paymentSimRows"
@@ -1522,8 +1435,9 @@ async function fetchSimulation() {
:money-formatter="formatMoney"
@close="showPaymentSim = false"
@change-limit="
(val) => {
async (val) => {
paymentSimLimit = val;
await fetchSimulation();
}
"
@toggle-verbose="