diff --git a/src/components/ui/VnConfirm.vue b/src/components/ui/VnConfirm.vue index 4fa374b62..ec9d0f48b 100644 --- a/src/components/ui/VnConfirm.vue +++ b/src/components/ui/VnConfirm.vue @@ -30,10 +30,10 @@ const props = defineProps({ }, }); -defineEmits(['confirm', ...useDialogPluginComponent.emits]); -defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() }); +const emit = defineEmits(['confirm', 'cancel', ...useDialogPluginComponent.emits]); -const { dialogRef, onDialogOK } = useDialogPluginComponent(); +const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = + useDialogPluginComponent(); const title = props.title || t('Confirm'); const message = @@ -53,9 +53,14 @@ async function confirm() { } onDialogOK(props.data); } + +function cancel() { + emit('cancel'); + onDialogCancel(); +} - + {{ title }} - + @@ -81,7 +93,7 @@ async function confirm() { color="primary" :disable="isLoading" flat - v-close-popup + @click="cancel()" /> { v-model="data.isExcludedFromAvailable" :label="t('entry.basicData.excludedFromAvailable')" /> - (data.value = useCardDescription(entity.supplier?.nickname, entity.id)); -const currentEntry = computed(() => state.get('entry')); - const getEntryRedirectionFilter = (entry) => { let entryTravel = entry && entry.travel; @@ -133,10 +136,10 @@ watch; :value="entity.travel?.warehouseOut?.name" /> - + {{ t('Inventory entry') }} - {{ t('Virtual entry') }} + + {{ + t('globals.raid', { + daysInForward: entity?.travel?.daysInForward, + }) + }} diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index c2f1e6b57..62e13551a 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -259,11 +259,6 @@ const fetchEntryBuys = async () => { v-model="entry.isBooked" :disable="true" /> - [ + { + name: 'status', + columnFilter: false, + }, { align: 'left', label: t('entry.list.tableVisibleColumns.id'), @@ -154,27 +158,8 @@ const columns = computed(() => [ cardVisible: true, }, { - align: 'left', label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), name: 'isExcludedFromAvailable', - chip: { - color: null, - condition: (value) => value, - icon: 'vn:inventory', - }, - columnFilter: { - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.list.tableVisibleColumns.isRaid'), - name: 'isRaid', - chip: { - color: null, - condition: (value) => value, - icon: 'vn:net', - }, columnFilter: { inWhere: true, }, @@ -225,6 +210,26 @@ onMounted(async () => { auto-load :right-search="false" > + + + + {{ + t('entry.list.tableVisibleColumns.isExcludedFromAvailable') + }} + + + + {{ + t('globals.raid', { daysInForward: row.daysInForward }) + }} + + + {{ row.supplierName }} diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index f9dbd0589..cd5113d84 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -1,7 +1,6 @@ entryList: list: inventoryEntry: Inventory entry - virtualEntry: Virtual entry entryFilter: filter: search: General search diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index feeea1fc9..3007c5d44 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -4,7 +4,6 @@ You can search by entry reference: Puedes buscar por referencia de la entrada entryList: list: inventoryEntry: Es inventario - virtualEntry: Es una redada entryFilter: filter: search: Búsqueda general diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 8b84a0463..7529d4908 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -2,6 +2,7 @@ import { onMounted, ref, computed, onUnmounted, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter, useRoute } from 'vue-router'; +import { useQuasar } from 'quasar'; import FetchData from 'components/FetchData.vue'; import FetchedTags from 'components/ui/FetchedTags.vue'; @@ -23,6 +24,7 @@ import useNotify from 'src/composables/useNotify.js'; import axios from 'axios'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; +import VnConfirm from 'src/components/ui/VnConfirm.vue'; const route = useRoute(); const router = useRouter(); @@ -32,7 +34,7 @@ const { notify } = useNotify(); const { openConfirmationModal } = useVnConfirm(); const editPriceProxyRef = ref(null); const stateBtnDropdownRef = ref(null); - +const quasar = useQuasar(); const arrayData = useArrayData('ticketData'); const { store } = arrayData; const selectedRows = ref([]); @@ -51,6 +53,7 @@ const transfer = ref({ sales: [], }); const tableRef = ref([]); +const canProceed = ref(); watch( () => route.params.id, @@ -214,7 +217,9 @@ const addSale = async (sale) => { } }; -const changeQuantity = (sale) => { +const changeQuantity = async (sale) => { + canProceed.value = await isSalePrepared(sale); + if (!canProceed.value) return; if ( !sale.itemFk || sale.quantity == null || @@ -226,6 +231,8 @@ const changeQuantity = (sale) => { }; const updateConcept = async (sale) => { + canProceed.value = await isSalePrepared(sale); + if (!canProceed.value) return; try { const data = { newConcept: sale.concept }; await axios.post(`Sales/${sale.id}/updateConcept`, data); @@ -286,6 +293,8 @@ const onOpenEditDiscountPopover = async (sale) => { }; const updatePrice = async (sale) => { + canProceed.value = await isSalePrepared(sale); + if (!canProceed.value) return; try { const newPrice = edit.value.price; if (newPrice != null && newPrice != sale.price) { @@ -300,12 +309,18 @@ const updatePrice = async (sale) => { } }; -const changeDiscount = (sale) => { +const changeDiscount = async (sale) => { + canProceed.value = await isSalePrepared(sale); + if (!canProceed.value) return; const newDiscount = edit.value.discount; if (newDiscount != null && newDiscount != sale.discount) updateDiscount([sale]); }; const updateDiscount = async (sales, newDiscount = null) => { + for (const sale of sales) { + const canProceed = await isSalePrepared(sale); + if (!canProceed) return; + } const saleIds = sales.map((sale) => sale.id); const _newDiscount = newDiscount || edit.value.discount; const params = { @@ -433,7 +448,9 @@ onUnmounted(() => (stateStore.rightDrawer = false)); const items = ref([]); const newRow = ref({}); -const updateItem = (row) => { +const updateItem = async (row) => { + canProceed.value = await isSalePrepared(row); + if (!canProceed.value) return; const selectedItem = items.value.find((item) => item.id === row.itemFk); if (selectedItem) { row.item = selectedItem; @@ -476,6 +493,55 @@ const endNewRow = (row) => { } }; +async function isSalePrepared(item) { + const filter = { + params: { + where: { ticketFk: route.params.id }, + order: ['concept ASC', 'quantity DESC'], + }, + }; + const { data } = await axios.get(`SaleTrackings/${route.params.id}/filter`, { + params: { + filter: JSON.stringify(filter), + }, + }); + + const matchingSale = data.find((sale) => sale.itemFk === item.itemFk); + if (!matchingSale) { + return true; + } + + if ( + matchingSale.hasSaleGroupDetail || + matchingSale.isControled || + matchingSale.isPrepared || + matchingSale.isPrevious || + matchingSale.isPreviousSelected + ) { + try { + await new Promise((resolve, reject) => { + quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('Item prepared'), + message: t( + 'This item is already prepared. Do you want to continue?' + ), + data: item, + }, + }) + .onOk(() => resolve(true)) + .onCancel(() => reject(new Error('cancelled'))); + }); + } catch (error) { + tableRef.value.reload(); + return false; + } + } + return true; +} + watch( () => newRow.value.itemFk, (newItemFk) => { @@ -821,4 +887,6 @@ es: You are going to delete lines of the ticket: Vas a eliminar lineas del ticket Add item: Añadir artículo Transfer lines: Transferir líneas + Item prepared: Artículo preparado + This item is already prepared. Do you want to continue?: Este artículo ya esta preparado. Desea continuar? diff --git a/src/pages/Travel/Card/TravelBasicData.vue b/src/pages/Travel/Card/TravelBasicData.vue index a3620a6ba..d6245e655 100644 --- a/src/pages/Travel/Card/TravelBasicData.vue +++ b/src/pages/Travel/Card/TravelBasicData.vue @@ -72,6 +72,16 @@ const agenciesOptions = ref([]); + + + + {{ t('raidDays') }} + + + + + +es: + raidDays: Al rellenarlo, generamos una redada. Indica los días que un travel se moverá automáticamente en el tiempo +en: + raidDays: When filling, a raid is generated. Enter the number of days the travel will automatically forward in time + diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue index bda29903b..6025ad045 100644 --- a/src/pages/Travel/Card/TravelDescriptor.vue +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -32,6 +32,7 @@ const filter = { 'warehouseOutFk', 'cargoSupplierFk', 'agencyModeFk', + 'daysInForward', ], include: [ { @@ -77,6 +78,22 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. + + + + + {{ + t('globals.raid', { daysInForward: entity.daysInForward }) + }} + + + `#/travel/${entityId.value}/${param}`; + diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index a8c0e69cb..334640bff 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -45,6 +45,10 @@ const redirectCreateEntryView = (travelData) => { }; const columns = computed(() => [ + { + name: 'status', + columnFilter: false, + }, { align: 'left', name: 'id', @@ -221,6 +225,17 @@ const columns = computed(() => [ :is-editable="false" :use-model="true" > + + + + + {{ + t('globals.raid', { daysInForward: row.daysInForward }) + }} + + + { + let wrapper; + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockImplementation(() => ({ data: [] })); + wrapper = createWrapper(TicketAdvance); + vm = wrapper.vm; + vi.spyOn(vm.vnTableRef, 'reload').mockImplementation(() => vi.fn()); + vm.vnTableRef.value = { params: {} }; + }); + beforeEach(() => { + Notify.create = vi.fn(); + }); + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('requestComponentUpdate()', () => { + const mockTicket = { + futureId: 1, + futureClientFk: 1, + nickname: 'test', + futureAddressFk: 1, + futureAgencyModeFk: 1, + futureWarehouseFk: 1, + futureCompanyFk: 1, + landed: '2023-01-02', + zoneFk: 1, + }; + const mockParams = { + clientFk: 1, + nickname: 'test', + agencyModeFk: 1, + addressFk: 1, + zoneFk: 1, + warehouseFk: 1, + companyFk: 1, + landed: '2023-01-02', + shipped: '2023-01-01', + isDeleted: false, + isWithoutNegatives: false, + newTicket: undefined, + keepPrice: true, + }; + const queryResult = 'tickets/1/componentUpdate'; + + it('should return query and params when ticket has no landed', async () => { + vm.vnTableRef.params.dateToAdvance = '2023-01-01'; + await nextTick(); + + const mockLanded = { landed: '2023-01-02', zoneFk: 1 }; + vi.spyOn(vm, 'getLanded').mockResolvedValue(mockLanded); + + const { query, params } = await vm.requestComponentUpdate(mockTicket, false); + + expect(query).toBe(queryResult); + expect(params).toEqual(mockParams); + }); + + it('should return query and params when ticket has landed', async () => { + const { query, params } = await vm.requestComponentUpdate(mockTicket, false); + + expect(query).toBe(queryResult); + expect(params).toEqual(mockParams); + }); + }); + + describe('moveTicketsAdvance()', () => { + it('should move tickets and notify success', async () => { + const tickets = [ + { + id: 1, + futureId: 2, + futureShipped: '2023-01-01', + shipped: '2023-01-02', + workerFk: 1, + }, + { + id: 2, + futureId: 3, + futureShipped: '2023-01-01', + shipped: '2023-01-02', + workerFk: 1, + }, + ]; + vm.selectedTickets = tickets; + vi.spyOn(axios, 'post').mockResolvedValue({}); + await vm.moveTicketsAdvance(); + + expect(axios.post).toHaveBeenCalledOnce('Tickets/merge', { + tickets: [ + { + originId: 2, + destinationId: 1, + originShipped: '2023-01-01', + destinationShipped: '2023-01-02', + workerFk: 1, + }, + { + originId: 3, + destinationId: 2, + originShipped: '2023-01-01', + destinationShipped: '2023-01-02', + workerFk: 1, + }, + ], + }); + expect(vm.vnTableRef.reload).toHaveBeenCalled(); + expect(Notify.create).toHaveBeenCalled(); + expect(vm.selectedTickets).toEqual([]); + }); + }); +}); diff --git a/test/vitest/helper.js b/test/vitest/helper.js index e201535ec..4bfae5dc8 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -70,8 +70,10 @@ class FormDataMock { vi.fn(); } } + global.FormData = FormDataMock; global.URL = class URL {}; +global.Date.vnNew = () => new Date(Date.UTC(2001, 0, 1, 11)); export function createWrapper(component, options) { const defaultOptions = {