535 lines
18 KiB
Vue
535 lines
18 KiB
Vue
<script setup>
|
|
import { ref, reactive, computed, onBeforeMount } from 'vue';
|
|
import { useRouter, onBeforeRouteLeave } from 'vue-router';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useQuasar } from 'quasar';
|
|
import axios from 'axios';
|
|
import { toCurrency, toDate } from 'src/filters';
|
|
import { useRole } from 'src/composables/useRole';
|
|
import { downloadFile } from 'src/composables/downloadFile';
|
|
import { useArrayData } from 'src/composables/useArrayData';
|
|
import { usePrintService } from 'composables/usePrintService';
|
|
import VnLv from 'src/components/ui/VnLv.vue';
|
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
|
import FetchData from 'src/components/FetchData.vue';
|
|
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
|
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
|
import { useCapitalize } from 'src/composables/useCapitalize';
|
|
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
|
import InvoiceInToBook from '../InvoiceInToBook.vue';
|
|
|
|
const $props = defineProps({ id: { type: Number, default: null } });
|
|
|
|
const { push, currentRoute } = useRouter();
|
|
|
|
const quasar = useQuasar();
|
|
const { hasAny } = useRole();
|
|
const { t } = useI18n();
|
|
const { openReport, sendEmail } = usePrintService();
|
|
const { store } = useArrayData(currentRoute.value.meta.moduleName);
|
|
|
|
const invoiceIn = computed(() => store.data);
|
|
const cardDescriptorRef = ref();
|
|
const correctionDialogRef = ref();
|
|
const entityId = computed(() => $props.id || +currentRoute.value.params.id);
|
|
const totalAmount = ref();
|
|
const currentAction = ref();
|
|
const config = ref();
|
|
const cplusRectificationTypes = ref([]);
|
|
const siiTypeInvoiceOuts = ref([]);
|
|
const invoiceCorrectionTypes = ref([]);
|
|
const actions = {
|
|
unbook: {
|
|
title: t('assertAction', { action: t('unbook') }),
|
|
action: toUnbook,
|
|
},
|
|
delete: {
|
|
title: t('assertAction', { action: t('delete') }),
|
|
action: deleteInvoice,
|
|
},
|
|
clone: {
|
|
title: t('assertAction', { action: t('clone') }),
|
|
action: cloneInvoice,
|
|
},
|
|
showPdf: { cb: showPdfInvoice },
|
|
sendPdf: { cb: sendPdfInvoiceConfirmation },
|
|
correct: { cb: () => correctionDialogRef.value.show() },
|
|
};
|
|
const filter = {
|
|
include: [
|
|
{
|
|
relation: 'supplier',
|
|
scope: {
|
|
include: {
|
|
relation: 'contacts',
|
|
scope: {
|
|
where: {
|
|
email: { neq: null },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
relation: 'invoiceInDueDay',
|
|
},
|
|
{
|
|
relation: 'company',
|
|
},
|
|
{
|
|
relation: 'currency',
|
|
},
|
|
],
|
|
};
|
|
const invoiceInCorrection = reactive({ correcting: [], corrected: null });
|
|
const routes = reactive({
|
|
getSupplier: (id) => {
|
|
return { name: 'SupplierCard', params: { id } };
|
|
},
|
|
getTickets: (id) => {
|
|
return {
|
|
name: 'InvoiceInList',
|
|
query: {
|
|
params: JSON.stringify({ supplierFk: id }),
|
|
},
|
|
};
|
|
},
|
|
getCorrection: (invoiceInCorrection) => {
|
|
if (invoiceInCorrection.correcting.length > 1) {
|
|
return {
|
|
name: 'InvoiceInList',
|
|
query: {
|
|
params: JSON.stringify({ correctedFk: entityId.value }),
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
name: 'InvoiceInCard',
|
|
params: {
|
|
id: invoiceInCorrection.corrected ?? invoiceInCorrection.correcting[0],
|
|
},
|
|
};
|
|
},
|
|
getEntry: (id) => {
|
|
return { name: 'EntryCard', params: { id } };
|
|
},
|
|
});
|
|
const correctionFormData = reactive({
|
|
invoiceReason: 2,
|
|
invoiceType: 2,
|
|
invoiceClass: 6,
|
|
});
|
|
const isNotFilled = computed(() => Object.values(correctionFormData).includes(null));
|
|
|
|
onBeforeMount(async () => {
|
|
await setInvoiceCorrection(entityId.value);
|
|
const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`);
|
|
totalAmount.value = data.totalDueDay;
|
|
});
|
|
|
|
onBeforeRouteLeave(async (to, from) => {
|
|
invoiceInCorrection.correcting.length = 0;
|
|
invoiceInCorrection.corrected = null;
|
|
if (to.params.id !== from.params.id) await setInvoiceCorrection(entityId.value);
|
|
});
|
|
|
|
async function setInvoiceCorrection(id) {
|
|
const [{ data: correctingData }, { data: correctedData }] = await Promise.all([
|
|
axios.get('InvoiceInCorrections', {
|
|
params: {
|
|
filter: {
|
|
where: {
|
|
correctingFk: id,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
axios.get('InvoiceInCorrections', {
|
|
params: {
|
|
filter: {
|
|
where: {
|
|
correctedFk: id,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (correctingData[0]) invoiceInCorrection.corrected = correctingData[0].correctedFk;
|
|
|
|
invoiceInCorrection.correcting = correctedData.map(
|
|
(corrected) => corrected.correctingFk
|
|
);
|
|
}
|
|
|
|
function openDialog() {
|
|
quasar.dialog({
|
|
component: VnConfirm,
|
|
componentProps: {
|
|
title: t(currentAction.value.title),
|
|
promise: currentAction.value.action,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function toUnbook() {
|
|
const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`);
|
|
const { isLinked, bookEntry, accountingEntries } = data;
|
|
|
|
const type = isLinked ? 'warning' : 'positive';
|
|
const message = isLinked
|
|
? t('isLinked', { bookEntry, accountingEntries })
|
|
: t('isNotLinked', { bookEntry });
|
|
|
|
quasar.notify({ type, message });
|
|
if (!isLinked) store.data.isBooked = false;
|
|
}
|
|
|
|
async function deleteInvoice() {
|
|
await axios.delete(`InvoiceIns/${entityId.value}`);
|
|
quasar.notify({
|
|
type: 'positive',
|
|
message: t('Invoice deleted'),
|
|
});
|
|
push({ path: '/invoice-in' });
|
|
}
|
|
|
|
async function cloneInvoice() {
|
|
const { data } = await axios.post(`InvoiceIns/${entityId.value}/clone`);
|
|
quasar.notify({
|
|
type: 'positive',
|
|
message: t('Invoice cloned'),
|
|
});
|
|
push({ path: `/invoice-in/${data.id}/summary` });
|
|
}
|
|
|
|
const isAdministrative = () => hasAny(['administrative']);
|
|
|
|
const isAgricultural = () =>
|
|
invoiceIn.value?.supplier?.sageWithholdingFk === config.value[0]?.sageWithholdingFk;
|
|
|
|
function showPdfInvoice() {
|
|
if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`);
|
|
}
|
|
|
|
function sendPdfInvoiceConfirmation() {
|
|
quasar.dialog({
|
|
component: SendEmailDialog,
|
|
componentProps: {
|
|
data: {
|
|
address: invoiceIn.value.supplier.contacts[0].email,
|
|
},
|
|
promise: sendPdfInvoice,
|
|
},
|
|
});
|
|
}
|
|
|
|
function sendPdfInvoice({ address }) {
|
|
if (!address)
|
|
quasar.notify({
|
|
type: 'negative',
|
|
message: t(`The email can't be empty`),
|
|
});
|
|
else
|
|
return sendEmail(`InvoiceIns/${entityId.value}/invoice-in-email`, {
|
|
recipientId: invoiceIn.value.supplier.id,
|
|
recipient: address,
|
|
});
|
|
}
|
|
|
|
function triggerMenu(type) {
|
|
currentAction.value = actions[type];
|
|
if (currentAction.value.cb) currentAction.value.cb();
|
|
else openDialog(type);
|
|
}
|
|
|
|
const createInvoiceInCorrection = async () => {
|
|
const { data: correctingId } = await axios.post(
|
|
'InvoiceIns/corrective',
|
|
Object.assign(correctionFormData, { id: entityId.value })
|
|
);
|
|
push({ path: `/invoice-in/${correctingId}/summary` });
|
|
};
|
|
</script>
|
|
<template>
|
|
<FetchData
|
|
url="InvoiceInConfigs"
|
|
:where="{ fields: ['sageWithholdingFk'] }"
|
|
auto-load
|
|
@on-fetch="(data) => (config = data)"
|
|
/>
|
|
<FetchData
|
|
url="CplusRectificationTypes"
|
|
@on-fetch="(data) => (cplusRectificationTypes = data)"
|
|
auto-load
|
|
/>
|
|
<FetchData
|
|
url="SiiTypeInvoiceOuts"
|
|
:where="{ code: { like: 'R%' } }"
|
|
@on-fetch="(data) => (siiTypeInvoiceOuts = data)"
|
|
auto-load
|
|
/>
|
|
<FetchData
|
|
url="InvoiceCorrectionTypes"
|
|
@on-fetch="(data) => (invoiceCorrectionTypes = data)"
|
|
auto-load
|
|
/>
|
|
<CardDescriptor
|
|
ref="cardDescriptorRef"
|
|
module="InvoiceIn"
|
|
data-key="InvoiceIn"
|
|
:url="`InvoiceIns/${entityId}`"
|
|
:filter="filter"
|
|
title="supplierRef"
|
|
>
|
|
<template #menu="{ entity }">
|
|
<InvoiceInToBook>
|
|
<template #content="{ book }">
|
|
<QItem
|
|
v-if="!entity?.isBooked && isAdministrative()"
|
|
v-ripple
|
|
clickable
|
|
@click="book(entityId)"
|
|
>
|
|
<QItemSection>{{ t('To book') }}</QItemSection>
|
|
</QItem>
|
|
</template>
|
|
</InvoiceInToBook>
|
|
<QItem
|
|
v-if="entity?.isBooked && isAdministrative()"
|
|
v-ripple
|
|
clickable
|
|
@click="triggerMenu('unbook')"
|
|
>
|
|
<QItemSection>
|
|
{{ t('To unbook') }}
|
|
</QItemSection>
|
|
</QItem>
|
|
<QItem
|
|
v-if="isAdministrative()"
|
|
v-ripple
|
|
clickable
|
|
@click="triggerMenu('delete')"
|
|
>
|
|
<QItemSection>{{ t('Delete invoice') }}</QItemSection>
|
|
</QItem>
|
|
<QItem
|
|
v-if="isAdministrative()"
|
|
v-ripple
|
|
clickable
|
|
@click="triggerMenu('clone')"
|
|
>
|
|
<QItemSection>{{ t('Clone invoice') }}</QItemSection>
|
|
</QItem>
|
|
<QItem
|
|
v-if="isAgricultural()"
|
|
v-ripple
|
|
clickable
|
|
@click="triggerMenu('showPdf')"
|
|
>
|
|
<QItemSection>{{ t('Show agricultural receipt as PDF') }}</QItemSection>
|
|
</QItem>
|
|
<QItem
|
|
v-if="isAgricultural()"
|
|
v-ripple
|
|
clickable
|
|
@click="triggerMenu('sendPdf')"
|
|
>
|
|
<QItemSection
|
|
>{{ t('Send agricultural receipt as PDF') }}...</QItemSection
|
|
>
|
|
</QItem>
|
|
<QItem
|
|
v-if="!invoiceInCorrection.corrected"
|
|
v-ripple
|
|
clickable
|
|
@click="triggerMenu('correct')"
|
|
>
|
|
<QItemSection>{{ t('Create rectificative invoice') }}...</QItemSection>
|
|
</QItem>
|
|
<QItem
|
|
v-if="entity.dmsFk"
|
|
v-ripple
|
|
clickable
|
|
@click="downloadFile(entity.dmsFk)"
|
|
>
|
|
<QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection>
|
|
</QItem>
|
|
</template>
|
|
<template #body="{ entity }">
|
|
<VnLv :label="t('invoiceIn.card.issued')" :value="toDate(entity.issued)" />
|
|
<VnLv :label="t('invoiceIn.summary.booked')" :value="toDate(entity.booked)" />
|
|
<VnLv
|
|
:label="t('invoiceIn.card.amount')"
|
|
:value="toCurrency(totalAmount, entity.currency?.code)"
|
|
/>
|
|
<VnLv :label="t('invoiceIn.summary.supplier')">
|
|
<template #value>
|
|
<span class="link">
|
|
{{ entity?.supplier?.nickname }}
|
|
<SupplierDescriptorProxy :id="entity?.supplierFk" />
|
|
</span>
|
|
</template>
|
|
</VnLv>
|
|
</template>
|
|
<template #action="{ entity }">
|
|
<QCardActions>
|
|
<QBtn
|
|
size="md"
|
|
icon="vn:supplier"
|
|
color="primary"
|
|
:to="routes.getSupplier(entity.supplierFk)"
|
|
>
|
|
<QTooltip>{{ t('invoiceIn.list.supplier') }}</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
size="md"
|
|
icon="vn:entry"
|
|
color="primary"
|
|
:to="routes.getEntry(entity.entryFk)"
|
|
>
|
|
<QTooltip>{{ t('Entry') }}</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
size="md"
|
|
icon="vn:ticket"
|
|
color="primary"
|
|
:to="routes.getTickets(entity.supplierFk)"
|
|
>
|
|
<QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip>
|
|
</QBtn>
|
|
<QBtn
|
|
v-if="
|
|
invoiceInCorrection.corrected ||
|
|
invoiceInCorrection.correcting.length
|
|
"
|
|
size="md"
|
|
:icon="
|
|
invoiceInCorrection.corrected
|
|
? 'vn:link-to-corrected'
|
|
: 'vn:link-to-correcting'
|
|
"
|
|
color="primary"
|
|
:to="routes.getCorrection(invoiceInCorrection)"
|
|
>
|
|
<QTooltip>{{
|
|
invoiceInCorrection.corrected
|
|
? t('Original invoice')
|
|
: t('Rectificative invoice')
|
|
}}</QTooltip>
|
|
</QBtn>
|
|
</QCardActions>
|
|
</template>
|
|
</CardDescriptor>
|
|
<QDialog ref="correctionDialogRef">
|
|
<QCard>
|
|
<QCardSection>
|
|
<QItem class="q-px-none">
|
|
<span class="text-primary text-h6 full-width">
|
|
{{ t('Create rectificative invoice') }}
|
|
</span>
|
|
<QBtn icon="close" flat round dense v-close-popup />
|
|
</QItem>
|
|
</QCardSection>
|
|
<QCardSection>
|
|
<QItem>
|
|
<QItemSection>
|
|
<QInput
|
|
:label="t('Original invoice')"
|
|
v-model="entityId"
|
|
readonly
|
|
/>
|
|
<VnSelect
|
|
:label="`${useCapitalize(t('globals.class'))}*`"
|
|
v-model="correctionFormData.invoiceClass"
|
|
:options="siiTypeInvoiceOuts"
|
|
option-value="id"
|
|
option-label="code"
|
|
:required="true"
|
|
/>
|
|
</QItemSection>
|
|
<QItemSection>
|
|
<VnSelect
|
|
:label="`${useCapitalize(t('globals.type'))}*`"
|
|
v-model="correctionFormData.invoiceType"
|
|
:options="cplusRectificationTypes"
|
|
option-value="id"
|
|
option-label="description"
|
|
:required="true"
|
|
/>
|
|
<VnSelect
|
|
:label="`${useCapitalize(t('globals.reason'))}*`"
|
|
v-model="correctionFormData.invoiceReason"
|
|
:options="invoiceCorrectionTypes"
|
|
option-value="id"
|
|
option-label="description"
|
|
:required="true"
|
|
/>
|
|
</QItemSection>
|
|
</QItem>
|
|
</QCardSection>
|
|
<QCardActions class="justify-end q-mr-sm">
|
|
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
|
|
<QBtn
|
|
:label="t('globals.save')"
|
|
color="primary"
|
|
v-close-popup
|
|
@click="createInvoiceInCorrection"
|
|
:disable="isNotFilled"
|
|
/>
|
|
</QCardActions>
|
|
</QCard>
|
|
</QDialog>
|
|
</template>
|
|
<style lang="scss" scoped>
|
|
.q-dialog {
|
|
.q-card {
|
|
max-width: 45em;
|
|
.q-item__section > .q-input {
|
|
padding-bottom: 1.4em;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media (max-width: $breakpoint-xs) {
|
|
.q-dialog {
|
|
.q-card__section:nth-child(2) {
|
|
.q-item,
|
|
.q-item__section {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
<i18n>
|
|
en:
|
|
isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries
|
|
isLinked: The entry {bookEntry} has been linked to Sage. Please contact administration for further information
|
|
assertAction: Are you sure you want to {action} this invoice?
|
|
es:
|
|
book: asentar
|
|
unbook: desasentar
|
|
delete: eliminar
|
|
clone: clonar
|
|
To book: Contabilizar
|
|
To unbook: Descontabilizar
|
|
Delete invoice: Eliminar factura
|
|
Invoice deleted: Factura eliminada
|
|
Clone invoice: Clonar factura
|
|
Invoice cloned: Factura clonada
|
|
Show agricultural receipt as PDF: Ver recibo agrícola como PDF
|
|
Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
|
|
Are you sure you want to send it?: Estás seguro que quieres enviarlo?
|
|
Send PDF invoice: Enviar factura a PDF
|
|
Create rectificative invoice: Crear factura rectificativa
|
|
Rectificative invoice: Factura rectificativa
|
|
Original invoice: Factura origen
|
|
Entry: entrada
|
|
isNotLinked: Se ha eliminado el asiento nº {bookEntry} con {accountingEntries} apuntes
|
|
isLinked: El asiento {bookEntry} fue enlazado a Sage, por favor contacta con administración
|
|
assertAction: Estas seguro de querer {action} esta factura?
|
|
</i18n>
|