added option to import payments from csv file
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<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';
|
||||
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,
|
||||
@@ -13,10 +13,10 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
name: '',
|
||||
description: '',
|
||||
source_type: 'csv',
|
||||
default_record_type: '',
|
||||
name: "",
|
||||
description: "",
|
||||
source_type: "csv",
|
||||
default_record_type: "",
|
||||
is_active: true,
|
||||
client_uuid: null,
|
||||
entities: [],
|
||||
@@ -24,33 +24,63 @@ const form = useForm({
|
||||
segment_id: null,
|
||||
decision_id: null,
|
||||
action_id: null,
|
||||
delimiter: '',
|
||||
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);
|
||||
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;
|
||||
});
|
||||
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'), {
|
||||
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>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
Create Import Template
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<div class="py-6">
|
||||
@@ -58,11 +88,13 @@ function submit() {
|
||||
<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>
|
||||
<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"
|
||||
:reduce="(c) => c.uuid"
|
||||
track-by="uuid"
|
||||
label="name"
|
||||
placeholder="Global (no client)"
|
||||
@@ -70,71 +102,159 @@ function submit() {
|
||||
: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>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Leave empty to make this template global (visible to all clients).
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Entities (tables)</label>
|
||||
<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' },
|
||||
]"
|
||||
:multiple="true"
|
||||
track-by="value"
|
||||
label="label"
|
||||
:reduce="o => o.value"
|
||||
placeholder="Select one or more entities"
|
||||
:searchable="false"
|
||||
class="mt-1"
|
||||
/>
|
||||
<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 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">
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<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" />
|
||||
<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" />
|
||||
<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">
|
||||
<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>
|
||||
@@ -143,23 +263,44 @@ function submit() {
|
||||
</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" />
|
||||
<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>
|
||||
<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
|
||||
@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-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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user