From 6ea9ffa7393d9c5fe760afcb6a6a0aa51462446b Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 15:14:59 +0100 Subject: [PATCH] feat: updates --- src/pages/Item/components/ItemProposal.vue | 10 +- .../Item/components/ItemProposalProxy.vue | 11 +- src/pages/Ticket/Card/TicketSummary.vue | 94 +----- .../Card/components/TicketSaleTable.vue | 112 +++++++ .../Card/components/TicketSaleTableDialog.vue | 42 +++ .../Ticket/Negative/TicketLackDetail.vue | 3 +- .../Negative/components/HandleSplitDialog.vue | 52 +++- .../Negative/components/HandleSplited.vue | 281 ------------------ 8 files changed, 222 insertions(+), 383 deletions(-) create mode 100644 src/pages/Ticket/Card/components/TicketSaleTable.vue create mode 100644 src/pages/Ticket/Card/components/TicketSaleTableDialog.vue delete mode 100644 src/pages/Ticket/Negative/components/HandleSplited.vue diff --git a/src/pages/Item/components/ItemProposal.vue b/src/pages/Item/components/ItemProposal.vue index 57d4a1d21..7b6128859 100644 --- a/src/pages/Item/components/ItemProposal.vue +++ b/src/pages/Item/components/ItemProposal.vue @@ -6,9 +6,10 @@ import { toCurrency } from 'filters/index'; import VnStockValueDisplay from 'src/components/ui/VnStockValueDisplay.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import axios from 'axios'; -import notifyResults from 'src/utils/notifyResults'; +import { showResultsAsTable } from 'src/composables/showResultsTable'; import FetchData from 'components/FetchData.vue'; - +const { openTable } = showResultsAsTable(); +import HandleSplitDialog from 'src/pages/Ticket/Negative/components/HandleSplitDialog.vue'; const MATCH = 'match'; const { t } = useI18n(); @@ -200,9 +201,10 @@ const isSelectionAvailable = (itemProposal) => { async function change({ itemFk: substitutionFk }) { try { - const promises = $props.sales.map(({ saleFk, quantity }) => { + const promises = $props.sales.map(({ saleFk, quantity, ticketFk }) => { const params = { saleFk, + ticketFk, substitutionFk, quantity, }; @@ -210,7 +212,7 @@ async function change({ itemFk: substitutionFk }) { }); const results = await Promise.allSettled(promises); - notifyResults(results, 'saleFk'); + openTable(HandleSplitDialog, results, 'ticketFk', 'changeItem'); emit('itemReplaced', { type: 'refresh', itemProposal: proposalSelected.value[0], diff --git a/src/pages/Item/components/ItemProposalProxy.vue b/src/pages/Item/components/ItemProposalProxy.vue index 2027ad449..fafd50cef 100644 --- a/src/pages/Item/components/ItemProposalProxy.vue +++ b/src/pages/Item/components/ItemProposalProxy.vue @@ -19,12 +19,18 @@ const $props = defineProps({ default: () => [], }, }); -const { dialogRef } = useDialogPluginComponent(); +const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = + useDialogPluginComponent(); const emit = defineEmits(['onDialogClosed', ...useDialogPluginComponent.emits]); defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() }); </script> <template> - <QDialog ref="dialogRef" transition-show="scale" transition-hide="scale"> + <QDialog + ref="dialogRef" + transition-show="scale" + transition-hide="scale" + @hide="onDialogHide" + > <QCard class="dialog-width"> <QCardSection class="row items-center q-pb-none"> <span class="text-h6 text-grey">{{ $t('Item proposal') }}</span> @@ -36,6 +42,7 @@ defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.h v-bind="$props" @item-replaced=" () => { + emit('onDialogClosed', true); dialogRef.hide(); } " diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index 5df08b881..d7aeca77f 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -6,7 +6,6 @@ import { useI18n } from 'vue-i18n'; import axios from 'axios'; import { dashIfEmpty, toDate, toCurrency } from 'src/filters'; import CardSummary from 'components/ui/CardSummary.vue'; -import FetchedTags from 'components/ui/FetchedTags.vue'; import InvoiceOutDescriptorProxy from 'pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; @@ -20,8 +19,7 @@ import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; -import TicketProblems from 'src/components/TicketProblems.vue'; - +import TicketSaleTable from './components/TicketSaleTable.vue'; const route = useRoute(); const { notify } = useNotify(); const { t } = useI18n(); @@ -291,95 +289,7 @@ onMounted(async () => { :url="toTicketUrl('sale')" :text="t('ticket.summary.saleLines')" /> - <QTable :rows="entity.sales" style="text-align: center"> - <template #body-cell="{ value }"> - <QTd>{{ value }}</QTd> - </template> - <template #header="props"> - <QTr class="tr-header" :props="props"> - <QTh auto-width></QTh> - <QTh auto-width>{{ t('globals.item') }}</QTh> - <QTh auto-width>{{ t('globals.visible') }}</QTh> - <QTh auto-width>{{ t('ticket.summary.available') }}</QTh> - <QTh auto-width>{{ t('globals.quantity') }}</QTh> - <QTh auto-width>{{ t('globals.description') }}</QTh> - <QTh auto-width>{{ t('globals.price') }}</QTh> - <QTh auto-width>{{ t('ticket.summary.discount') }}</QTh> - <QTh auto-width>{{ t('globals.amount') }}</QTh> - <QTh auto-width>{{ t('ticket.summary.packing') }}</QTh> - </QTr> - </template> - <template #body="props"> - <QTr :props="props"> - <QTd class="q-gutter-x-xs"> - <TicketProblems :row="props.row" /> - </QTd> - <QTd> - <QBtn class="link" flat> - {{ props.row.itemFk }} - <ItemDescriptorProxy - :id="props.row.itemFk" - :sale-fk="props.row.id" - :warehouse-fk="ticket.warehouseFk" - /> - </QBtn> - </QTd> - <QTd> - <QChip - v-if="props.row.visible < 0" - dense - rounded - :color="'negative'" - text-color="white" - > - {{ props.row.visible }} - </QChip> - <span v-else> - {{ props.row.visible }} - </span> - </QTd> - <QTd> - <QChip - v-if="props.row.available < 0" - dense - rounded - :color="'negative'" - text-color="white" - > - {{ props.row.available }} - </QChip> - <span v-else> - {{ props.row.available }} - </span> - </QTd> - <QTd>{{ props.row.quantity }}</QTd> - <QTd class="description-cell"> - <div class="row full-width justify-between"> - {{ props.row.concept }} - <div v-if="props.row.item.subName" class="subName"> - {{ props.row.item.subName.toUpperCase() }} - </div> - </div> - <FetchedTags - class="fetched-tags" - :item="props.row.item" - ></FetchedTags> - </QTd> - <QTd>{{ props.row.price }} €</QTd> - <QTd>{{ props.row.discount }} %</QTd> - <QTd - >{{ - toCurrency( - props.row.quantity * - props.row.price * - ((100 - props.row.discount) / 100), - ) - }} - </QTd> - <QTd>{{ dashIfEmpty(props.row.item.itemPackingTypeFk) }}</QTd> - </QTr> - </template> - </QTable> + <TicketSaleTable :ticket="ticket" :sales="entity.sales" /> </QCard> <QCard class="vn-max" v-if="ticket.packagings.length != 0"> <VnTitle :url="toTicketUrl('package')" :text="t('globals.packages')" /> diff --git a/src/pages/Ticket/Card/components/TicketSaleTable.vue b/src/pages/Ticket/Card/components/TicketSaleTable.vue new file mode 100644 index 000000000..136ac4e46 --- /dev/null +++ b/src/pages/Ticket/Card/components/TicketSaleTable.vue @@ -0,0 +1,112 @@ +<script setup> +import TicketProblems from 'src/components/TicketProblems.vue'; +import FetchedTags from 'src/components/ui/FetchedTags.vue'; +import { dashIfEmpty, toDate, toCurrency } from 'src/filters'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import { ref } from 'vue'; + +const props = defineProps({ + sales: { + type: Array, + required: true, + }, + ticket: { + type: Object, + required: true, + }, +}); + +const sales = ref(props?.sales); +</script> + +<template> + <QTable v-if="sales" :rows="sales" style="text-align: center"> + <template #body-cell="{ value }"> + <QTd>{{ value }}</QTd> + </template> + <template #header="props"> + <QTr class="tr-header" :props="props"> + <QTh auto-width></QTh> + <QTh auto-width>{{ $t('globals.item') }}</QTh> + <QTh auto-width>{{ $t('globals.visible') }}</QTh> + <QTh auto-width>{{ $t('ticket.summary.available') }}</QTh> + <QTh auto-width>{{ $t('globals.quantity') }}</QTh> + <QTh auto-width>{{ $t('globals.description') }}</QTh> + <QTh auto-width>{{ $t('globals.price') }}</QTh> + <QTh auto-width>{{ $t('ticket.summary.discount') }}</QTh> + <QTh auto-width>{{ $t('globals.amount') }}</QTh> + <QTh auto-width>{{ $t('ticket.summary.packing') }}</QTh> + </QTr> + </template> + <template #body="props"> + <QTr :props="props"> + <QTd class="q-gutter-x-xs"> + <TicketProblems :row="props.row" /> + </QTd> + <QTd> + <QBtn class="link" flat> + {{ props.row.itemFk }} + <ItemDescriptorProxy + :id="props.row.itemFk" + :sale-fk="props.row.id" + :warehouse-fk="$props.ticket.warehouseFk" + /> + </QBtn> + </QTd> + <QTd> + <QChip + v-if="props.row.visible < 0" + dense + rounded + :color="'negative'" + text-color="white" + > + {{ props.row.visible }} + </QChip> + <span v-else> + {{ props.row.visible }} + </span> + </QTd> + <QTd> + <QChip + v-if="props.row.available < 0" + dense + rounded + :color="'negative'" + text-color="white" + > + {{ props.row.available }} + </QChip> + <span v-else> + {{ props.row.available }} + </span> + </QTd> + <QTd>{{ props.row.quantity }}</QTd> + <QTd class="description-cell"> + <div class="row full-width justify-between"> + {{ props.row.concept }} + <div v-if="props.row.item.subName" class="subName"> + {{ props.row.item.subName.toUpperCase() }} + </div> + </div> + <FetchedTags + class="fetched-tags" + :item="props.row.item" + ></FetchedTags> + </QTd> + <QTd>{{ props.row.price }} €</QTd> + <QTd>{{ props.row.discount }} %</QTd> + <QTd + >{{ + toCurrency( + props.row.quantity * + props.row.price * + ((100 - props.row.discount) / 100), + ) + }} + </QTd> + <QTd>{{ dashIfEmpty(props.row.item.itemPackingTypeFk) }}</QTd> + </QTr> + </template> + </QTable> +</template> diff --git a/src/pages/Ticket/Card/components/TicketSaleTableDialog.vue b/src/pages/Ticket/Card/components/TicketSaleTableDialog.vue new file mode 100644 index 000000000..e7b7aeccd --- /dev/null +++ b/src/pages/Ticket/Card/components/TicketSaleTableDialog.vue @@ -0,0 +1,42 @@ +<script setup> +import FetchData from 'src/components/FetchData.vue'; +import { dashIfEmpty, toDate, toCurrency } from 'src/filters'; +import { ref } from 'vue'; +import TicketSaleTable from './TicketSaleTable.vue'; + +const props = defineProps({ + sales: { + type: Array, + required: true, + }, + ticket: { + type: Number, + required: true, + }, +}); + +const sales = ref(props?.sales); +</script> + +<template> + <QDialog ref="dialogRef" full-width data-cy="ticketSaleTableDialog"> + <FetchData + v-if="!sales" + :url="`Tickets/${$props.ticket}/getSales`" + @on-fetch="(data) => (sales = data)" + auto-load + ></FetchData> + <QCard class="q-pa-sm"> + <QCardSection class="row items-center q-pb-none"> + <span class="text-h6 text-grey">{{ + $t('ticket.summary.saleLines') + }}</span> + <QSpace /> + <QBtn icon="close" flat round dense v-close-popup /> + </QCardSection> + <QCardSection> + <TicketSaleTable v-if="sales" :ticket="$props.ticket" :sales="sales" /> + </QCardSection> + </QCard> + </QDialog> +</template> diff --git a/src/pages/Ticket/Negative/TicketLackDetail.vue b/src/pages/Ticket/Negative/TicketLackDetail.vue index 97e19366d..9a1bf3921 100644 --- a/src/pages/Ticket/Negative/TicketLackDetail.vue +++ b/src/pages/Ticket/Negative/TicketLackDetail.vue @@ -68,7 +68,8 @@ const showItemProposal = () => { sales: selectedRows.value, }, }) - .onOk(itemProposalEvt); + .onOk(reload) + .onCancel(reload); }; </script> diff --git a/src/pages/Ticket/Negative/components/HandleSplitDialog.vue b/src/pages/Ticket/Negative/components/HandleSplitDialog.vue index 574ad8d76..de32227cc 100644 --- a/src/pages/Ticket/Negative/components/HandleSplitDialog.vue +++ b/src/pages/Ticket/Negative/components/HandleSplitDialog.vue @@ -4,7 +4,11 @@ import { useI18n } from 'vue-i18n'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import FetchData from 'components/FetchData.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import TicketSaleTable from 'src/pages/Ticket/Card/components/TicketSaleTable.vue'; +import { useQuasar } from 'quasar'; +import TicketSaleTableDialog from '../../Card/components/TicketSaleTableDialog.vue'; +const quasar = useQuasar(); const { t } = useI18n(); const $props = defineProps({ results: { @@ -47,6 +51,12 @@ const columns = computed(() => [ sortable: true, align: 'center', }, + { + name: 'actions', + label: '', + sortable: false, + align: 'right', + }, ]); function getState(value) { return states.value.find((state) => state.id === value)?.name; @@ -54,6 +64,9 @@ function getState(value) { function getTicketRejected(row) { return JSON.parse(row.reason.config.data)?.ticketFk; } +function getTicketFulFilled(row) { + return JSON.parse(row.value.config.data).ticketFk; +} function getIcon(value) { const icons = { fulfilled: { @@ -71,10 +84,24 @@ function getIcon(value) { }; return icons[value]; } + +function openLinesDialog(row) { + const ticket = + row.status === 'fulfilled' + ? (row.value.data[$props.tag] ?? getTicketFulFilled(row)) + : getTicketRejected(row); + quasar.dialog({ + component: TicketSaleTableDialog, + componentProps: { + ticket: ticket, + sales: null, + }, + }); +} </script> <template> - <QDialog ref="dialogRef" @hide="onDialogHide" data-cy="handleSplitDialog"> + <QDialog ref="dialogRef" @hide="onDialogHide" data-cy="handleSplitDialog" persistent> <FetchData v-if="$props.action === 'changeState'" url="States" @@ -136,8 +163,12 @@ function getIcon(value) { > </TicketDescriptorProxy> </span> - <span v-else> - {{ JSON.parse(row.value.config.data).ticketFk }} + <span v-else + >{{ getTicketFulFilled(row) + }}<TicketDescriptorProxy + :id="getTicketFulFilled(row)" + > + </TicketDescriptorProxy> </span> </div> <span v-else> @@ -173,6 +204,21 @@ function getIcon(value) { <div v-else>{{ row.reason.response.data.error.message }}</div> </QTd> </template> + <template #body-cell-actions="props"> + <QTd align="center"> + <div v-if="['changeItem'].includes($props.action)"> + <QIcon + name="list" + size="xs" + color="primary" + class="cursor-pointer" + @click="openLinesDialog(props.row)" + > + <QTooltip>{{ t('ticketList.toLines') }} </QTooltip> + </QIcon> + </div> + </QTd> + </template> </QTable> </QCardSection> </QCard> diff --git a/src/pages/Ticket/Negative/components/HandleSplited.vue b/src/pages/Ticket/Negative/components/HandleSplited.vue deleted file mode 100644 index b2bf94f26..000000000 --- a/src/pages/Ticket/Negative/components/HandleSplited.vue +++ /dev/null @@ -1,281 +0,0 @@ -<script setup> -import { computed, onMounted, ref, toRefs } from 'vue'; -import { useI18n } from 'vue-i18n'; -import axios from 'axios'; -import { useDialogPluginComponent } from 'quasar'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import FetchData from 'components/FetchData.vue'; -import VnPaginate from 'src/components/ui/VnPaginate.vue'; - -import VnInputDate from 'src/components/common/VnInputDate.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -const { t } = useI18n(); -const showSplitDialog = ref(false); -const newState = ref(null); -const resultSplit = ref([]); -const { dialogRef, onDialogHide } = useDialogPluginComponent(); -const $props = defineProps({ - tickets: { - type: Array, - default: () => [], - }, -}); -const tickets = ref($props.tickets ?? []); -const rowBtnDisable = () => - !( - formData.value?.agencyModeFk && - formData.value?.date && - rowsSelected.value.length > 0 - ); -const rowsSelected = ref([]); - -const columns = computed(() => [ - { - name: 'status', - label: t('negative.split.status'), - field: ({ status }) => status, - sortable: true, - }, - { - name: 'ticket', - label: t('negative.split.ticket'), - field: ({ ticket }) => ticket, - sortable: true, - }, - { - name: 'newTicket', - label: t('negative.split.newTicket'), - field: ({ newTicket }) => newTicket, - sortable: true, - }, - { - name: 'message', - label: t('negative.split.message'), - field: ({ message }) => message, - sortable: true, - }, - // { - // name: 'actions', - // align: 'center', - // label: t('negative.split.actions'), - // // style: 'padding-left: 100px', - // // headerStyle: 'padding-left: 100px', - // }, -]); - -const formData = ref({ agencies: [] }); -const handleDateChanged = async () => { - const { data: agencyData } = await axios.get('Agencies/getLanded', { - params: { - addressFk: 123, - agencyModeFk: 8, - warehouseFk: 1, - shipped: '2001-02-08T23:00:00.000Z', - }, - }); - if (!agencyData) formData.value.agencies = []; - const { zoneFk } = agencyData; - const { data: zoneData } = await axios.get('Zones/Includingexpired', { - params: { filter: { fields: ['id', 'name'], where: { id: zoneFk } } }, - }); - formData.value.agencies = zoneData; - if (zoneData.length === 1) formData.value.agencyModeFk = zoneData[0]; - // formData.value.dateChanged = false; -}; -const ticketsSelected = ref([]); -onMounted(() => { - ticketsSelected.value = [...new Set($props.tickets.map(({ ticketFk }) => ticketFk))]; -}); - -const updateState = async () => { - try { - showSplitDialog.value = true; - const rowsToUpdate = $props.tickets.map(({ ticketFk }) => - axios.post(`Tickets/state`, { - ticketFk, - code: newState.value, - }) - ); - await Promise.all(rowsToUpdate); - } catch (err) { - return err; - } finally { - dialogRef.value.hide({ type: 'refresh', refresh: true }); - } -}; - -function getIcon(value) { - const icons = { - split: { - name: 'check_circle', - color: 'secondary', - }, - noSplit: { - name: 'warning', - color: 'primary', - }, - error: { - name: 'close', - color: 'negative', - }, - }; - return icons[value]; -} - -const updateNewTickets = async () => { - tickets.value = $props.tickets.filter((ticket) => ticket.newTicket !== 1000005); - console.log('updateNewTickets'); - rowsSelected.value = []; -}; -</script> - -<template> - <QDialog ref="dialogRef" @hide="onDialogHide" v-model="showSplitDialog"> - <QCard class="q-pa-sm"> - <QCardSection class="row items-center q-pb-none"> - <QAvatar - :icon="icon" - color="primary" - text-color="white" - size="xl" - v-if="icon" - /> - <span class="text-h6 text-grey">{{ - t('negative.detail.modal.handleSplited.title') - }}</span> - <QSpace /> - <QBtn icon="close" flat round dense v-close-popup /> - </QCardSection> - <QCardSection class="row items-center justify-center column items-stretch"> - <Qform> - <VnRow class="row q-gutter-md q-mb-md"> - <VnInputDate - :label="t('Max date')" - v-model="formData.date" - @update:model-value="(evt) => handleDateChanged()" /> - - <VnSelect - :disable="formData.agencies.length < 1" - :label="t('Agency')" - v-model="formData.agencyModeFk" - :options="formData.agencies" - option-label="name" - option-value="id" /> - - <QBtn - icon="save" - :disable="rowBtnDisable()" - color="primary" - flat - rounded - @click="updateNewTickets" - /></VnRow> - </Qform> - <VnPaginate data-key="splitLack" :data="tickets"> - <template #body="{ rows }"> - <QTable - :rows="rows" - :columns="columns" - selection="multiple" - row-key="newTicket" - v-model:selected="rowsSelected" - :no-data-label="t('globals.noResults')" - flat - dense - hide-bottom - auto-load - :rows-per-page-options="[0]" - hide-pagination - :pagination="{ rowsPerPage: null }" - > - <template #header="props"> - <QTr :props="props"> - <QTh></QTh> - <QTh - v-for="col in props.cols" - :key="col.name" - :props="props" - > - {{ t(col.label) }} - </QTh> - </QTr> - </template> - <template #body="props"> - <QTr :props="props"> - <Qtd> - <QCheckbox v-model="props.selected" /> - </Qtd> - <QTd - v-for="col in props.cols" - :key="col.name" - :props="props" - > - <span - v-if=" - ![ - 'status', - 'message', - 'actions', - ].includes(col.name) - " - > - {{ col.value }} - </span> - <span v-if="'status' === col.name"> - <QIcon - :name="`${getIcon(col.value).name}`" - size="xs" - class="cursor-pointer" - :color="getIcon(col.value).color" - > - </QIcon> - </span> - <span v-if="'message' === col.name">message</span> - </QTd></QTr - ></template - > - </QTable></template - > - </VnPaginate> - </QCardSection> - <QCardActions align="right"> - <QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup /> - <QBtn - :label="t('globals.confirm')" - color="primary" - :disable="!newState" - @click="updateState" - unelevated - autofocus - /> </QCardActions - ></QCard> - </QDialog> -</template> - -<style lang="scss" scoped> -.splitRow { - border: 1px solid #ec8916; - border-width: 1px 0 1px 0; -} -.list { - max-height: 100%; - padding: 15px; - width: 100%; -} - -.grid-style-transition { - transition: transform 0.28s, background-color 0.28s; -} - -#true { - background-color: $positive; -} - -#false { - background-color: $negative; -} - -div.q-dialog__inner > div { - max-width: fit-content !important; -} -</style>