7525-devToTest #419

Merged
alexm merged 177 commits from 7525-devToTest into test 2024-06-04 08:06:27 +00:00
4 changed files with 141 additions and 90 deletions
Showing only changes of commit db632f3c11 - Show all commits

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, reactive, computed, onBeforeMount, watch } from 'vue'; import { ref, reactive, computed, onBeforeMount, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios'; import axios from 'axios';
@ -18,23 +18,25 @@ import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import { useCapitalize } from 'src/composables/useCapitalize'; import { useCapitalize } from 'src/composables/useCapitalize';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceInToBook from '../InvoiceInToBook.vue';
const $props = defineProps({ const $props = defineProps({
id: { type: Number, default: null }, id: { type: Number, default: null },
}); });
const route = useRoute(); const { push, currentRoute } = useRouter();
const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { hasAny } = useRole(); const { hasAny } = useRole();
const { t } = useI18n(); const { t } = useI18n();
const { openReport, sendEmail } = usePrintService(); const { openReport, sendEmail } = usePrintService();
const arrayData = useArrayData('InvoiceIn'); const { store } = useArrayData('InvoiceIn');
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => store.data);
const isBooked = ref();
const cardDescriptorRef = ref(); const cardDescriptorRef = ref();
const correctionDialogRef = ref(); const correctionDialogRef = ref();
const entityId = computed(() => $props.id || +route.params.id); const entityId = computed(() => $props.id || +currentRoute.value.params.id);
const totalAmount = ref(); const totalAmount = ref();
const currentAction = ref(); const currentAction = ref();
const config = ref(); const config = ref();
@ -42,11 +44,6 @@ const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]); const siiTypeInvoiceOuts = ref([]);
const invoiceCorrectionTypes = ref([]); const invoiceCorrectionTypes = ref([]);
const actions = { const actions = {
book: {
title: 'Are you sure you want to book this invoice?',
cb: checkToBook,
action: toBook,
},
unbook: { unbook: {
title: 'Are you sure you want to unbook this invoice?', title: 'Are you sure you want to unbook this invoice?',
action: toUnbook, action: toUnbook,
@ -143,7 +140,7 @@ const isNotFilled = computed(() => Object.values(correctionFormData).includes(nu
onBeforeMount(async () => await setInvoiceCorrection(entityId.value)); onBeforeMount(async () => await setInvoiceCorrection(entityId.value));
watch( watch(
() => route.params.id, () => currentRoute.value.params.id,
async (newId) => { async (newId) => {
invoiceInCorrection.correcting.length = 0; invoiceInCorrection.correcting.length = 0;
invoiceInCorrection.corrected = null; invoiceInCorrection.corrected = null;
@ -182,6 +179,7 @@ async function setInvoiceCorrection(id) {
async function setData(entity) { async function setData(entity) {
data.value = useCardDescription(entity.supplierRef, entity.id); data.value = useCardDescription(entity.supplierRef, entity.id);
isBooked.value = entity.isBooked;
const { totalDueDay } = await getTotals(); const { totalDueDay } = await getTotals();
totalAmount.value = totalDueDay; totalAmount.value = totalDueDay;
} }
@ -201,36 +199,6 @@ function openDialog() {
}); });
} }
async function checkToBook() {
let directBooking = true;
const totals = await getTotals();
const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase;
const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat;
if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) directBooking = false;
const { data: dueDaysCount } = await axios.get('InvoiceInDueDays/count', {
where: {
invoiceInFk: entityId.value,
dueDated: { gte: Date.vnNew() },
},
});
if (dueDaysCount) directBooking = false;
if (!directBooking) openDialog();
else toBook();
}
async function toBook() {
await axios.post(`InvoiceIns/${entityId.value}/toBook`);
quasar.notify({ type: 'positive', message: t('globals.dataSaved') });
await cardDescriptorRef.value.getData();
setTimeout(() => location.reload(), 500);
}
async function toUnbook() { async function toUnbook() {
const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`); const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`);
const { isLinked, bookEntry, accountingEntries } = data; const { isLinked, bookEntry, accountingEntries } = data;
@ -241,9 +209,7 @@ async function toUnbook() {
: t('isNotLinked', { bookEntry }); : t('isNotLinked', { bookEntry });
quasar.notify({ type, message }); quasar.notify({ type, message });
await cardDescriptorRef.value.getData(); store.data.isBooked = false;
if (!isLinked) setTimeout(() => location.reload(), 500);
} }
async function deleteInvoice() { async function deleteInvoice() {
@ -252,7 +218,7 @@ async function deleteInvoice() {
type: 'positive', type: 'positive',
message: t('Invoice deleted'), message: t('Invoice deleted'),
}); });
router.push({ path: '/invoice-in' }); push({ path: '/invoice-in' });
} }
async function cloneInvoice() { async function cloneInvoice() {
@ -261,7 +227,7 @@ async function cloneInvoice() {
type: 'positive', type: 'positive',
message: t('Invoice cloned'), message: t('Invoice cloned'),
}); });
router.push({ path: `/invoice-in/${data.id}/summary` }); push({ path: `/invoice-in/${data.id}/summary` });
} }
const isAdministrative = () => hasAny(['administrative']); const isAdministrative = () => hasAny(['administrative']);
@ -309,7 +275,7 @@ const createInvoiceInCorrection = async () => {
'InvoiceIns/corrective', 'InvoiceIns/corrective',
Object.assign(correctionFormData, { id: entityId.value }) Object.assign(correctionFormData, { id: entityId.value })
); );
router.push({ path: `/invoice-in/${correctingId}/summary` }); push({ path: `/invoice-in/${correctingId}/summary` });
}; };
</script> </script>
@ -347,16 +313,20 @@ const createInvoiceInCorrection = async () => {
data-key="invoiceInData" data-key="invoiceInData"
> >
<template #menu="{ entity }"> <template #menu="{ entity }">
<InvoiceInToBook>
<template #content="{ book }">
<QItem
v-if="!invoiceIn?.isBooked && isAdministrative()"
v-ripple
clickable
@click="book(entityId)"
>
<QItemSection>{{ t('To book') }}</QItemSection>
</QItem>
</template>
</InvoiceInToBook>
<QItem <QItem
v-if="!entity.isBooked && isAdministrative()" v-if="invoiceIn?.isBooked && isAdministrative()"
v-ripple
clickable
@click="triggerMenu('book')"
>
<QItemSection>{{ t('To book') }}</QItemSection>
</QItem>
<QItem
v-if="entity.isBooked && isAdministrative()"
v-ripple v-ripple
clickable clickable
@click="triggerMenu('unbook')" @click="triggerMenu('unbook')"

View File

@ -3,17 +3,19 @@ import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceIntoBook from '../InvoiceInToBook.vue';
onMounted(async () => { onMounted(async () => {
salixUrl.value = await getUrl(''); salixUrl.value = await getUrl('');
invoiceInUrl.value = salixUrl.value + `invoiceIn/${entityId.value}/`; invoiceInUrl.value = salixUrl.value + `invoiceIn/${entityId.value}/`;
}); });
const route = useRoute(); const { params } = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const $props = defineProps({ const $props = defineProps({
@ -23,12 +25,15 @@ const $props = defineProps({
}, },
}); });
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || params.id);
const arrayData = useArrayData('InvoiceIn');
const invoiceIn = computed(() => arrayData.store.data);
const salixUrl = ref(); const salixUrl = ref();
const invoiceInUrl = ref(); const invoiceInUrl = ref();
const amountsNotMatch = ref(null); const amountsNotMatch = ref(null);
const intrastatTotals = ref({}); const intrastatTotals = ref({});
const isBooked = ref();
const vatColumns = ref([ const vatColumns = ref([
{ {
@ -182,6 +187,7 @@ function getTaxTotal(tax) {
function setData(entity) { function setData(entity) {
if (!entity) return false; if (!entity) return false;
isBooked.value = entity.isBooked;
amountsNotMatch.value = getAmountNotMatch(entity.totals); amountsNotMatch.value = getAmountNotMatch(entity.totals);
if (entity.invoiceInIntrastat.length) if (entity.invoiceInIntrastat.length)
@ -203,10 +209,22 @@ function getLink(param) {
:url="`InvoiceIns/${entityId}/summary`" :url="`InvoiceIns/${entityId}/summary`"
@on-fetch="(data) => setData(data)" @on-fetch="(data) => setData(data)"
> >
<template #header="{ entity: invoiceIn }"> <template #header="{ entity }">
<div>{{ invoiceIn.id }} - {{ invoiceIn.supplier?.name }}</div> <div>{{ entity.id }} - {{ entity.supplier?.name }}</div>
</template> </template>
<template #body="{ entity: invoiceIn }"> <template #header-right v-if="!invoiceIn?.isBooked">
<InvoiceIntoBook>
<template #content="{ book }">
<QBtn
:label="t('To book')"
color="orange-11"
text-color="black"
@click="book(entityId)"
/>
</template>
</InvoiceIntoBook>
</template>
<template #body="{ entity }">
<!--Basic Data--> <!--Basic Data-->
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
@ -217,26 +235,26 @@ function getLink(param) {
</QCardSection> </QCardSection>
<VnLv <VnLv
:label="t('invoiceIn.summary.supplier')" :label="t('invoiceIn.summary.supplier')"
:value="invoiceIn.supplier?.name" :value="entity.supplier?.name"
> >
<template #value> <template #value>
<span class="link"> <span class="link">
{{ invoiceIn.supplier?.name }} {{ entity.supplier?.name }}
<SupplierDescriptorProxy :id="invoiceIn.supplierFk" /> <SupplierDescriptorProxy :id="entity.supplierFk" />
</span> </span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('invoiceIn.summary.supplierRef')" :label="t('invoiceIn.summary.supplierRef')"
:value="invoiceIn.supplierRef" :value="entity.supplierRef"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.currency')" :label="t('invoiceIn.summary.currency')"
:value="invoiceIn.currency?.code" :value="entity.currency?.code"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.docNumber')" :label="t('invoiceIn.summary.docNumber')"
:value="`${invoiceIn.serial}/${invoiceIn.serialNumber}`" :value="`${entity.serial}/${entity.serialNumber}`"
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
@ -249,19 +267,19 @@ function getLink(param) {
<VnLv <VnLv
:ellipsis-value="false" :ellipsis-value="false"
:label="t('invoiceIn.summary.issued')" :label="t('invoiceIn.summary.issued')"
:value="toDate(invoiceIn.issued)" :value="toDate(entity.issued)"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.operated')" :label="t('invoiceIn.summary.operated')"
:value="toDate(invoiceIn.operated)" :value="toDate(entity.operated)"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.bookEntried')" :label="t('invoiceIn.summary.bookEntried')"
:value="toDate(invoiceIn.bookEntried)" :value="toDate(entity.bookEntried)"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.bookedDate')" :label="t('invoiceIn.summary.bookedDate')"
:value="toDate(invoiceIn.booked)" :value="toDate(entity.booked)"
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
@ -273,17 +291,18 @@ function getLink(param) {
</QCardSection> </QCardSection>
<VnLv <VnLv
:label="t('invoiceIn.summary.sage')" :label="t('invoiceIn.summary.sage')"
:value="invoiceIn.sageWithholding?.withholding" :value="entity.sageWithholding?.withholding"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.vat')" :label="t('invoiceIn.summary.vat')"
:value="invoiceIn.expenseDeductible?.name" :value="entity.expenseDeductible?.name"
/> />
<VnLv <VnLv
:label="t('invoiceIn.summary.company')" :label="t('invoiceIn.summary.company')"
:value="invoiceIn.company?.code" :value="entity.company?.code"
/> />
<QCheckbox <QCheckbox
v-if="invoiceIn"
:label="t('invoiceIn.summary.booked')" :label="t('invoiceIn.summary.booked')"
v-model="invoiceIn.isBooked" v-model="invoiceIn.isBooked"
:disable="true" :disable="true"
@ -296,9 +315,9 @@ function getLink(param) {
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<VnLv <VnLv
:label="t('invoiceIn.summary.taxableBase')" :label="t('invoiceIn.summary.taxableBase')"
:value="toCurrency(invoiceIn.totals.totalTaxableBase)" :value="toCurrency(entity.totals.totalTaxableBase)"
/> />
<VnLv label="Total" :value="toCurrency(invoiceIn.totals.totalVat)" /> <VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" />
<VnLv :label="t('invoiceIn.summary.dueTotal')"> <VnLv :label="t('invoiceIn.summary.dueTotal')">
<template #value> <template #value>
<QChip <QChip
@ -311,21 +330,21 @@ function getLink(param) {
: t('invoiceIn.summary.dueTotal') : t('invoiceIn.summary.dueTotal')
" "
> >
{{ toCurrency(invoiceIn.totals.totalDueDay) }} {{ toCurrency(entity.totals.totalDueDay) }}
</QChip> </QChip>
</template> </template>
</VnLv> </VnLv>
</QCardSection> </QCardSection>
</QCard> </QCard>
<!--Vat--> <!--Vat-->
<QCard v-if="invoiceIn.invoiceInTax.length" class="vn-two"> <QCard v-if="entity.invoiceInTax.length" class="vn-two">
<a class="header header-link" :href="getLink('vat')"> <a class="header header-link" :href="getLink('vat')">
{{ t('invoiceIn.card.vat') }} {{ t('invoiceIn.card.vat') }}
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</a> </a>
<QTable <QTable
:columns="vatColumns" :columns="vatColumns"
:rows="invoiceIn.invoiceInTax" :rows="entity.invoiceInTax"
flat flat
hide-pagination hide-pagination
> >
@ -339,19 +358,17 @@ function getLink(param) {
<template #bottom-row> <template #bottom-row>
<QTr class="bg"> <QTr class="bg">
<QTd></QTd> <QTd></QTd>
<QTd>{{ toCurrency(invoiceIn.totals.totalTaxableBase) }}</QTd> <QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd>
<QTd></QTd> <QTd></QTd>
<QTd></QTd> <QTd></QTd>
<QTd>{{ <QTd>{{ toCurrency(getTaxTotal(entity.invoiceInTax)) }}</QTd>
toCurrency(getTaxTotal(invoiceIn.invoiceInTax))
}}</QTd>
<QTd></QTd> <QTd></QTd>
</QTr> </QTr>
</template> </template>
</QTable> </QTable>
</QCard> </QCard>
<!--Due Day--> <!--Due Day-->
<QCard v-if="invoiceIn.invoiceInDueDay.length" class="vn-one"> <QCard v-if="entity.invoiceInDueDay.length" class="vn-one">
<a class="header header-link" :href="getLink('due-day')"> <a class="header header-link" :href="getLink('due-day')">
{{ t('invoiceIn.card.dueDay') }} {{ t('invoiceIn.card.dueDay') }}
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
@ -359,7 +376,7 @@ function getLink(param) {
<QTable <QTable
class="full-width" class="full-width"
:columns="dueDayColumns" :columns="dueDayColumns"
:rows="invoiceIn.invoiceInDueDay" :rows="entity.invoiceInDueDay"
flat flat
hide-pagination hide-pagination
> >
@ -374,21 +391,21 @@ function getLink(param) {
<QTr class="bg"> <QTr class="bg">
<QTd></QTd> <QTd></QTd>
<QTd></QTd> <QTd></QTd>
<QTd>{{ toCurrency(invoiceIn.totals.totalDueDay) }}</QTd> <QTd>{{ toCurrency(entity.totals.totalDueDay) }}</QTd>
<QTd></QTd> <QTd></QTd>
</QTr> </QTr>
</template> </template>
</QTable> </QTable>
</QCard> </QCard>
<!--Intrastat--> <!--Intrastat-->
<QCard v-if="invoiceIn.invoiceInIntrastat.length"> <QCard v-if="entity.invoiceInIntrastat.length">
<a class="header header-link" :href="getLink('intrastat')"> <a class="header header-link" :href="getLink('intrastat')">
{{ t('invoiceIn.card.intrastat') }} {{ t('invoiceIn.card.intrastat') }}
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</a> </a>
<QTable <QTable
:columns="intrastatColumns" :columns="intrastatColumns"
:rows="invoiceIn.invoiceInIntrastat" :rows="entity.invoiceInIntrastat"
flat flat
hide-pagination hide-pagination
> >
@ -429,4 +446,5 @@ function getLink(param) {
Search invoice: Buscar factura recibida Search invoice: Buscar factura recibida
You can search by invoice reference: Puedes buscar por referencia de la factura You can search by invoice reference: Puedes buscar por referencia de la factura
Totals: Totales Totals: Totales
To book: Contabilizar
</i18n> </i18n>

View File

@ -0,0 +1,62 @@
<script setup>
import axios from 'axios';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import { useArrayData } from 'src/composables/useArrayData';
const { notify, dialog } = useQuasar();
const { t } = useI18n();
defineExpose({ checkToBook });
const { store } = useArrayData('InvoiceIn');
async function checkToBook(id) {
let directBooking = true;
const { data: totals } = await axios.get(`InvoiceIns/${id}/getTotals`);
const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase;
const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat;
if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) directBooking = false;
const { data: dueDaysCount } = await axios.get('InvoiceInDueDays/count', {
where: {
invoiceInFk: id,
dueDated: { gte: Date.vnNew() },
},
});
if (dueDaysCount) directBooking = false;
if (directBooking) return toBook(id);
dialog({
component: VnConfirm,
componentProps: { title: t('Are you sure you want to book this invoice?') },
}).onOk(async () => await toBook(id));
}
async function toBook(id) {
let type = 'positive';
let message = t('globals.dataSaved');
try {
await axios.post(`InvoiceIns/${id}/toBook`);
store.data.isBooked = true;
} catch (e) {
type = 'negative';
message = t('It was not able to book the invoice');
} finally {
notify({ type, message });
}
}
</script>
<template>
<slot name="content" :book="checkToBook" />
</template>
<i18n>
es:
Are you sure you want to book this invoice?: ¿Estás seguro de querer asentar esta factura?
It was not able to book the invoice: No se pudo contabilizar la factura
</i18n>

View File

@ -1,7 +1,7 @@
describe('InvoiceInDescriptor', () => { describe('InvoiceInDescriptor', () => {
const saveDialog = '.q-btn--unelevated > .q-btn__content > .block'; const saveDialog = '.q-btn--unelevated > .q-btn__content > .block';
const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section'; const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section';
const screen = '.q-drawer-container > .fullscreen';
it('should booking and unbooking the invoice properly', () => { it('should booking and unbooking the invoice properly', () => {
cy.login('developer'); cy.login('developer');
cy.visit(`/#/invoice-in/1/summary?limit=10`); cy.visit(`/#/invoice-in/1/summary?limit=10`);
@ -10,6 +10,7 @@ describe('InvoiceInDescriptor', () => {
cy.openActionsDescriptor(); cy.openActionsDescriptor();
cy.get(firstDescritorOpt).click(); cy.get(firstDescritorOpt).click();
cy.get(saveDialog).click(); cy.get(saveDialog).click();
cy.get(screen).click();
cy.get('.q-checkbox').should('have.attr', 'aria-checked', 'true'); cy.get('.q-checkbox').should('have.attr', 'aria-checked', 'true');
cy.openLeftMenu(); cy.openLeftMenu();