diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index fbab06966..2b8639ef7 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -448,6 +448,7 @@ ticket: futureTickets: Future tickets purchaseRequest: Purchase request weeklyTickets: Weekly tickets + saleTracking: Sale tracking list: nickname: Nickname state: State diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index fec78d5e6..218976e76 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -447,6 +447,7 @@ ticket: futureTickets: Tickets a futuro purchaseRequest: Petición de compra weeklyTickets: Tickets programados + saleTracking: Líneas preparadas list: nickname: Alias state: Estado diff --git a/src/pages/Ticket/Card/TicketSaleTracking.vue b/src/pages/Ticket/Card/TicketSaleTracking.vue new file mode 100644 index 000000000..a03f75387 --- /dev/null +++ b/src/pages/Ticket/Card/TicketSaleTracking.vue @@ -0,0 +1,539 @@ +<script setup> +import { ref, computed, nextTick, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; + +import FetchData from 'components/FetchData.vue'; +import FetchedTags from 'components/ui/FetchedTags.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; + +import { dashIfEmpty } from 'src/filters'; +import useNotify from 'src/composables/useNotify.js'; +import { toDateTimeFormat } from 'src/filters/date'; + +import axios from 'axios'; + +const route = useRoute(); +const { t } = useI18n(); +const { notify } = useNotify(); +const saleTrackingTableDialogRef = ref(null); +const itemShelvingSaleDialogRef = ref(null); +const saleTrackingFetchDataRef = ref(null); + +const sales = ref([]); +const saleTrackings = ref([]); +const itemShelvignsSales = ref([]); +const shelvingsOptions = ref([]); +const parkingsOptions = ref([]); +const saleTrackingUrl = computed(() => `SaleTrackings/${route.params.id}/filter`); + +watch( + () => route.params.id, + async () => nextTick(async () => await saleTrackingFetchDataRef.value.fetch()) +); + +const columns = computed(() => [ + { + label: t('ticketSaleTracking.isChecked'), + name: 'isChecked', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.item'), + name: 'item', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.description'), + name: 'description', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.quantity'), + name: 'quantity', + field: 'quantity', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.parking'), + name: 'parking', + field: 'parkingCode', + align: 'left', + sortable: true, + format: (value) => dashIfEmpty(value), + }, + { + label: '', + name: 'actions', + align: 'left', + sortable: true, + }, +]); + +const logTableColumns = computed(() => [ + { + label: t('ticketSaleTracking.quantity'), + name: 'quantity', + field: 'quantity', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.original'), + name: 'original', + field: 'originalQuantity', + align: 'original', + sortable: true, + }, + { + label: t('ticketSaleTracking.worker'), + name: 'worker', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.state'), + name: 'state', + field: 'state', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.created'), + name: 'created', + field: 'created', + align: 'left', + sortable: true, + format: (value) => toDateTimeFormat(value), + }, +]); + +const shelvingsTableColumns = computed(() => [ + { + label: t('ticketSaleTracking.quantity'), + name: 'quantity', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.worker'), + name: 'worker', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.shelving'), + name: 'shelving', + align: 'original', + sortable: true, + }, + { + label: t('ticketSaleTracking.parking'), + name: 'parking', + align: 'left', + sortable: true, + }, + { + label: t('ticketSaleTracking.created'), + name: 'created', + field: 'created', + align: 'left', + sortable: true, + format: (value) => toDateTimeFormat(value), + }, +]); + +const getSaleTrackings = async (sale) => { + try { + const filter = { + where: { saleFk: sale.saleFk }, + order: ['itemFk DESC'], + }; + const { data } = await axios.get(`SaleTrackings/listSaleTracking`, { + params: { filter: JSON.stringify(filter) }, + }); + saleTrackings.value = data; + } catch (error) { + console.error(error); + } +}; + +const showLog = async (sale) => { + await getSaleTrackings(sale); + saleTrackingTableDialogRef.value.show(); +}; + +const getItemShelvingSales = async (sale) => { + try { + const filter = { + where: { saleFk: sale.saleFk }, + }; + const { data } = await axios.get(`ItemShelvingSales/filter`, { + params: { filter: JSON.stringify(filter) }, + }); + itemShelvignsSales.value = data; + } catch (error) { + console.error(error); + } +}; + +const showShelving = async (sale) => { + await getItemShelvingSales(sale); + itemShelvingSaleDialogRef.value.show(); +}; + +const updateQuantity = async (sale) => { + try { + const params = { + quantity: sale.quantity, + }; + await axios.patch(`ItemShelvingSales/${sale.id}`, params); + } catch (error) { + console.error(error); + } +}; + +const updateParking = async (sale) => { + try { + const filter = { + fields: ['id'], + where: { + code: sale.shelvingFk, + }, + }; + const { data } = await axios.get(`Shelvings/findOne`, { + params: { filter: JSON.stringify(filter) }, + }); + const params = { + parkingFk: sale.parkingFk, + }; + await axios.patch(`Shelvings/${data.id}`, params); + } catch (error) { + console.error(error); + } +}; + +const updateShelving = async (sale) => { + const params = { + shelvingFk: sale.shelvingFk, + }; + + const { data: patchResponseData } = await axios.patch( + `ItemShelvings/${sale.itemShelvingFk}`, + params + ); + const filter = { + fields: ['parkingFk'], + where: { + code: patchResponseData.shelvingFk, + }, + }; + const { data: getResponseData } = await axios.get(`Shelvings/findOne`, { filter }); + sale.parkingFk = getResponseData.parkingFk; +}; + +const saleTrackingNew = async (sale, stateCode, isChecked) => { + try { + const params = { + saleFk: sale.saleFk, + isChecked: isChecked, + quantity: sale.quantity, + stateCode: stateCode, + }; + await axios.post(`SaleTrackings/new`, params); + notify(t('globals.dataSaved'), 'positive'); + } catch (error) { + console.error(error); + } +}; + +const saleTrackingDel = async (sale, stateCode) => { + try { + const params = { + saleFk: sale.saleFk, + stateCodes: [stateCode], + }; + await axios.post(`SaleTrackings/delete`, params); + notify(t('globals.dataSaved'), 'positive'); + } catch (error) { + console.error(error); + } +}; + +const clickSaleGroupDetail = async (sale) => { + try { + if (!sale.saleGroupDetailFk) return; + + await axios.delete(`SaleGroupDetails/${sale.saleGroupDetailFk}`); + sale.hasSaleGroupDetail = false; + notify(t('globals.dataSaved'), 'positive'); + } catch (error) { + console.error(error); + } +}; + +const clickPreviousSelected = (sale) => { + try { + if (!sale.isPreviousSelected) { + saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false); + sale.isPreviousSelected = true; + } else { + saleTrackingDel(sale, 'PREVIOUS_PREPARATION'); + sale.isPreviousSelected = false; + sale.isPrevious = false; + } + } catch (error) { + console.error(error); + } +}; + +const clickPrevious = (sale) => { + try { + if (!sale.isPrevious) { + saleTrackingNew(sale, 'PREVIOUS_PREPARATION', true); + sale.isPrevious = true; + sale.isPreviousSelected = true; + } else { + saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false); + sale.isPrevious = false; + } + } catch (error) { + console.error(error); + } +}; + +const clickPrepared = (sale) => { + try { + if (!sale.isPrepared) { + saleTrackingNew(sale, 'PREPARED', true); + sale.isPrepared = true; + } else { + saleTrackingDel(sale, 'PREPARED'); + sale.isPrepared = false; + } + } catch (error) { + console.error(error); + } +}; + +const clickControled = (sale) => { + try { + if (!sale.isControled) { + saleTrackingNew(sale, 'CHECKED', true); + sale.isControled = true; + } else { + saleTrackingDel(sale, 'CHECKED'); + sale.isControled = false; + } + } catch (error) { + console.error(error); + } +}; +</script> + +<template> + <FetchData + ref="saleTrackingFetchDataRef" + :url="saleTrackingUrl" + :filter="{ order: ['concept ASC', 'quantity DESC'] }" + auto-load + @on-fetch="(data) => (sales = data)" + /> + <FetchData + url="Shelvings" + auto-load + @on-fetch="(data) => (shelvingsOptions = data)" + /> + <FetchData url="Parkings" auto-load @on-fetch="(data) => (parkingsOptions = data)" /> + <QTable + :rows="sales" + :columns="columns" + row-key="id" + :pagination="{ rowsPerPage: 0 }" + class="full-width q-mt-md" + :no-data-label="t('globals.noResults')" + > + <template #body-cell-isChecked="{ row }"> + <QTd @click.stop> + <QCheckbox + :model-value="!!row.hasSaleGroupDetail" + color="pink" + :toggle-indeterminate="false" + @update:model-value="clickSaleGroupDetail(row)" + > + <QTooltip> + {{ t('ticketSaleTracking.saleGroupDetail') }} + </QTooltip> + </QCheckbox> + <QCheckbox + :model-value="!!row.isPreviousSelected" + color="info" + :toggle-indeterminate="false" + @update:model-value="clickPreviousSelected(row)" + > + <QTooltip> + {{ t('ticketSaleTracking.previousSelected') }} + </QTooltip> + </QCheckbox> + <QCheckbox + :model-value="!!row.isPrevious" + color="cyan" + :toggle-indeterminate="false" + @update:model-value="clickPrevious(row)" + > + <QTooltip> + {{ t('ticketSaleTracking.previous') }} + </QTooltip> + </QCheckbox> + <QCheckbox + :model-value="!!row.isPrepared" + color="warning" + :toggle-indeterminate="false" + @update:model-value="clickPrepared(row)" + > + <QTooltip> + {{ t('ticketSaleTracking.prepared') }} + </QTooltip> + </QCheckbox> + <QCheckbox + :model-value="!!row.isControled" + color="yellow" + :toggle-indeterminate="false" + @update:model-value="clickControled(row)" + > + <QTooltip> + {{ t('ticketSaleTracking.checked') }} + </QTooltip> + </QCheckbox> + </QTd> + </template> + <template #body-cell-item="{ row }"> + <QTd @click.stop> + <div> + <QBtn flat color="primary"> + {{ row.itemFk }} + </QBtn> + <ItemDescriptorProxy :id="row.itemFk" /> + </div> + </QTd> + </template> + <template #body-cell-description="{ row }"> + <QTd class="col"> + <div class="column"> + <span>{{ row.concept }}</span> + <span v-if="row.subName" class="color-vn-label"> + {{ row.subName }} + </span> + <FetchedTags :item="row" :max-length="6" /> + </div> + </QTd> + </template> + <template #body-cell-actions="{ row }"> + <QTd> + <QBtn + @click.stop="showLog(row)" + color="primary" + icon="history" + size="md" + flat + > + <QTooltip class="text-no-wrap"> + {{ t('ticketSaleTracking.historyAction') }} + </QTooltip> + </QBtn> + <QBtn + @click.stop="showShelving(row)" + color="primary" + icon="vn:inventory" + size="md" + flat + > + <QTooltip class="text-no-wrap"> + {{ t('ticketSaleTracking.shelvingAction') }} + </QTooltip> + </QBtn> + </QTd> + </template> + </QTable> + <QDialog + ref="saleTrackingTableDialogRef" + transition-show="scale" + transition-hide="scale" + > + <QTable + data-key="saleTrackingLog" + :rows="saleTrackings" + :columns="logTableColumns" + class="q-pa-sm" + > + <template #body-cell-worker="{ row }"> + <QTd auto-width> + <QBtn flat dense color="primary">{{ row.name }}</QBtn> + <WorkerDescriptorProxy :id="row.workerFk" /> + </QTd> + </template> + </QTable> + </QDialog> + <QDialog + ref="itemShelvingSaleDialogRef" + transition-show="scale" + transition-hide="scale" + > + <QTable + data-key="itemShelvingsSales" + :rows="itemShelvignsSales" + :columns="shelvingsTableColumns" + class="q-pa-sm" + > + <template #body-cell-quantity="{ row }"> + <QTd auto-width> + <VnInput + v-model.number="row.quantity" + @keyup.enter="updateQuantity(row)" + @blur="updateQuantity(row)" + @focus="edit.oldQuantity = row.quantity" + /> + </QTd> + </template> + <template #body-cell-worker="{ row }"> + <QTd auto-width> + <QBtn flat dense color="primary">{{ row.name }}</QBtn> + <WorkerDescriptorProxy :id="row.userFk" /> + </QTd> + </template> + <template #body-cell-shelving="{ row }"> + <QTd auto-width> + <VnSelect + :options="shelvingsOptions" + hide-selected + option-label="code" + option-value="code" + v-model="row.shelvingFk" + @update:model-value="updateShelving(row)" + style="max-width: 120px" + /> + </QTd> + </template> + <template #body-cell-parking="{ row }"> + <QTd auto-width> + <VnSelect + :options="parkingsOptions" + hide-selected + option-label="code" + option-value="id" + v-model="row.parkingFk" + @update:model-value="updateParking(row)" + style="max-width: 120px" + /> + </QTd> + </template> + </QTable> + </QDialog> +</template> diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index 2c648e7f2..2b173eae9 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -136,3 +136,21 @@ weeklyTickets: salesperson: Salesperson search: Search weekly tickets searchInfo: Search weekly tickets by id or client id +ticketSaleTracking: + isChecked: Is checked + item: Item + description: Description + quantity: Quantity + parking: Parking + historyAction: Log states + shelvingAction: Shelvings sale + original: Original + worker: Worker + state: State + created: Created + shelving: Shelving + saleGroupDetail: sale group detail + previousSelected: previous selected + previous: previous + prepared: prepared + checked: checked diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index 3ce4c0545..f5f4d89f5 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -138,3 +138,21 @@ ticketSale: shipped: F. Envío agency: Agencia address: Consignatario +ticketSaleTracking: + isChecked: Comprobado + item: Artículo + description: Descripción + quantity: Cantidad + parking: Parking + historyAction: Historial estados + shelvingAction: Carros línea + original: Original + worker: Trabajador + state: Estado + created: Fecha creación + shelving: Matrícula + saleGroupDetail: detalle grupo líneas + previousSelected: previa seleccionado + previous: previa + prepared: preparado + checked: revisado diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index 81ca405ee..5850458bb 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -19,6 +19,7 @@ export default { 'TicketSale', 'TicketLog', 'TicketPurchaseRequest', + 'TicketSaleTracking', ], }, children: [ @@ -147,6 +148,16 @@ export default { }, component: () => import('src/pages/Ticket/Card/TicketSms.vue'), }, + { + path: 'sale-tracking', + name: 'TicketSaleTracking', + meta: { + title: 'saleTracking', + icon: 'vn:buyrequest', + }, + component: () => + import('src/pages/Ticket/Card/TicketSaleTracking.vue'), + }, ], }, ],