Changes to import and notifications

This commit is contained in:
Simon Pocrnjič
2025-10-13 21:14:10 +02:00
parent 0bbed64542
commit 79b3e20b02
28 changed files with 2173 additions and 438 deletions
+158
View File
@@ -82,6 +82,74 @@ const previewColumns = ref([]);
const previewTruncated = ref(false);
const previewLimit = ref(200);
// Import options (persisted on Import): show_missing and reactivate
const showMissingEnabled = ref(Boolean(props.import?.show_missing ?? false));
const reactivateEnabled = ref(Boolean(props.import?.reactivate ?? false));
async function saveImportOptions() {
if (!importId.value) return;
try {
await axios.post(
route("imports.options", { import: importId.value }),
{
show_missing: !!showMissingEnabled.value,
// keep existing reactivate value if UI doesn't expose it here
reactivate: !!reactivateEnabled.value,
},
{ headers: { Accept: "application/json" }, withCredentials: true }
);
} catch (e) {
console.error(
"Save import options failed",
e.response?.status || "",
e.response?.data || e
);
}
}
// Missing contracts (post-finish) UI state
const showMissingContracts = ref(false);
const missingContractsLoading = ref(false);
const missingContracts = ref([]);
const contractRefIsKeyref = computed(() => {
return (persistedMappings.value || []).some((m) => {
const tf = String(m?.target_field || "")
.toLowerCase()
.trim();
const am = String(m?.apply_mode || "")
.toLowerCase()
.trim();
return ["contract.reference", "contracts.reference"].includes(tf) && am === "keyref";
});
});
const canShowMissingButton = computed(() => {
return contractRefIsKeyref.value && !!showMissingEnabled.value;
});
async function openMissingContracts() {
if (!importId.value || !contractRefIsKeyref.value) return;
showMissingContracts.value = true;
missingContractsLoading.value = true;
try {
const { data } = await axios.get(
route("imports.missing-contracts", { import: importId.value }),
{
headers: { Accept: "application/json" },
withCredentials: true,
}
);
missingContracts.value = Array.isArray(data?.missing) ? data.missing : [];
} catch (e) {
console.error(
"Missing contracts fetch failed",
e.response?.status || "",
e.response?.data || e
);
missingContracts.value = [];
} finally {
missingContractsLoading.value = false;
}
}
// Determine if all detected columns are mapped with entity+field
function evaluateMappingSaved() {
console.log("here the evaluation happen of mapping save!");
@@ -1028,6 +1096,11 @@ async function fetchSimulation() {
:class="['px-2 py-0.5 rounded-full text-xs font-medium', statusInfo.classes]"
>{{ statusInfo.label }}</span
>
<span
v-if="showMissingEnabled"
class="text-[10px] px-1 py-0.5 rounded bg-amber-100 text-amber-700 align-middle"
>seznam manjkajočih</span
>
</div>
</div>
</template>
@@ -1065,6 +1138,22 @@ async function fetchSimulation() {
<span class="font-medium">{{ props.import?.valid_rows ?? "—" }}</span>
</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"
@click.prevent="openPreview"
>
Ogled CSV
</button>
<button
v-if="canShowMissingButton"
class="px-3 py-1.5 bg-indigo-600 text-white text-xs rounded"
@click.prevent="openMissingContracts"
title="Prikaži aktivne pogodbe, ki niso bile prisotne v uvozu (samo keyref)"
>
Ogled manjkajoče
</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<TemplateControls
@@ -1096,6 +1185,24 @@ 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>
<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>
@@ -1173,6 +1280,57 @@ async function fetchSimulation() {
@change-limit="(val) => (previewLimit = val)"
@refresh="fetchPreview"
/>
<!-- Missing contracts modal -->
<Modal
:show="showMissingContracts"
max-width="2xl"
@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', row.case_uuid)"
class="text-blue-600 hover:underline text-xs"
>Odpri primer</a
>
</div>
</li>
</ul>
</div>
</div>
</Modal>
<SimulationModal
:show="showPaymentSim"
:rows="paymentSimRows"
@@ -11,10 +11,21 @@ const props = defineProps({
limit: { type: Number, default: 50 },
loading: { type: Boolean, default: false },
entities: { type: Array, default: () => [] },
// passthrough verbose from parent to render extra sources in table
verbose: { type: Boolean, default: false },
});
// Emits
const emit = defineEmits(["close", "update:limit"]);
const emit = defineEmits(["close", "update:limit", "toggle-verbose"]);
// Local handlers for header controls
function onLimit(e) {
const val = Number(e?.target?.value ?? props.limit ?? 50);
emit("update:limit", isNaN(val) ? 50 : val);
}
function toggleVerbose() {
emit("toggle-verbose");
}
// Map technical entity keys to localized labels
const entityLabelMap = {
@@ -70,6 +81,8 @@ const entitiesWithRows = computed(() => {
const activeEntity = ref(null);
const hideChain = ref(false);
const showOnlyChanged = ref(false);
// Show only rows skipped due to missing contract.reference in keyref mode (contract/account)
const showOnlyKeyrefSkipped = ref(false);
watch(
entitiesWithRows,
(val) => {
@@ -156,6 +169,15 @@ const visibleRows = computed(() => {
.filter((r) => {
if (!r.entities || !r.entities[activeEntity.value]) return false;
const ent = r.entities[activeEntity.value];
// Filter: only rows explicitly skipped due to keyref missing
if (showOnlyKeyrefSkipped.value) {
if (Array.isArray(ent)) {
const anySkipped = ent.some((i) => i && i.skipped_due_to_keyref);
if (!anySkipped) return false;
} else {
if (!ent.skipped_due_to_keyref) return false;
}
}
if (!Array.isArray(ent)) {
if (hideChain.value && ent.existing_chain) return false;
}
@@ -286,7 +308,7 @@ function referenceOf(entityName, ent) {
class="text-[11px] px-2 py-1 rounded border bg-white hover:bg-gray-50"
@click="toggleVerbose"
>
{{ verbose ? "Manj" : "Več" }} podrobnosti
{{ props.verbose ? "Manj" : "Več" }} podrobnosti
</button>
<label class="flex items-center gap-1 text-[11px] text-gray-600">
<input type="checkbox" v-model="hideChain" class="rounded border-gray-300" />
@@ -300,6 +322,14 @@ function referenceOf(entityName, ent) {
/>
Samo spremenjeni
</label>
<label class="flex items-center gap-1 text-[11px] text-gray-600" title="Prikaži le vrstice preskočene zaradi manjkajoče contract.reference v načinu keyref (pogodbe/računi)">
<input
type="checkbox"
v-model="showOnlyKeyrefSkipped"
class="rounded border-gray-300"
/>
Samo preskočene (keyref)
</label>
<button
type="button"
class="text-[11px] px-2 py-1 rounded bg-gray-800 text-white hover:bg-gray-700"