#6899 end invoiceOut migration with VnTable #513

Merged
jon merged 18 commits from 6899_InvoiceOut_End into dev 2024-07-08 09:34:44 +00:00
16 changed files with 521 additions and 425 deletions

View File

@ -46,22 +46,6 @@ const onDataSaved = async (formData, requestResponse) => {
@on-fetch="(data) => (taxAreasOptions = data)" @on-fetch="(data) => (taxAreasOptions = data)"
auto-load auto-load
/> />
<FetchData
url="Tickets"
:filter="{
fields: ['id', 'nickname'],
where: { refFk: null },
order: 'shipped DESC',
}"
@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 <FormModelPopup
ref="formModelPopupRef" ref="formModelPopupRef"
:title="t('Create manual invoice')" :title="t('Create manual invoice')"
@ -84,6 +68,10 @@ const onDataSaved = async (formData, requestResponse) => {
option-value="id" option-value="id"
v-model="data.ticketFk" v-model="data.ticketFk"
@update:model-value="data.clientFk = null" @update:model-value="data.clientFk = null"
url="Tickets"
:where="{ refFk: null }"
:fields="['id', 'nickname']"
:filter-options="{ order: 'shipped DESC' }"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -105,6 +93,9 @@ const onDataSaved = async (formData, requestResponse) => {
option-value="id" option-value="id"
v-model="data.clientFk" v-model="data.clientFk"
@update:model-value="data.ticketFk = null" @update:model-value="data.ticketFk = null"
url="Clients"
:fields="['id', 'name']"
:filter-options="{ order: 'name ASC' }"
/> />
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" /> <VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</VnRow> </VnRow>
@ -116,7 +107,6 @@ const onDataSaved = async (formData, requestResponse) => {
option-label="description" option-label="description"
option-value="code" option-value="code"
v-model="data.serial" v-model="data.serial"
:required="true"
/> />
<VnSelect <VnSelect
:label="t('Area')" :label="t('Area')"
@ -125,7 +115,6 @@ const onDataSaved = async (formData, requestResponse) => {
option-label="code" option-label="code"
option-value="code" option-value="code"
v-model="data.taxArea" v-model="data.taxArea"
:required="true"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">

View File

@ -8,7 +8,7 @@ import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import FormPopup from './FormPopup.vue'; import FormPopup from './FormPopup.vue';
import { useDialogPluginComponent } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -18,7 +18,7 @@ const $props = defineProps({
default: () => {}, default: () => {},
}, },
}); });
const { dialogRef } = useDialogPluginComponent();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
@ -116,90 +116,92 @@ const makeInvoice = async () => {
@on-fetch="(data) => (invoiceCorrectionTypesOptions = data)" @on-fetch="(data) => (invoiceCorrectionTypesOptions = data)"
auto-load auto-load
/> />
<FormPopup <QDialog ref="dialogRef">
<FormPopup
jsegarra marked this conversation as resolved
Review

Has probado a usar FormPopupDialog?

Has probado a usar FormPopupDialog?
@on-submit="makeInvoice()" @on-submit="makeInvoice()"
:title="t('Transfer invoice')" :title="t('Transfer invoice')"
:custom-submit-button-label="t('Transfer client')" :custom-submit-button-label="t('Transfer client')"
:default-cancel-button="false" :default-cancel-button="false"
> >
<template #form-inputs> <template #form-inputs>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnSelect <VnSelect
:label="t('Client')" :label="t('Client')"
:options="clientsOptions" :options="clientsOptions"
hide-selected hide-selected
option-label="name" option-label="name"
option-value="id" option-value="id"
v-model="transferInvoiceParams.newClientFk" v-model="transferInvoiceParams.newClientFk"
:required="true" :required="true"
url="Clients" url="Clients"
:fields="['id', 'name', 'hasToInvoiceByAddress']" :fields="['id', 'name', 'hasToInvoiceByAddress']"
auto-load auto-load
> >
<template #option="scope"> <template #option="scope">
<QItem <QItem
v-bind="scope.itemProps" v-bind="scope.itemProps"
@click="selectedClient(scope.opt)" @click="selectedClient(scope.opt)"
> >
<QItemSection> <QItemSection>
<QItemLabel> <QItemLabel>
#{{ scope.opt?.id }} - {{ scope.opt?.name }} #{{ scope.opt?.id }} - {{ scope.opt?.name }}
</QItemLabel> </QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
<VnSelect <VnSelect
:label="t('Rectificative type')" :label="t('Rectificative type')"
:options="rectificativeTypeOptions" :options="rectificativeTypeOptions"
hide-selected hide-selected
option-label="description" option-label="description"
option-value="id" option-value="id"
v-model="transferInvoiceParams.cplusRectificationTypeFk" v-model="transferInvoiceParams.cplusRectificationTypeFk"
:required="true" :required="true"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnSelect <VnSelect
:label="t('Class')" :label="t('Class')"
:options="siiTypeInvoiceOutsOptions" :options="siiTypeInvoiceOutsOptions"
hide-selected hide-selected
option-label="description" option-label="description"
option-value="id" option-value="id"
v-model="transferInvoiceParams.siiTypeInvoiceOutFk" v-model="transferInvoiceParams.siiTypeInvoiceOutFk"
:required="true" :required="true"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel> <QItemLabel>
{{ scope.opt?.code }} - {{ scope.opt?.code }} -
{{ scope.opt?.description }} {{ scope.opt?.description }}
</QItemLabel> </QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
<VnSelect <VnSelect
:label="t('Type')" :label="t('Type')"
:options="invoiceCorrectionTypesOptions" :options="invoiceCorrectionTypesOptions"
hide-selected hide-selected
option-label="description" option-label="description"
option-value="id" option-value="id"
v-model="transferInvoiceParams.invoiceCorrectionTypeFk" v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
:required="true" :required="true"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div> <div>
<QCheckbox :label="t('Bill destination client')" v-model="checked" /> <QCheckbox :label="t('Bill destination client')" v-model="checked" />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm"> <QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip>{{ t('transferInvoiceInfo') }}</QTooltip> <QTooltip>{{ t('transferInvoiceInfo') }}</QTooltip>
</QIcon> </QIcon>
</div> </div>
</VnRow> </VnRow>
</template> </template>
</FormPopup> </FormPopup>
</QDialog>
</template> </template>
<i18n> <i18n>

View File

@ -67,6 +67,10 @@ const $props = defineProps({
type: Object, type: Object,
default: () => ({ card: false, table: false }), default: () => ({ card: false, table: false }),
}, },
table: {
type: Object,
default: () => ({}),
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -249,7 +253,7 @@ defineExpose({
</template> </template>
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
v-bind="$attrs['q-table']" v-bind="table"
class="vnTable" class="vnTable"
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
:rows="rows" :rows="rows"

View File

@ -165,9 +165,9 @@ const toModule = computed(() =>
<QTooltip> <QTooltip>
{{ t('components.cardDescriptor.moreOptions') }} {{ t('components.cardDescriptor.moreOptions') }}
</QTooltip> </QTooltip>
<QMenu> <QMenu ref="menuRef">
<QList> <QList>
<slot name="menu" :entity="entity" /> <slot name="menu" :entity="entity" :menu-ref="menuRef" />
</QList> </QList>
</QMenu> </QMenu>
</QBtn> </QBtn>

View File

@ -63,8 +63,8 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
@on-fetch="setData" @on-fetch="setData"
data-key="invoiceOutData" data-key="invoiceOutData"
> >
<template #menu="{ entity }"> <template #menu="{ entity, menuRef }">
<InvoiceOutDescriptorMenu :invoice-out-data="entity" /> <InvoiceOutDescriptorMenu :invoice-out-data="entity" :menu-ref="menuRef" />
</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,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -11,13 +11,20 @@ import useNotify from 'src/composables/useNotify';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios'; import axios from 'axios';
onBeforeMount(async () => (salixUrl.value = await getUrl('')));
const $props = defineProps({ const $props = defineProps({
invoiceOutData: { invoiceOutData: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
menuRef: {
type: Object,
default: () => {},
},
}); });
const { notify } = useNotify(); const { notify } = useNotify();
@ -28,8 +35,7 @@ const { t } = useI18n();
const { openReport, sendEmail } = usePrintService(); const { openReport, sendEmail } = usePrintService();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const quasar = useQuasar(); const quasar = useQuasar();
const salixUrl = ref();
const transferInvoiceDialogRef = ref();
const invoiceFormType = ref('pdf'); const invoiceFormType = ref('pdf');
const defaultEmailAddress = ref($props.invoiceOutData.client?.email); const defaultEmailAddress = ref($props.invoiceOutData.client?.email);
@ -115,6 +121,7 @@ const refundInvoice = async (withWarehouse) => {
try { try {
const params = { ref: $props.invoiceOutData.ref, withWarehouse: withWarehouse }; const params = { ref: $props.invoiceOutData.ref, withWarehouse: withWarehouse };
const { data } = await axios.post('InvoiceOuts/refund', params); const { data } = await axios.post('InvoiceOuts/refund', params);
location.href = window.origin + `/#/ticket/${data[0].id}/sale`;
jon marked this conversation as resolved Outdated

podemos probar a usar window.origin en vez de salixURL?

podemos probar a usar window.origin en vez de salixURL?
notify( notify(
t('refundInvoiceSuccessMessage', { t('refundInvoiceSuccessMessage', {
refundTicket: data[0].id, refundTicket: data[0].id,
@ -125,11 +132,22 @@ const refundInvoice = async (withWarehouse) => {
console.error('Error generating invoice out pdf', err); console.error('Error generating invoice out pdf', err);
} }
}; };
const showTransferInvoiceForm = async () => {
quasar.dialog({
jsegarra marked this conversation as resolved
Review

Ufff... me suena que esto ya pasó una vez y lo corregimos

Ufff... me suena que esto ya pasó una vez y lo corregimos
component: TransferInvoiceForm,
componentProps: {
invoiceOutData: $props.invoiceOutData,
},
});
};
</script> </script>
<template> <template>
<QItem v-ripple clickable @click="transferInvoiceDialogRef.show()"> <QItem v-ripple clickable>
<QItemSection>{{ t('Transfer invoice to...') }}</QItemSection> <QItemSection @click="showTransferInvoiceForm()">{{
t('Transfer invoice to...')
}}</QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection>{{ t('Show invoice...') }}</QItemSection> <QItemSection>{{ t('Show invoice...') }}</QItemSection>
@ -222,10 +240,6 @@ const refundInvoice = async (withWarehouse) => {
{{ t('Create a single ticket with all the content of the current invoice') }} {{ t('Create a single ticket with all the content of the current invoice') }}
</QTooltip> </QTooltip>
</QItem> </QItem>
<QDialog ref="transferInvoiceDialogRef">
<TransferInvoiceForm :invoice-out-data="invoiceOutData" />
</QDialog>
</template> </template>
<i18n> <i18n>

View File

@ -3,7 +3,7 @@ import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate, toPercentage } from 'src/filters';
jon marked this conversation as resolved
Review

Hay una traducción que no va bien

Hay una traducción que no va bien
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';
@ -57,12 +57,14 @@ const taxColumns = ref([
name: 'quantity', name: 'quantity',
label: 'invoiceOut.summary.rate', label: 'invoiceOut.summary.rate',
field: (row) => row.rate, field: (row) => row.rate,
format: (value) => toPercentage(value / 100),
sortable: true, sortable: true,
}, },
{ {
name: 'invoiceOuted', name: 'invoiceOuted',
label: 'invoiceOut.summary.fee', label: 'invoiceOut.summary.fee',
field: (row) => row.vat, field: (row) => row.vat,
format: (value) => toCurrency(value),
sortable: true, sortable: true,
}, },
]); ]);
@ -113,7 +115,7 @@ const ticketsColumns = ref([
</template> </template>
<template #body="{ entity: { invoiceOut } }"> <template #body="{ entity: { invoiceOut } }">
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle :text="t('invoiceOut.pageTitles.basicData')" /> <VnTitle :text="t('globals.pageTitles.basicData')" />
<VnLv <VnLv
:label="t('invoiceOut.summary.issued')" :label="t('invoiceOut.summary.issued')"
:value="toDate(invoiceOut.issued)" :value="toDate(invoiceOut.issued)"
@ -164,7 +166,7 @@ const ticketsColumns = ref([
</template> </template>
<template #body-cell-item="{ value }"> <template #body-cell-item="{ value }">
<QTd> <QTd>
<QBtn flat color="primary"> <QBtn flat class="link">
{{ value }} {{ value }}
<TicketDescriptorProxy :id="value" /> <TicketDescriptorProxy :id="value" />
</QBtn> </QBtn>
@ -172,7 +174,7 @@ const ticketsColumns = ref([
</template> </template>
<template #body-cell-quantity="{ value, row }"> <template #body-cell-quantity="{ value, row }">
<QTd> <QTd>
<QBtn class="no-uppercase" flat color="primary" dense> <QBtn class="no-uppercase link" flat dense>
{{ value }} {{ value }}
<CustomerDescriptorProxy :id="row.id" /> <CustomerDescriptorProxy :id="row.id" />
</QBtn> </QBtn>

View File

@ -41,7 +41,7 @@ function setWorkers(data) {
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #body="{ params, searchFn }"> <template #body="{ params }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
@ -93,7 +93,6 @@ function setWorkers(data) {
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
:label="t('Has PDF')" :label="t('Has PDF')"
@update:model-value="searchFn()"
toggle-indeterminate toggle-indeterminate
v-model="params.hasPdf" v-model="params.hasPdf"
/> />

View File

@ -30,7 +30,7 @@ const selectedCustomerId = ref(null);
const tableColumnComponents = { const tableColumnComponents = {
clientId: { clientId: {
component: QBtn, component: QBtn,
props: { flat: true, color: 'blue' }, props: { flat: true, class: 'link' },
event: (prop) => selectCustomerId(prop.value), event: (prop) => selectCustomerId(prop.value),
}, },
clientName: { clientName: {
@ -52,7 +52,12 @@ const tableColumnComponents = {
}; };
const columns = computed(() => [ const columns = computed(() => [
{ label: 'Id', field: 'clientId', name: 'clientId', align: 'left' }, {
align: 'left',
name: 'clientId',
label: 'Id',
field: 'clientId',
},
{ {
label: t('invoiceOut.globalInvoices.table.client'), label: t('invoiceOut.globalInvoices.table.client'),
field: 'clientName', field: 'clientName',

View File

@ -48,7 +48,7 @@ const getStatus = computed({
onMounted(async () => { onMounted(async () => {
await invoiceOutGlobalStore.init(); await invoiceOutGlobalStore.init();
formData.value = { ...formInitialData.value }; formData.value = formInitialData.value.invoiceDate;
}); });
</script> </script>

View File

@ -1,96 +1,177 @@
<script setup> <script setup>
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.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 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'; import { usePrintService } from 'composables/usePrintService';
import RightMenu from 'src/components/common/RightMenu.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import { toCurrency, toDate } from 'src/filters/index';
import { useStateStore } from 'stores/useStateStore';
import { QBtn } from 'quasar';
import { watchEffect } from 'vue';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter();
const stateStore = useStateStore(); const stateStore = useStateStore();
const session = useSession();
const tokenMultimedia = session.getTokenMultimedia();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const tableRef = ref();
const invoiceOutSerialsOptions = ref([]);
const ticketsOptions = ref([]);
const customerOptions = ref([]);
const selectedRows = ref([]);
const hasSelectedCards = computed(() => selectedRows.value.length > 0);
const MODEL = 'InvoiceOuts';
const { openReport } = usePrintService();
const manualInvoiceDialogRef = ref(null); const columns = computed(() => [
const selectedCards = ref(new Map()); {
align: 'left',
name: 'id',
label: t('invoiceOutList.tableVisibleColumns.id'),
chip: {
condition: () => true,
},
isId: true,
},
{
align: 'left',
name: 'ref',
label: t('invoiceOutList.tableVisibleColumns.ref'),
isTitle: true,
component: 'select',
attrs: {
url: MODEL,
jon marked this conversation as resolved Outdated

5 veces aparece la misma url.

Propuesta, crear una constante que sea MODEL = 'InvoiceOuts'

5 veces aparece la misma url. Propuesta, crear una constante que sea MODEL = 'InvoiceOuts'
optionLabel: 'ref',
optionValue: 'id',
},
columnField: {
component: null,
},
},
{
align: 'left',
name: 'Issued',
label: t('invoiceOutList.tableVisibleColumns.issued'),
component: 'date',
format: (row) => toDate(row.issued),
columnField: {
component: null,
},
},
{
align: 'left',
name: 'clientSocialName',
label: t('invoiceOutModule.customer'),
cardVisible: true,
component: 'select',
attrs: {
url: 'Clients',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
},
{
align: 'left',
name: 'companyCode',
label: t('invoiceOutModule.company'),
cardVisible: true,
component: 'select',
attrs: {
url: 'Companies',
optionLabel: 'code',
optionValue: 'id',
},
columnField: {
component: null,
},
},
{
align: 'left',
name: 'amount',
label: t('invoiceOutModule.amount'),
cardVisible: true,
format: (row) => toCurrency(row.amount),
},
{
align: 'left',
name: 'created',
label: t('invoiceOutList.tableVisibleColumns.created'),
component: 'date',
columnField: {
component: null,
},
format: (row) => toDate(row.created),
},
{
align: 'left',
name: 'dued',
label: t('invoiceOutList.tableVisibleColumns.dueDate'),
component: 'date',
columnField: {
component: null,
},
format: (row) => toDate(row.dued),
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('InvoiceOutSummary'),
jsegarra marked this conversation as resolved
Review

Si no pones label, no se rompe nada.
Yo también he pecado de lo mismo

Si no pones label, no se rompe nada. Yo también he pecado de lo mismo
icon: 'preview',
action: (row) => viewSummary(row.id, InvoiceOutSummary),
},
{
title: t('DownloadPdf'),
icon: 'vn:ticket',
isPrimary: true,
action: (row) => openPdf(row.id),
},
],
},
]);
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) { function openPdf(id) {
router.push({ path: `/invoice-out/${id}` }); try {
openReport(`${MODEL}/${id}/download`);
} catch (err) {
console.error('Error opening PDF', err);
}
} }
const toggleIndividualCard = (cardData) => { function downloadPdf() {
if (selectedCards.value.has(cardData.id)) {
selectedCards.value.delete(cardData.id);
return;
}
selectedCards.value.set(cardData.id, cardData);
};
const toggleAllCards = (cardsData) => {
const allSelected = selectedCards.value.size === cardsData.length;
if (!allSelected) {
// Si no todas las tarjetas están seleccionadas, selecciónalas todas
cardsData.forEach((data) => {
if (!selectedCards.value.has(data.id)) {
selectedCards.value.set(data.id, data);
}
});
} else {
// Si todas las tarjetas están seleccionadas, deselecciónalas todas
selectedCards.value.clear();
}
};
const openPdf = () => {
try { try {
if (selectedCards.value.size === 0) return; if (selectedRows.value.size === 0) return;
jon marked this conversation as resolved Outdated

hay un composable que hace justo esto.
Se llama usePrintservice
Pruébalo y sino cumple las necesidades, vemos si aplicarlo o no

En la línea 170 también

hay un composable que hace justo esto. Se llama usePrintservice Pruébalo y sino cumple las necesidades, vemos si aplicarlo o no En la línea 170 también
const selectedCardsArray = Array.from(selectedCards.value.values()); const selectedCardsArray = Array.from(selectedRows.value.values());
if (selectedCards.value.size === 1) { if (selectedRows.value.size === 1) {
const [invoiceOut] = selectedCardsArray; const [invoiceOut] = selectedCardsArray;
const url = `api/InvoiceOuts/${invoiceOut.id}/download?access_token=${tokenMultimedia}`; openPdf(invoiceOut.id);
window.open(url, '_blank');
} else { } else {
const invoiceOutIdsArray = selectedCardsArray.map( const invoiceOutIdsArray = selectedCardsArray.map(
(invoiceOut) => invoiceOut.id (invoiceOut) => invoiceOut.id
); );
const invoiceOutIds = invoiceOutIdsArray.join(','); const invoiceOutIds = invoiceOutIdsArray.join(',');
const params = new URLSearchParams({ const params = {
access_token: tokenMultimedia,
ids: invoiceOutIds, ids: invoiceOutIds,
}); };
const url = `api/InvoiceOuts/downloadZip?${params}`; openReport(`${MODEL}/downloadZip`, params);
window.open(url, '_blank');
} }
} catch (err) { } catch (err) {
console.error('Error opening PDF'); console.error('Error opening PDF');
} }
}; }
jsegarra marked this conversation as resolved Outdated

Eliminar comentarios

Eliminar comentarios
const openCreateInvoiceModal = () => { watchEffect(selectedRows);
manualInvoiceDialogRef.value.show();
};
</script> </script>
<template> <template>
@ -99,115 +180,84 @@ const openCreateInvoiceModal = () => {
:label="t('searchInvoice')" :label="t('searchInvoice')"
data-key="InvoiceOutList" data-key="InvoiceOutList"
/> />
<RightMenu> <VnSubToolbar>
<template #right-panel> <template #st-actions>
<InvoiceOutFilter data-key="InvoiceOutList" /> <QBtn
color="primary"
icon-right="cloud_download"
@click="downloadPdf()"
:disable="!hasSelectedCards"
>
<QTooltip>{{ t('globals.downloadPdf') }}</QTooltip>
</QBtn>
</template> </template>
</RightMenu> </VnSubToolbar>
<QPage> <VnTable
<VnPaginate ref="tableRef"
auto-load data-key="invoiceOut"
data-key="InvoiceOutList" :url="`${MODEL}/filter`"
:order="['issued DESC', 'id DESC']" :create="{
url="InvoiceOuts/filter" urlCreate: 'InvoiceOuts/createManualInvoice',
> title: t('Create Manual Invoice'),
<template #body="{ rows }"> onDataSaved: ({ id }) => tableRef.redirect(id),
<VnSubToolbar class="justify-end"> formInitialData: {
<template #st-actions> active: true,
<QBtn },
@click="openPdf()" }"
class="q-mr-md" v-model:selected="selectedRows"
color="primary" order="id DESC"
icon="cloud_download" :columns="columns"
:disable="selectedCards.size === 0" default-mode="table"
> redirect="invoice-out"
<QTooltip>{{ t('globals.downloadPdf') }}</QTooltip> auto-load
</QBtn> :table="{
<QCheckbox 'row-key': 'id',
left-label selection: 'multiple',
:label="t('globals.markAll')" }"
@click="toggleAllCards(rows)" >
:model-value="selectedCards.size === rows.length" <template #more-create-dialog="{ data }">
class="q-mr-md" <VnSelect
/> url="Tickets"
jsegarra marked this conversation as resolved Outdated

Duda: era q-table o table?

Duda: era q-table o table?
</template> v-model="data.ticketFk"
</VnSubToolbar> :label="t('invoiceOutList.tableVisibleColumns.ticket')"
<div class="flex flex-center q-pa-md"> :options="ticketsOptions"
<div class="vn-card-list"> option-label="nickname"
<CardList option-value="id"
:element="row" />
:id="row.id" <VnSelect
:show-checkbox="true" url="Clients"
:is-selected="selectedCards.has(row.id)" v-model="data.clientFk"
:key="row.id" :label="t('invoiceOutModule.customer')"
:title="row.ref" :options="customerOptions"
@click="navigate(row.id)" option-label="name"
@toggle-card-check="toggleIndividualCard(row)" option-value="id"
v-for="row of rows" />
> <VnInputDate
<template #list-items> :label="t('invoiceOutList.tableVisibleColumns.dueDate')"
<VnLv v-model="data.maxShipped"
:label="t('invoiceOut.list.issued')" />
:value="toDate(row.issued)" <VnSelect
/> url="InvoiceOutSerials"
<VnLv v-model="data.invoiceOutSerial"
:label="t('invoiceOut.list.amount')" :label="t('invoiceOutList.tableVisibleColumns.invoiceOutSerial')"
:value="toCurrency(row.amount)" :options="invoiceOutSerialsOptions"
/> option-label="description"
<VnLv :label="t('invoiceOut.list.client')"> option-value="code"
<template #value> />
<span class="link" @click.stop> <VnSelect
{{ row?.clientSocialName }} url="TaxAreas"
<CustomerDescriptorProxy v-model="data.area"
:id="row?.clientFk" :label="t('invoiceOutList.tableVisibleColumns.taxArea')"
/> :options="taxAreasOptions"
</span> option-label="code"
</template> option-value="code"
</VnLv> />
<VnLv <QInput
:label="t('invoiceOut.list.shortCreated')" v-model="data.reference"
:title-label="t('invoiceOut.list.created')" :label="t('invoiceOutList.tableVisibleColumns.ref')"
:value="toDate(row.created)" />
/> </template>
<VnLv </VnTable>
:label="t('invoiceOut.list.company')"
:value="row.companyCode"
/>
<VnLv
:label="t('invoiceOut.list.shortDued')"
:title-label="t('invoiceOut.list.dued')"
:value="toDate(row.dued)"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, InvoiceOutSummary)"
color="primary"
style="margin-top: 15px"
type="submit"
/>
</template>
</CardList>
</div>
</div>
</template>
</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>
</template> </template>
<i18n> <i18n>

View File

@ -1,200 +1,177 @@
<script setup> <script setup>
import { computed, onBeforeMount } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.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 VnTable from 'src/components/VnTable/VnTable.vue';
import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js'; import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js';
import { useStateStore } from 'stores/useStateStore'; import { useArrayData } from 'src/composables/useArrayData';
import { useArrayData } from 'composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue';
const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref();
const arrayData = useArrayData('InvoiceOutNegative', { const invoiceOutGlobalStore = useInvoiceOutGlobalStore();
url: 'InvoiceOuts/negativeBases', const userParams = {
limit: 0, from: Date.vnFirstDayOfMonth().toISOString(),
userParams: { to: Date.vnLastDayOfMonth().toISOString(),
from: Date.vnFirstDayOfMonth().toISOString(), };
jsegarra marked this conversation as resolved
Review

Se ha decidido dejar así para no hacer dos llamadas iguales(la del Vntable y la del arrayData)

Se ha decidido dejar así para no hacer dos llamadas iguales(la del Vntable y la del arrayData)
to: Date.vnLastDayOfMonth().toISOString(), useArrayData('InvoiceOutNegative', { userParams });
},
exprBuilder: (param, value) => {
switch (param) {
case 'from':
case 'to':
return;
default:
return { [param]: value };
}
},
});
onBeforeMount(async () => {
await arrayData.fetch({ append: false });
stateStore.rightDrawer = true;
});
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('invoiceOut.negativeBases.company'), align: 'left',
field: 'company',
name: 'company', name: 'company',
align: 'left', label: t('invoiceOutModule.company'),
component: 'select',
cardVisible: true,
attrs: {
url: 'Companies',
optionLabel: 'code',
optionValue: 'id',
},
columnField: {
component: null,
},
}, },
{ {
label: t('invoiceOut.negativeBases.country'), align: 'left',
field: 'country',
name: 'country', name: 'country',
align: 'left', label: t('negativeBases.country'),
component: 'select',
attrs: {
url: 'Countries',
optionLabel: 'name',
optionValue: 'id',
},
columnField: {
component: null,
},
}, },
{ {
label: t('invoiceOut.negativeBases.clientId'), align: 'left',
field: 'clientId',
name: 'clientId', name: 'clientId',
align: 'left', label: t('negativeBases.clientId'),
cardVisible: true,
}, },
{ {
label: t('invoiceOut.negativeBases.client'),
field: 'clientSocialName',
name: 'client',
align: 'left', align: 'left',
name: 'clientSocialName',
label: t('invoiceOutModule.customer'),
component: 'select',
isTitle: true,
cardVisible: true,
attrs: {
url: 'Clients',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
}, },
{ {
label: t('invoiceOut.negativeBases.amount'), align: 'left',
field: 'amount',
name: 'amount', name: 'amount',
align: 'left', label: t('invoiceOutModule.amount'),
format: (value) => toCurrency(value), format: (row) => toCurrency(row.amount),
cardVisible: true,
}, },
{ {
label: t('invoiceOut.negativeBases.base'),
field: 'taxableBase',
name: 'base',
align: 'left', align: 'left',
name: 'taxableBase',
label: t('negativeBases.base'),
}, },
{ {
label: t('invoiceOut.negativeBases.ticketId'),
field: 'ticketFk',
name: 'ticketId',
align: 'left', align: 'left',
name: 'ticketFk',
label: t('negativeBases.ticketId'),
cardVisible: true,
}, },
{ {
label: t('invoiceOut.negativeBases.active'),
field: 'isActive',
name: 'active',
align: 'left', align: 'left',
name: 'isActive',
label: t('negativeBases.active'),
}, },
{ {
label: t('invoiceOut.negativeBases.hasToInvoice'), align: 'left',
field: 'hasToInvoice',
name: 'hasToInvoice', name: 'hasToInvoice',
align: 'left', label: t('negativeBases.hasToInvoice'),
}, },
{ {
label: t('invoiceOut.negativeBases.verifiedData'),
field: 'isTaxDataChecked',
name: 'verifiedData',
align: 'left', align: 'left',
name: 'hasVerifiedData',
label: t('negativeBases.verifiedData'),
}, },
{ {
label: t('invoiceOut.negativeBases.comercial'),
field: 'workerName',
name: 'worker',
align: 'left', align: 'left',
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
name: 'workerName',
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.workerName),
}, },
]); ]);
const downloadCSV = async () => { const downloadCSV = async () => {
const params = {}; // filter.value;
const filterParams = { const filterParams = {
limit: 20, limit: 20,
where: { where: {
and: [], and: [],
}, },
}; };
for (const param in params) {
if (params[param]) filterParams.where.and.push({ [param]: params[param] }); for (const [key, value] of Object.entries(userParams)) {
if (value) {
filterParams.where.and.push({ [key]: value });
}
} }
await invoiceOutGlobalStore.getNegativeBasesCsv( await invoiceOutGlobalStore.getNegativeBasesCsv(
arrayData.value.store.userParams.from, userParams.from,
arrayData.value.store.userParams.to, userParams.to,
params filterParams
); );
}; };
</script> </script>
<template> <template>
<VnSubToolbar> <VnSubToolbar>
<template #st-actions> <template #st-actions>
<QBtn color="primary" icon-right="download" no-caps @click="downloadCSV()"> <QBtn color="primary" icon-right="download" @click="downloadCSV()">
<QTooltip>{{ t('Download as CSV') }}</QTooltip> <QTooltip>{{ t('Download as CSV') }}</QTooltip>
</QBtn> </QBtn>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<RightMenu> <VnTable
<template #right-panel> ref="tableRef"
<InvoiceOutNegativeFilter data-key="InvoiceOutNegative" /> data-key="negativeFilter"
</template> url="InvoiceOuts/negativeBases"
</RightMenu> :user-params="userParams"
<QPage class="column items-center q-pa-md"> :expr-builder="
<QTable (param, value) => {
:columns="columns" switch (param) {
:rows="arrayData.store.data" case 'from':
row-key="clientId" case 'to':
class="full-width q-mt-md" return;
> default:
<template #body-cell-clientId="{ row }"> return { [param]: value };
<QTd> }
<QBtn flat dense class="link"> {{ row.clientId }}</QBtn> }
<CustomerDescriptorProxy :id="row.clientId" /> "
</QTd> :limit="0"
</template> :columns="columns"
<template #body-cell-ticketId="{ row }"> default-mode="table"
<QTd> auto-load
<QBtn flat dense class="link"> {{ row.ticketFk }}</QBtn> :is-editable="false"
<TicketDescriptorProxy :id="row.ticketFk" /> :use-model="true"
</QTd> >
jsegarra marked this conversation as resolved Outdated

👀

👀
</template> </VnTable>
<template #body-cell-worker="{ row }">
<QTd>
<QBtn class="no-uppercase link" flat dense>{{ row.workerName }}</QBtn>
<WorkerDescriptorProxy :id="row.comercialId" />
</QTd>
</template>
<template #body-cell-active="{ row }">
<QTd>
<QCheckbox :model-value="!!row.isActive" disable />
</QTd>
</template>
<template #body-cell-hasToInvoice="{ row }">
<QTd>
<QCheckbox :model-value="!!row.hasToInvoice" disable />
</QTd>
</template>
<template #body-cell-verifiedData="{ row }">
<QTd>
<QCheckbox :model-value="!!row.isTaxDataChecked" disable />
</QTd>
</template>
</QTable>
</QPage>
</template> </template>
<style lang="scss" scoped>
.col-content {
border-radius: 4px;
padding: 6px;
}
.no-uppercase {
text-transform: none;
}
</style>
<i18n> <i18n>
es: es:
Download as CSV: Descargar como CSV Download as CSV: Descargar como CSV

View File

@ -0,0 +1,25 @@
invoiceOutModule:
customer: Client
amount: Amount
company: Company
invoiceOutList:
tableVisibleColumns:
id: ID
ref: Reference
issued: Issued
created: Created
dueDate: Max date
invoiceOutSerial: Serial
ticket: Ticket
taxArea: Tax area
DownloadPdf: Download PDF
InvoiceOutSummary: Summary
negativeBases:
country: Country
clientId: Client ID
base: Base
ticketId: Ticket
active: Active
hasToInvoice: Has to invoice
verifiedData: Verified data
commercial: Commercial

View File

@ -1,2 +1,31 @@
Search invoice: Buscar factura emitida Search invoice: Buscar factura emitida
You can search by invoice reference: Puedes buscar por referencia de la factura You can search by invoice reference: Puedes buscar por referencia de la factura
invoiceOutModule:
customer: Cliente
amount: Importe
company: Empresa
invoiceOutList:
tableVisibleColumns:
id: ID
ref: Referencia
issued: Fecha emisión
customer: Cliente
company: Empresa
amount: Importe
created: F. creación
dueDate: F. máxima
invoiceOutSerial: Serial
ticket: Ticket
taxArea: Area
DownloadPdf: Descargar PDF
InvoiceOutSummary: Resumen
negativeBases:
country: País
clientId: ID del cliente
client: Cliente
base: Base
ticketId: Ticket
active: Activo
hasToInvoice: Debe facturar
verifiedData: Datos verificados
commercial: Comercial

View File

@ -35,7 +35,7 @@ export default {
name: 'InvoiceOutGlobal', name: 'InvoiceOutGlobal',
meta: { meta: {
title: 'globalInvoicing', title: 'globalInvoicing',
icon: 'contact_support', icon: 'Date_Range',
}, },
component: () => import('src/pages/InvoiceOut/InvoiceOutGlobal.vue'), component: () => import('src/pages/InvoiceOut/InvoiceOutGlobal.vue'),
}, },

View File

@ -237,7 +237,7 @@ export const useInvoiceOutGlobalStore = defineStore({
async getNegativeBasesCsv() { async getNegativeBasesCsv() {
try { try {
const arrayData = useArrayData('InvoiceOutNegative'); const arrayData = useArrayData('InvoiceOutNegative');
const params = arrayData.store.currentFilter; const params = arrayData.store.userParams;
Review

Debido al cambio en el uso de arrayData en negativeBases la store deja de tener currentFitler, y le pasa los userParams declarados

Debido al cambio en el uso de arrayData en negativeBases la store deja de tener currentFitler, y le pasa los userParams declarados
const { data } = await axios.get('InvoiceOuts/negativeBasesCsv', { const { data } = await axios.get('InvoiceOuts/negativeBasesCsv', {
params, params,