changes to import added activity entity

This commit is contained in:
Simon Pocrnjič
2025-12-16 19:35:51 +01:00
parent aa40ebed5c
commit a596177a68
9 changed files with 946 additions and 72 deletions
+72
View File
@@ -15,6 +15,7 @@ import Modal from "@/Components/Modal.vue"; // still potentially used elsewhere
import CsvPreviewModal from "./Partials/CsvPreviewModal.vue";
import SimulationModal from "./Partials/SimulationModal.vue";
import { useCurrencyFormat } from "./useCurrencyFormat.js";
import DialogModal from "@/Components/DialogModal.vue";
// Reintroduce props definition lost during earlier edits
const props = defineProps({
@@ -185,6 +186,23 @@ function downloadUnresolvedCsv() {
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(() => {
const foundList = props.import?.meta?.history_found_contracts;
const hasFound = Array.isArray(foundList) && foundList.length > 0;
return Boolean(
props.import?.template?.meta?.history_import ??
props.import?.import_template?.meta?.history_import ??
props.import?.meta?.history_import ??
hasFound
);
});
const historyFoundContracts = computed(() => {
const list = props.import?.meta?.history_found_contracts;
return Array.isArray(list) ? list : [];
});
const showFoundContracts = ref(false);
// Determine if all detected columns are mapped with entity+field
function evaluateMappingSaved() {
console.log("here the evaluation happen of mapping save!");
@@ -1145,6 +1163,21 @@ async function fetchSimulation() {
<div class="py-6">
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg p-6 space-y-6">
<div
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"
@click.prevent="showFoundContracts = true"
title="Prikaži pogodbe, ki so bile najdene in že obstajajo v bazi"
>
Najdene pogodbe
</button>
<span v-if="historyFoundContracts.length" class="text-xs text-gray-600">
{{ historyFoundContracts.length }} že obstoječih
</span>
</div>
<div v-if="isCompleted" class="p-3 border rounded bg-gray-50 text-sm">
<div class="flex flex-wrap gap-x-6 gap-y-1">
<div>
@@ -1378,6 +1411,45 @@ async function fetchSimulation() {
</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>
<!-- Unresolved keyref rows modal -->
<Modal :show="showUnresolved" max-width="5xl" @close="showUnresolved = false">
<div class="p-4 max-h-[75vh] overflow-auto">
@@ -28,6 +28,8 @@ const form = useForm({
delimiter: "",
// Payments import mode
payments_import: false,
// History import mode
history_import: false,
// For payments mode: how to locate Contract - use single key 'reference'
contract_key_mode: null,
},
@@ -59,6 +61,9 @@ const prevEntities = ref([]);
watch(
() => form.meta.payments_import,
(enabled) => {
if (enabled && form.meta.history_import) {
form.meta.history_import = false;
}
if (enabled) {
// Save current selection and lock to the required chain
prevEntities.value = Array.isArray(form.entities) ? [...form.entities] : [];
@@ -74,6 +79,35 @@ watch(
}
}
);
// History import: restrict entities and auto-add accounts when contracts selected
watch(
() => form.meta.history_import,
(enabled) => {
if (enabled && form.meta.payments_import) {
form.meta.payments_import = false;
form.meta.contract_key_mode = null;
}
const allowed = ["person", "person_addresses", "person_phones", "contracts", "activities", "client_cases"];
if (enabled) {
const current = Array.isArray(form.entities) ? [...form.entities] : [];
let filtered = current.filter((e) => allowed.includes(e));
if (filtered.includes("contracts") && !filtered.includes("accounts")) {
filtered = [...filtered, "accounts"];
}
form.entities = filtered;
}
}
);
watch(
() => form.entities,
(vals) => {
if (form.meta.history_import && Array.isArray(vals) && vals.includes("contracts") && ! vals.includes("accounts")) {
form.entities = [...vals, "accounts"];
}
}
);
</script>
<template>
@@ -112,14 +146,24 @@ watch(
<label class="block text-sm font-medium text-gray-700"
>Entities (tables)</label
>
<label class="inline-flex items-center gap-2 text-sm">
<input
type="checkbox"
v-model="form.meta.payments_import"
class="rounded"
/>
<span>Payments import</span>
</label>
<div class="flex items-center gap-4 text-sm">
<label class="inline-flex items-center gap-2">
<input
type="checkbox"
v-model="form.meta.history_import"
class="rounded"
/>
<span>History import</span>
</label>
<label class="inline-flex items-center gap-2">
<input
type="checkbox"
v-model="form.meta.payments_import"
class="rounded"
/>
<span>Payments import</span>
</label>
</div>
</div>
<template v-if="!form.meta.payments_import">
<Multiselect
@@ -128,11 +172,13 @@ watch(
{ value: 'person', label: 'Person' },
{ value: 'person_addresses', label: 'Person Addresses' },
{ value: 'person_phones', label: 'Person Phones' },
{ value: 'client_cases', label: 'Client Cases' },
{ value: 'emails', label: 'Emails' },
{ value: 'accounts', label: 'Accounts' },
{ value: 'contracts', label: 'Contracts' },
{ value: 'case_objects', label: 'Case Objects' },
{ value: 'payments', label: 'Payments' },
{ value: 'activities', label: 'Activities' },
]"
:multiple="true"
track-by="value"
@@ -156,6 +202,9 @@ watch(
Choose which tables this template targets. You can still define per-column
mappings later.
</p>
<div v-if="form.meta.history_import" class="mt-2 text-xs text-gray-600">
History mode allows only person/address/phone/contracts/activities/client cases. Accounts are auto-added when contracts are present and balances stay unchanged.
</div>
<div v-if="form.meta.payments_import" class="mt-2 text-xs text-gray-600">
Payments mode locks entities to:
<span class="font-medium">Contracts Accounts Payments</span> and
+126 -29
View File
@@ -26,6 +26,10 @@ const form = useForm({
// Add meta with default delimiter support
meta: {
...(props.template.meta || {}),
payments_import: props.template.meta?.payments_import ?? false,
history_import: props.template.meta?.history_import ?? false,
activity_action_id: props.template.meta?.activity_action_id ?? props.template.meta?.action_id ?? null,
activity_decision_id: props.template.meta?.activity_decision_id ?? props.template.meta?.decision_id ?? null,
delimiter: (props.template.meta && props.template.meta.delimiter) || "",
},
});
@@ -35,6 +39,21 @@ const decisionsForSelectedAction = vComputed(() => {
return act?.decisions || [];
});
const decisionsForActivitiesAction = vComputed(() => {
const act = (props.actions || []).find((a) => a.id === form.meta.activity_action_id);
return act?.decisions || [];
});
const activityCreatedAtInput = computed({
get() {
if (!form.meta.activity_created_at) return "";
return String(form.meta.activity_created_at).replace(" ", "T");
},
set(v) {
form.meta.activity_created_at = v ? String(v).replace("T", " ") : null;
},
});
vWatch(
() => form.meta.action_id,
() => {
@@ -42,6 +61,13 @@ vWatch(
}
);
vWatch(
() => form.meta.activity_action_id,
() => {
form.meta.activity_decision_id = null;
}
);
const entities = computed(() => props.template.meta?.entities || []);
const hasMappings = computed(() => (props.template.mappings?.length || 0) > 0);
const canChangeClient = computed(() => !hasMappings.value); // guard reassignment when mappings exist (optional rule)
@@ -67,6 +93,15 @@ const unassignedSourceColumns = computed(() => {
}
return Array.from(set).sort((a, b) => a.localeCompare(b));
});
const allSourceColumns = computed(() => {
const set = new Set();
(props.template.sample_headers || []).forEach((h) => set.add(h));
(props.template.mappings || []).forEach((m) => {
if (m.source_column) set.add(m.source_column);
});
return Array.from(set).sort((a, b) => a.localeCompare(b));
});
const unassignedState = ref({});
// Dynamic Import Entity definitions and field options from API
@@ -252,6 +287,11 @@ const save = () => {
// drop client change when blocked
delete payload.client_uuid;
}
const hasActivities = Array.isArray(payload.meta?.entities) && payload.meta.entities.includes('activities');
if (hasActivities && (!payload.meta?.activity_action_id || !payload.meta?.activity_decision_id)) {
alert('Activity imports require selecting an Action and Decision (Activities section).');
return;
}
// Normalize empty delimiter: remove from meta to allow auto-detect
if (
payload.meta &&
@@ -300,11 +340,27 @@ watch(
watch(
() => form.meta.payments_import,
(enabled) => {
if (enabled) {
if (form.meta.history_import) {
form.meta.history_import = false;
}
}
if (enabled && !form.meta.contract_key_mode) {
form.meta.contract_key_mode = "reference";
}
}
);
// History mode is mutually exclusive with payments mode
watch(
() => form.meta.history_import,
(enabled) => {
if (enabled && form.meta.payments_import) {
form.meta.payments_import = false;
form.meta.contract_key_mode = null;
}
}
);
</script>
<template>
@@ -423,7 +479,7 @@ watch(
</div>
<div>
<label class="block text-sm font-medium text-gray-700"
>Privzeto Dejanja (Activity)</label
>Privzeto Dejanja (post-contract activity)</label
>
<select
v-model="form.meta.action_id"
@@ -437,7 +493,7 @@ watch(
</div>
<div>
<label class="block text-sm font-medium text-gray-700"
>Privzeta Odločitev</label
>Privzeta Odločitev (post-contract)</label
>
<select
v-model="form.meta.decision_id"
@@ -484,22 +540,31 @@ watch(
</div>
</div>
<!-- Payments import toggle and settings -->
<!-- History / Payments import toggles and settings -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<div class="flex items-center gap-2">
<input
id="payments_import"
v-model="form.meta.payments_import"
type="checkbox"
class="rounded"
/>
<label for="payments_import" class="text-sm font-medium text-gray-700"
>Payments import</label
>
<div class="space-y-2">
<div class="flex items-center flex-wrap gap-4">
<label class="inline-flex items-center gap-2">
<input
id="history_import"
v-model="form.meta.history_import"
type="checkbox"
class="rounded"
/>
<span class="text-sm font-medium text-gray-700">History import</span>
</label>
<label class="inline-flex items-center gap-2">
<input
id="payments_import"
v-model="form.meta.payments_import"
type="checkbox"
class="rounded"
/>
<span class="text-sm font-medium text-gray-700">Payments import</span>
</label>
</div>
<p class="text-xs text-gray-500 mt-1">
When enabled, entities are locked to Contracts Accounts Payments.
<p class="text-xs text-gray-500">
History allows person/address/phone/contracts/activities/client cases; accounts are auto-added with contracts. Payments locks entities to Contracts Accounts Payments.
</p>
</div>
<div v-if="form.meta.payments_import">
@@ -856,6 +921,41 @@ watch(
<span class="text-xs text-gray-500">Klikni za razširitev</span>
</summary>
<div class="mt-4 space-y-4">
<div v-if="entity === 'activities'" class="grid grid-cols-1 sm:grid-cols-2 gap-4 p-3 bg-gray-50 rounded border">
<div>
<label class="block text-sm font-medium text-gray-700">Activity action *</label>
<select
v-model="form.meta.activity_action_id"
class="mt-1 block w-full border rounded p-2"
required
>
<option :value="null">(Select action)</option>
<option v-for="a in props.actions" :key="a.id" :value="a.id">{{ a.name }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Activity decision *</label>
<select
v-model="form.meta.activity_decision_id"
class="mt-1 block w-full border rounded p-2"
:disabled="!form.meta.activity_action_id"
required
>
<option :value="null">(Select decision)</option>
<option v-for="d in decisionsForActivitiesAction" :key="d.id" :value="d.id">{{ d.name }}</option>
</select>
<p v-if="!form.meta.activity_action_id" class="text-xs text-gray-500 mt-1">Choose an action first.</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Activity created at (override)</label>
<input
v-model="activityCreatedAtInput"
type="datetime-local"
class="mt-1 block w-full border rounded p-2"
/>
<p class="text-xs text-gray-500 mt-1">Optional: set a fixed timestamp for inserted activities (history imports).</p>
</div>
</div>
<!-- Existing mappings for this entity -->
<div
v-if="props.template.mappings && props.template.mappings.length"
@@ -954,22 +1054,19 @@ watch(
<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
>Source column (lahko uporabiš večkrat)</label
>
<select
<input
v-model="(newRows[entity] ||= {}).source"
class="mt-1 w-full border rounded p-2"
>
<option value="" disabled>(izberi)</option>
<option v-for="s in unassignedSourceColumns" :key="s" :value="s">
{{ s }}
</option>
</select>
<p
v-if="!unassignedSourceColumns.length"
class="text-xs text-gray-500 mt-1"
>
Ni nedodeljenih virov. Uporabi Bulk ali najprej dodaj vire.
list="src-opts-{{ entity }}"
placeholder="npr.: note, description"
/>
<datalist :id="`src-opts-${entity}`">
<option v-for="s in allSourceColumns" :key="s" :value="s">{{ s }}</option>
</datalist>
<p class="text-xs text-gray-500 mt-1">
Več stolpcev lahko povežeš na isto polje (npr. activity.note).
</p>
</div>
<div>