0
0
Fork 0

form integration and view content

This commit is contained in:
William Buezas 2023-11-20 14:58:41 -03:00
parent 505052d84d
commit c3b9f013f6
7 changed files with 600 additions and 138 deletions

View File

@ -0,0 +1,15 @@
import { Notify } from 'quasar';
import { i18n } from 'src/boot/i18n';
export default function useNotify() {
const notify = (message, type) => {
Notify.create({
message: i18n.global.t(message),
type: type,
});
};
return {
notify,
};
}

View File

@ -390,6 +390,18 @@ export default {
shipped: 'Shipped', shipped: 'Shipped',
totalWithVat: 'Amount', totalWithVat: 'Amount',
}, },
globalInvoices: {
errors: {
chooseValidClient: 'Choose a valid client',
chooseValidCompany: 'Choose a valid company',
chooseValidPrinter: 'Choose a valid printer',
fillDates: 'Invoice date and the max date should be filled',
invoiceDateLessThanMaxDate: "Invoice date can't be less than max date",
invoiceWithFutureDate: 'Exists an invoice with a future date',
noTicketsToInvoice: "There aren't clients to invoice",
criticalInvoiceError: 'Critical invoicing error, process stopped',
},
},
}, },
worker: { worker: {
pageTitles: { pageTitles: {

View File

@ -390,6 +390,20 @@ export default {
shipped: 'F. envío', shipped: 'F. envío',
totalWithVat: 'Importe', totalWithVat: 'Importe',
}, },
globalInvoices: {
errors: {
chooseValidClient: 'Selecciona un cliente válido',
chooseValidCompany: 'Selecciona una empresa válida',
chooseValidPrinter: 'Selecciona una impresora válida',
fillDates:
'La fecha de la factura y la fecha máxima deben estar completas',
invoiceDateLessThanMaxDate:
'La fecha de la factura no puede ser menor que la fecha máxima',
invoiceWithFutureDate: 'Existe una factura con una fecha futura',
noTicketsToInvoice: 'No hay clientes para facturar',
criticalInvoiceError: 'Error crítico en la facturación, proceso detenido',
},
},
}, },
worker: { worker: {
pageTitles: { pageTitles: {

View File

@ -1,17 +1,95 @@
<script setup> <script setup>
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import InvoiceOutGlobalForm from './InvoiceOutGlobalForm.vue'; import InvoiceOutGlobalForm from './InvoiceOutGlobalForm.vue';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { storeToRefs } from 'pinia';
import { QBadge, QBtn } from 'quasar';
import CustomerDescriptor from 'src/pages/Customer/Card/CustomerDescriptor.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters
const {
status,
getPercentage,
getAddressNumber,
getNAddresses,
nPdfs,
totalPdfs,
errors,
} = storeToRefs(invoiceOutGlobalStore);
const selectedCustomerId = ref(null);
const tableColumnComponents = {
clientId: {
component: QBtn,
props: { flat: true, color: 'blue' },
event: (prop) => selectCustomerId(prop.value),
},
clientName: {
component: 'span',
props: {},
},
id: {
component: 'span',
props: {},
},
nickname: {
component: 'span',
props: {},
},
message: {
component: QBadge,
props: { color: 'red' },
},
};
const columns = ref([
{ label: 'Id', field: 'clientId', name: 'clientId', align: 'left' },
{ label: 'Cliente', field: 'clientName', name: 'clientName', align: 'left' },
{ label: 'Id dirección', field: 'id', name: 'id', align: 'left' },
{ label: 'Dirección fiscal', field: 'nickname', name: 'nickname', align: 'left' },
{ label: 'Error', field: 'message', name: 'message', align: 'left' },
]);
const cardStatusText = computed(() => {
return t(`status.${status.value}`);
});
const percentageStatusText = computed(() => {
return `${getPercentage.value}% (${getAddressNumber.value} ${t('of')} ${
getNAddresses.value
})`;
});
const pdfStatusText = computed(() => {
return `${nPdfs.value} ${t('of')} ${totalPdfs.value} PDFs`;
});
const rows = computed(() => {
if (!errors && !errors.length > 0) return [];
return errors.value.map((row) => {
return {
...row.client,
message: row.message,
};
});
});
const selectCustomerId = (id) => {
selectedCustomerId.value = id;
};
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => {
stateStore.rightDrawer = false;
invoiceOutGlobalStore.$reset();
});
</script> </script>
<template> <template>
@ -20,13 +98,96 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<InvoiceOutGlobalForm /> <InvoiceOutGlobalForm />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPage class="column items-center q-pa-md"> Aca iria una tabla </QPage>
<QPage class="column items-center q-pa-md">
<QCard v-if="status" class="card">
<QCardSection class="card-section">
<span class="status-text">{{ cardStatusText }}</span>
<span class="text">{{ percentageStatusText }}</span>
<span class="text">{{ pdfStatusText }}</span>
</QCardSection>
</QCard>
<QTable
v-if="rows.length > 0"
:rows="rows"
:columns="columns"
hide-bottom
row-key="id"
class="full-width q-mt-md"
>
<template #body-cell="props">
<QTd :props="props">
<component
:is="tableColumnComponents[props.col.name].component"
v-bind="tableColumnComponents[props.col.name].props"
@click="tableColumnComponents[props.col.name].event(props)"
class="col-content"
>
{{ props.value }}
<QPopupProxy>
<CustomerDescriptor
v-if="selectedCustomerId === props.value"
:id="selectedCustomerId"
/>
</QPopupProxy>
</component>
</QTd>
</template>
</QTable>
</QPage>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.card {
display: flex;
justify-content: center;
width: 100%;
background-color: #292929;
padding: 16px;
<!-- <i18n> .card-section {
es: display: flex;
Search invoice: Buscar factura emitida flex-direction: column;
You can search by invoice reference: Puedes buscar por referencia de la factura padding: 0px;
</i18n> --> }
.status-text {
font-size: 14px;
color: #eeeeee;
}
.text {
font-size: 14px;
color: #aaaaaa;
}
}
.col-content {
border-radius: 4px;
padding: 6px 6px 6px 6px;
}
</style>
<i18n>
{
"en": {
"status": {
"packageInvoicing": "Build packaging tickets",
"invoicing": "Invoicing client",
"stopping": "Stopping process",
"done": "Ended process"
},
"of": "of"
},
"es": {
"status":{
"packageInvoicing": "Generación de tickets de empaque",
"invoicing": "Facturando a cliente",
"stopping": "Deteniendo proceso",
"done": "Proceso detenido",
},
"of": "de"
}
}
</i18n>

View File

@ -1,100 +1,60 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref, computed } from 'vue';
import invoiceOutService from 'src/services/InvoiceOut.service.js';
import { useUserConfig } from 'src/composables/useUserConfig';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { storeToRefs } from 'pinia';
const { t } = useI18n(); const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters
const {
initialDataLoading,
formInitialData,
clientsOptions,
companiesOptions,
printersOptions,
invoicing,
printer,
status,
} = storeToRefs(invoiceOutGlobalStore);
// invoiceOutGlobalStore actions
const { makeInvoice, setPrinterValue, setStatusValue } = invoiceOutGlobalStore;
const formData = ref({ const formData = ref({
companyFk: null, companyFk: null,
invoiceDate: null, invoiceDate: null,
maxShipped: null, maxShipped: null,
clientId: null, clientId: null,
printer: null,
}); });
const parallelismVal = ref();
const clients = ref('all'); const getPrinter = computed({
const initialLoading = ref(true); get() {
const companiesOptions = ref([]); return printer.value;
const printersOptions = ref([]); },
const clientsOptions = ref([]); set(value) {
setPrinterValue(value.value);
},
});
const getStatus = computed({
get() {
return status.value;
},
set(value) {
setStatusValue(value.value);
},
});
const clientsToInvoice = ref('all');
const filteredClientsOptions = ref([]); const filteredClientsOptions = ref([]);
onMounted(async () => { onMounted(async () => {
const date = Date.vnNew(); await invoiceOutGlobalStore.init();
const userInfo = await useUserConfig().fetch(); formData.value = { ...formInitialData.value };
formData.value.maxShipped = new Date(date.getFullYear(), date.getMonth(), 0)
.toISOString()
.substring(0, 10);
await Promise.all([
fetchClients(),
fetchParallelism(),
fetchCompanies(userInfo.companyFk),
fetchPrinters(),
fetchInvoiceOutConfig(userInfo.companyFk),
]);
initialLoading.value = false;
}); });
// Estos methods de fetch idealmente deberían ir en un store
const fetchClients = async () => {
const clientsFilter = { fields: ['id', 'name'], order: 'id', limit: 30 };
const clientsResponse = await invoiceOutService.getClients(clientsFilter);
clientsOptions.value = clientsResponse.map((client) => {
return { value: client.id, label: client.name };
});
};
const fetchCompanies = async (companyFk) => {
const companiesFilters = { order: ['code'] };
const companiesResponse = await invoiceOutService.getCompanies(companiesFilters);
companiesOptions.value = companiesResponse.map((company) => {
return { value: company.id, label: company.code };
});
formData.value.companyFk = companiesOptions.value.find(
(company) => companyFk === company.value
);
};
const fetchPrinters = async () => {
const printersFilters = {
fields: ['id', 'name'],
where: { isLabeler: false },
order: 'name ASC',
limit: 30,
};
const printersResponse = await invoiceOutService.getPrinters(printersFilters);
printersOptions.value = printersResponse.map((printer) => {
return { value: printer.id, label: printer.name };
});
};
const fetchInvoiceOutConfig = async (companyFk) => {
const params = { companyFk: companyFk };
const { issued } = await invoiceOutService.getInvoiceOutConfig(
'InvoiceOuts/getInvoiceDate',
params
);
formData.value.invoiceDate = issued
? new Date(issued).toISOString().substring(0, 10)
: null;
};
const fetchParallelism = async () => {
const filter = { fields: ['parallelism'] };
const { parallelism } = await invoiceOutService.getInvoiceOutConfig(
'InvoiceOutConfigs/findOne',
filter
);
parallelismVal.value = parallelism;
console.log('parallelism.value:: ', parallelismVal.value);
};
const inputSelectfilter = (val, update) => { const inputSelectfilter = (val, update) => {
if (val === '') { if (val === '') {
update(() => { update(() => {
@ -112,29 +72,31 @@ const inputSelectfilter = (val, update) => {
); );
}); });
}; };
const makeInvoice = () => {};
</script> </script>
<template> <template>
<QForm v-if="!initialLoading" @submit="makeInvoice()" class="q-pa-md text-white"> <QForm
v-if="!initialDataLoading"
@submit="makeInvoice(formData, clientsToInvoice)"
class="q-pa-md text-white"
>
<div class="q-gutter-md"> <div class="q-gutter-md">
<QRadio <QRadio
v-model="clients" v-model="clientsToInvoice"
dense dense
val="all" val="all"
:label="t('allClients')" :label="t('allClients')"
:dark="true" :dark="true"
/> />
<QRadio <QRadio
v-model="clients" v-model="clientsToInvoice"
dense dense
val="one" val="one"
:label="t('oneClient')" :label="t('oneClient')"
:dark="true" :dark="true"
/> />
<QSelect <QSelect
v-if="clients === 'one'" v-if="clientsToInvoice === 'one'"
:options="filteredClientsOptions" :options="filteredClientsOptions"
v-model="formData.clientId" v-model="formData.clientId"
filled filled
@ -169,13 +131,14 @@ const makeInvoice = () => {};
<QSelect <QSelect
filled filled
:options="printersOptions" :options="printersOptions"
v-model="formData.printer" v-model="getPrinter"
:label="t('printer')" :label="t('printer')"
transition-show="jump-up" transition-show="jump-up"
transition-hide="jump-up" transition-hide="jump-up"
/> />
</div> </div>
<QBtn <QBtn
v-if="!invoicing"
:label="t('invoiceOut')" :label="t('invoiceOut')"
type="submit" type="submit"
color="primary" color="primary"
@ -184,6 +147,16 @@ const makeInvoice = () => {};
rounded rounded
dense dense
/> />
<QBtn
v-if="invoicing"
:label="t('stop')"
color="primary"
class="full-width q-mt-md"
unelevated
rounded
dense
@click="getStatus = 'stopping'"
/>
</QForm> </QForm>
</template> </template>
@ -197,7 +170,8 @@ const makeInvoice = () => {};
"company": "Company", "company": "Company",
"printer": "Printer", "printer": "Printer",
"invoiceOut": "Invoice out", "invoiceOut": "Invoice out",
"client": "Client" "client": "Client",
"stop": "Stop"
}, },
"es": { "es": {
"invoiceDate": "Fecha de factura", "invoiceDate": "Fecha de factura",
@ -207,8 +181,8 @@ const makeInvoice = () => {};
"company": "Empresa", "company": "Empresa",
"printer": "Impresora", "printer": "Impresora",
"invoiceOut": "Facturar", "invoiceOut": "Facturar",
"client": "Cliente" "client": "Cliente",
"stop": "Parar"
} }
} }
</i18n> </i18n>

View File

@ -1,52 +1,52 @@
import axios from 'axios'; import axios from 'axios';
const invoiceOutService = { const request = async (method, url, params = {}) => {
getInvoiceOutConfig: async (path = '', params) => {
try { try {
const { data } = await axios.get(path, { let response;
params,
});
return data; if (method === 'GET') {
} catch (err) { response = await axios.get(url, { params });
console.error('Error fetching invoice date'); } else if (method === 'POST') {
response = await axios.post(url, params);
} }
return response.data;
} catch (err) {
console.error(`Error with ${method} request to ${url}`, err);
return err.response;
}
};
const invoiceOutService = {
getInvoiceDate: async (params) => {
return await request('GET', 'InvoiceOuts/getInvoiceDate', params);
},
getFindOne: async (params) => {
return await request('GET', 'InvoiceOutConfigs/findOne', params);
}, },
getCompanies: async (filter) => { getCompanies: async (filter) => {
try { return await request('GET', 'Companies', { filter });
const { data } = await axios.get('Companies', {
filter,
});
return data;
} catch (err) {
console.error('Error fetching companies');
}
}, },
getPrinters: async (filter) => { getPrinters: async (filter) => {
try { return await request('GET', 'Printers', { filter });
const { data } = await axios.get('Printers', {
filter,
});
return data;
} catch (err) {
console.error('Error fetching printers');
}
}, },
getClients: async (filter) => { getClients: async (filter) => {
try { return await request('GET', 'Clients', { filter });
const { data } = await axios.get('Clients', { },
filter,
});
return data; getClientsToInvoice: async (params) => {
} catch (err) { return await request('POST', 'InvoiceOuts/clientsToInvoice', params);
console.error('Error fetching clients'); },
}
invoiceClient: async (params) => {
return await request('POST', 'InvoiceOuts/invoiceClient', params);
},
makePdfAndNotify: async (invoiceId, params) => {
return await request('POST', `InvoiceOuts/${invoiceId}/makePdfAndNotify`, params);
}, },
}; };

View File

@ -0,0 +1,286 @@
import { defineStore } from 'pinia';
import { useUserConfig } from 'src/composables/useUserConfig';
import invoiceOutService from 'src/services/InvoiceOut.service.js';
import useNotify from 'src/composables/useNotify.js';
const { notify } = useNotify();
export const useInvoiceOutGlobalStore = defineStore({
id: 'invoiceOutGlobal',
state: () => ({
initialDataLoading: true,
formInitialData: {
companyFk: null,
invoiceDate: null,
maxShipped: null,
clientId: null,
},
clientsOptions: [],
companiesOptions: [],
printersOptions: [],
addresses: [],
minInvoicingDate: null,
parallelism: null,
invoicing: false,
isInvoicing: false,
status: null,
addressIndex: 0,
printer: null,
errors: [],
nRequests: 0,
nPdfs: 0,
totalPdfs: 0,
}),
actions: {
async init() {
await this.fetchAllData();
},
async fetchAllData() {
try {
const userInfo = await useUserConfig().fetch();
const date = Date.vnNew();
this.formInitialData.maxShipped = new Date(
date.getFullYear(),
date.getMonth(),
0
)
.toISOString()
.substring(0, 10);
await Promise.all([
this.fetchClients(),
this.fetchParallelism(),
this.fetchCompanies(userInfo.companyFk),
this.fetchPrinters(),
this.fetchInvoiceOutConfig(userInfo.companyFk),
]);
this.initialDataLoading = false;
} catch (err) {
console.error('Error fetching invoice out global initial data');
}
},
async fetchClients() {
const clientsFilter = { fields: ['id', 'name'], order: 'id', limit: 30 };
const clientsResponse = await invoiceOutService.getClients(clientsFilter);
this.clientsOptions = clientsResponse.map((client) => {
return { value: client.id, label: client.name };
});
},
async fetchCompanies(companyFk) {
const companiesFilters = { order: ['code'] };
const companiesResponse = await invoiceOutService.getCompanies(
companiesFilters
);
this.companiesOptions = companiesResponse.map((company) => {
return { value: company.id, label: company.code };
});
this.formInitialData.companyFk = this.companiesOptions.find(
(company) => companyFk === company.value
);
},
async fetchPrinters() {
const printersFilters = {
fields: ['id', 'name'],
where: { isLabeler: false },
order: 'name ASC',
limit: 30,
};
const printersResponse = await invoiceOutService.getPrinters(printersFilters);
this.printersOptions = printersResponse.map((printer) => {
return { value: printer.id, label: printer.name };
});
},
async fetchInvoiceOutConfig(companyFk) {
const params = { companyFk: companyFk };
const { issued } = await invoiceOutService.getInvoiceDate(params);
const stringDate = issued.substring(0, 10);
this.minInvoicingDate = stringDate;
this.formInitialData.invoiceDate = stringDate;
},
async fetchParallelism() {
const filter = { fields: ['parallelism'] };
const { parallelism } = await invoiceOutService.getFindOne(filter);
this.parallelism = parallelism;
},
async makeInvoice(formData, clientsToInvoice) {
this.invoicing = true;
this.status = 'packageInvoicing';
try {
const params = {
invoiceDate: new Date(formData.invoiceDate),
maxShipped: new Date(formData.maxShipped),
clientId: formData.clientId ? formData.clientId.value : null,
companyFk: formData.companyFk.value,
};
this.validateMakeInvoceParams(params, clientsToInvoice);
if (clientsToInvoice == 'all') params.clientId = undefined;
const addressesResponse = await invoiceOutService.getClientsToInvoice(
params
);
this.addresses = addressesResponse;
if (!this.addresses || !this.addresses.length > 0) {
notify(
'invoiceOut.globalInvoices.errors.noTicketsToInvoice',
'negative'
);
throw new Error("There aren't addresses to invoice");
}
this.addresses.forEach(async (address) => {
await this.invoiceClient(address, formData);
});
} catch (err) {
this.handleError(err);
}
},
validateMakeInvoceParams(params, clientsToInvoice) {
if (clientsToInvoice === 'one' && !params.clientId) {
notify('invoiceOut.globalInvoices.errors.chooseValidClient', 'negative');
throw new Error('Invalid client');
}
if (!params.invoiceDate || !params.maxShipped) {
notify('invoiceOut.globalInvoices.errors.fillDates', 'negative');
throw new Error('Missing dates');
}
if (params.invoiceDate < params.maxShipped) {
notify(
'invoiceOut.globalInvoices.errors.invoiceDateLessThanMaxDate',
'negative'
);
throw new Error('Invalid date range');
}
const invoiceDateTime = new Date(params.invoiceDate).getTime();
const minInvoiceDateTime = new Date(this.minInvoicingDate).getTime();
if (this.minInvoicingDate && invoiceDateTime < minInvoiceDateTime) {
notify(
'invoiceOut.globalInvoices.errors.invoiceWithFutureDate',
'negative'
);
throw new Error('Invoice date in the future');
}
if (!params.companyFk) {
notify('invoiceOut.globalInvoices.errors.chooseValidCompany', 'negative');
throw new Error('Invalid company');
}
if (!this.printer) {
notify('invoiceOut.globalInvoices.errors.chooseValidPrinter', 'negative');
throw new Error('Invalid printer');
}
},
async invoiceClient(address, formData) {
if (this.nRequests === this.parallelism || this.isInvoicing) return;
if (this.status === 'stopping') {
if (this.nRequests) return;
this.invoicing = false;
this.status = 'done';
return;
}
const params = {
clientId: address.clientId,
addressId: address.id,
invoiceDate: new Date(formData.invoiceDate),
maxShipped: new Date(formData.maxShipped),
companyFk: formData.companyFk.value,
};
this.status = 'invoicing';
this.invoicing = true;
const invoiceResponse = await invoiceOutService.invoiceClient(params);
if (invoiceResponse.data.error) {
if (invoiceResponse.status >= 400 && invoiceResponse.status < 500) {
this.invoiceClientError(address, invoiceResponse);
this.addressIndex++;
return;
} else {
this.invoicing = false;
this.status = 'done';
notify(
'invoiceOut.globalInvoices.errors.criticalInvoiceError',
'negative'
);
throw new Error('Critical invoicing error, process stopped');
}
} else {
this.isInvoicing = false;
if (invoiceResponse.data) {
this.makePdfAndNotify(invoiceResponse.data, address);
}
}
},
async makePdfAndNotify(invoiceId, client) {
try {
this.nRequests++;
this.totalPdfs++;
const params = { printerFk: this.printer.value };
await invoiceOutService.makePdfAndNotify(invoiceId, params);
this.nPdfs++;
this.nRequests--;
} catch (err) {
this.invoiceClientError(client, err, true);
}
},
invoiceClientError(client, response, isWarning) {
const message = response.data?.error?.message || response.message;
this.errors.unshift({ client, message, isWarning });
},
handleError(err) {
this.invoicing = false;
this.status = null;
throw err;
},
// State mutations actions
setPrinterValue(printer) {
this.printer = printer;
},
setStatusValue(status) {
this.status = status;
},
},
getters: {
getNAddresses(state) {
return state.addresses.length;
},
getPercentage(state) {
if (this.getNAdresses <= 0 || !state.addressIndex) {
return 0;
}
let porcentaje = (state.addressIndex / this.getNAddresses) * 100;
return porcentaje;
},
getAddressNumber(state) {
return state.addressIndex;
},
},
});