diff --git a/src/components/FormModelPopup.vue b/src/components/FormModelPopup.vue
index 8737bc859..1a812b78a 100644
--- a/src/components/FormModelPopup.vue
+++ b/src/components/FormModelPopup.vue
@@ -1,5 +1,5 @@
+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();
+};
+
+
+
+
+
+
+
+
+ {{ title }}
+ {{ subtitle }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/TransferInvoiceForm.vue b/src/components/TransferInvoiceForm.vue
new file mode 100644
index 000000000..b7d8f3081
--- /dev/null
+++ b/src/components/TransferInvoiceForm.vue
@@ -0,0 +1,168 @@
+
+
+
+ (clientsOptions = data)"
+ :filter="{ fields: ['id', 'name'], order: 'id', limit: 30 }"
+ auto-load
+ />
+ (rectificativeTypeOptions = data)"
+ auto-load
+ />
+ (siiTypeInvoiceOutsOptions = data)"
+ auto-load
+ />
+ (invoiceCorrectionTypesOptions = data)"
+ auto-load
+ />
+
+
+
+
+
+
+
+
+
+ #{{ scope.opt?.id }} -
+ {{ scope.opt?.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.opt?.code }} -
+ {{ scope.opt?.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+es:
+ Transfer invoice: Transferir factura
+ Transfer client: Transferir cliente
+ Client: Cliente
+ Rectificative type: Tipo rectificativa
+ Class: Clase
+ Type: Tipo
+ Transferred invoice: Factura transferida
+
diff --git a/src/composables/useSession.js b/src/composables/useSession.js
index 580efbdf8..45d439b46 100644
--- a/src/composables/useSession.js
+++ b/src/composables/useSession.js
@@ -2,9 +2,11 @@ import { useState } from './useState';
import { useRole } from './useRole';
import { useUserConfig } from './useUserConfig';
import axios from 'axios';
-
+import useNotify from './useNotify';
export function useSession() {
+ const { notify } = useNotify();
+
function getToken() {
const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token');
@@ -27,38 +29,28 @@ export function useSession() {
sessionStorage.setItem('tokenMultimedia', data.tokenMultimedia);
}
}
-
+ async function destroyToken(url, storage, key) {
+ if (storage.getItem(key)) {
+ try {
+ await axios.post(url, null, {
+ headers: { Authorization: storage.getItem(key) },
+ });
+ } catch (error) {
+ notify('errors.statusUnauthorized', 'negative');
+ } finally {
+ storage.removeItem(key);
+ }
+ }
+ }
async function destroy() {
- if (localStorage.getItem('tokenMultimedia')){
- await axios.post('VnUsers/logoutMultimedia', null, {
- headers: {Authorization: localStorage.getItem('tokenMultimedia') }
- });
- localStorage.removeItem('tokenMultimedia')
-
+ const tokens = {
+ tokenMultimedia: 'Accounts/logout',
+ token: 'VnUsers/logout',
+ };
+ for (const [key, url] of Object.entries(tokens)) {
+ await destroyToken(url, localStorage, key);
+ await destroyToken(url, sessionStorage, key);
}
- if (localStorage.getItem('token')){
- await axios.post('VnUsers/logout', null, {
- headers: {Authorization: localStorage.getItem('token') }
- });
- localStorage.removeItem('token')
- }
-
-
- if (sessionStorage.getItem('tokenMultimedia')){
- await axios.post('VnUsers/logoutMultimedia', null, {
- headers: {Authorization: sessionStorage.getItem('tokenMultimedia') }
- });
- sessionStorage.removeItem('tokenMultimedia')
-
- }
- if (sessionStorage.getItem('token')){
- await axios.post('VnUsers/logout', null, {
- headers: {Authorization: sessionStorage.getItem('token') }
- });
- sessionStorage.removeItem('token')
- }
-
-
const { setUser } = useState();
diff --git a/src/composables/useVnConfirm.js b/src/composables/useVnConfirm.js
new file mode 100644
index 000000000..76c3f4f28
--- /dev/null
+++ b/src/composables/useVnConfirm.js
@@ -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 };
+}
diff --git a/src/css/app.scss b/src/css/app.scss
index 67406128b..8e2853ab2 100644
--- a/src/css/app.scss
+++ b/src/css/app.scss
@@ -97,6 +97,10 @@ select:-webkit-autofill {
background-color: var(--vn-light-gray);
}
+.fill-icon {
+ font-variation-settings: 'FILL' 1;
+}
+
.vn-table-separation-row {
height: 16px !important;
background-color: var(--vn-gray) !important;
diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js
index 883fe8000..d63fab8c6 100644
--- a/src/i18n/en/index.js
+++ b/src/i18n/en/index.js
@@ -613,6 +613,7 @@ export default {
company: 'Company',
dued: 'Due date',
shortDued: 'Due date',
+ amount: 'Amount',
},
card: {
issued: 'Issued',
@@ -646,7 +647,7 @@ export default {
fillDates: 'Invoice date and the max date should be filled',
invoiceDateLessThanMaxDate: 'Invoice date can not be less than max 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',
},
table: {
diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js
index e6e843482..8a562d6db 100644
--- a/src/i18n/es/index.js
+++ b/src/i18n/es/index.js
@@ -635,6 +635,7 @@ export default {
company: 'Empresa',
dued: 'Fecha vencimineto',
shortDued: 'F. vencimiento',
+ amount: 'Importe',
},
card: {
issued: 'Fecha emisión',
@@ -670,7 +671,7 @@ export default {
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',
+ noTicketsToInvoice: 'No existen tickets para facturar',
criticalInvoiceError: 'Error crítico en la facturación, proceso detenido',
},
table: {
diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue
index 80757c57b..7ffd84e39 100644
--- a/src/pages/Claim/Card/ClaimSummary.vue
+++ b/src/pages/Claim/Card/ClaimSummary.vue
@@ -271,7 +271,7 @@ function openDialog(dmsId) {
>
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue
index 9c1bb3649..e01706e84 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue
@@ -2,13 +2,15 @@
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
-import { toCurrency, toDate } from 'src/filters';
+
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnLv from 'src/components/ui/VnLv.vue';
-import useCardDescription from 'src/composables/useCardDescription';
import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue';
+import useCardDescription from 'src/composables/useCardDescription';
+import { toCurrency, toDate } from 'src/filters';
+
const $props = defineProps({
id: {
type: Number,
@@ -23,7 +25,6 @@ const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
-const descriptor = ref();
const filter = {
include: [
@@ -42,6 +43,8 @@ const filter = {
],
};
+const descriptor = ref();
+
function ticketFilter(invoice) {
return JSON.stringify({ refFk: invoice.ref });
}
@@ -61,7 +64,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
data-key="invoiceOutData"
>
-
+
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue
index ab8b6470b..10af631bc 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue
@@ -1,40 +1,260 @@
-
- {{ t('Transfer invoice to') }}
+
+ {{ t('Transfer invoice to...') }}
- {{ t('See invoice') }}
+ {{ t('Show invoice...') }}
+
+
+
+
+
+
+ {{ t('As PDF') }}
+
+
+ {{ t('As CSV') }}
+
+
+
- {{ t('Send invoice') }}
+ {{ t('Send invoice...') }}
+
+
+
+
+
+
+ {{ t('Send PDF') }}
+
+
+ {{ t('Send CSV') }}
+
+
+
-
+
{{ t('Delete invoice') }}
-
- {{ t('Post invoice') }}
+
+ {{ t('Book invoice') }}
+
+
+ {{ t('Generate PDF invoice') }}
- {{ t('Regenerate invoice PDF') }}
-
-
- {{ t('Pass') }}
+ {{ t('Refund...') }}
+
+
+
+
+
+
+ {{ t('With warehouse') }}
+
+
+ {{ t('Without warehouse') }}
+
+
+
+
+ {{ t('Create a single ticket with all the content of the current invoice') }}
+
+
+
+
+
es:
- Transfer invoice to: Transferir factura a
- See invoice: Ver factura
- Send invoice: Enviar factura
+ Transfer invoice to...: Transferir factura a...
+ Show invoice...: Ver factura...
+ Send invoice...: Enviar factura...
Delete invoice: Eliminar factura
- Post invoice: Asentar factura
- Regenerate invoice PDF: Regenerar PDF factura
- Pass: Abono
+ Book invoice: Asentar factura
+ Generate PDF invoice: Generar PDF factura
+ 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}
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
index c5c17fa87..64019b483 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
@@ -7,6 +7,8 @@ import { toCurrency, toDate } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
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 () => {
fetch();
@@ -67,29 +69,33 @@ const taxColumns = ref([
const ticketsColumns = ref([
{
name: 'item',
- label: 'invoiceOut.summary.ticketId',
+ label: t('invoiceOut.summary.ticketId'),
field: (row) => row.id,
sortable: true,
+ align: 'left',
},
{
name: 'quantity',
- label: 'invoiceOut.summary.nickname',
+ label: t('invoiceOut.summary.nickname'),
field: (row) => row.nickname,
sortable: true,
+ align: 'left',
},
{
name: 'landed',
- label: 'invoiceOut.summary.shipped',
+ label: t('invoiceOut.summary.shipped'),
field: (row) => row.shipped,
format: (value) => toDate(value),
sortable: true,
+ align: 'left',
},
{
name: 'landed',
- label: 'invoiceOut.summary.totalWithVat',
+ label: t('invoiceOut.summary.totalWithVat'),
field: (row) => row.totalWithVat,
format: (value) => toCurrency(value),
sortable: true,
+ align: 'left',
},
]);
@@ -151,12 +157,21 @@ const ticketsColumns = ref([
-
-
-
- {{ t(col.label) }}
-
-
+
+
+
+ {{ value }}
+
+
+
+
+
+
+
+ {{ value }}
+
+
+
diff --git a/src/pages/InvoiceOut/InvoiceOutGlobal.vue b/src/pages/InvoiceOut/InvoiceOutGlobal.vue
index 7e2b43a76..6f7e4c7fd 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobal.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobal.vue
@@ -21,6 +21,7 @@ const {
nPdfs,
totalPdfs,
errors,
+ addresses,
} = storeToRefs(invoiceOutGlobalStore);
const selectedCustomerId = ref(null);
@@ -86,6 +87,14 @@ const selectCustomerId = (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));
onUnmounted(() => {
stateStore.rightDrawer = false;
@@ -103,7 +112,7 @@ onUnmounted(() => {
- {{ t(`status.${status}`) }}
+ {{ statusText }}
{{
t('invoiceOut.globalInvoices.statusCard.percentageText', {
getPercentage: getPercentage,
diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
index c61b9f7ff..a000d6d4f 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
@@ -13,13 +13,8 @@ const { t } = useI18n();
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
// invoiceOutGlobalStore state and getters
-const {
- initialDataLoading,
- formInitialData,
-
- invoicing,
- status,
-} = storeToRefs(invoiceOutGlobalStore);
+const { initialDataLoading, formInitialData, invoicing, status } =
+ storeToRefs(invoiceOutGlobalStore);
// invoiceOutGlobalStore actions
const { makeInvoice, setStatusValue } = invoiceOutGlobalStore;
@@ -32,13 +27,7 @@ const printersOptions = ref([]);
const clientsOptions = ref([]);
-const formData = ref({
- companyFk: null,
- invoiceDate: null,
- maxShipped: null,
- clientId: null,
- printer: null,
-});
+const formData = ref({});
const optionsInitialData = computed(() => {
return (
diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue
index 08f893db2..cf33b2cec 100644
--- a/src/pages/InvoiceOut/InvoiceOutList.vue
+++ b/src/pages/InvoiceOut/InvoiceOutList.vue
@@ -2,25 +2,32 @@
import { onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
-import { exportFile, useQuasar } from 'quasar';
-import { useStateStore } from 'stores/useStateStore';
+
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
-import { toDate, toCurrency } from 'src/filters/index';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
-import InvoiceOutFilter from './InvoiceOutFilter.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.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 { useSession } from 'src/composables/useSession';
const { t } = useI18n();
-const selectedCards = ref(new Map());
-const quasar = useQuasar();
const router = useRouter();
const stateStore = useStateStore();
+const session = useSession();
+const token = session.getToken();
const { viewSummary } = useSummaryDialog();
+const manualInvoiceDialogRef = ref(null);
+const selectedCards = ref(new Map());
+
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
@@ -52,37 +59,37 @@ const toggleAllCards = (cardsData) => {
}
};
-const downloadCsv = () => {
- if (selectedCards.value.size === 0) return;
- const selectedCardsArray = Array.from(selectedCards.value.values());
- let file;
- for (var i = 0; i < selectedCardsArray.length; i++) {
- if (i == 0) file += Object.keys(selectedCardsArray[i]).join(';') + '\n';
- file +=
- Object.keys(selectedCardsArray[i])
- .map(function (key) {
- return selectedCardsArray[i][key];
- })
- .join(';') + '\n';
- }
- const status = exportFile('file.csv', file, {
- encoding: 'windows-1252',
- mimeType: 'text/csv;charset=windows-1252;',
- });
- if (status === true) {
- quasar.notify({
- message: t('fileAllowed'),
- color: 'positive',
- icon: 'check',
- });
- } else {
- quasar.notify({
- message: t('fileDenied'),
- color: 'negative',
- icon: 'warning',
- });
+const openPdf = () => {
+ try {
+ if (selectedCards.value.size === 0) return;
+ const selectedCardsArray = Array.from(selectedCards.value.values());
+
+ if (selectedCards.value.size === 1) {
+ const [invoiceOut] = selectedCardsArray;
+ const url = `api/InvoiceOuts/${invoiceOut.id}/download?access_token=${token}`;
+ window.open(url, '_blank');
+ } else {
+ const invoiceOutIdsArray = selectedCardsArray.map(
+ (invoiceOut) => invoiceOut.id
+ );
+ const invoiceOutIds = invoiceOutIdsArray.join(',');
+
+ const params = new URLSearchParams({
+ access_token: token,
+ ids: invoiceOutIds,
+ });
+
+ const url = `api/InvoiceOuts/downloadZip?${params}`;
+ window.open(url, '_blank');
+ }
+ } catch (err) {
+ console.error('Error opening PDF');
}
};
+
+const openCreateInvoiceModal = () => {
+ manualInvoiceDialogRef.value.show();
+};
@@ -119,52 +126,21 @@ const downloadCsv = () => {
-
+ {{ t('downloadPdf') }}
+
{
>
-
+
+
+
+ {{ row?.clientSocialName }}
+
+
+
+
{
/>
-
{