0
0
Fork 0

Merge branch 'salix-dev' into feature/worker-log

This commit is contained in:
Kevin Martinez 2024-03-21 08:01:57 -03:00
commit f6e3b3607a
19 changed files with 917 additions and 276 deletions

View File

@ -0,0 +1,174 @@
<script setup>
import { reactive, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue';
import VnInputDate from './common/VnInputDate.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const router = useRouter();
const manualInvoiceFormData = reactive({
maxShipped: Date.vnNew(),
});
const formModelPopupRef = ref();
const invoiceOutSerialsOptions = ref([]);
const taxAreasOptions = ref([]);
const ticketsOptions = ref([]);
const clientsOptions = ref([]);
const isLoading = computed(() => formModelPopupRef.value?.isLoading);
const onDataSaved = async (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
if (requestResponse && requestResponse.id)
router.push({ name: 'InvoiceOutSummary', params: { id: requestResponse.id } });
};
</script>
<template>
<FetchData
url="InvoiceOutSerials"
:filter="{ where: { code: { neq: 'R' } }, order: ['code'] }"
@on-fetch="(data) => (invoiceOutSerialsOptions = data)"
auto-load
/>
<FetchData
url="TaxAreas"
:filter="{ order: ['code'] }"
@on-fetch="(data) => (taxAreasOptions = data)"
auto-load
/>
<FetchData
url="Tickets"
:filter="{ fields: ['id', 'nickname'], order: 'shipped DESC', limit: 30 }"
@on-fetch="(data) => (ticketsOptions = data)"
auto-load
/>
<FetchData
url="Clients"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
@on-fetch="(data) => (clientsOptions = data)"
auto-load
/>
<FormModelPopup
ref="formModelPopupRef"
:title="t('Create manual invoice')"
url-create="InvoiceOuts/createManualInvoice"
model="invoiceOut"
:form-initial-data="manualInvoiceFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<span v-if="isLoading" class="text-primary invoicing-text">
<QIcon name="warning" class="fill-icon q-mr-sm" size="md" />
{{ t('Invoicing in progress...') }}
</span>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Ticket')"
:options="ticketsOptions"
hide-selected
option-label="id"
option-value="id"
v-model="data.ticketFk"
@update:model-value="data.clientFk = null"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel caption>{{
scope.opt?.nickname
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<span class="row items-center" style="max-width: max-content">{{
t('Or')
}}</span>
<div class="col">
<VnSelectFilter
:label="t('Client')"
:options="clientsOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.clientFk"
@update:model-value="data.ticketFk = null"
/>
</div>
<div class="col">
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Serial')"
:options="invoiceOutSerialsOptions"
hide-selected
option-label="description"
option-value="code"
v-model="data.serial"
:required="true"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('Area')"
:options="taxAreasOptions"
hide-selected
option-label="code"
option-value="code"
v-model="data.taxArea"
:required="true"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
:label="t('Reference')"
type="textarea"
v-model="data.reference"
fill-input
autogrow
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<style lang="scss" scoped>
.invoicing-text {
display: flex;
justify-content: center;
align-items: center;
color: $primary;
font-size: 24px;
margin-bottom: 8px;
}
</style>
<i18n>
es:
Create manual invoice: Crear factura manual
Ticket: Ticket
Client: Cliente
Max date: Fecha límite
Serial: Serie
Area: Area
Reference: Referencia
Or: O
Invoicing in progress...: Facturación en progreso...
</i18n>

View File

@ -78,10 +78,6 @@ const $props = defineProps({
const emit = defineEmits(['onFetch', 'onDataSaved']); const emit = defineEmits(['onFetch', 'onDataSaved']);
defineExpose({
save,
});
const componentIsRendered = ref(false); const componentIsRendered = ref(false);
onMounted(async () => { onMounted(async () => {
@ -227,6 +223,11 @@ watch(formUrl, async () => {
reset(); reset();
fetch(); fetch();
}); });
defineExpose({
save,
isLoading,
});
</script> </script>
<template> <template>
<div class="column items-center full-width"> <div class="column items-center full-width">

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
@ -39,21 +39,28 @@ const $props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const formModelRef = ref(null);
const closeButton = ref(null); const closeButton = ref(null);
const isLoading = ref(false);
const onDataSaved = (formData, requestResponse) => { const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse); emit('onDataSaved', formData, requestResponse);
closeForm(); closeForm();
}; };
const closeForm = () => { const isLoading = computed(() => formModelRef.value?.isLoading);
const closeForm = async () => {
if (closeButton.value) closeButton.value.click(); if (closeButton.value) closeButton.value.click();
}; };
defineExpose({
isLoading,
});
</script> </script>
<template> <template>
<FormModel <FormModel
ref="formModelRef"
:form-initial-data="formInitialData" :form-initial-data="formInitialData"
:observe-form-changes="false" :observe-form-changes="false"
:default-actions="false" :default-actions="false"

View File

@ -0,0 +1,96 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const emit = defineEmits(['onSubmit']);
const $props = defineProps({
title: {
type: String,
default: '',
},
subtitle: {
type: String,
default: '',
},
defaultSubmitButton: {
type: Boolean,
default: true,
},
defaultCancelButton: {
type: Boolean,
default: true,
},
customSubmitButtonLabel: {
type: String,
default: '',
},
});
const { t } = useI18n();
const closeButton = ref(null);
const isLoading = ref(false);
const onSubmit = () => {
emit('onSubmit');
closeForm();
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
</script>
<template>
<QForm
@submit="onSubmit($event)"
class="all-pointer-events full-width"
style="max-width: 800px"
>
<QCard class="q-pa-lg">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">{{ title }}</h1>
<p>{{ subtitle }}</p>
<slot name="form-inputs" />
<div class="q-mt-lg row justify-end">
<QBtn
v-if="defaultSubmitButton"
:label="customSubmitButtonLabel || t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
<QBtn
v-if="defaultCancelButton"
:label="t('globals.cancel')"
color="primary"
flat
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
v-close-popup
/>
<slot name="customButtons" />
</div>
</QCard>
</QForm>
</template>
<style lang="scss" scoped>
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,168 @@
<script setup>
import { ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import FormPopup from './FormPopup.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const $props = defineProps({
invoiceOutData: {
type: Object,
default: () => {},
},
});
const { t } = useI18n();
const router = useRouter();
const { notify } = useNotify();
const transferInvoiceParams = reactive({
id: $props.invoiceOutData?.id,
refFk: $props.invoiceOutData?.ref,
});
const closeButton = ref(null);
const clientsOptions = ref([]);
const rectificativeTypeOptions = ref([]);
const siiTypeInvoiceOutsOptions = ref([]);
const invoiceCorrectionTypesOptions = ref([]);
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
const transferInvoice = async () => {
try {
const { data } = await axios.post(
'InvoiceOuts/transferInvoice',
transferInvoiceParams
);
notify(t('Transferred invoice'), 'positive');
closeForm();
router.push('InvoiceOutSummary', { id: data.id });
} catch (err) {
console.error('Error transfering invoice', err);
}
};
</script>
<template>
<FetchData
url="Clients"
@on-fetch="(data) => (clientsOptions = data)"
:filter="{ fields: ['id', 'name'], order: 'id', limit: 30 }"
auto-load
/>
<FetchData
url="CplusRectificationTypes"
:filter="{ order: 'description' }"
@on-fetch="(data) => (rectificativeTypeOptions = data)"
auto-load
/>
<FetchData
url="SiiTypeInvoiceOuts"
:filter="{ where: { code: { like: 'R%' } } }"
@on-fetch="(data) => (siiTypeInvoiceOutsOptions = data)"
auto-load
/>
<FetchData
url="InvoiceCorrectionTypes"
@on-fetch="(data) => (invoiceCorrectionTypesOptions = data)"
auto-load
/>
<FormPopup
@on-submit="transferInvoice()"
:title="t('Transfer invoice')"
:custom-submit-button-label="t('Transfer client')"
:default-cancel-button="false"
>
<template #form-inputs>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Client')"
:options="clientsOptions"
hide-selected
option-label="name"
option-value="id"
v-model="transferInvoiceParams.newClientFk"
:required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
#{{ scope.opt?.id }} -
{{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnSelectFilter
:label="t('Rectificative type')"
:options="rectificativeTypeOptions"
hide-selected
option-label="description"
option-value="id"
v-model="transferInvoiceParams.cplusRectificationTypeFk"
:required="true"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('Class')"
:options="siiTypeInvoiceOutsOptions"
hide-selected
option-label="description"
option-value="id"
v-model="transferInvoiceParams.siiTypeInvoiceOutFk"
:required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.code }} -
{{ scope.opt?.description }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnSelectFilter
:label="t('Type')"
:options="invoiceCorrectionTypesOptions"
hide-selected
option-label="description"
option-value="id"
v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
:required="true"
/>
</div>
</VnRow>
</template>
</FormPopup>
</template>
<i18n>
es:
Transfer invoice: Transferir factura
Transfer client: Transferir cliente
Client: Cliente
Rectificative type: Tipo rectificativa
Class: Clase
Type: Tipo
Transferred invoice: Factura transferida
</i18n>

View File

@ -0,0 +1,23 @@
import VnConfirm from 'components/ui/VnConfirm.vue';
import { useQuasar } from 'quasar';
export function useVnConfirm() {
const quasar = useQuasar();
const openConfirmationModal = (title, message, promise, successFn) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: title,
message: message,
promise: promise,
},
})
.onOk(async () => {
if (successFn) successFn();
});
};
return { openConfirmationModal };
}

View File

@ -97,6 +97,10 @@ select:-webkit-autofill {
background-color: var(--vn-light-gray); background-color: var(--vn-light-gray);
} }
.fill-icon {
font-variation-settings: 'FILL' 1;
}
.vn-table-separation-row { .vn-table-separation-row {
height: 16px !important; height: 16px !important;
background-color: var(--vn-gray) !important; background-color: var(--vn-gray) !important;

View File

@ -592,6 +592,7 @@ export default {
company: 'Company', company: 'Company',
dued: 'Due date', dued: 'Due date',
shortDued: 'Due date', shortDued: 'Due date',
amount: 'Amount',
}, },
card: { card: {
issued: 'Issued', issued: 'Issued',
@ -625,7 +626,7 @@ export default {
fillDates: 'Invoice date and the max date should be filled', fillDates: 'Invoice date and the max date should be filled',
invoiceDateLessThanMaxDate: 'Invoice date can not be less than max date', invoiceDateLessThanMaxDate: 'Invoice date can not be less than max date',
invoiceWithFutureDate: 'Exists an invoice with a future date', invoiceWithFutureDate: 'Exists an invoice with a future date',
noTicketsToInvoice: 'There are not clients to invoice', noTicketsToInvoice: 'There are not tickets to invoice',
criticalInvoiceError: 'Critical invoicing error, process stopped', criticalInvoiceError: 'Critical invoicing error, process stopped',
}, },
table: { table: {

View File

@ -592,6 +592,7 @@ export default {
company: 'Empresa', company: 'Empresa',
dued: 'Fecha vencimineto', dued: 'Fecha vencimineto',
shortDued: 'F. vencimiento', shortDued: 'F. vencimiento',
amount: 'Importe',
}, },
card: { card: {
issued: 'Fecha emisión', issued: 'Fecha emisión',
@ -627,7 +628,7 @@ export default {
invoiceDateLessThanMaxDate: invoiceDateLessThanMaxDate:
'La fecha de la factura no puede ser menor que la fecha máxima', 'La fecha de la factura no puede ser menor que la fecha máxima',
invoiceWithFutureDate: 'Existe una factura con una fecha futura', invoiceWithFutureDate: 'Existe una factura con una fecha futura',
noTicketsToInvoice: 'No hay clientes para facturar', noTicketsToInvoice: 'No existen tickets para facturar',
criticalInvoiceError: 'Error crítico en la facturación, proceso detenido', criticalInvoiceError: 'Error crítico en la facturación, proceso detenido',
}, },
table: { table: {

View File

@ -2,13 +2,15 @@
import { ref, computed } from 'vue'; import { 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 CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue';
import useCardDescription from 'src/composables/useCardDescription';
import { toCurrency, toDate } from 'src/filters';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: Number,
@ -23,7 +25,6 @@ const { t } = useI18n();
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
}); });
const descriptor = ref();
const filter = { const filter = {
include: [ include: [
@ -42,6 +43,8 @@ const filter = {
], ],
}; };
const descriptor = ref();
function ticketFilter(invoice) { function ticketFilter(invoice) {
return JSON.stringify({ refFk: invoice.ref }); return JSON.stringify({ refFk: invoice.ref });
} }
@ -61,7 +64,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
data-key="invoiceOutData" data-key="invoiceOutData"
> >
<template #menu="{ entity }"> <template #menu="{ entity }">
<InvoiceOutDescriptorMenu :invoice-out="entity" /> <InvoiceOutDescriptorMenu :invoice-out-data="entity" />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" /> <VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" />

View File

@ -1,40 +1,260 @@
<script setup> <script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import TransferInvoiceForm from 'src/components/TransferInvoiceForm.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import useNotify from 'src/composables/useNotify';
import { useSession } from 'src/composables/useSession';
import { usePrintService } from 'composables/usePrintService';
import { useVnConfirm } from 'composables/useVnConfirm';
import axios from 'axios';
const $props = defineProps({
invoiceOutData: {
type: Object,
default: () => {},
},
});
const { notify } = useNotify();
const router = useRouter();
const session = useSession();
const token = session.getToken();
const { t } = useI18n(); const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
const { openConfirmationModal } = useVnConfirm();
const quasar = useQuasar();
const transferInvoiceDialogRef = ref();
const invoiceFormType = ref('pdf');
const defaultEmailAddress = ref($props.invoiceOutData.client?.email);
const showInvoicePdf = () => {
const url = `api/InvoiceOuts/${$props.invoiceOutData.id}/download?access_token=${token}`;
window.open(url, '_blank');
};
const showInvoiceCsv = () => {
openReport(`InvoiceOuts/${$props.invoiceOutData.ref}/invoice-csv`, {
recipientId: $props.invoiceOutData.client.id,
});
};
const showSendInvoiceDialog = (type) => {
invoiceFormType.value = type;
quasar.dialog({
component: SendEmailDialog,
componentProps: {
data: {
address: defaultEmailAddress.value,
},
promise: sendEmailInvoice,
},
});
};
const sendEmailInvoice = async ({ address }) => {
try {
if (!address) notify(`The email can't be empty`, 'negative');
if (invoiceFormType.value === 'pdf') {
return sendEmail(`InvoiceOuts/${$props.invoiceOutData.ref}/invoice-email`, {
recipientId: $props.invoiceOutData.client.id,
recipient: address,
});
} else {
return sendEmail(
`InvoiceOuts/${$props.invoiceOutData.ref}/invoice-csv-email`,
{
recipientId: $props.invoiceOutData.client.id,
recipient: address,
}
);
}
} catch (err) {
console.error('Error sending email', err);
}
};
const redirectToInvoiceOutList = () => {
router.push({ name: 'InvoiceOutList' });
};
const deleteInvoice = async () => {
try {
await axios.post(`InvoiceOuts/${$props.invoiceOutData.id}/delete`);
notify(t('InvoiceOut deleted'), 'positive');
} catch (err) {
console.error('Error deleting invoice out', err);
}
};
const bookInvoice = async () => {
try {
await axios.post(`InvoiceOuts/${$props.invoiceOutData.ref}/book`);
notify(t('InvoiceOut booked'), 'positive');
} catch (err) {
console.error('Error booking invoice out', err);
}
};
const generateInvoicePdf = async () => {
try {
await axios.post(`InvoiceOuts/${$props.invoiceOutData.id}/createPdf`);
notify(t('The invoice PDF document has been regenerated'), 'positive');
} catch (err) {
console.error('Error generating invoice out pdf', err);
}
};
const refundInvoice = async (withWarehouse) => {
try {
const params = { ref: $props.invoiceOutData.ref, withWarehouse: withWarehouse };
const { data } = await axios.post('InvoiceOuts/refund', params);
notify(
t('refundInvoiceSuccessMessage', {
refundTicket: data[0].id,
}),
'positive'
);
} catch (err) {
console.error('Error generating invoice out pdf', err);
}
};
</script> </script>
<template> <template>
<QItem v-ripple clickable> <QItem v-ripple clickable @click="transferInvoiceDialogRef.show()">
<QItemSection>{{ t('Transfer invoice to') }}</QItemSection> <QItemSection>{{ t('Transfer invoice to...') }}</QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection>{{ t('See invoice') }}</QItemSection> <QItemSection>{{ t('Show invoice...') }}</QItemSection>
<QItemSection side>
<QIcon name="keyboard_arrow_right" />
</QItemSection>
<QMenu anchor="top end" self="top start">
<QList>
<QItem v-ripple clickable @click="showInvoicePdf()">
<QItemSection>{{ t('As PDF') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="showInvoiceCsv()">
<QItemSection>{{ t('As CSV') }}</QItemSection>
</QItem>
</QList>
</QMenu>
</QItem> </QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection>{{ t('Send invoice') }}</QItemSection> <QItemSection>{{ t('Send invoice...') }}</QItemSection>
<QItemSection side>
<QIcon name="keyboard_arrow_right" />
</QItemSection>
<QMenu anchor="top end" self="top start">
<QList>
<QItem v-ripple clickable @click="showSendInvoiceDialog('pdf')">
<QItemSection>{{ t('Send PDF') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="showSendInvoiceDialog('csv')">
<QItemSection>{{ t('Send CSV') }}</QItemSection>
</QItem>
</QList>
</QMenu>
</QItem> </QItem>
<QItem v-ripple clickable> <QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('Confirm deletion'),
t('Are you sure you want to delete this invoice?'),
deleteInvoice,
redirectToInvoiceOutList
)
"
>
<QItemSection>{{ t('Delete invoice') }}</QItemSection> <QItemSection>{{ t('Delete invoice') }}</QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable> <QItem
<QItemSection>{{ t('Post invoice') }}</QItemSection> v-ripple
clickable
@click="
openConfirmationModal(
'',
t('Are you sure you want to book this invoice?'),
bookInvoice
)
"
>
<QItemSection>{{ t('Book invoice') }}</QItemSection>
</QItem>
<QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('Generate PDF invoice document'),
t('Are you sure you want to generate/regenerate the PDF invoice?'),
generateInvoicePdf
)
"
>
<QItemSection>{{ t('Generate PDF invoice') }}</QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection>{{ t('Regenerate invoice PDF') }}</QItemSection> <QItemSection>{{ t('Refund...') }}</QItemSection>
</QItem> <QItemSection side>
<QItem v-ripple clickable> <QIcon name="keyboard_arrow_right" />
<QItemSection>{{ t('Pass') }}</QItemSection> </QItemSection>
<QMenu anchor="top end" self="top start">
<QList>
<QItem v-ripple clickable @click="refundInvoice(true)">
<QItemSection>{{ t('With warehouse') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="refundInvoice(false)">
<QItemSection>{{ t('Without warehouse') }}</QItemSection>
</QItem>
</QList>
</QMenu>
<QTooltip>
{{ t('Create a single ticket with all the content of the current invoice') }}
</QTooltip>
</QItem> </QItem>
<QDialog ref="transferInvoiceDialogRef">
<TransferInvoiceForm :invoice-out-data="invoiceOutData" />
</QDialog>
</template> </template>
<i18n> <i18n>
es: es:
Transfer invoice to: Transferir factura a Transfer invoice to...: Transferir factura a...
See invoice: Ver factura Show invoice...: Ver factura...
Send invoice: Enviar factura Send invoice...: Enviar factura...
Delete invoice: Eliminar factura Delete invoice: Eliminar factura
Post invoice: Asentar factura Book invoice: Asentar factura
Regenerate invoice PDF: Regenerar PDF factura Generate PDF invoice: Generar PDF factura
Pass: Abono Refund...: Abono
As PDF: como PDF
As CSV: como CSV
Send PDF: Enviar PDF
Send CSV: Enviar CSV
With warehouse: Con almacén
Without warehouse: Sin almacén
InvoiceOut deleted: Factura eliminada
Confirm deletion: Confirmar eliminación
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
InvoiceOut booked: Factura asentada
Generate PDF invoice document: Generar PDF de la factura
Are you sure you want to generate/regenerate the PDF invoice?: ¿Seguro que quieres generar/regenerar el PDF de la factura?
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
Create a single ticket with all the content of the current invoice: Crear un ticket único con todo el contenido de la factura actual
refundInvoiceSuccessMessage: Se ha creado el siguiente ticket de abono {refundTicket}
The email can't be empty: El email no puede estar vacío
en:
refundInvoiceSuccessMessage: The following refund ticket have been created {refundTicket}
</i18n> </i18n>

View File

@ -7,6 +7,8 @@ import { toCurrency, toDate } from 'src/filters';
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 { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
onMounted(async () => { onMounted(async () => {
fetch(); fetch();
@ -67,29 +69,33 @@ const taxColumns = ref([
const ticketsColumns = ref([ const ticketsColumns = ref([
{ {
name: 'item', name: 'item',
label: 'invoiceOut.summary.ticketId', label: t('invoiceOut.summary.ticketId'),
field: (row) => row.id, field: (row) => row.id,
sortable: true, sortable: true,
align: 'left',
}, },
{ {
name: 'quantity', name: 'quantity',
label: 'invoiceOut.summary.nickname', label: t('invoiceOut.summary.nickname'),
field: (row) => row.nickname, field: (row) => row.nickname,
sortable: true, sortable: true,
align: 'left',
}, },
{ {
name: 'landed', name: 'landed',
label: 'invoiceOut.summary.shipped', label: t('invoiceOut.summary.shipped'),
field: (row) => row.shipped, field: (row) => row.shipped,
format: (value) => toDate(value), format: (value) => toDate(value),
sortable: true, sortable: true,
align: 'left',
}, },
{ {
name: 'landed', name: 'landed',
label: 'invoiceOut.summary.totalWithVat', label: t('invoiceOut.summary.totalWithVat'),
field: (row) => row.totalWithVat, field: (row) => row.totalWithVat,
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
sortable: true, sortable: true,
align: 'left',
}, },
]); ]);
</script> </script>
@ -151,12 +157,21 @@ const ticketsColumns = ref([
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</a> </a>
<QTable v-if="tickets" :columns="ticketsColumns" :rows="tickets" flat> <QTable v-if="tickets" :columns="ticketsColumns" :rows="tickets" flat>
<template #header="props"> <template #body-cell-item="{ value }">
<QTr :props="props"> <QTd>
<QTh v-for="col in props.cols" :key="col.name" :props="props"> <QBtn flat color="primary">
{{ t(col.label) }} {{ value }}
</QTh> <TicketDescriptorProxy :id="value" />
</QTr> </QBtn>
</QTd>
</template>
<template #body-cell-quantity="{ value, row }">
<QTd>
<QBtn flat color="primary" dense>
{{ value }}
<CustomerDescriptorProxy :id="row.id" />
</QBtn>
</QTd>
</template> </template>
</QTable> </QTable>
</QCard> </QCard>

View File

@ -21,6 +21,7 @@ const {
nPdfs, nPdfs,
totalPdfs, totalPdfs,
errors, errors,
addresses,
} = storeToRefs(invoiceOutGlobalStore); } = storeToRefs(invoiceOutGlobalStore);
const selectedCustomerId = ref(null); const selectedCustomerId = ref(null);
@ -86,6 +87,14 @@ const selectCustomerId = (id) => {
selectedCustomerId.value = id; selectedCustomerId.value = id;
}; };
const statusText = computed(() => {
return status.value === 'invoicing'
? `${t(`status.${status.value}`)} ${
addresses.value[getAddressNumber.value]?.clientId
}`
: t(`status.${status.value}`);
});
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => { onUnmounted(() => {
stateStore.rightDrawer = false; stateStore.rightDrawer = false;
@ -103,7 +112,7 @@ onUnmounted(() => {
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QCard v-if="status" class="card"> <QCard v-if="status" class="card">
<QCardSection class="card-section"> <QCardSection class="card-section">
<span class="text">{{ t(`status.${status}`) }}</span> <span class="text">{{ statusText }}</span>
<span class="text">{{ <span class="text">{{
t('invoiceOut.globalInvoices.statusCard.percentageText', { t('invoiceOut.globalInvoices.statusCard.percentageText', {
getPercentage: getPercentage, getPercentage: getPercentage,

View File

@ -13,13 +13,8 @@ const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore(); const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters // invoiceOutGlobalStore state and getters
const { const { initialDataLoading, formInitialData, invoicing, status } =
initialDataLoading, storeToRefs(invoiceOutGlobalStore);
formInitialData,
invoicing,
status,
} = storeToRefs(invoiceOutGlobalStore);
// invoiceOutGlobalStore actions // invoiceOutGlobalStore actions
const { makeInvoice, setStatusValue } = invoiceOutGlobalStore; const { makeInvoice, setStatusValue } = invoiceOutGlobalStore;
@ -32,13 +27,7 @@ const printersOptions = ref([]);
const clientsOptions = ref([]); const clientsOptions = ref([]);
const formData = ref({ const formData = ref({});
companyFk: null,
invoiceDate: null,
maxShipped: null,
clientId: null,
printer: null,
});
const optionsInitialData = computed(() => { const optionsInitialData = computed(() => {
return ( return (

View File

@ -2,25 +2,32 @@
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { exportFile, useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue'; import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import { toDate, toCurrency } from 'src/filters/index';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue'; import CardList from 'src/components/ui/CardList.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
import CreateManualInvoiceForm from 'src/components/CreateManualInvoiceForm.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue';
import { toDate, toCurrency } from 'src/filters/index';
import { useStateStore } from 'stores/useStateStore';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useSession } from 'src/composables/useSession';
const { t } = useI18n(); const { t } = useI18n();
const selectedCards = ref(new Map());
const quasar = useQuasar();
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore(); const stateStore = useStateStore();
const session = useSession();
const token = session.getToken();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const manualInvoiceDialogRef = ref(null);
const selectedCards = ref(new Map());
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
@ -52,37 +59,37 @@ const toggleAllCards = (cardsData) => {
} }
}; };
const downloadCsv = () => { const openPdf = () => {
if (selectedCards.value.size === 0) return; try {
const selectedCardsArray = Array.from(selectedCards.value.values()); if (selectedCards.value.size === 0) return;
let file; const selectedCardsArray = Array.from(selectedCards.value.values());
for (var i = 0; i < selectedCardsArray.length; i++) {
if (i == 0) file += Object.keys(selectedCardsArray[i]).join(';') + '\n'; if (selectedCards.value.size === 1) {
file += const [invoiceOut] = selectedCardsArray;
Object.keys(selectedCardsArray[i]) const url = `api/InvoiceOuts/${invoiceOut.id}/download?access_token=${token}`;
.map(function (key) { window.open(url, '_blank');
return selectedCardsArray[i][key]; } else {
}) const invoiceOutIdsArray = selectedCardsArray.map(
.join(';') + '\n'; (invoiceOut) => invoiceOut.id
} );
const status = exportFile('file.csv', file, { const invoiceOutIds = invoiceOutIdsArray.join(',');
encoding: 'windows-1252',
mimeType: 'text/csv;charset=windows-1252;', const params = new URLSearchParams({
}); access_token: token,
if (status === true) { ids: invoiceOutIds,
quasar.notify({ });
message: t('fileAllowed'),
color: 'positive', const url = `api/InvoiceOuts/downloadZip?${params}`;
icon: 'check', window.open(url, '_blank');
}); }
} else { } catch (err) {
quasar.notify({ console.error('Error opening PDF');
message: t('fileDenied'),
color: 'negative',
icon: 'warning',
});
} }
}; };
const openCreateInvoiceModal = () => {
manualInvoiceDialogRef.value.show();
};
</script> </script>
<template> <template>
@ -119,52 +126,21 @@ const downloadCsv = () => {
<VnPaginate <VnPaginate
auto-load auto-load
data-key="InvoiceOutList" data-key="InvoiceOutList"
order="issued DESC, id DESC" :order="['issued DESC', 'id DESC']"
url="InvoiceOuts/filter" url="InvoiceOuts/filter"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<VnSubToolbar class="bg-vn-dark justify-end"> <VnSubToolbar class="bg-vn-dark justify-end">
<template #st-actions> <template #st-actions>
<QBtn <QBtn
@click="downloadCsv()" @click="openPdf()"
class="q-mr-xl" class="q-mr-md"
color="primary" color="primary"
icon="cloud_download"
:disable="selectedCards.size === 0" :disable="selectedCards.size === 0"
:label="t('globals.download')"
/>
<!-- <QBtnDropdown
class="q-mr-xl"
color="primary"
:disable="!manageCheckboxes && arrayElements.length < 1"
:label="t('globals.download')"
v-else
> >
<QList> <QTooltip>{{ t('downloadPdf') }}</QTooltip>
<QItem clickable v-close-popup @click="downloadCsv(rows)"> </QBtn>
<QItemSection>
<QItemLabel>
{{
t('globals.allRows', {
numberRows: rows.length,
})
}}
</QItemLabel>
</QItemSection>
</QItem>
<QItem clickable v-close-popup @click="downloadCsv(rows)">
<QItemSection>
<QItemLabel>
{{
t('globals.selectRows', {
numberRows: rows.length,
})
}}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QBtnDropdown> -->
<QCheckbox <QCheckbox
left-label left-label
:label="t('globals.markAll')" :label="t('globals.markAll')"
@ -189,18 +165,23 @@ const downloadCsv = () => {
> >
<template #list-items> <template #list-items>
<VnLv <VnLv
:label="t('invoiceOut.list.shortIssued')" :label="t('invoiceOut.list.issued')"
:title-label="t('invoiceOut.list.issued')"
:value="toDate(row.issued)" :value="toDate(row.issued)"
/> />
<VnLv <VnLv
:label="t('invoiceOut.list.amount')" :label="t('invoiceOut.list.amount')"
:value="toCurrency(row.amount)" :value="toCurrency(row.amount)"
/> />
<VnLv <VnLv :label="t('invoiceOut.list.client')">
:label="t('invoiceOut.list.client')" <template #value>
:value="row.clientSocialName" <span class="link" @click.stop>
/> {{ row?.clientSocialName }}
<CustomerDescriptorProxy
:id="row?.clientFk"
/>
</span>
</template>
</VnLv>
<VnLv <VnLv
:label="t('invoiceOut.list.shortCreated')" :label="t('invoiceOut.list.shortCreated')"
:title-label="t('invoiceOut.list.created')" :title-label="t('invoiceOut.list.created')"
@ -217,13 +198,6 @@ const downloadCsv = () => {
/> />
</template> </template>
<template #actions> <template #actions>
<QBtn
:label="t('components.smartCard.openCard')"
@click.stop="navigate(row.id)"
class="bg-vn-dark"
outline
type="reset"
/>
<QBtn <QBtn
:label="t('components.smartCard.openSummary')" :label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, InvoiceOutSummary)" @click.stop="viewSummary(row.id, InvoiceOutSummary)"
@ -237,6 +211,20 @@ const downloadCsv = () => {
</div> </div>
</template> </template>
</VnPaginate> </VnPaginate>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="openCreateInvoiceModal()" />
<QTooltip>
{{ t('createInvoice') }}
</QTooltip>
</QPageSticky>
<QDialog
ref="manualInvoiceDialogRef"
transition-show="scale"
transition-hide="scale"
>
<CreateManualInvoiceForm />
</QDialog>
</QPage> </QPage>
</template> </template>
@ -246,9 +234,13 @@ en:
fileDenied: Browser denied file download... fileDenied: Browser denied file download...
fileAllowed: Successful download of CSV file fileAllowed: Successful download of CSV file
youCanSearchByInvoiceReference: You can search by invoice reference youCanSearchByInvoiceReference: You can search by invoice reference
downloadPdf: Download PDF
createInvoice: Make invoice
es: es:
searchInvoice: Buscar factura emitida searchInvoice: Buscar factura emitida
fileDenied: El navegador denegó la descarga de archivos... fileDenied: El navegador denegó la descarga de archivos...
fileAllowed: Descarga exitosa de archivo CSV fileAllowed: Descarga exitosa de archivo CSV
youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura
downloadPdf: Descargar PDF
createInvoice: Crear factura
</i18n> </i18n>

View File

@ -1,17 +1,17 @@
<script setup> <script setup>
import { ref, computed, onBeforeMount } from 'vue'; import { ref, computed, onBeforeMount, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QCheckbox, QBtn } from 'quasar';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import InvoiceOutNegativeFilter from './InvoiceOutNegativeBasesFilter.vue'; import InvoiceOutNegativeFilter from './InvoiceOutNegativeBasesFilter.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js'; import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
const invoiceOutGlobalStore = useInvoiceOutGlobalStore(); const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -45,78 +45,16 @@ onBeforeMount(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
}); });
const componentIsRendered = ref(false);
onMounted(() =>
nextTick(() => {
componentIsRendered.value = true;
})
);
const rows = computed(() => arrayData.value.store.data); const rows = computed(() => arrayData.value.store.data);
const selectedCustomerId = ref(0);
const selectedWorkerId = ref(0);
const tableColumnComponents = {
company: {
component: 'span',
props: () => {},
event: () => {},
},
country: {
component: 'span',
props: () => {},
event: () => {},
},
clientId: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectCustomerId(prop.value),
},
client: {
component: 'span',
props: () => {},
event: () => {},
},
amount: {
component: 'span',
props: () => {},
event: () => {},
},
base: {
component: 'span',
props: () => {},
event: () => {},
},
ticketId: {
component: 'span',
props: () => {},
event: () => {},
},
active: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
hasToInvoice: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
verifiedData: {
component: QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
event: () => {},
},
comercial: {
component: QBtn,
props: () => ({ flat: true, color: 'blue' }),
event: (prop) => selectWorkerId(prop.row.comercialId),
},
};
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('invoiceOut.negativeBases.company'), label: t('invoiceOut.negativeBases.company'),
@ -205,20 +143,17 @@ const downloadCSV = async () => {
params params
); );
}; };
const selectCustomerId = (id) => {
selectedCustomerId.value = id;
};
const selectWorkerId = (id) => {
selectedWorkerId.value = id;
};
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> <Teleport
<QBtn color="primary" icon-right="archive" no-caps @click="downloadCSV()" /> to="#st-actions"
v-if="stateStore?.isSubToolbarShown() && componentIsRendered"
>
<QBtn color="primary" icon-right="download" no-caps @click="downloadCSV()">
<QTooltip>{{ t('Download as CSV') }}</QTooltip>
</QBtn>
</Teleport> </Teleport>
</template> </template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
@ -236,31 +171,37 @@ const selectWorkerId = (id) => {
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md" class="full-width q-mt-md"
> >
<template #body-cell="props"> <template #body-cell-clientId="{ row }">
<QTd :props="props"> <QTd>
<component <QBtn flat dense color="blue"> {{ row.clientId }}</QBtn>
:is="tableColumnComponents[props.col.name].component" <CustomerDescriptorProxy :id="row.clientId" />
class="col-content" </QTd>
v-bind="tableColumnComponents[props.col.name].props(props)" </template>
@click="tableColumnComponents[props.col.name].event(props)" <template #body-cell-ticketId="{ row }">
> <QTd>
<template <QBtn flat dense color="blue"> {{ row.ticketFk }}</QBtn>
v-if=" <TicketDescriptorProxy :id="row.ticketFk" />
props.col.name !== 'active' && </QTd>
props.col.name !== 'hasToInvoice' && </template>
props.col.name !== 'verifiedData' <template #body-cell-comercial="{ row }">
" <QTd>
>{{ props.value }} <QBtn flat dense color="blue">{{ row.comercialName }}</QBtn>
</template> <WorkerDescriptorProxy :id="row.comercialId" />
<CustomerDescriptorProxy </QTd>
v-if="props.col.name === 'clientId'" </template>
:id="selectedCustomerId" <template #body-cell-active="{ row }">
/> <QTd>
<VnUserLink <QCheckbox :model-value="!!row.isActive" disable />
v-if="props.col.name === 'comercial'" </QTd>
:worker-id="selectedWorkerId" </template>
/> <template #body-cell-hasToInvoice="{ row }">
</component> <QTd>
<QCheckbox :model-value="!!row.hasToInvoice" disable />
</QTd>
</template>
<template #body-cell-verifiedData="{ row }">
<QTd>
<QCheckbox :model-value="!!row.isTaxDataChecked" disable />
</QTd> </QTd>
</template> </template>
</QTable> </QTable>
@ -274,4 +215,7 @@ const selectWorkerId = (id) => {
} }
</style> </style>
<i18n></i18n> <i18n>
es:
Download as CSV: Descargar como CSV
</i18n>

View File

@ -44,7 +44,7 @@ export default {
name: 'InvoiceOutNegativeBases', name: 'InvoiceOutNegativeBases',
meta: { meta: {
title: 'negativeBases', title: 'negativeBases',
icon: 'view_list', icon: 'vn:ticket',
}, },
component: () => component: () =>
import('src/pages/InvoiceOut/InvoiceOutNegativeBases.vue'), import('src/pages/InvoiceOut/InvoiceOutNegativeBases.vue'),

View File

@ -73,6 +73,9 @@ export const useInvoiceOutGlobalStore = defineStore({
const stringDate = data.issued.substring(0, 10); const stringDate = data.issued.substring(0, 10);
this.minInvoicingDate = stringDate; this.minInvoicingDate = stringDate;
this.formInitialData.invoiceDate = stringDate; this.formInitialData.invoiceDate = stringDate;
this.minInvoicingDate = new Date(data.issued);
this.formInitialData.invoiceDate = this.minInvoicingDate;
} catch (err) { } catch (err) {
console.error('Error fetching invoice out global initial data'); console.error('Error fetching invoice out global initial data');
throw new Error(); throw new Error();
@ -103,12 +106,8 @@ export const useInvoiceOutGlobalStore = defineStore({
if (clientsToInvoice == 'all') params.clientId = undefined; if (clientsToInvoice == 'all') params.clientId = undefined;
const addressesResponse = await await axios.post( const { data } = await axios.post('InvoiceOuts/clientsToInvoice', params);
'InvoiceOuts/clientsToInvoice', this.addresses = data;
params
);
this.addresses = addressesResponse.data;
if (!this.addresses || !this.addresses.length > 0) { if (!this.addresses || !this.addresses.length > 0) {
notify( notify(
@ -118,9 +117,9 @@ export const useInvoiceOutGlobalStore = defineStore({
throw new Error("There aren't addresses to invoice"); throw new Error("There aren't addresses to invoice");
} }
this.addresses.forEach(async (address) => { for (const address of this.addresses) {
await this.invoiceClient(address, formData); await this.invoiceClient(address, formData);
}); }
} catch (err) { } catch (err) {
this.handleError(err); this.handleError(err);
} }
@ -186,15 +185,10 @@ export const useInvoiceOutGlobalStore = defineStore({
this.status = 'invoicing'; this.status = 'invoicing';
this.invoicing = true; this.invoicing = true;
const invoiceResponse = await axios.post( const { data } = await axios.post('InvoiceOuts/invoiceClient', params);
'InvoiceOuts/invoiceClient',
params
);
if (invoiceResponse.data) {
this.makePdfAndNotify(invoiceResponse.data, address);
}
if (data) await this.makePdfAndNotify(data, address);
this.addressIndex++;
this.isInvoicing = false; this.isInvoicing = false;
} catch (err) { } catch (err) {
if ( if (
@ -203,8 +197,7 @@ export const useInvoiceOutGlobalStore = defineStore({
err.response.status >= 400 && err.response.status >= 400 &&
err.response.status < 500 err.response.status < 500
) { ) {
this.invoiceClientError(address, err.response); this.invoiceClientError(address, err.response?.data?.error?.message);
this.addressIndex++;
return; return;
} else { } else {
this.invoicing = false; this.invoicing = false;
@ -227,12 +220,11 @@ export const useInvoiceOutGlobalStore = defineStore({
this.nPdfs++; this.nPdfs++;
this.nRequests--; this.nRequests--;
} catch (err) { } catch (err) {
this.invoiceClientError(client, err, true); this.invoiceClientError(client, err.response?.data?.error?.message, true);
} }
}, },
invoiceClientError(client, response, isWarning) { invoiceClientError(client, message, isWarning) {
const message = response.data?.error?.message || response.message;
this.errors.unshift({ client, message, isWarning }); this.errors.unshift({ client, message, isWarning });
}, },

View File

@ -2,7 +2,7 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper'; import { createWrapper, axios } from 'app/test/vitest/helper';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
describe.skip('VnPaginate', () => { describe('VnPaginate', () => {
const expectedUrl = '/api/customers'; const expectedUrl = '/api/customers';
let vm; let vm;
@ -49,6 +49,8 @@ describe.skip('VnPaginate', () => {
], ],
}); });
vm.arrayData.hasMoreData.value = true; vm.arrayData.hasMoreData.value = true;
await vm.$nextTick();
vm.store.data = [ vm.store.data = [
{ id: 1, name: 'Tony Stark' }, { id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' }, { id: 2, name: 'Jessica Jones' },
@ -57,13 +59,13 @@ describe.skip('VnPaginate', () => {
await vm.paginate(); await vm.paginate();
expect(vm.store.skip).toEqual(6); expect(vm.store.skip).toEqual(3);
expect(vm.store.data.length).toEqual(9); expect(vm.store.data.length).toEqual(6);
await vm.paginate(); await vm.paginate();
expect(vm.store.skip).toEqual(9); expect(vm.store.skip).toEqual(6);
expect(vm.store.data.length).toEqual(12); expect(vm.store.data.length).toEqual(9);
}); });
}); });