WIP: corrective section created: refs #4466
gitea/salix-front/pipeline/head This commit looks good Details

This commit is contained in:
Jorge Penadés 2023-12-28 16:02:04 +01:00
parent 8761f4baaa
commit 232827ef48
10 changed files with 460 additions and 29 deletions

View File

@ -76,8 +76,13 @@ defineExpose({
reset, reset,
hasChanges, hasChanges,
saveChanges, saveChanges,
getFormData,
}); });
function getFormData() {
return formData.value;
}
async function fetch(data) { async function fetch(data) {
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
let $index = 0; let $index = 0;
@ -196,12 +201,13 @@ function getChanges() {
const creates = []; const creates = [];
const pk = $props.primaryKey; const pk = $props.primaryKey;
for (const [i, row] of formData.value.entries()) { for (const [i, row] of formData.value.entries()) {
console.log(pk, row);
if (!row[pk]) { if (!row[pk]) {
creates.push(row); creates.push(row);
} else if (originalData.value) { } else if (originalData.value) {
const data = getDifferences(originalData.value[i], row); const data = getDifferences(originalData.value[i], row);
console.log(data);
if (!isEmpty(data)) { if (!isEmpty(data)) {
updates.push({ updates.push({
data, data,
@ -211,7 +217,7 @@ function getChanges() {
} }
} }
const changes = { updates, creates }; const changes = { updates, creates };
console.log('ddd', changes);
for (let prop in changes) { for (let prop in changes) {
if (changes[prop].length === 0) changes[prop] = undefined; if (changes[prop].length === 0) changes[prop] = undefined;
} }

View File

@ -50,7 +50,7 @@ function addChildren(module, route, parent) {
const matches = findMatches(mainMenus, route); const matches = findMatches(mainMenus, route);
for (const child of matches) { for (const child of matches) {
navigation.addMenuItem(module, child, parent); if (!child.meta.hidden) navigation.addMenuItem(module, child, parent);
} }
} }
} }

View File

@ -102,7 +102,7 @@ const value = computed({
name="close" name="close"
@click.stop="value = null" @click.stop="value = null"
class="cursor-pointer" class="cursor-pointer"
size="18px" size="xs"
/> />
</template> </template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <template v-for="(_, slotName) in $slots" #[slotName]="slotData">

View File

@ -63,6 +63,10 @@ export default {
selectRows: 'Select all { numberRows } row(s)', selectRows: 'Select all { numberRows } row(s)',
allRows: 'All { numberRows } row(s)', allRows: 'All { numberRows } row(s)',
markAll: 'Mark all', markAll: 'Mark all',
requiredField: 'Required field',
class: 'clase',
type: 'type',
reason: 'reason',
}, },
errors: { errors: {
statusUnauthorized: 'Access denied', statusUnauthorized: 'Access denied',
@ -493,6 +497,7 @@ export default {
vat: 'VAT', vat: 'VAT',
dueDay: 'Due day', dueDay: 'Due day',
intrastat: 'Intrastat', intrastat: 'Intrastat',
corrective: 'Corrective',
log: 'Logs', log: 'Logs',
}, },
list: { list: {

View File

@ -62,6 +62,10 @@ export default {
selectRows: 'Seleccionar las { numberRows } filas(s)', selectRows: 'Seleccionar las { numberRows } filas(s)',
allRows: 'Todo { numberRows } filas(s)', allRows: 'Todo { numberRows } filas(s)',
markAll: 'Marcar todo', markAll: 'Marcar todo',
requiredField: 'Campo obligatorio',
class: 'clase',
type: 'tipo',
reason: 'motivo',
}, },
errors: { errors: {
statusUnauthorized: 'Acceso denegado', statusUnauthorized: 'Acceso denegado',
@ -494,6 +498,7 @@ export default {
vat: 'IVA', vat: 'IVA',
dueDay: 'Vencimiento', dueDay: 'Vencimiento',
intrastat: 'Intrastat', intrastat: 'Intrastat',
corrective: 'Rectificativa',
log: 'Registros de auditoría', log: 'Registros de auditoría',
}, },
list: { list: {

View File

@ -37,7 +37,7 @@ const inputFileRef = ref();
const editDmsRef = ref(); const editDmsRef = ref();
const createDmsRef = ref(); const createDmsRef = ref();
const requiredFieldRule = (val) => val || t('Required field'); const requiredFieldRule = (val) => val || t('globals.requiredField');
const dateMask = '####-##-##'; const dateMask = '####-##-##';
const fillMask = '_'; const fillMask = '_';
@ -697,7 +697,6 @@ async function upsert() {
Type: Tipo Type: Tipo
Description: Descripción Description: Descripción
Generate identifier for original file: Generar identificador para archivo original Generate identifier for original file: Generar identificador para archivo original
Required field: Campo obligatorio
File: Fichero File: Fichero
Create document: Crear documento Create document: Crear documento
Select a file: Seleccione un fichero Select a file: Seleccione un fichero

View File

@ -43,18 +43,16 @@ const arrayData = useArrayData('InvoiceIn', {
filter, filter,
}); });
onMounted(async () => { onMounted(async () => await arrayData.fetch({ append: false }));
await arrayData.fetch({ append: false }); watch(
watch( () => route.params.id,
() => route.params.id, async (newId, oldId) => {
async (newId, oldId) => { if (newId) {
if (newId) { arrayData.store.url = `InvoiceIns/${newId}`;
arrayData.store.url = `InvoiceIns/${newId}`; await arrayData.fetch({ append: false });
await arrayData.fetch({ append: false });
}
} }
); }
}); );
</script> </script>
<template> <template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> <Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">

View File

@ -0,0 +1,160 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData';
import { useFirstUpper } from 'src/composables/useFirstUpper';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const invoiceId = route.params.id;
const arrayData = useArrayData('InvoiceIn');
const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref();
const filter = {
include: { relation: 'invoiceIn' },
where: { correctingFk: invoiceId },
};
const columns = computed(() => [
{
name: 'origin',
label: t('Original invoice'),
field: (row) => row.correctedFk,
sortable: true,
tabIndex: 1,
align: 'left',
},
{
name: 'type',
label: useFirstUpper(t('globals.type')),
field: (row) => row.cplusRectificationTypeFk,
options: cplusRectificationTypes.value,
model: 'cplusRectificationTypeFk',
optionValue: 'id',
optionLabel: 'description',
sortable: true,
tabIndex: 1,
align: 'left',
},
{
name: 'class',
label: useFirstUpper(t('globals.class')),
field: (row) => row.siiTypeInvoiceOutFk,
options: siiTypeInvoiceOuts.value,
model: 'siiTypeInvoiceOutFk',
optionValue: 'id',
optionLabel: 'id',
sortable: true,
tabIndex: 1,
align: 'left',
},
{
name: 'reason',
label: useFirstUpper(t('globals.reason')),
field: (row) => row.invoiceCorrectionTypeFk,
options: invoiceCorrectionTypes.value,
model: 'invoiceCorrectionTypeFk',
optionValue: 'id',
optionLabel: 'description',
sortable: true,
tabIndex: 1,
align: 'left',
},
]);
const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]);
const invoiceCorrectionTypes = ref([]);
const rowsSelected = ref([]);
const requiredFieldRule = (val) => val || t('globals.requiredField');
const onSave = (data) => data.deletes && router.push(`/invoice-in/${invoiceId}/summary`);
</script>
<template>
<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
/>
<CrudModel
ref="invoiceInCorrectionRef"
v-if="invoiceIn"
data-key="InvoiceInCorrection"
url="InvoiceInCorrections"
:filter="filter"
auto-load
v-model:selected="rowsSelected"
primary-key="correctingFk"
@save-changes="onSave"
>
<template #body="{ rows }">
<QTable
v-model:selected="rowsSelected"
:columns="columns"
:rows="rows"
row-key="$index"
selection="single"
hide-pagination
:grid="$q.screen.lt.sm"
:pagination="{ rowsPerPage: 0 }"
>
<template #body-cell-type="{ row, col }">
<QTd>
<VnSelectFilter
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:readonly="row.invoiceIn.isBooked"
/>
</QTd>
</template>
<template #body-cell-class="{ row, col }">
<QTd>
<VnSelectFilter
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:rules="[requiredFieldRule]"
:readonly="row.invoiceIn.isBooked"
/>
</QTd>
</template>
<template #body-cell-reason="{ row, col }">
<QTd>
<VnSelectFilter
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:rules="[requiredFieldRule]"
:readonly="row.invoiceIn.isBooked"
/>
</QTd>
</template>
</QTable>
</template>
</CrudModel>
</template>
<style scoped></style>
<i18n>
es:
Original invoice: Factura origen
</i18n>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, reactive, computed, onBeforeMount, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -15,6 +15,8 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue'; import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import { useFirstUpper } from 'src/composables/useFirstUpper';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -34,11 +36,14 @@ const arrayData = useArrayData('InvoiceIn');
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const cardDescriptorRef = ref(); const cardDescriptorRef = ref();
const correctionDialogRef = ref();
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || route.params.id);
const totalAmount = ref(); const totalAmount = ref();
const currentAction = ref(); const currentAction = ref();
const config = ref(); const config = ref();
const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]);
const invoiceCorrectionTypes = ref([]);
const actions = { const actions = {
book: { book: {
title: 'Are you sure you want to book this invoice?', title: 'Are you sure you want to book this invoice?',
@ -59,6 +64,9 @@ const actions = {
sendPdf: { sendPdf: {
cb: sendPdfInvoiceConfirmation, cb: sendPdfInvoiceConfirmation,
}, },
correct: {
cb: () => correctionDialogRef.value.show(),
},
}; };
const filter = { const filter = {
include: [ include: [
@ -86,8 +94,90 @@ const filter = {
}, },
], ],
}; };
const data = ref(useCardDescription()); const data = ref(useCardDescription());
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;
// Se crea una sección aparte?
/* return {
name: 'InvoiceInList',
params: {
search: invoiceInCorrection.correcting.join(),
},
}; */
}
return {
name: 'InvoiceInCard',
params: {
id: invoiceInCorrection.corrected ?? invoiceInCorrection.correcting[0],
},
};
},
});
const correctionFormData = reactive({
id: +entityId.value,
invoiceReason: 2,
invoiceType: 2,
invoiceClass: 6,
});
const isNotFilled = computed(() => Object.values(correctionFormData).includes(null));
onBeforeMount(async () => await setInvoiceCorrection(entityId.value));
watch(
() => route.params.id,
async (newId, oldId) => {
invoiceInCorrection.correcting.length = 0;
invoiceInCorrection.corrected = null;
if (newId) await setInvoiceCorrection(entityId.value);
}
);
async function setInvoiceCorrection(id) {
const [{ data: correctingData }, { data: correctedData }] = await Promise.all([
axios.get('InvoiceInCorrections', {
params: {
filter: {
where: {
correctingFk: id,
},
},
},
}),
await axios.get('InvoiceInCorrections', {
params: {
filter: {
where: {
correctedFk: id,
},
},
},
}),
]);
if (correctingData[0]) invoiceInCorrection.corrected = correctingData[0].correctedFk;
invoiceInCorrection.correcting = correctedData.map(
(corrected) => corrected.correctingFk
);
}
async function setData(entity) { async function setData(entity) {
data.value = useCardDescription(entity.supplierRef, entity.id); data.value = useCardDescription(entity.supplierRef, entity.id);
@ -104,7 +194,7 @@ function openDialog() {
quasar.dialog({ quasar.dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
title: currentAction.value.title, title: t(currentAction.value.title),
promise: currentAction.value.action, promise: currentAction.value.action,
}, },
}); });
@ -163,6 +253,8 @@ async function cloneInvoice() {
router.push({ path: `/invoice-in/${data.id}/summary` }); router.push({ path: `/invoice-in/${data.id}/summary` });
} }
const requiredFieldRule = (val) => val || t('globals.requiredField');
const isAdministrative = () => hasAny(['administrative']); const isAdministrative = () => hasAny(['administrative']);
const isAgricultural = () => const isAgricultural = () =>
@ -202,6 +294,14 @@ function triggerMenu(type) {
if (currentAction.value.cb) currentAction.value.cb(); if (currentAction.value.cb) currentAction.value.cb();
else openDialog(type); else openDialog(type);
} }
const createInvoiceInCorrection = async () => {
const { data: correctingId } = await axios.post(
'InvoiceIns/corrective',
correctionFormData
);
router.push({ path: `/invoice-in/${correctingId}/summary` });
};
</script> </script>
<template> <template>
@ -211,6 +311,22 @@ function triggerMenu(type) {
auto-load auto-load
@on-fetch="(data) => (config = data)" @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
/>
<!--Refactor para añadir en el arrayData--> <!--Refactor para añadir en el arrayData-->
<CardDescriptor <CardDescriptor
ref="cardDescriptorRef" ref="cardDescriptorRef"
@ -265,6 +381,22 @@ function triggerMenu(type) {
>{{ t('Send agricultural receipt as PDF') }}...</QItemSection >{{ t('Send agricultural receipt as PDF') }}...</QItemSection
> >
</QItem> </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>
<QItem <QItem
v-if="entity.dmsFk" v-if="entity.dmsFk"
v-ripple v-ripple
@ -285,29 +417,122 @@ function triggerMenu(type) {
</template> </template>
<template #actions="{ entity }"> <template #actions="{ entity }">
<QCardActions> <QCardActions>
<!--Sección proveedores no disponible--> <!--Falta crear supplier descriptor-->
<!-- <QBtn
size="md"
icon="person"
color="primary"
:to="routes.getSupplierRoute(entity.supplierFk)"
>
<QTooltip>{{ t('invoiceIn.list.supplier') }}</QTooltip>
</QBtn> -->
<!--Sección entradas no disponible--> <!--Sección entradas no disponible-->
<QBtn <QBtn
size="md" size="md"
icon="vn:ticket" icon="vn:ticket"
color="primary" color="primary"
:to="{ :to="routes.getTickets(entity.supplierFk)"
name: 'InvoiceInList',
query: {
params: JSON.stringify({ supplierFk: entity.supplierFk }),
},
}"
> >
<QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip> <QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip>
</QBtn> </QBtn>
<!--Falta crear los iconos con la flecha-->
<QBtn
v-if="
invoiceInCorrection.corrected ||
invoiceInCorrection.correcting.length
"
size="md"
icon="attachment"
color="primary"
:to="routes.getCorrection(invoiceInCorrection)"
>
<QTooltip>{{
invoiceInCorrection.corrected
? t('Original invoice')
: t('Rectificative invoice')
}}</QTooltip>
</QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </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="correctionFormData.id"
readonly
/>
<VnSelectFilter
:label="`${useFirstUpper(t('globals.class'))}*`"
v-model="correctionFormData.invoiceClass"
:options="siiTypeInvoiceOuts"
option-value="id"
option-label="code"
:rules="[requiredFieldRule]"
/>
</QItemSection>
<QItemSection>
<VnSelectFilter
:label="`${useFirstUpper(t('globals.type'))}*`"
v-model="correctionFormData.invoiceType"
:options="cplusRectificationTypes"
option-value="id"
option-label="description"
:rules="[requiredFieldRule]"
/>
<VnSelectFilter
:label="`${useFirstUpper(t('globals.reason'))}*`"
v-model="correctionFormData.invoiceReason"
:options="invoiceCorrectionTypes"
option-value="id"
option-label="description"
:rules="[requiredFieldRule]"
/>
</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> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-dialog { .q-dialog {
.q-card { .q-card {
width: 35em; 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> </style>
@ -324,4 +549,7 @@ es:
Send agricultural receipt as PDF: Enviar 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? Are you sure you want to send it?: Estás seguro que quieres enviarlo?
Send PDF invoice: Enviar factura a PDF Send PDF invoice: Enviar factura a PDF
Create rectificative invoice: Crear factura rectificativa
Rectificative invoice: Factura rectificativa
Original invoice: Factura origen
</i18n> </i18n>

View File

@ -1,4 +1,5 @@
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
import axios from 'axios';
export default { export default {
path: '/invoice-in', path: '/invoice-in',
@ -16,6 +17,7 @@ export default {
'InvoiceInVat', 'InvoiceInVat',
'InvoiceInDueDay', 'InvoiceInDueDay',
'InvoiceInIntrastat', 'InvoiceInIntrastat',
'InvoiceInCorrective',
], ],
}, },
children: [ children: [
@ -40,6 +42,24 @@ export default {
name: 'InvoiceInCard', name: 'InvoiceInCard',
path: ':id', path: ':id',
component: () => import('src/pages/InvoiceIn/Card/InvoiceInCard.vue'), component: () => import('src/pages/InvoiceIn/Card/InvoiceInCard.vue'),
beforeEnter: async (to, from, next) => {
const card = to.matched.find((match) => match.name == 'InvoiceInCard');
const corrective = card.children.find(
(child) => child.name == 'InvoiceInCorrective'
);
const { data: correctings } = await axios.get('InvoiceInCorrections', {
params: {
filter: {
where: {
correctingFk: to.params.id,
},
},
},
});
corrective.meta.hidden = !correctings.length > 0;
next();
},
redirect: { name: 'InvoiceInSummary' }, redirect: { name: 'InvoiceInSummary' },
children: [ children: [
{ {
@ -92,6 +112,16 @@ export default {
component: () => component: () =>
import('src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'), import('src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'),
}, },
{
name: 'InvoiceInCorrective',
path: 'corrective',
meta: {
title: 'corrective',
icon: 'attachment',
},
component: () =>
import('src/pages/InvoiceIn/Card/InvoiceInCorrective.vue'),
},
], ],
}, },
], ],