diff --git a/cypress.config.js b/cypress.config.js index 1924144f62..a9e27fcfdc 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -14,8 +14,8 @@ export default defineConfig({ downloadsFolder: 'test/cypress/downloads', video: false, specPattern: 'test/cypress/integration/**/*.spec.js', - experimentalRunAllSpecs: true, - watchForFileChanges: true, + experimentalRunAllSpecs: false, + watchForFileChanges: false, reporter: 'cypress-mochawesome-reporter', reporterOptions: { charts: true, diff --git a/quasar.config.js b/quasar.config.js index 6d545c0265..9467c92af4 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -30,7 +30,6 @@ export default configure(function (/* ctx */) { // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'], - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], diff --git a/src/components/RefundInvoiceForm.vue b/src/components/RefundInvoiceForm.vue index 590acede00..6dcb8b3903 100644 --- a/src/components/RefundInvoiceForm.vue +++ b/src/components/RefundInvoiceForm.vue @@ -9,6 +9,7 @@ import VnSelect from 'components/common/VnSelect.vue'; import FormPopup from './FormPopup.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const $props = defineProps({ invoiceOutData: { @@ -131,15 +132,11 @@ const refund = async () => { :required="true" /> - - - - {{ t('Inherit warehouse tooltip') }} - - + diff --git a/src/components/TransferInvoiceForm.vue b/src/components/TransferInvoiceForm.vue index aa71070d6c..c4ef1454ad 100644 --- a/src/components/TransferInvoiceForm.vue +++ b/src/components/TransferInvoiceForm.vue @@ -10,6 +10,7 @@ import VnSelect from 'components/common/VnSelect.vue'; import FormPopup from './FormPopup.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import VnCheckbox from './common/VnCheckbox.vue'; const $props = defineProps({ invoiceOutData: { @@ -186,15 +187,11 @@ const makeInvoice = async () => { /> - - - - {{ t('transferInvoiceInfo') }} - - + diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 9da79fcd6d..4c9536f61f 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -601,6 +601,7 @@ const checkbox = ref(null); + - - + + + + + {{ info }} + + + diff --git a/src/components/common/VnPopupProxy.vue b/src/components/common/VnPopupProxy.vue new file mode 100644 index 0000000000..f386bfff81 --- /dev/null +++ b/src/components/common/VnPopupProxy.vue @@ -0,0 +1,38 @@ + + + + + + + + + + + + {{ $t($props.tooltip) }} + + + diff --git a/src/components/ui/VnStockValueDisplay.vue b/src/components/ui/VnStockValueDisplay.vue new file mode 100644 index 0000000000..d8f43323bb --- /dev/null +++ b/src/components/ui/VnStockValueDisplay.vue @@ -0,0 +1,41 @@ + + + + + {{ toPercentage(formattedValue) }} + + + + diff --git a/src/composables/useRole.js b/src/composables/useRole.js index 3ec65dd0af..ff54b409cd 100644 --- a/src/composables/useRole.js +++ b/src/composables/useRole.js @@ -27,6 +27,15 @@ export function useRole() { return false; } + function likeAny(roles) { + const roleStore = state.getRoles(); + for (const role of roles) { + if (!roleStore.value.findIndex((rs) => rs.startsWith(role)) !== -1) + return true; + } + + return false; + } function isEmployee() { return hasAny(['employee']); } @@ -35,6 +44,7 @@ export function useRole() { isEmployee, fetch, hasAny, + likeAny, state, }; } diff --git a/src/css/app.scss b/src/css/app.scss index d409ad9dca..6871b303f1 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -232,10 +232,12 @@ input::-webkit-inner-spin-button { max-width: 100%; } -.q-table__container { - /* ===== Scrollbar CSS ===== / - / Firefox */ +.remove-bg { + filter: brightness(1.1); + mix-blend-mode: multiply; +} +.q-table__container { * { scrollbar-width: auto; scrollbar-color: var(--vn-label-color) transparent; diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index 9f215c8b86..48d86e7f58 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -13,7 +13,7 @@ // Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: to add new colors https://quasar.dev/style/color-palette/#adding-your-own-colors $primary: #ec8916; -$secondary: $primary; +$secondary: #89be34; $positive: #c8e484; $negative: #fb5252; $info: #84d0e2; diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 912038102d..44759769a6 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -168,6 +168,7 @@ globals: workCenters: Work centers modes: Modes zones: Zones + negative: Negative zonesList: List deliveryDays: Delivery days upcomingDeliveries: Upcoming deliveries @@ -175,6 +176,7 @@ globals: alias: Alias aliasUsers: Users subRoles: Subroles + myAccount: Mi cuenta inheritedRoles: Inherited Roles customers: Customers customerCreate: New customer diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 2ac80c7ab1..2f8e6c1d19 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -37,6 +37,7 @@ globals: clone: Clonar confirm: Confirmar assign: Asignar + replace: Sustituir back: Volver yes: Si no: No @@ -49,6 +50,7 @@ globals: rowRemoved: Fila eliminada pleaseWait: Por favor espera... noPinnedModules: No has fijado ningún módulo + split: Split summary: basicData: Datos básicos daysOnward: Días adelante @@ -77,8 +79,10 @@ globals: requiredField: Campo obligatorio class: clase type: Tipo - reason: motivo + reason: Motivo + removeSelection: Eliminar selección noResults: Sin resultados + results: resultados system: Sistema notificationSent: Notificación enviada warehouse: Almacén @@ -167,6 +171,7 @@ globals: agency: Agencia workCenters: Centros de trabajo modes: Modos + negative: Tickets negativos zones: Zonas zonesList: Listado deliveryDays: Días de entrega @@ -287,9 +292,9 @@ globals: buyRequest: Peticiones de compra wasteBreakdown: Deglose de mermas itemCreate: Nuevo artículo - tax: 'IVA' - botanical: 'Botánico' - barcode: 'Código de barras' + tax: IVA + botanical: Botánico + barcode: Código de barras itemTypeCreate: Nueva familia family: Familia lastEntries: Últimas entradas @@ -492,6 +497,38 @@ ticket: freightItemName: Nombre packageItemName: Embalaje longName: Descripción + pageTitles: + tickets: Tickets + list: Listado + ticketCreate: Nuevo ticket + summary: Resumen + basicData: Datos básicos + boxing: Encajado + sms: Sms + notes: Notas + sale: Lineas del pedido + dms: Gestión documental + negative: Tickets negativos + volume: Volumen + observation: Notas + ticketAdvance: Adelantar tickets + futureTickets: Tickets a futuro + expedition: Expedición + purchaseRequest: Petición de compra + weeklyTickets: Tickets programados + saleTracking: Líneas preparadas + services: Servicios + tracking: Estados + components: Componentes + pictures: Fotos + packages: Bultos + list: + nickname: Alias + state: Estado + shipped: Enviado + landed: Entregado + salesPerson: Comercial + total: Total card: customerId: ID cliente customerCard: Ficha del cliente @@ -778,8 +815,8 @@ wagon: volumeNotEmpty: El volumen no puede estar vacío typeNotEmpty: El tipo no puede estar vacío maxTrays: Has alcanzado el número máximo de bandejas - minHeightBetweenTrays: 'La distancia mínima entre bandejas es ' - maxWagonHeight: 'La altura máxima del vagón es ' + minHeightBetweenTrays: La distancia mínima entre bandejas es + maxWagonHeight: La altura máxima del vagón es uncompleteTrays: Hay bandejas sin completar params: label: Etiqueta @@ -926,7 +963,7 @@ components: cardDescriptor: mainList: Listado principal summary: Resumen - moreOptions: 'Más opciones' + moreOptions: Más opciones leftMenu: addToPinned: Añadir a fijados removeFromPinned: Eliminar de fijados diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index ab16e07ff2..3b40f42243 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -12,6 +12,7 @@ import VnInputPassword from 'src/components/common/VnInputPassword.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import { useQuasar } from 'quasar'; import { useRouter } from 'vue-router'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const $props = defineProps({ hasAccount: { @@ -121,18 +122,14 @@ onMounted(() => { :promise="sync" > - {{ shouldSyncPassword }} - - - {{ t('account.card.actions.sync.tooltip') }} - + color="primary" + /> { > {{ t('customer.card.isDisabled') }} - + + + {{ t('Allowed substitution') }} + + {{ t('customer.card.isFrozen') }} { .join('&'); useOpenURL(`/#/${type}/list?${params}`); }; +const updateSubstitutionAllowed = async () => { + try { + await axios.patch(`Clients/${route.params.id}`, { + substitutionAllowed: !$props.customer.substitutionAllowed, + }); + notify('globals.notificationSent', 'positive'); + } catch (error) { + notify(error.message, 'positive'); + } +}; @@ -69,6 +79,13 @@ const openCreateForm = (type) => { {{ t('globals.pageTitles.createTicket') }} + + {{ + $props.customer.substitutionAllowed + ? t('Disable substitution') + : t('Allow substitution') + }} + {{ t('Send SMS') }} diff --git a/src/pages/Customer/Card/CustomerFiscalData.vue b/src/pages/Customer/Card/CustomerFiscalData.vue index b256c001a9..bd887acb75 100644 --- a/src/pages/Customer/Card/CustomerFiscalData.vue +++ b/src/pages/Customer/Card/CustomerFiscalData.vue @@ -9,6 +9,7 @@ import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnLocation from 'src/components/common/VnLocation.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const { t } = useI18n(); const route = useRoute(); @@ -110,14 +111,11 @@ function handleLocation(data, location) { - - - - - {{ t('whenActivatingIt') }} - - - + @@ -129,17 +127,11 @@ function handleLocation(data, location) { - - - - - {{ t('inOrderToInvoice') }} - - - + diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index eae97d1be8..21de8fa9bb 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -1,4 +1,3 @@ - + + + + + + + + {{ statusConditionalValue(row) }}% + + + + {{ row.longName }} + + + + + + + {{ row.value5 }} + + + {{ row.value6 }} + + + {{ row.value7 }} + + + {{ row.counter }} + + + {{ row.minQuantity }} + + + + + {{ + toCurrency(row.price2) + }} + + + + + diff --git a/src/pages/Item/components/ItemProposalProxy.vue b/src/pages/Item/components/ItemProposalProxy.vue new file mode 100644 index 0000000000..7da0ce398f --- /dev/null +++ b/src/pages/Item/components/ItemProposalProxy.vue @@ -0,0 +1,56 @@ + + + + + + {{ $t('Item proposal') }} + + + + + { + emit('itemReplaced', data); + dialogRef.hide(); + } + " + > + + + + diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 6d33b46b50..9d27fc96e2 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -131,6 +131,7 @@ item: origin: Orig. userName: Buyer weight: Weight + color: Color weightByPiece: Weight/stem stemMultiplier: Multiplier producer: Producer @@ -216,4 +217,24 @@ item: specie: Specie search: 'Search item' searchInfo: 'You can search by id' - regularizeStock: Regularize stock \ No newline at end of file + regularizeStock: Regularize stock +itemProposal: Items proposal +proposal: + difference: Difference + title: Items proposal + itemFk: Item + longName: Name + subName: Producer + value5: value5 + value6: value6 + value7: value7 + value8: value8 + available: Available + minQuantity: minQuantity + price2: Price + located: Located + counter: Counter + groupingPrice: Grouping Price + itemOldPrice: itemOld Price + status: State + quantityToReplace: Quanity to replace diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 8c8759101c..935f5160bf 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -136,6 +136,7 @@ item: size: Medida origin: Orig. weight: Peso + color: Color weightByPiece: Peso/tallo userName: Comprador stemMultiplier: Multiplicador @@ -221,5 +222,30 @@ item: achieved: 'Conseguido' concept: 'Concepto' state: 'Estado' - search: 'Buscar artículo' - searchInfo: 'Puedes buscar por id' +itemProposal: Artículos similares +proposal: + substitutionAvailable: Sustitución disponible + notSubstitutionAvailableByPrice: Sustitución no disponible, 30% de diferencia por precio o cantidad + compatibility: Compatibilidad + title: Items de sustitución para los tickets seleccionados + itemFk: Item + longName: Nombre + subName: Productor + value5: value5 + value6: value6 + value7: value7 + value8: value8 + available: Disponible + minQuantity: Min. cantidad + price2: Precio + located: Ubicado + counter: Contador + difference: Diferencial + groupingPrice: Precio Grouping + itemOldPrice: Precio itemOld + status: Estado + quantityToReplace: Cantidad a reemplazar + replace: Sustituir + replaceAndConfirm: Sustituir y confirmar precio +search: 'Buscar artículo' +searchInfo: 'Puedes buscar por id' diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue index 6153b2d3ee..1b864de6f8 100644 --- a/src/pages/Order/Card/OrderLines.vue +++ b/src/pages/Order/Card/OrderLines.vue @@ -238,7 +238,7 @@ watch( lineFilter.value.where.orderFk = router.currentRoute.value.params.id; tableLinesRef.value.reload(); - } + }, ); diff --git a/src/pages/Supplier/Card/SupplierFiscalData.vue b/src/pages/Supplier/Card/SupplierFiscalData.vue index e569eb2360..ecee5b76b0 100644 --- a/src/pages/Supplier/Card/SupplierFiscalData.vue +++ b/src/pages/Supplier/Card/SupplierFiscalData.vue @@ -10,6 +10,7 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnLocation from 'src/components/common/VnLocation.vue'; import VnAccountNumber from 'src/components/common/VnAccountNumber.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const route = useRoute(); const { t } = useI18n(); @@ -182,18 +183,11 @@ function handleLocation(data, location) { v-model="data.isTrucker" :label="t('supplier.fiscalData.isTrucker')" /> - - - - - {{ - t( - 'When activating it, do not enter the country code in the ID field.' - ) - }} - - - + @@ -201,6 +195,8 @@ function handleLocation(data, location) { +en: + whenActivatingIt: When activating it, do not enter the country code in the ID field. es: - When activating it, do not enter the country code in the ID field.: Al activarlo, no informar el código del país en el campo nif + whenActivatingIt: Al activarlo, no informar el código del país en el campo nif. diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue index 44f2bf7fb4..055c9a0ffd 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -9,6 +9,7 @@ import FetchData from 'components/FetchData.vue'; import { useStateStore } from 'stores/useStateStore'; import { toCurrency } from 'filters/index'; import { useRole } from 'src/composables/useRole'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const haveNegatives = defineModel('have-negatives', { type: Boolean, required: true }); const formData = defineModel({ type: Object, required: true }); @@ -182,22 +183,19 @@ onMounted(async () => { - - - - {{ t('basicData.withoutNegativesInfo') }} - - diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 97d87ccf88..004bcbe792 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -14,7 +14,7 @@ import VnImg from 'src/components/ui/VnImg.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import TicketSaleMoreActions from './TicketSaleMoreActions.vue'; -import TicketTransfer from './TicketTransfer.vue'; +import TicketTransferProxy from './TicketTransferProxy.vue'; import { toCurrency, toPercentage } from 'src/filters'; import { useArrayData } from 'composables/useArrayData'; @@ -609,8 +609,9 @@ watch( @click="setTransferParams()" data-cy="ticketSaleTransferBtn" > - {{ t('Transfer lines') }} - {{ t('ticketSale.transferLines') }} + +import { ref } from 'vue'; + +import VnInputDate from 'src/components/common/VnInputDate.vue'; +import split from './components/split'; +const emit = defineEmits(['ticketTransfered']); + +const $props = defineProps({ + ticket: { + type: [Array, Object], + default: () => {}, + }, +}); + +const splitDate = ref(Date.vnNew()); + +const splitSelectedRows = async () => { + const tickets = Array.isArray($props.ticket) ? $props.ticket : [$props.ticket]; + await split(tickets, splitDate.value); + emit('ticketTransfered', tickets); +}; + + + + + + + + +es: + Sales to transfer: Líneas a transferir + Destination ticket: Ticket destinatario + diff --git a/src/pages/Ticket/Card/TicketTransfer.vue b/src/pages/Ticket/Card/TicketTransfer.vue index 005d74a0e4..ffa964c920 100644 --- a/src/pages/Ticket/Card/TicketTransfer.vue +++ b/src/pages/Ticket/Card/TicketTransfer.vue @@ -1,11 +1,11 @@ - - - - - - - - - - - handleRowClick(row)" - > - - - - {{ row.nickname }} - {{ row.name }} - {{ row.street }} - {{ row.postalCode }} - {{ row.city }} - - - {{ row.nickname }} - {{ row.name }} - {{ row.street }} - {{ row.postalCode }} - {{ row.city }} - - - + + + + + + + + + handleRowClick(row)" + :no-data-label="t('globals.noResults')" + :pagination="{ rowsPerPage: 0 }" + > + + + + {{ row.nickname }} + {{ row.name }} + {{ row.street }} + {{ row.postalCode }} + {{ row.city }} + + + {{ row.nickname }} + {{ row.name }} + {{ row.street }} + {{ row.postalCode }} + {{ row.city }} + + + - - - - - - - - - + + + + + + + - + es: Sales to transfer: Líneas a transferir Destination ticket: Ticket destinatario - Transfer to ticket: Transferir a ticket - New ticket: Nuevo ticket diff --git a/src/pages/Ticket/Card/TicketTransferProxy.vue b/src/pages/Ticket/Card/TicketTransferProxy.vue new file mode 100644 index 0000000000..3f3f018df3 --- /dev/null +++ b/src/pages/Ticket/Card/TicketTransferProxy.vue @@ -0,0 +1,54 @@ + + + + + + + + + + + + + diff --git a/src/pages/Ticket/Card/components/split.js b/src/pages/Ticket/Card/components/split.js new file mode 100644 index 0000000000..afa1d5cd6e --- /dev/null +++ b/src/pages/Ticket/Card/components/split.js @@ -0,0 +1,22 @@ +import axios from 'axios'; +import notifyResults from 'src/utils/notifyResults'; + +export default async function (data, date) { + const reducedData = data.reduce((acc, item) => { + const existing = acc.find(({ ticketFk }) => ticketFk === item.id); + if (existing) { + existing.sales.push(item.saleFk); + } else { + acc.push({ ticketFk: item.id, sales: [item.saleFk], date }); + } + return acc; + }, []); + + const promises = reducedData.map((params) => axios.post(`Tickets/split`, params)); + + const results = await Promise.allSettled(promises); + + notifyResults(results, 'ticketFk'); + + return results; +} diff --git a/src/pages/Ticket/Negative/TicketLackDetail.vue b/src/pages/Ticket/Negative/TicketLackDetail.vue new file mode 100644 index 0000000000..dcf835d030 --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackDetail.vue @@ -0,0 +1,198 @@ + + + + (editableStates = data)" + auto-load + /> + (item = data)" + auto-load + /> + + + (selectedRows = value)" + > + + + + + + + + {{ t('ticketSale.transferLines') }} + + + + + + + {{ t('itemProposal') }} + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Ticket/Negative/TicketLackFilter.vue b/src/pages/Ticket/Negative/TicketLackFilter.vue new file mode 100644 index 0000000000..3762f453d8 --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackFilter.vue @@ -0,0 +1,175 @@ + + + + (warehouses = data)" auto-load /> + (categoriesOptions = data)" + auto-load + /> + + (itemTypesOptions = data)" + auto-load + /> + + + + + {{ t(`negative.${tag.label}`) }} + {{ formatFn(tag.value) }} + + + + + + + { + setUserParams(params); + } + " + /> + + + + + + + + + + + + + + + + + + onCategoryChange($event, searchFn) + " + :options="categoriesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + /> + + + + + + + + + + {{ scope.opt?.name }} + {{ + scope.opt?.category?.name + }} + + + + + + + + + + + diff --git a/src/pages/Ticket/Negative/TicketLackList.vue b/src/pages/Ticket/Negative/TicketLackList.vue new file mode 100644 index 0000000000..851cf40f48 --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackList.vue @@ -0,0 +1,227 @@ + + + + + + + + + {{ filterRef }} + + + + {{ row.itemFk }} + + + + + {{ row.longName }} + + + + + + + diff --git a/src/pages/Ticket/Negative/TicketLackTable.vue b/src/pages/Ticket/Negative/TicketLackTable.vue new file mode 100644 index 0000000000..c7f224c64e --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackTable.vue @@ -0,0 +1,362 @@ + + + + (itemLack = data[0])" + auto-load + /> + (item = data)" + auto-load + /> + + + + + + + + + + + {{ item?.longName ?? item.name }} + + + + + + + + + + + + + + + {{ t('negative.detail.isBasket') }} + + + {{ t('negative.detail.hasToIgnore') }} + + + {{ + t('negative.detail.hasObservation') + }} + {{ t('negative.detail.isRookie') }} + + + {{ t('negative.detail.peticionCompra') }} + + + {{ t('negative.detail.turno') }} + + + + + + {{ row.nickname }} + + + + + + {{ row.id }} + + + + + + + + + {{ row.zoneName }} + + + + + + + + diff --git a/src/pages/Ticket/Negative/components/ChangeItemDialog.vue b/src/pages/Ticket/Negative/components/ChangeItemDialog.vue new file mode 100644 index 0000000000..e419b85c08 --- /dev/null +++ b/src/pages/Ticket/Negative/components/ChangeItemDialog.vue @@ -0,0 +1,90 @@ + + + + + + {{ showChangeItemDialog }} + {{ $t('negative.detail.modal.changeItem.title') }} + + + + + + + + + diff --git a/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue b/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue new file mode 100644 index 0000000000..96cbd213db --- /dev/null +++ b/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue @@ -0,0 +1,84 @@ + + + + + + {{ $t('negative.detail.modal.changeQuantity.title') }} + + + + + + + + diff --git a/src/pages/Ticket/Negative/components/ChangeStateDialog.vue b/src/pages/Ticket/Negative/components/ChangeStateDialog.vue new file mode 100644 index 0000000000..1acc7e0ef6 --- /dev/null +++ b/src/pages/Ticket/Negative/components/ChangeStateDialog.vue @@ -0,0 +1,91 @@ + + + + (editableStates = data)" + auto-load + /> + + + {{ $t('negative.detail.modal.changeState.title') }} + + + + + + + + diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index d4bfd11036..cdbb22d9be 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -23,6 +23,8 @@ ticketSale: hasComponentLack: Component lack ok: Ok more: More + transferLines: Transfer lines(no basket)/ Split + transferBasket: Some row selected is basket advanceTickets: preparation: Preparation origin: Origin @@ -188,7 +190,6 @@ ticketList: accountPayment: Account payment sendDocuware: Set delivered and send delivery note(s) to the tablet addPayment: Add payment - date: Date company: Company amount: Amount reference: Reference @@ -202,8 +203,6 @@ ticketList: creditCard: Credit card transfers: Transfers province: Province - warehouse: Warehouse - hour: Hour closure: Closure toLines: Go to lines addressNickname: Address nickname @@ -214,3 +213,79 @@ ticketList: notVisible: Not visible clientFrozen: Client frozen componentLack: Component lack +negative: + hour: Hour + id: Id Article + longName: Article + supplier: Supplier + colour: Colour + size: Size + origen: Origin + value: Negative + itemFk: Article + producer: Producer + warehouse: Warehouse + warehouseFk: Warehouse + category: Category + categoryFk: Family + type: Type + typeFk: Type + lack: Negative + inkFk: inkFk + timed: timed + date: Date + minTimed: minTimed + negativeAction: Negative + totalNegative: Total negatives + days: Days + buttonsUpdate: + item: Item + state: State + quantity: Quantity + modalOrigin: + title: Update negatives + question: Select a state to update + modalSplit: + title: Confirm split selected + question: Select a state to update + detail: + saleFk: Sale + itemFk: Article + ticketFk: Ticket + code: Code + nickname: Alias + name: Name + zoneName: Agency name + shipped: Date + theoreticalhour: Theoretical hour + agName: Agency + quantity: Quantity + alertLevelCode: Group state + state: State + peticionCompra: Ticket request + isRookie: Is rookie + turno: Turn line + isBasket: Basket + hasObservation: Has substitution + hasToIgnore: VIP + modal: + changeItem: + title: Update item reference + placeholder: New item + changeState: + title: Update tickets state + placeholder: New state + changeQuantity: + title: Update tickets quantity + placeholder: New quantity + split: + title: Are you sure you want to split selected tickets? + subTitle: Confirm split action + handleSplited: + title: Handle splited tickets + subTitle: Confirm date and agency + split: + ticket: Old ticket + newTicket: New ticket + status: Result + message: Message diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index ff68461fad..75d3c6a2b7 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -127,6 +127,8 @@ ticketSale: ok: Ok more: Más address: Consignatario + transferLines: Transferir líneas(no cesta)/ Separar + transferBasket: No disponible para una cesta size: Medida ticketComponents: serie: Serie @@ -213,6 +215,81 @@ ticketList: toLines: Ir a lineas addressNickname: Alias consignatario ref: Referencia +negative: + hour: Hora + id: Id Articulo + longName: Articulo + supplier: Productor + colour: Color + size: Medida + origen: Origen + value: Negativo + warehouseFk: Almacen + producer: Producer + category: Categoría + categoryFk: Familia + typeFk: Familia + warehouse: Almacen + lack: Negativo + inkFk: Color + timed: Hora + date: Fecha + minTimed: Hora + type: Tipo + negativeAction: Negativo + totalNegative: Total negativos + days: Rango de dias + buttonsUpdate: + item: artículo + state: Estado + quantity: Cantidad + modalOrigin: + title: Actualizar negativos + question: Seleccione un estado para guardar + modalSplit: + title: Confirmar acción de split + question: Selecciona un estado + detail: + saleFk: Línea + itemFk: Artículo + ticketFk: Ticket + code: code + nickname: Alias + name: Nombre + zoneName: Agencia + shipped: F. envío + theoreticalhour: Hora teórica + agName: Agencia + quantity: Cantidad + alertLevelCode: Estado agrupado + state: Estado + peticionCompra: Petición compra + isRookie: Cliente nuevo + turno: Linea turno + isBasket: Cesta + hasObservation: Tiene sustitución + hasToIgnore: VIP + modal: + changeItem: + title: Actualizar referencia artículo + placeholder: Nuevo articulo + changeState: + title: Actualizar estado + placeholder: Nuevo estado + changeQuantity: + title: Actualizar cantidad + placeholder: Nueva cantidad + split: + title: ¿Seguro de separar los tickets seleccionados? + subTitle: Confirma separar tickets seleccionados + handleSplited: + title: Gestionar tickets spliteados + subTitle: Confir fecha y agencia + split: + ticket: Ticket viejo + newTicket: Ticket nuevo + status: Estado + message: Mensaje rounding: Redondeo noVerifiedData: Sin datos comprobados purchaseRequest: Petición de compra diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index e5b423f64d..bfcb78787e 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -192,7 +192,13 @@ export default { icon: 'vn:ticket', moduleName: 'Ticket', keyBinding: 't', - menu: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], + menu: [ + 'TicketList', + 'TicketAdvance', + 'TicketWeekly', + 'TicketFuture', + 'TicketNegative', + ], }, component: RouterView, redirect: { name: 'TicketMain' }, @@ -229,6 +235,32 @@ export default { }, component: () => import('src/pages/Ticket/TicketCreate.vue'), }, + { + path: 'negative', + redirect: { name: 'TicketNegative' }, + children: [ + { + name: 'TicketNegative', + meta: { + title: 'negative', + icon: 'exposure', + }, + component: () => + import('src/pages/Ticket/Negative/TicketLackList.vue'), + path: '', + }, + { + name: 'NegativeDetail', + path: ':id', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => + import('src/pages/Ticket/Negative/TicketLackDetail.vue'), + }, + ], + }, { path: 'weekly', name: 'TicketWeekly', diff --git a/src/utils/notifyResults.js b/src/utils/notifyResults.js new file mode 100644 index 0000000000..e87ad6c6f7 --- /dev/null +++ b/src/utils/notifyResults.js @@ -0,0 +1,19 @@ +import { Notify } from 'quasar'; + +export default function (results, key) { + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + const data = JSON.parse(result.value.config.data); + Notify.create({ + type: 'positive', + message: `Operación (${index + 1}) ${data[key]} completada con éxito.`, + }); + } else { + const data = JSON.parse(result.reason.config.data); + Notify.create({ + type: 'negative', + message: `Operación (${index + 1}) ${data[key]} fallida: ${result.reason.message}`, + }); + } + }); +} diff --git a/test/cypress/integration/item/ItemProposal.spec.js b/test/cypress/integration/item/ItemProposal.spec.js new file mode 100644 index 0000000000..b3ba9f6764 --- /dev/null +++ b/test/cypress/integration/item/ItemProposal.spec.js @@ -0,0 +1,11 @@ +/// +describe('ItemProposal', () => { + beforeEach(() => { + const ticketId = 1; + + cy.login('developer'); + cy.visit(`/#/ticket/${ticketId}/summary`); + }); + + describe('Handle item proposal selected', () => {}); +}); diff --git a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js new file mode 100644 index 0000000000..9ea1cff631 --- /dev/null +++ b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js @@ -0,0 +1,147 @@ +/// +describe('Ticket Lack detail', () => { + beforeEach(() => { + cy.login('developer'); + cy.intercept('GET', /\/api\/Tickets\/itemLack\/5.*$/, { + statusCode: 200, + body: [ + { + saleFk: 33, + code: 'OK', + ticketFk: 142, + nickname: 'Malibu Point', + shipped: '2000-12-31T23:00:00.000Z', + hour: 0, + quantity: 50, + agName: 'Super-Man delivery', + alertLevel: 0, + stateName: 'OK', + stateId: 3, + itemFk: 5, + price: 1.79, + alertLevelCode: 'FREE', + zoneFk: 9, + zoneName: 'Zone superMan', + theoreticalhour: '2011-11-01T22:59:00.000Z', + isRookie: 1, + turno: 1, + peticionCompra: 1, + hasObservation: 1, + hasToIgnore: 1, + isBasket: 1, + minTimed: 0, + customerId: 1104, + customerName: 'Tony Stark', + observationTypeCode: 'administrative', + }, + ], + }).as('getItemLack'); + + cy.visit('/#/ticket/negative/5'); + cy.wait('@getItemLack'); + }); + describe('Table actions', () => { + it.skip('should display only one row in the lack list', () => { + cy.location('href').should('contain', '#/ticket/negative/5'); + + cy.get('[data-cy="changeItem"]').should('be.disabled'); + cy.get('[data-cy="changeState"]').should('be.disabled'); + cy.get('[data-cy="changeQuantity"]').should('be.disabled'); + cy.get('[data-cy="itemProposal"]').should('be.disabled'); + cy.get('[data-cy="transferLines"]').should('be.disabled'); + cy.get('tr.cursor-pointer > :nth-child(1)').click(); + cy.get('[data-cy="changeItem"]').should('be.enabled'); + cy.get('[data-cy="changeState"]').should('be.enabled'); + cy.get('[data-cy="changeQuantity"]').should('be.enabled'); + cy.get('[data-cy="itemProposal"]').should('be.enabled'); + cy.get('[data-cy="transferLines"]').should('be.enabled'); + }); + }); + describe('Item proposal', () => { + beforeEach(() => { + cy.get('tr.cursor-pointer > :nth-child(1)').click(); + + cy.intercept('GET', /\/api\/Items\/getSimilar\?.*$/, { + statusCode: 200, + body: [ + { + id: 1, + longName: 'Ranged weapon longbow 50cm', + subName: 'Stark Industries', + tag5: 'Color', + value5: 'Brown', + match5: 0, + match6: 0, + match7: 0, + match8: 1, + tag6: 'Categoria', + value6: '+1 precission', + tag7: 'Tallos', + value7: '1', + tag8: null, + value8: null, + available: 20, + calc_id: 6, + counter: 0, + minQuantity: 1, + visible: null, + price2: 1, + }, + { + id: 2, + longName: 'Ranged weapon longbow 100cm', + subName: 'Stark Industries', + tag5: 'Color', + value5: 'Brown', + match5: 0, + match6: 1, + match7: 0, + match8: 1, + tag6: 'Categoria', + value6: '+1 precission', + tag7: 'Tallos', + value7: '1', + tag8: null, + value8: null, + available: 50, + calc_id: 6, + counter: 1, + minQuantity: 5, + visible: null, + price2: 10, + }, + { + id: 3, + longName: 'Ranged weapon longbow 200cm', + subName: 'Stark Industries', + tag5: 'Color', + value5: 'Brown', + match5: 1, + match6: 1, + match7: 1, + match8: 1, + tag6: 'Categoria', + value6: '+1 precission', + tag7: 'Tallos', + value7: '1', + tag8: null, + value8: null, + available: 185, + calc_id: 6, + counter: 10, + minQuantity: 10, + visible: null, + price2: 100, + }, + ], + }).as('getItemGetSimilar'); + cy.get('[data-cy="itemProposal"]').click(); + cy.wait('@getItemGetSimilar'); + }); + describe('Replace item if', () => { + it.only('Quantity is less than available', () => { + cy.get(':nth-child(1) > .text-right > .q-btn').click(); + }); + }); + }); +}); diff --git a/test/cypress/integration/ticket/negative/TicketLackList.spec.js b/test/cypress/integration/ticket/negative/TicketLackList.spec.js new file mode 100644 index 0000000000..01ab4f621d --- /dev/null +++ b/test/cypress/integration/ticket/negative/TicketLackList.spec.js @@ -0,0 +1,36 @@ +/// +describe('Ticket Lack list', () => { + beforeEach(() => { + cy.login('developer'); + cy.intercept('GET', /Tickets\/itemLack\?.*$/, { + statusCode: 200, + body: [ + { + itemFk: 5, + longName: 'Ranged weapon pistol 9mm', + warehouseFk: 1, + producer: null, + size: 15, + category: null, + warehouse: 'Warehouse One', + lack: -50, + inkFk: 'SLV', + timed: '2025-01-25T22:59:00.000Z', + minTimed: '23:59', + originFk: 'Holand', + }, + ], + }).as('getLack'); + + cy.visit('/#/ticket/negative'); + }); + + describe('Table actions', () => { + it('should display only one row in the lack list', () => { + cy.wait('@getLack', { timeout: 10000 }); + + cy.get('.q-virtual-scroll__content > :nth-child(1) > .sticky').click(); + cy.location('href').should('contain', '#/ticket/negative/5'); + }); + }); +});