Merge pull request 'warmfix: New payment from ticket' (!1801) from warmfix_ticket_newPayment into test
gitea/salix-front/pipeline/head This commit looks good Details

Reviewed-on: #1801
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
Javier Segarra 2025-05-19 11:17:05 +00:00
commit 0b5cc7d00f
3 changed files with 310 additions and 187 deletions

View File

@ -55,7 +55,6 @@ const filterBanks = {
fields: ['id', 'bank', 'accountingTypeFk'],
include: { relation: 'accountingType' },
order: 'id',
limit: 30,
};
const filterClientFindOne = {
@ -200,7 +199,6 @@ async function getAmountPaid() {
option-label="bank"
:include="{ relation: 'accountingType' }"
sort-by="id"
:limit="0"
@update:model-value="
(value, options) => setPaymentType(data, value, options)
"

View File

@ -14,8 +14,6 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import TicketFilter from './TicketFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'src/components/FetchData.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
@ -25,6 +23,7 @@ import TicketProblems from 'src/components/TicketProblems.vue';
import VnSection from 'src/components/common/VnSection.vue';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import TicketNewPayment from './components/TicketNewPayment.vue';
const route = useRoute();
const router = useRouter();
@ -73,11 +72,6 @@ const initializeFromQuery = () => {
const selectedRows = ref([]);
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
const showForm = ref(false);
const dialogData = ref();
const companiesOptions = ref([]);
const accountingOptions = ref([]);
const amountToReturn = ref();
const dataKey = 'TicketList';
const formInitialData = ref({});
@ -381,101 +375,22 @@ function openBalanceDialog(ticket) {
description.value.push(ticketData.id);
}
const balanceCreateDialog = ref({
const dialogData = ref({
amountPaid: amountPaid.value,
clientFk: clientFk.value,
description: `Albaran: ${description.value.join(', ')}`,
});
dialogData.value = balanceCreateDialog;
showForm.value = true;
}
async function onSubmit() {
const { data: email } = await axios.get('Clients', {
params: {
filter: JSON.stringify({ where: { id: dialogData.value.value.clientFk } }),
quasar.dialog({
component: TicketNewPayment,
componentProps: {
clientId: clientFk.value,
formData: dialogData.value,
},
});
const { data } = await axios.post(
`Clients/${dialogData.value.value.clientFk}/createReceipt`,
{
payed: dialogData.value.payed,
companyFk: dialogData.value.companyFk,
bankFk: dialogData.value.bankFk,
amountPaid: dialogData.value.value.amountPaid,
description: dialogData.value.value.description,
clientFk: dialogData.value.value.clientFk,
email: email[0].email,
},
);
if (data) notify('globals.dataSaved', 'positive');
showForm.value = false;
}
const setAmountToReturn = (newAmountGiven) => {
const amountPaid = dialogData.value.value.amountPaid;
amountToReturn.value = newAmountGiven - amountPaid;
};
function setReference(data) {
let newDescription = '';
switch (data) {
case 1:
newDescription = `${t(
'ticketList.creditCard',
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
'',
)}`;
break;
case 2:
newDescription = `${t(
'ticketList.cash',
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
'',
)}`;
break;
case 3:
newDescription = `${newDescription.replace(
/^(Credit Card, |Cash, |Transfers, )/,
'',
)}`;
break;
case 4:
newDescription = `${t(
'ticketList.transfers',
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
'',
)}`;
break;
case 3317:
newDescription = '';
break;
default:
break;
}
dialogData.value.value.description = newDescription;
}
</script>
<template>
<FetchData
url="Companies"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<FetchData
url="Accountings"
@on-fetch="(data) => (accountingOptions = data)"
auto-load
/>
<VnSection
:data-key="dataKey"
:columns="columns"
@ -722,99 +637,6 @@ function setReference(data) {
{{ t('ticketList.accountPayment') }}
</QTooltip>
</QPageSticky>
<QDialog ref="dialogRef" v-model="showForm">
<QCard class="q-pa-md q-mb-md">
<QForm @submit="onSubmit()" class="q-pa-sm">
{{ t('ticketList.addPayment') }}
<VnRow>
<VnInputDate
:label="t('ticketList.date')"
v-model="dialogData.payed"
/>
<VnSelect
:label="t('ticketList.company')"
v-model="dialogData.companyFk"
:options="companiesOptions"
option-label="code"
hide-selected
>
</VnSelect>
</VnRow>
<VnRow>
<VnSelect
:label="t('ticketList.bank')"
v-model="dialogData.bankFk"
:options="accountingOptions"
option-label="bank"
hide-selected
@update:model-value="setReference"
/>
<VnInput
:label="t('ticketList.amount')"
v-model="dialogData.value.amountPaid"
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<span>
{{ t('ticketList.cash') }}
</span>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<VnInput
:label="t('ticketList.deliveredAmount')"
v-model="dialogData.value.amountGiven"
@update:model-value="setAmountToReturn"
type="number"
/>
<VnInput
:label="t('ticketList.amountToReturn')"
:model-value="amountToReturn"
type="number"
readonly
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 3 || dialogData.bankFk === 3117">
<VnInput
:label="t('ticketList.compensation')"
v-model="dialogData.value.compensation"
type="text"
/>
</VnRow>
<VnRow>
<VnInput
:label="t('ticketList.reference')"
v-model="dialogData.value.description"
type="text"
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<QCheckbox
:label="t('ticketList.viewReceipt')"
v-model="dialogData.value.viewReceipt"
:toggle-indeterminate="false"
/>
<QCheckbox
:label="t('ticketList.sendEmail')"
v-model="dialogData.value.senEmail"
:toggle-indeterminate="false"
/>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
color="primary"
@click="onSubmit()"
/>
<QBtn
flat
:label="t('globals.close')"
color="primary"
v-close-popup
/>
</div>
</QForm>
</QCard>
</QDialog>
<QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2">
<QBtn
@click="sendDocuware(selectedRows)"

View File

@ -0,0 +1,303 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useDialogPluginComponent } from 'quasar';
import { usePrintService } from 'src/composables/usePrintService';
import useNotify from 'src/composables/useNotify.js';
import FormModelPopup from 'src/components/FormModelPopup.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
import { useState } from 'src/composables/useState';
const { t } = useI18n();
const { notify } = useNotify();
const { sendEmail, openReport } = usePrintService();
const { dialogRef } = useDialogPluginComponent();
const $props = defineProps({
formData: {
type: Object,
required: true,
},
clientId: {
type: Number,
required: true,
},
promise: {
type: Function,
default: null,
},
});
const closeButton = ref(null);
const viewReceipt = ref();
const shouldSendEmail = ref(false);
const maxAmount = ref();
const accountingType = ref({});
const isCash = ref(false);
const formModelRef = ref(false);
const amountToReturn = ref();
const filterBanks = {
fields: ['id', 'bank', 'accountingTypeFk'],
include: { relation: 'accountingType' },
order: 'id',
};
const state = useState();
const user = state.getUser();
const initialData = ref({
...$props.formData.value,
companyFk: user.value.companyFk,
payed: Date.vnNew(),
});
function setPaymentType(data, accounting) {
data.bankFk = accounting.id;
if (!accounting) return;
accountingType.value = accounting.accountingType;
data.description = [];
data.payed = Date.vnNew();
isCash.value = accountingType.value.code == 'cash';
viewReceipt.value = isCash.value;
if (accountingType.value.daysInFuture)
data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture);
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
if (accountingType.value.code == 'compensation') return (data.description = '');
let descriptions = [];
if (accountingType.value.receiptDescription)
descriptions.push(accountingType.value.receiptDescription);
if (data.description > 0) descriptions.push(data.description);
data.description = descriptions.join(', ');
}
const calculateFromAmount = (event) => {
initialData.value.amountToReturn = Number(
(parseFloat(initialData.value.deliveredAmount) + parseFloat(event) * -1).toFixed(
2,
),
);
};
const calculateFromDeliveredAmount = (event) => {
amountToReturn.value = Number((event - initialData.value.amountPaid).toFixed(2));
};
function onBeforeSave(data) {
const exceededAmount = data.amountPaid > maxAmount.value;
if (isCash.value && exceededAmount)
return notify(t('Amount exceeded', { maxAmount: maxAmount.value }), 'negative');
if (isCash.value && shouldSendEmail.value && !data.email)
return notify(t('There is no assigned email for this client'), 'negative');
return data;
}
async function onDataSaved({ email, id }) {
try {
if (shouldSendEmail.value && isCash.value)
await sendEmail(`Receipts/${id}/receipt-email`, {
recipient: email,
});
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank');
} finally {
if ($props.promise) $props.promise();
if (closeButton.value) closeButton.value.click();
}
}
async function getSupplierClientReferences(data) {
if (!data) return (initialData.value.description = '');
const params = { bankAccount: data.compensationAccount };
const { data: reference } = await axios(`Clients/getClientOrSupplierReference`, {
params,
});
if (reference.supplierId) {
data.description = t('Supplier Compensation Reference', {
supplierId: reference.supplierId,
supplierName: reference.supplierName,
});
return;
}
data.description = t('Client Compensation Reference', {
clientId: reference.clientId,
clientName: reference.clientName,
});
}
async function getAmountPaid() {
const filter = {
where: {
clientFk: $props.clientId,
companyFk: initialData.value.companyFk,
},
};
const { data } = await getClientRisk(filter);
initialData.value.amountPaid = (data?.length && data[0].amount) || undefined;
}
async function onSubmit(formData) {
const {
data: [{ email }],
} = await axios.get('Clients', {
params: {
filter: JSON.stringify({ where: { id: $props.clientFk } }),
},
});
const { data } = await axios.post(`Clients/${formData.clientFk}/createReceipt`, {
payed: formData.payed,
companyFk: formData.companyFk,
bankFk: formData.bankFk,
amountPaid: formData.amountPaid,
description: formData.description,
clientFk: $props.clientFk,
email,
});
if (data) notify('globals.dataSaved', 'positive');
await onDataSaved(data);
}
</script>
<template>
<QDialog ref="dialogRef" persistent>
<FormModelPopup
ref="formModelRef"
:form-initial-data="initialData"
:save-fn="onSubmit"
:prevent-submit="true"
:mapper="onBeforeSave"
>
<template #form-inputs="{ data, validate }">
<h5 class="q-mt-none">{{ t('New payment') }}</h5>
<VnRow>
<VnSelect
autofocus
:label="t('Bank')"
v-model="data.bankFk"
url="Accountings"
:filter="filterBanks"
option-label="bank"
:include="{ relation: 'accountingType' }"
sort-by="id"
@update:model-value="
(value, options) => setPaymentType(data, value, options)
"
:emit-value="false"
data-cy="paymentBank"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.id }}:&ensp;{{ scope.opt.bank }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInputNumber
:label="t('Amount')"
:required="true"
@update:model-value="calculateFromAmount($event)"
clearable
v-model.number="data.amountPaid"
data-cy="paymentAmount"
:positive="false"
/>
</VnRow>
<VnRow>
<VnInputDate
:label="t('Date')"
v-model="data.payed"
:required="true"
/>
<VnSelect
url="Companies"
:label="t('Company')"
:required="true"
:rules="validate('entry.companyFk')"
hide-selected
option-label="code"
v-model="data.companyFk"
@update:model-value="getAmountPaid()"
/>
</VnRow>
<div v-if="accountingType.code == 'compensation'">
<div class="text-h6">
{{ t('Compensation') }}
</div>
<VnRow>
<VnAccountNumber
:label="t('Compensation account')"
clearable
v-model="data.compensationAccount"
@blur="getSupplierClientReferences(data)"
/>
</VnRow>
</div>
<VnInput
:label="t('Reference')"
:required="true"
clearable
v-model="data.description"
/>
<div v-if="accountingType.code == 'cash'">
<div class="text-h6">{{ t('Cash') }}</div>
<VnRow>
<VnInputNumber
:label="t('Delivered amount')"
@update:model-value="calculateFromDeliveredAmount($event)"
clearable
v-model="data.deliveredAmount"
/>
<VnInputNumber
:label="t('Amount to return')"
disable
v-model="amountToReturn"
/>
</VnRow>
<VnRow>
<QCheckbox v-model="viewReceipt" :label="t('View recipt')" />
<QCheckbox v-model="shouldSendEmail" :label="t('Send email')" />
</VnRow>
</div>
</template>
</FormModelPopup>
</QDialog>
</template>
<i18n>
en:
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
es:
New payment: Añadir pago
Date: Fecha
Company: Empresa
Bank: Caja
Amount: Importe
Reference: Referencia
Cash: Efectivo
Delivered amount: Cantidad entregada
Amount to return: Cantidad a devolver
View recipt: Ver recibido
Send email: Enviar correo
Compensation: Compensación
Compensation account: Cuenta para compensar
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
There is no assigned email for this client: No hay correo asignado para este cliente
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {maxAmount}
</i18n>