311 lines
11 KiB
Vue
311 lines
11 KiB
Vue
<script setup>
|
|
import AppLayout from "@/Layouts/AppLayout.vue";
|
|
import { ref } from "vue";
|
|
import { useForm } from "@inertiajs/vue3";
|
|
import Multiselect from "vue-multiselect";
|
|
import { computed, watch } from "vue";
|
|
|
|
const props = defineProps({
|
|
clients: Array,
|
|
segments: Array,
|
|
decisions: Array,
|
|
actions: Array,
|
|
});
|
|
|
|
const form = useForm({
|
|
name: "",
|
|
description: "",
|
|
source_type: "csv",
|
|
default_record_type: "",
|
|
is_active: true,
|
|
client_uuid: null,
|
|
entities: [],
|
|
meta: {
|
|
segment_id: null,
|
|
decision_id: null,
|
|
action_id: null,
|
|
delimiter: "",
|
|
// Payments import mode
|
|
payments_import: false,
|
|
// For payments mode: how to locate Contract - use single key 'reference'
|
|
contract_key_mode: null,
|
|
},
|
|
});
|
|
|
|
const decisionsForSelectedAction = computed(() => {
|
|
const act = (props.actions || []).find((a) => a.id === form.meta.action_id);
|
|
return act?.decisions || [];
|
|
});
|
|
|
|
watch(
|
|
() => form.meta.action_id,
|
|
() => {
|
|
// Clear decision when action changes to enforce valid pair
|
|
form.meta.decision_id = null;
|
|
}
|
|
);
|
|
|
|
function submit() {
|
|
form.post(route("importTemplates.store"), {
|
|
onSuccess: () => {
|
|
// You can redirect or show a success message here
|
|
},
|
|
});
|
|
}
|
|
|
|
// Payments mode: lock entities to Contract -> Account -> Payment and provide key mode
|
|
const prevEntities = ref([]);
|
|
watch(
|
|
() => form.meta.payments_import,
|
|
(enabled) => {
|
|
if (enabled) {
|
|
// Save current selection and lock to the required chain
|
|
prevEntities.value = Array.isArray(form.entities) ? [...form.entities] : [];
|
|
form.entities = ["contracts", "accounts", "payments"];
|
|
// default contract key mode to 'reference'
|
|
if (!form.meta.contract_key_mode) {
|
|
form.meta.contract_key_mode = "reference";
|
|
}
|
|
} else {
|
|
// Restore previous selection when turning off
|
|
form.entities = prevEntities.value?.length ? [...prevEntities.value] : [];
|
|
form.meta.contract_key_mode = null;
|
|
}
|
|
}
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout title="Create Import Template">
|
|
<template #header>
|
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
|
Create Import Template
|
|
</h2>
|
|
</template>
|
|
|
|
<div class="py-6">
|
|
<div class="max-w-3xl mx-auto sm:px-6 lg:px-8">
|
|
<div class="bg-white shadow sm:rounded-lg p-6 space-y-6">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700"
|
|
>Client (optional)</label
|
|
>
|
|
<Multiselect
|
|
v-model="form.client_uuid"
|
|
:options="props.clients || []"
|
|
:reduce="(c) => c.uuid"
|
|
track-by="uuid"
|
|
label="name"
|
|
placeholder="Global (no client)"
|
|
:searchable="true"
|
|
:allow-empty="true"
|
|
class="mt-1"
|
|
/>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
Leave empty to make this template global (visible to all clients).
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<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>
|
|
<template v-if="!form.meta.payments_import">
|
|
<Multiselect
|
|
v-model="form.entities"
|
|
:options="[
|
|
{ value: 'person', label: 'Person' },
|
|
{ value: 'person_addresses', label: 'Person Addresses' },
|
|
{ value: 'person_phones', label: 'Person Phones' },
|
|
{ value: 'emails', label: 'Emails' },
|
|
{ value: 'accounts', label: 'Accounts' },
|
|
{ value: 'contracts', label: 'Contracts' },
|
|
{ value: 'payments', label: 'Payments' },
|
|
]"
|
|
:multiple="true"
|
|
track-by="value"
|
|
label="label"
|
|
:reduce="(o) => o.value"
|
|
placeholder="Select one or more entities"
|
|
:searchable="false"
|
|
class="mt-1"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<div class="mt-1">
|
|
<div class="flex flex-wrap gap-2">
|
|
<span class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-800 px-3 py-1 text-xs font-medium">Contracts</span>
|
|
<span class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-800 px-3 py-1 text-xs font-medium">Accounts</span>
|
|
<span class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-800 px-3 py-1 text-xs font-medium">Payments</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
Choose which tables this template targets. You can still define per-column
|
|
mappings later.
|
|
</p>
|
|
<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
|
|
optimizes matching for payments import.
|
|
</div>
|
|
<div v-if="form.meta.payments_import" class="mt-3">
|
|
<label class="block text-sm font-medium text-gray-700"
|
|
>Contract match key</label
|
|
>
|
|
<select
|
|
v-model="form.meta.contract_key_mode"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
>
|
|
<option value="reference">
|
|
Reference (use only contract.reference to locate records)
|
|
</option>
|
|
</select>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
When importing payments, Contract records are located using the selected
|
|
key. Use your CSV mapping to map the appropriate column to the contract
|
|
reference.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Defaults: Segment / Decision / Action -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700"
|
|
>Default Segment</label
|
|
>
|
|
<select
|
|
v-model="form.meta.segment_id"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
>
|
|
<option :value="null">(none)</option>
|
|
<option v-for="s in props.segments || []" :key="s.id" :value="s.id">
|
|
{{ s.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700"
|
|
>Default Action (for Activity)</label
|
|
>
|
|
<select
|
|
v-model="form.meta.action_id"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
>
|
|
<option :value="null">(none)</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"
|
|
>Default Decision</label
|
|
>
|
|
<select
|
|
v-model="form.meta.decision_id"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
:disabled="!form.meta.action_id"
|
|
>
|
|
<option :value="null">(none)</option>
|
|
<option v-for="d in decisionsForSelectedAction" :key="d.id" :value="d.id">
|
|
{{ d.name }}
|
|
</option>
|
|
</select>
|
|
<p v-if="!form.meta.action_id" class="text-xs text-gray-500 mt-1">
|
|
Select an Action to see its Decisions.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700">Name</label>
|
|
<input
|
|
v-model="form.name"
|
|
type="text"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
|
<textarea
|
|
v-model="form.description"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
rows="3"
|
|
/>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700">Source Type</label>
|
|
<select
|
|
v-model="form.source_type"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
>
|
|
<option value="csv">CSV</option>
|
|
<option value="xml">XML</option>
|
|
<option value="xls">XLS</option>
|
|
<option value="xlsx">XLSX</option>
|
|
<option value="json">JSON</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700"
|
|
>Default Record Type (optional)</label
|
|
>
|
|
<input
|
|
v-model="form.default_record_type"
|
|
type="text"
|
|
class="mt-1 block w-full border rounded p-2"
|
|
placeholder="e.g., account, person"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<input
|
|
id="is_active"
|
|
v-model="form.is_active"
|
|
type="checkbox"
|
|
class="rounded"
|
|
/>
|
|
<label for="is_active" class="text-sm font-medium text-gray-700"
|
|
>Active</label
|
|
>
|
|
</div>
|
|
|
|
<div class="pt-4">
|
|
<button
|
|
@click.prevent="submit"
|
|
class="px-4 py-2 bg-emerald-600 text-white rounded"
|
|
:disabled="form.processing"
|
|
>
|
|
{{ form.processing ? "Saving…" : "Create Template" }}
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
v-if="form.errors && Object.keys(form.errors).length"
|
|
class="text-sm text-red-600"
|
|
>
|
|
<div v-for="(msg, key) in form.errors" :key="key">{{ msg }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
</template>
|