diff --git a/package.json b/package.json index d23ed0ced..e78b0cf3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "25.08.0", + "version": "25.10.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -71,4 +71,4 @@ "vite": "^6.0.11", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index cb31391b3..182c51e47 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -30,22 +30,5 @@ export default { } catch (error) { console.error(error); } - form.addEventListener('keyup', function (evt) { - if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) { - const input = evt.target; - if (input.type == 'textarea' && evt.shiftKey) { - evt.preventDefault(); - let { selectionStart, selectionEnd } = input; - input.value = - input.value.substring(0, selectionStart) + - '\n' + - input.value.substring(selectionEnd); - selectionStart = selectionEnd = selectionStart + 1; - return; - } - evt.preventDefault(); - that.onSubmit(); - } - }); }, }; diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index a92ba29ee..04ef13d45 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue'; +import { onMounted, onUnmounted, computed, ref, watch, nextTick, useAttrs } from 'vue'; import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -22,6 +22,7 @@ const { validate } = useValidator(); const { notify } = useNotify(); const route = useRoute(); const myForm = ref(null); +const attrs = useAttrs(); const $props = defineProps({ url: { type: String, @@ -106,14 +107,14 @@ const isLoading = ref(false); const isResetting = ref(false); const hasChanges = ref(!$props.observeFormChanges); const originalData = computed(() => state.get(modelValue)); -const formData = ref({}); +const formData = ref(); const defaultButtons = computed(() => ({ save: { dataCy: 'saveDefaultBtn', color: 'primary', icon: 'save', label: 'globals.save', - click: () => myForm.value.onSubmit(false), + click: async () => await save(), type: 'submit', }, reset: { @@ -208,8 +209,7 @@ async function fetch() { } } -async function save(prevent = false) { - if (prevent) return; +async function save() { if ($props.observeFormChanges && !hasChanges.value) return notify('globals.noChanges', 'negative'); @@ -247,6 +247,7 @@ async function saveAndGo() { } function reset() { + formData.value = JSON.parse(JSON.stringify(originalData.value)); updateAndEmit('onFetch', { val: originalData.value }); if ($props.observeFormChanges) { hasChanges.value = false; @@ -284,6 +285,22 @@ function trimData(data) { return data; } +async function onKeyup(evt) { + if (evt.key === 'Enter' && !('prevent-submit' in attrs)) { + const input = evt.target; + if (input.type == 'textarea' && evt.shiftKey) { + let { selectionStart, selectionEnd } = input; + input.value = + input.value.substring(0, selectionStart) + + '\n' + + input.value.substring(selectionEnd); + selectionStart = selectionEnd = selectionStart + 1; + return; + } + await save(); + } +} + defineExpose({ save, isLoading, @@ -298,12 +315,12 @@ defineExpose({ <QForm ref="myForm" v-if="formData" - @submit="save(!!$event)" + @submit.prevent + @keyup.prevent="onKeyup" @reset="reset" class="q-pa-md" :style="maxWidth ? 'max-width: ' + maxWidth : ''" id="formModel" - :prevent-submit="$attrs['prevent-submit']" > <QCard> <slot diff --git a/src/components/FormModelPopup.vue b/src/components/FormModelPopup.vue index 98b611743..672eeff7a 100644 --- a/src/components/FormModelPopup.vue +++ b/src/components/FormModelPopup.vue @@ -27,10 +27,15 @@ const formModelRef = ref(null); const closeButton = ref(null); const isSaveAndContinue = ref(false); const onDataSaved = (formData, requestResponse) => { - if (closeButton.value && isSaveAndContinue) closeButton.value.click(); + if (closeButton.value && !isSaveAndContinue.value) closeButton.value.click(); emit('onDataSaved', formData, requestResponse); }; +const onClick = async (saveAndContinue) => { + isSaveAndContinue.value = saveAndContinue; + await formModelRef.value.save(); +}; + const isLoading = computed(() => formModelRef.value?.isLoading); const reset = computed(() => formModelRef.value?.reset); @@ -78,10 +83,7 @@ defineExpose({ :flat="showSaveAndContinueBtn" :label="t('globals.save')" :title="t('globals.save')" - @click=" - formModelRef.save(); - isSaveAndContinue = false; - " + @click="onClick(false)" color="primary" class="q-ml-sm" :disabled="isLoading" @@ -99,10 +101,7 @@ defineExpose({ :loading="isLoading" data-cy="FormModelPopup_isSaveAndContinue" z-max - @click=" - isSaveAndContinue = true; - formModelRef.save(); - " + @click="onClick(true)" /> </div> </template> diff --git a/src/components/TicketProblems.vue b/src/components/TicketProblems.vue index 17d9602af..783f2556f 100644 --- a/src/components/TicketProblems.vue +++ b/src/components/TicketProblems.vue @@ -5,6 +5,18 @@ defineProps({ row: { type: Object, required: true } }); </script> <template> <span class="q-gutter-x-xs"> + <router-link + v-if="row.claim?.claimFk" + :to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }" + class="link" + > + <QIcon name="vn:claims" size="xs"> + <QTooltip> + {{ t('ticketSale.claim') }}: + {{ row.claim?.claimFk }} + </QTooltip> + </QIcon> + </router-link> <QIcon v-if="row?.risk" name="vn:risk" @@ -56,7 +68,7 @@ defineProps({ row: { type: Object, required: true } }); <QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip> </QIcon> <QIcon - v-if="!row?.isTaxDataChecked === 0" + v-if="row?.isTaxDataChecked !== 0" name="vn:no036" color="primary" size="xs" diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index 44364cca1..d0e245388 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -1,6 +1,6 @@ <script setup> import { markRaw, computed } from 'vue'; -import { QIcon, QCheckbox, QToggle } from 'quasar'; +import { QIcon, QToggle } from 'quasar'; import { dashIfEmpty } from 'src/filters'; import VnSelect from 'components/common/VnSelect.vue'; diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index c1188ec47..91a9a627c 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -14,10 +14,10 @@ import { import { useArrayData } from 'src/composables/useArrayData'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; -import { useQuasar } from 'quasar'; +import { useQuasar, date } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; import { useFilterParams } from 'src/composables/useFilterParams'; -import { dashIfEmpty } from 'src/filters'; +import { dashIfEmpty, toDate } from 'src/filters'; import CrudModel from 'src/components/CrudModel.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; @@ -164,7 +164,6 @@ const app = inject('app'); const editingRow = ref(null); const editingField = ref(null); const isTableMode = computed(() => mode.value == TABLE_MODE); -const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); const selectRegex = /select/; const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); const tableModes = [ @@ -345,7 +344,7 @@ const clickHandler = async (event) => { if (isDateElement || isTimeElement || isQselectDropDown) return; if (clickedElement === null) { - destroyInput(editingRow.value, editingField.value); + await destroyInput(editingRow.value, editingField.value); return; } const rowIndex = clickedElement.getAttribute('data-row-index'); @@ -355,7 +354,7 @@ const clickHandler = async (event) => { if (editingRow.value !== null && editingField.value !== null) { if (editingRow.value == rowIndex && editingField.value == colField) return; - destroyInput(editingRow.value, editingField.value); + await destroyInput(editingRow.value, editingField.value); } if (isEditableColumn(column)) { @@ -365,7 +364,7 @@ const clickHandler = async (event) => { async function handleTabKey(event, rowIndex, colField) { if (editingRow.value == rowIndex && editingField.value == colField) - destroyInput(editingRow.value, editingField.value); + await destroyInput(editingRow.value, editingField.value); const direction = event.shiftKey ? -1 : 1; const { nextRowIndex, nextColumnName } = await handleTabNavigation( @@ -425,7 +424,8 @@ async function renderInput(rowId, field, clickedElement) { await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row); }, keyup: async (event) => { - if (event.key === 'Enter') handleBlur(rowId, field, clickedElement); + if (event.key === 'Enter') + await destroyInput(rowIndex, field, clickedElement); }, keydown: async (event) => { switch (event.key) { @@ -434,7 +434,7 @@ async function renderInput(rowId, field, clickedElement) { event.stopPropagation(); break; case 'Escape': - destroyInput(rowId, field, clickedElement); + await destroyInput(rowId, field, clickedElement); break; default: break; @@ -456,12 +456,13 @@ async function renderInput(rowId, field, clickedElement) { node.el?.querySelector('span > div > div').focus(); } -function destroyInput(rowIndex, field, clickedElement) { +async function destroyInput(rowIndex, field, clickedElement) { if (!clickedElement) clickedElement = document.querySelector( `[data-row-index="${rowIndex}"][data-col-field="${field}"]`, ); if (clickedElement) { + await nextTick(); render(null, clickedElement); Array.from(clickedElement.childNodes).forEach((child) => { child.style.visibility = 'visible'; @@ -473,10 +474,6 @@ function destroyInput(rowIndex, field, clickedElement) { editingField.value = null; } -function handleBlur(rowIndex, field, clickedElement) { - destroyInput(rowIndex, field, clickedElement); -} - async function handleTabNavigation(rowIndex, colName, direction) { const columns = $props.columns; const totalColumns = columns.length; @@ -527,11 +524,36 @@ function formatColumnValue(col, row, dashIfEmpty) { } else { return col.format(row, dashIfEmpty); } + } + + if (col?.component === 'date') return dashIfEmpty(toDate(row[col?.name])); + + if (col?.component === 'time') + return row[col?.name] >= 5 + ? dashIfEmpty(date.formatDate(new Date(row[col?.name]), 'HH:mm')) + : row[col?.name]; + + if (selectRegex.test(col?.component) && $props.isEditable) { + const { find, url } = col.attrs; + const urlRelation = url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1); + + if (col?.attrs.options) { + const find = col?.attrs.options.find((option) => option.id === row[col.name]); + if (!col.attrs?.optionLabel || !find) return dashIfEmpty(row[col?.name]); + return dashIfEmpty(find[col.attrs?.optionLabel ?? 'name']); + } + + if (typeof row[urlRelation] == 'object') { + if (typeof find == 'object') + return dashIfEmpty(row[urlRelation][find?.label ?? 'name']); + + return dashIfEmpty(row[urlRelation][col?.attrs.optionLabel ?? 'name']); + } + if (typeof row[urlRelation] == 'string') return dashIfEmpty(row[urlRelation]); } else { return dashIfEmpty(row[col?.name]); } } -const checkbox = ref(null); function cardClick(_, row) { if ($props.redirect) router.push({ path: `/${$props.redirect}/${row.id}` }); } @@ -618,14 +640,6 @@ function cardClick(_, row) { dense :options="tableModes.filter((mode) => !mode.disable)" /> - - <QBtn - v-if="showRightIcon" - icon="filter_alt" - class="bg-vn-section-color q-ml-sm" - dense - @click="stateStore.toggleRightDrawer()" - /> </template> <template #header-cell="{ col }"> <QTh @@ -730,7 +744,11 @@ function cardClick(_, row) { <span v-else :class="hasEditableFormat(col)" - :style="col?.style ? col.style(row) : null" + :style=" + typeof col?.style == 'function' + ? col.style(row) + : col?.style + " style="bottom: 0" > {{ formatColumnValue(col, row, dashIfEmpty) }} @@ -783,7 +801,7 @@ function cardClick(_, row) { <QCardSection vertical class="no-margin no-padding" - :class="colsMap.tableActions ? '' : 'fit'" + :class="colsMap.tableActions ? 'w-80' : 'fit'" > <!-- Chips --> <QCardSection @@ -822,11 +840,11 @@ function cardClick(_, row) { col, index ) of splittedColumns.cardVisible" :key="col.name" + class="fields" > <VnLv :label="col.label + ':'"> <template #value> <span - class="q-pl-xs" @click="stopEventPropagation($event)" > <slot @@ -861,6 +879,7 @@ function cardClick(_, row) { :key="index" :title="btn.title" :icon="btn.icon" + data-cy="cardBtn" class="q-pa-xs" :class=" btn.isPrimary diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 17812f146..3dce04374 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -57,6 +57,7 @@ describe('FormModel', () => { vm.state.set(model, formInitialData); expect(vm.hasChanges).toBe(false); + await vm.$nextTick(); vm.formData.mockKey = 'newVal'; await vm.$nextTick(); expect(vm.hasChanges).toBe(true); @@ -94,8 +95,12 @@ describe('FormModel', () => { it('should call axios.patch with the right data', async () => { const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); const { vm } = mount({ propsData: { url, model } }); - vm.formData.mockKey = 'newVal'; + + vm.formData = {}; await vm.$nextTick(); + vm.formData = { mockKey: 'newVal' }; + await vm.$nextTick(); + await vm.save(); expect(spy).toHaveBeenCalled(); vm.formData.mockKey = 'mockVal'; diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index e6e7e6fa0..6f122ecd2 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -193,22 +193,24 @@ const toModule = computed(() => </div> </QItemLabel> <QItem> - <QItemLabel class="subtitle" caption> + <QItemLabel class="subtitle"> #{{ getValueFromPath(subtitle) ?? entity.id }} - <QBtn - round - flat - dense - size="sm" - icon="content_copy" - color="primary" - @click.stop="copyIdText(entity.id)" - > - <QTooltip> - {{ t('globals.copyId') }} - </QTooltip> - </QBtn> </QItemLabel> + + <QBtn + round + flat + dense + size="sm" + icon="content_copy" + color="primary" + @click.stop="copyIdText(entity.id)" + > + <QTooltip> + {{ t('globals.copyId') }} + </QTooltip> + </QBtn> + <!-- </QItemLabel> --> </QItem> </QList> <div class="list-box q-mt-xs"> diff --git a/src/composables/getColAlign.js b/src/composables/getColAlign.js index ed6fe30d4..08721ee9f 100644 --- a/src/composables/getColAlign.js +++ b/src/composables/getColAlign.js @@ -7,6 +7,7 @@ export function getColAlign(col) { case 'number': align = 'right'; break; + case 'time': case 'date': case 'time': case 'checkbox': diff --git a/src/composables/useCau.js b/src/composables/useCau.js index a71300464..43bfc5180 100644 --- a/src/composables/useCau.js +++ b/src/composables/useCau.js @@ -11,7 +11,7 @@ export async function useCau(res, message) { const { config, headers, request, status, statusText, data } = res || {}; const { params, url, method, signal, headers: confHeaders } = config || {}; const { message: resMessage, code, name } = data?.error || {}; - delete confHeaders.Authorization; + delete confHeaders?.Authorization; const additionalData = { path: location.hash, diff --git a/src/css/app.scss b/src/css/app.scss index 0c5dc97fa..994ae7ff1 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -335,3 +335,7 @@ input::-webkit-inner-spin-button { border: 1px solid; box-shadow: 0 4px 6px #00000000; } + +.containerShrinked { + width: 80%; +} diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 669d776b4..114f81d9b 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -49,6 +49,7 @@ globals: rowRemoved: Row removed pleaseWait: Please wait... noPinnedModules: You don't have any pinned modules + enterToConfirm: Press Enter to confirm summary: basicData: Basic data daysOnward: Days onward diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 44fb56e75..b64346dc9 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -51,6 +51,7 @@ globals: pleaseWait: Por favor espera... noPinnedModules: No has fijado ningún módulo split: Split + enterToConfirm: Pulsa Enter para confirmar summary: basicData: Datos básicos daysOnward: Días adelante diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index af1b9c160..f852c160a 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -233,7 +233,7 @@ function handleLocation(data, location) { postcode: data.postalCode, city: data.city, province: data.province, - country: data.province.country, + country: data.province?.country, }" @update:model-value="(location) => handleLocation(data, location)" ></VnLocation> diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index 7f45cd7db..8f61bac89 100644 --- a/src/pages/Customer/components/CustomerNewPayment.vue +++ b/src/pages/Customer/components/CustomerNewPayment.vue @@ -114,7 +114,7 @@ function onBeforeSave(data) { if (isCash.value && shouldSendEmail.value && !data.email) return notify(t('There is no assigned email for this client'), 'negative'); - data.bankFk = data.bankFk.id; + data.bankFk = data.bankFk?.id; return data; } @@ -189,7 +189,7 @@ async function getAmountPaid() { :url-create="urlCreate" :mapper="onBeforeSave" @on-data-saved="onDataSaved" - :prevent-submit="true" + prevent-submit > <template #form="{ data, validate }"> <span ref="closeButton" class="row justify-end close-icon" v-close-popup> diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue index eb72563e1..1fd9f3e92 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue @@ -103,7 +103,7 @@ const refundInvoice = async (withWarehouse) => { t('refundInvoiceSuccessMessage', { refundTicket: data[0].id, }), - 'positive' + 'positive', ); }; @@ -124,6 +124,13 @@ const showRefundInvoiceForm = () => { }, }); }; + +const showExportationLetter = () => { + openReport(`InvoiceOuts/${$props.invoiceOutData.ref}/exportation-pdf`, { + recipientId: $props.invoiceOutData.client.id, + refFk: $props.invoiceOutData.ref, + }); +}; </script> <template> @@ -172,7 +179,7 @@ const showRefundInvoiceForm = () => { t('Confirm deletion'), t('Are you sure you want to delete this invoice?'), deleteInvoice, - redirectToInvoiceOutList + redirectToInvoiceOutList, ) " > @@ -185,7 +192,7 @@ const showRefundInvoiceForm = () => { openConfirmationModal( '', t('Are you sure you want to book this invoice?'), - bookInvoice + bookInvoice, ) " > @@ -198,7 +205,7 @@ const showRefundInvoiceForm = () => { openConfirmationModal( t('Generate PDF invoice document'), t('Are you sure you want to generate/regenerate the PDF invoice?'), - generateInvoicePdf + generateInvoicePdf, ) " > @@ -226,6 +233,14 @@ const showRefundInvoiceForm = () => { {{ t('Create a single ticket with all the content of the current invoice') }} </QTooltip> </QItem> + <QItem + v-if="$props.invoiceOutData.serial === 'E'" + v-ripple + clickable + @click="showExportationLetter()" + > + <QItemSection>{{ t('Show CITES letter') }}</QItemSection> + </QItem> </template> <i18n> @@ -255,7 +270,7 @@ es: Create a single ticket with all the content of the current invoice: Crear un ticket único con todo el contenido de la factura actual refundInvoiceSuccessMessage: Se ha creado el siguiente ticket de abono {refundTicket} The email can't be empty: El email no puede estar vacío - + Show CITES letter: Ver carta CITES en: refundInvoiceSuccessMessage: The following refund ticket have been created {refundTicket} </i18n> diff --git a/src/pages/InvoiceOut/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index cdc9f037a..648b8e4e6 100644 --- a/src/pages/InvoiceOut/InvoiceOutFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutFilter.vue @@ -22,7 +22,7 @@ const states = ref(); <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`invoiceOut.params.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> @@ -84,15 +84,6 @@ const states = ref(); /> </QItemSection> </QItem> - <QItem> - <QItemSection> - <VnInputDate - v-model="params.issued" - :label="t('Issued')" - is-outlined - /> - </QItemSection> - </QItem> <QItem> <QItemSection> <VnInputDate @@ -110,37 +101,3 @@ const states = ref(); </template> </VnFilterPanel> </template> - -<i18n> -en: - params: - search: Contains - clientFk: Customer - fi: FI - amount: Amount - min: Min - max: Max - hasPdf: Has PDF - issued: Issued - created: Created - dued: Dued -es: - params: - search: Contiene - clientFk: Cliente - fi: CIF - amount: Importe - min: Min - max: Max - hasPdf: Tiene PDF - issued: Emitida - created: Creada - dued: Vencida - Customer ID: ID cliente - FI: CIF - Amount: Importe - Has PDF: Tiene PDF - Issued: Fecha emisión - Created: Fecha creación - Dued: Fecha vencimiento -</i18n> diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index c7d7ba9f4..873ab030f 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -71,14 +71,6 @@ const columns = computed(() => [ inWhere: true, }, }, - { - align: 'left', - name: 'issued', - label: t('invoiceOut.summary.issued'), - component: 'date', - format: (row) => toDate(row.issued), - columnField: { component: null }, - }, { align: 'left', name: 'clientFk', diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue index 135eb9aca..b062678a0 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue @@ -10,6 +10,8 @@ import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vu import TicketDescriptorProxy from '../Ticket/Card/TicketDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; +import InvoiceOutNegativeBasesFilter from './InvoiceOutNegativeBasesFilter.vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; const { t } = useI18n(); const tableRef = ref(); @@ -97,16 +99,19 @@ const columns = computed(() => [ align: 'left', name: 'isActive', label: t('invoiceOut.negativeBases.active'), + component: 'checkbox', }, { align: 'left', name: 'hasToInvoice', label: t('invoiceOut.negativeBases.hasToInvoice'), + component: 'checkbox', }, { align: 'left', - name: 'hasVerifiedData', + name: 'isTaxDataChecked', label: t('invoiceOut.negativeBases.verifiedData'), + component: 'checkbox', }, { align: 'left', @@ -142,7 +147,7 @@ const downloadCSV = async () => { await invoiceOutGlobalStore.getNegativeBasesCsv( userParams.from, userParams.to, - filterParams + filterParams, ); }; </script> @@ -154,6 +159,11 @@ const downloadCSV = async () => { </QBtn> </template> </VnSubToolbar> + <RightMenu> + <template #right-panel> + <InvoiceOutNegativeBasesFilter data-key="negativeFilter" /> + </template> + </RightMenu> <VnTable ref="tableRef" data-key="negativeFilter" @@ -174,6 +184,7 @@ const downloadCSV = async () => { auto-load :is-editable="false" :use-model="true" + :right-search="false" > <template #column-clientId="{ row }"> <span class="link" @click.stop> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue index 6ceec61e4..cd9836bb7 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue @@ -2,9 +2,10 @@ import { useI18n } from 'vue-i18n'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; -import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const props = defineProps({ @@ -24,11 +25,11 @@ const props = defineProps({ > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`invoiceOut.params.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> - <template #body="{ params }"> + <template #body="{ params, searchFn }"> <QItem> <QItemSection> <VnInputDate @@ -49,38 +50,70 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.company" + <VnSelect + url="Companies" :label="t('globals.company')" - is-outlined - /> + v-model="params.company" + option-label="code" + option-value="code" + dense + outlined + rounded + @update:model-value="searchFn()" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt?.code }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput + <VnSelect + url="Countries" + :label="t('globals.params.countryFk')" v-model="params.country" - :label="t('globals.country')" - is-outlined - /> - </QItemSection> - </QItem> - - <QItem> - <QItemSection> - <VnInput - v-model="params.clientId" - :label="t('invoiceOut.negativeBases.clientId')" - is-outlined - /> + option-label="name" + option-value="name" + outlined + dense + rounded + @update:model-value="searchFn()" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt?.name }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.clientSocialName" + <VnSelect + url="Clients" :label="t('globals.client')" - is-outlined + v-model="params.clientId" + outlined + dense + rounded + @update:model-value="searchFn()" /> </QItemSection> </QItem> @@ -90,15 +123,18 @@ const props = defineProps({ v-model="params.amount" :label="t('globals.amount')" is-outlined + :positive="false" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.comercialName" + <VnSelectWorker :label="t('invoiceOut.negativeBases.comercial')" + v-model="params.workerName" + option-value="name" is-outlined + @update:model-value="searchFn()" /> </QItemSection> </QItem> diff --git a/src/pages/InvoiceOut/locale/en.yml b/src/pages/InvoiceOut/locale/en.yml index ee6ba57e6..9dd31d186 100644 --- a/src/pages/InvoiceOut/locale/en.yml +++ b/src/pages/InvoiceOut/locale/en.yml @@ -4,7 +4,7 @@ invoiceOut: params: company: Company country: Country - clientId: Client ID + clientId: Client clientSocialName: Client taxableBase: Base ticketFk: Ticket @@ -12,6 +12,18 @@ invoiceOut: hasToInvoice: Has to invoice hasVerifiedData: Verified data workerName: Worker + isTaxDataChecked: Verified data + amount: Amount + clientFk: Client + companyFk: Company + created: Created + dued: Dued + customsAgentFk: Custom Agent + ref: Reference + fi: FI + min: Min + max: Max + hasPdf: Has PDF card: issued: Issued customerCard: Customer card @@ -53,7 +65,7 @@ invoiceOut: active: Active hasToInvoice: Has to Invoice verifiedData: Verified Data - comercial: Commercial + comercial: Sales person errors: downloadCsvFailed: CSV download failed invoiceOutModule: diff --git a/src/pages/InvoiceOut/locale/es.yml b/src/pages/InvoiceOut/locale/es.yml index a059ce18d..79ceb4aa8 100644 --- a/src/pages/InvoiceOut/locale/es.yml +++ b/src/pages/InvoiceOut/locale/es.yml @@ -4,7 +4,7 @@ invoiceOut: params: company: Empresa country: País - clientId: ID del cliente + clientId: Cliente clientSocialName: Cliente taxableBase: Base ticketFk: Ticket @@ -12,6 +12,18 @@ invoiceOut: hasToInvoice: Debe facturar hasVerifiedData: Datos verificados workerName: Comercial + isTaxDataChecked: Datos comprobados + amount: Importe + clientFk: Cliente + companyFk: Empresa + created: Creada + dued: Vencida + customsAgentFk: Agente aduanas + ref: Referencia + fi: CIF + min: Min + max: Max + hasPdf: Tiene PDF card: issued: Fecha emisión customerCard: Ficha del cliente diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index b6d0ba8c4..503cd1941 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -6,6 +6,8 @@ import VnLv from 'components/ui/VnLv.vue'; import { dashIfEmpty, toDate } from 'src/filters'; import RouteDescriptorMenu from 'pages/Route/Card/RouteDescriptorMenu.vue'; import filter from './RouteFilter.js'; +import useCardDescription from 'src/composables/useCardDescription'; +import axios from 'axios'; const $props = defineProps({ id: { @@ -16,7 +18,6 @@ const $props = defineProps({ }); const route = useRoute(); -const { t } = useI18n(); const zone = ref(); const zoneId = ref(); const entityId = computed(() => { @@ -50,9 +51,9 @@ onMounted(async () => { width="lg-width" > <template #body="{ entity }"> - <VnLv :label="t('Date')" :value="toDate(entity?.dated)" /> - <VnLv :label="t('Agency')" :value="entity?.agencyMode?.name" /> - <VnLv :label="t('Zone')" :value="zone" /> + <VnLv :label="$t('Date')" :value="toDate(entity?.dated)" /> + <VnLv :label="$t('Agency')" :value="entity?.agencyMode?.name" /> + <VnLv :label="$t('Zone')" :value="zone" /> <VnLv :label="$t('Volume')" :value="`${dashIfEmpty(entity?.m3)} / ${dashIfEmpty( diff --git a/src/pages/Route/Card/RouteFilter.js b/src/pages/Route/Card/RouteFilter.js index 16d200c99..90ee71bf7 100644 --- a/src/pages/Route/Card/RouteFilter.js +++ b/src/pages/Route/Card/RouteFilter.js @@ -14,7 +14,6 @@ export default { 'started', 'finished', 'cost', - 'zoneFk', 'isOk', ], include: [ @@ -23,7 +22,6 @@ export default { relation: 'vehicle', scope: { fields: ['id', 'm3'] }, }, - { relation: 'zone', scope: { fields: ['id', 'name'] } }, { relation: 'worker', scope: { diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue index 3f7cfa30b..667204b15 100644 --- a/src/pages/Route/Card/RouteForm.vue +++ b/src/pages/Route/Card/RouteForm.vue @@ -28,52 +28,6 @@ const defaultInitialData = { isOk: false, }; const maxDistance = ref(); - -const routeFilter = { - fields: [ - 'id', - 'workerFk', - 'agencyModeFk', - 'dated', - 'm3', - 'warehouseFk', - 'description', - 'vehicleFk', - 'kmStart', - 'kmEnd', - 'started', - 'finished', - 'cost', - 'isOk', - ], - include: [ - { relation: 'agencyMode', scope: { fields: ['id', 'name'] } }, - { - relation: 'vehicle', - scope: { fields: ['id', 'm3'] }, - }, - { - relation: 'ticket', - scope: { - fields: ['id', 'name', 'zoneFk'], - include: { relation: 'zone', scope: { fields: ['id', 'name'] } }, - }, - }, - { - relation: 'worker', - scope: { - fields: ['id'], - include: { - relation: 'user', - scope: { - fields: ['id'], - include: { relation: 'emailUser', scope: { fields: ['email'] } }, - }, - }, - }, - }, - ], -}; const onSave = (data, response) => { if (isNew) { axios.post(`Routes/${response?.id}/updateWorkCenter`); diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index a55658a07..14eec9db9 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -21,6 +21,10 @@ const $props = defineProps({ type: String, default: 'mana', }, + sale: { + type: Object, + default: null, + }, }); const emit = defineEmits(['save', 'cancel']); @@ -29,8 +33,8 @@ const { t } = useI18n(); const QPopupProxyRef = ref(null); const manaCode = ref($props.manaCode); -const save = () => { - emit('save'); +const save = (sale = $props.sale) => { + emit('save', sale); QPopupProxyRef.value.hide(); }; @@ -38,10 +42,11 @@ const cancel = () => { emit('cancel'); QPopupProxyRef.value.hide(); }; +defineExpose({ save }); </script> <template> - <QPopupProxy ref="QPopupProxyRef"> + <QPopupProxy ref="QPopupProxyRef" data-cy="ticketEditManaProxy"> <div class="container"> <QSpinner v-if="!mana" color="primary" size="md" /> <div v-else> diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 118761b23..e680fb290 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -22,7 +22,6 @@ import { useVnConfirm } from 'composables/useVnConfirm'; 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'; import TicketProblems from 'src/components/TicketProblems.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; @@ -33,6 +32,7 @@ const { t } = useI18n(); const { notify } = useNotify(); const { openConfirmationModal } = useVnConfirm(); const editPriceProxyRef = ref(null); +const editManaProxyRef = ref(null); const stateBtnDropdownRef = ref(null); const quasar = useQuasar(); const arrayData = useArrayData('Ticket'); @@ -53,7 +53,6 @@ const transfer = ref({ sales: [], }); const tableRef = ref([]); -const canProceed = ref(); watch( () => route.params.id, @@ -133,7 +132,6 @@ const columns = computed(() => [ align: 'left', label: t('globals.amount'), name: 'amount', - format: (row) => toCurrency(getSaleTotal(row)), }, { align: 'left', @@ -183,8 +181,6 @@ const resetChanges = async () => { }; const rowToUpdate = ref(null); const changeQuantity = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; if ( !sale.itemFk || sale.quantity == null || @@ -193,11 +189,21 @@ const changeQuantity = async (sale) => { return; if (!sale.id) return addSale(sale); + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateQuantity(sale)); + } else await updateQuantity(sale); +}; + +const updateQuantity = async (sale) => { try { + let { quantity, id } = sale; if (!rowToUpdate.value) return; rowToUpdate.value = null; sale.isNew = false; - await updateQuantity(sale); + const params = { quantity: quantity }; + await axios.post(`Sales/${id}/updateQuantity`, params); + notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); } catch (e) { const { quantity } = tableRef.value.CrudModelRef.originalData.find( (s) => s.id === sale.id, @@ -207,12 +213,6 @@ const changeQuantity = async (sale) => { } }; -const updateQuantity = async ({ quantity, id }) => { - const params = { quantity: quantity }; - await axios.post(`Sales/${id}/updateQuantity`, params); - notify('globals.dataSaved', 'positive'); -}; - const addSale = async (sale) => { const params = { barcode: sale.itemFk, @@ -237,13 +237,17 @@ const addSale = async (sale) => { sale.isNew = false; arrayData.fetch({}); }; +const changeConcept = async (sale) => { + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateConcept(sale)); + } else await updateConcept(sale); +}; const updateConcept = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; const data = { newConcept: sale.concept }; await axios.post(`Sales/${sale.id}/updateConcept`, data); notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); }; const DEFAULT_EDIT = { @@ -295,33 +299,43 @@ const onOpenEditDiscountPopover = async (sale) => { }; } }; - -const updatePrice = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; +const changePrice = async (sale) => { const newPrice = edit.value.price; if (newPrice != null && newPrice != sale.price) { - await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); - sale.price = newPrice; - edit.value = { ...DEFAULT_EDIT }; - notify('globals.dataSaved', 'positive'); + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updatePrice(sale, newPrice)); + } else updatePrice(sale, newPrice); } - await getMana(); }; +const updatePrice = async (sale, newPrice) => { + await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); + sale.price = newPrice; + edit.value = { ...DEFAULT_EDIT }; + notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); +}; 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]); + if (newDiscount != null && newDiscount != sale.discount) { + if (await isSalePrepared(sale)) + await confirmUpdate(() => updateDiscount([sale], newDiscount)); + else await updateDiscount([sale], newDiscount); + } +}; + +const updateDiscounts = async (sales, newDiscount = null) => { + const salesTracking = await fetchSalesTracking(); + + const someSaleIsPrepared = salesTracking.some((sale) => + matchSale(salesTracking, sale), + ); + if (someSaleIsPrepared) await confirmUpdate(() => updateDiscount(sales, newDiscount)); + else updateDiscount(sales, newDiscount); }; 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 = { @@ -424,9 +438,13 @@ onMounted(async () => { const items = ref([]); const newRow = ref({}); +const changeItem = async (sale) => { + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateItem(sale)); + } else await updateItem(sale); +}; + 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; @@ -470,7 +488,18 @@ const endNewRow = (row) => { } }; -async function isSalePrepared(item) { +async function confirmUpdate(cb) { + await quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('Item prepared'), + message: t('This item is already prepared. Do you want to continue?'), + }, + }) + .onOk(cb); +} +async function fetchSalesTracking() { const filter = { params: { where: { ticketFk: route.params.id }, @@ -482,48 +511,37 @@ async function isSalePrepared(item) { 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; + return data; } +async function isSalePrepared(sale) { + const data = await fetchSalesTracking(); + return matchSale(data, sale); +} +function matchSale(data, sale) { + const matchingSale = data.find(({ itemFk }) => itemFk === sale.itemFk); + + if (!matchingSale) { + return false; + } + + return isPrepared(matchingSale); +} +function isPrepared(sale) { + const flagsToCheck = [ + 'hasSaleGroupDetail', + 'isControled', + 'isPrepared', + 'isPrevious', + 'isPreviousSelected', + ]; + return flagsToCheck.some((flag) => sale[flag] === 1); +} watch( () => newRow.value.itemFk, (newItemFk) => { if (newItemFk) { - updateItem(newRow.value); + changeItem(newRow.value); } }, ); @@ -584,7 +602,7 @@ watch( :mana="mana" :ticket-config="ticketConfig" @get-mana="getMana()" - @update-discounts="updateDiscount" + @update-discounts="updateDiscounts" @refresh-table="resetChanges" /> <QBtn @@ -715,7 +733,7 @@ watch( option-value="id" v-model="row.itemFk" :use-like="false" - @update:model-value="updateItem(row)" + @update:model-value="changeItem(row)" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -741,16 +759,21 @@ watch( </div> <FetchedTags :item="row" :max-length="6" /> <QPopupProxy v-if="row.id && isTicketEditable"> - <VnInput v-model="row.concept" @change="updateConcept(row)" /> + <VnInput + v-model="row.concept" + @keyup.enter.stop="changeConcept(row)" + :hint="t('globals.enterToConfirm')" + /> </QPopupProxy> </template> <template #column-quantity="{ row }"> <VnInput + data-cy="ticketSaleQuantityInput" v-if="row.isNew || isTicketEditable" type="number" v-model.number="row.quantity" @blur="changeQuantity(row)" - @keyup.enter="changeQuantity(row)" + @keyup.enter.stop="changeQuantity(row)" @update:model-value="() => (rowToUpdate = row)" @focus="edit.oldQuantity = row.quantity" /> @@ -764,10 +787,12 @@ watch( <TicketEditManaProxy ref="editPriceProxyRef" :mana="mana" + :sale="row" :new-price="getNewPrice" - @save="updatePrice(row)" + @save="changePrice" > <VnInput + @keyup.enter.stop="() => editManaProxyRef.save(row)" v-model.number="edit.price" :label="t('basicData.price')" type="number" @@ -781,31 +806,30 @@ watch( <QBtn flat class="link" dense @click="onOpenEditDiscountPopover(row)"> {{ toPercentage(row.discount / 100) }} </QBtn> + <TicketEditManaProxy + ref="editManaProxyRef" :mana="mana" + :sale="row" :new-price="getNewPrice" :uses-mana="usesMana" :mana-code="manaCode" - @save="changeDiscount(row)" + @save="changeDiscount" > - <template #default="{ popup }"> - <VnInput - autofocus - @keyup.enter=" - () => { - changeDiscount(row); - popup.hide(); - } - " - v-model.number="edit.discount" - :label="t('ticketSale.discount')" - type="number" - /> - </template> + <VnInput + autofocus + @keyup.enter.stop="() => editManaProxyRef.save(row)" + v-model.number="edit.discount" + :label="t('ticketSale.discount')" + type="number" + /> </TicketEditManaProxy> </template> <span v-else>{{ toPercentage(row.discount / 100) }}</span> </template> + <template #column-amount="{ row }"> + {{ toCurrency(getSaleTotal(row)) }} + </template> </VnTable> <QPageSticky :offset="[20, 20]" style="z-index: 2"> diff --git a/src/pages/Ticket/TicketFuture.vue b/src/pages/Ticket/TicketFuture.vue index 9876ced78..92911cd25 100644 --- a/src/pages/Ticket/TicketFuture.vue +++ b/src/pages/Ticket/TicketFuture.vue @@ -16,6 +16,7 @@ import useNotify from 'src/composables/useNotify.js'; import { useState } from 'src/composables/useState'; import { toDateTimeFormat } from 'src/filters/date.js'; import axios from 'axios'; +import TicketProblems from 'src/components/TicketProblems.vue'; const state = useState(); const { t } = useI18n(); @@ -286,71 +287,7 @@ watch( </span> </QTooltip> </QIcon> - <QIcon - v-if="row.isTaxDataChecked === 0" - color="primary" - name="vn:no036" - size="xs" - > - <QTooltip> - {{ t('futureTickets.noVerified') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.hasTicketRequest" - color="primary" - name="vn:buyrequest" - size="xs" - > - <QTooltip> - {{ t('futureTickets.purchaseRequest') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.itemShortage" - color="primary" - name="vn:unavailable" - size="xs" - > - <QTooltip> - {{ t('ticketSale.noVisible') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.isFreezed" - color="primary" - name="vn:frozen" - size="xs" - > - <QTooltip> - {{ t('futureTickets.clientFrozen') }} - </QTooltip> - </QIcon> - <QIcon v-if="row.risk" color="primary" name="vn:risk" size="xs"> - <QTooltip> - {{ t('futureTickets.risk') }}: {{ row.risk }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.hasComponentLack" - color="primary" - name="vn:components" - size="xs" - > - <QTooltip> - {{ t('futureTickets.componentLack') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.hasRounding" - color="primary" - name="sync_problem" - size="xs" - > - <QTooltip> - {{ t('futureTickets.rounding') }} - </QTooltip> - </QIcon> + <TicketProblems :row /> </span> </template> <template #column-id="{ row }"> diff --git a/src/pages/Travel/Card/TravelThermographsForm.vue b/src/pages/Travel/Card/TravelThermographsForm.vue index 7aec32972..446e5d506 100644 --- a/src/pages/Travel/Card/TravelThermographsForm.vue +++ b/src/pages/Travel/Card/TravelThermographsForm.vue @@ -209,7 +209,7 @@ const onThermographCreated = async (data) => { }" sort-by="thermographFk ASC" option-label="thermographFk" - option-filter-value="id" + option-filter-value="thermographFk" :disable="viewAction === 'edit'" :tooltip="t('New thermograph')" :roles-allowed-to-create="['logistic']" diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue index dee9d923a..ac46caa44 100644 --- a/src/pages/Travel/ExtraCommunity.vue +++ b/src/pages/Travel/ExtraCommunity.vue @@ -2,6 +2,7 @@ import { onMounted, ref, computed, watch } from 'vue'; import { QBtn } from 'quasar'; import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; @@ -22,6 +23,8 @@ import VnPopup from 'src/components/common/VnPopup.vue'; const stateStore = useStateStore(); const { t } = useI18n(); const { openReport } = usePrintService(); +const route = useRoute(); +const tableParams = ref(); const shippedFrom = ref(Date.vnNew()); const landedTo = ref(Date.vnNew()); @@ -143,7 +146,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('globals.pageTitles.supplier'), + label: t('extraCommunity.cargoShip'), field: 'cargoSupplierNickname', name: 'cargoSupplierNickname', align: 'left', @@ -171,7 +174,7 @@ const columns = computed(() => [ ? value.reduce((sum, entry) => { return sum + (entry.invoiceAmount || 0); }, 0) - : 0 + : 0, ), }, { @@ -200,7 +203,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('kg'), + label: t('extraCommunity.kg'), field: 'kg', name: 'kg', align: 'left', @@ -208,7 +211,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('physicKg'), + label: t('extraCommunity.physicKg'), field: 'loadedKg', name: 'loadedKg', align: 'left', @@ -232,7 +235,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('shipped'), + label: t('extraCommunity.shipped'), field: 'shipped', name: 'shipped', align: 'left', @@ -249,7 +252,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('landed'), + label: t('extraCommunity.landed'), field: 'landed', name: 'landed', align: 'left', @@ -258,7 +261,7 @@ const columns = computed(() => [ format: (value) => toDate(value), }, { - label: t('notes'), + label: t('extraCommunity.notes'), field: '', name: 'notes', align: 'center', @@ -284,7 +287,7 @@ watch( if (!arrayData.store.data) return; onStoreDataChange(); }, - { deep: true, immediate: true } + { deep: true, immediate: true }, ); const openReportPdf = () => { @@ -451,13 +454,24 @@ const getColor = (percentage) => { for (const { value, className } of travelKgPercentages.value) if (percentage > value) return className; }; + +const filteredEntries = (entries) => { + if (!tableParams?.value?.entrySupplierFk) return entries; + return entries?.filter( + (entry) => entry.supplierFk === tableParams?.value?.entrySupplierFk, + ); +}; + +watch(route, () => { + tableParams.value = JSON.parse(route.query.table); +}); </script> <template> <VnSearchbar data-key="ExtraCommunity" :limit="20" - :label="t('searchExtraCommunity')" + :label="t('extraCommunity.searchExtraCommunity')" /> <RightMenu> <template #right-panel> @@ -521,7 +535,7 @@ const getColor = (percentage) => { ? tableColumnComponents[col.name].event( rows[props.rowIndex][col.field], col.field, - props.rowIndex + props.rowIndex, ) : {} " @@ -546,7 +560,7 @@ const getColor = (percentage) => { }, { link: ['id', 'cargoSupplierNickname'].includes( - col.name + col.name, ), }, ]" @@ -564,9 +578,8 @@ const getColor = (percentage) => { </component> </QTd> </QTr> - <QTr - v-for="(entry, index) in props.row.entries" + v-for="(entry, index) in filteredEntries(props.row.entries)" :key="index" :props="props" class="bg-vn-secondary-row cursor-pointer" @@ -598,7 +611,7 @@ const getColor = (percentage) => { name="warning" color="negative" size="md" - :title="t('requiresInspection')" + :title="t('extraCommunity.requiresInspection')" > </QIcon> </QTd> @@ -709,24 +722,3 @@ const getColor = (percentage) => { width: max-content; } </style> -<i18n> -en: - searchExtraCommunity: Search for extra community shipping - kg: BI. KG - physicKg: Phy. KG - shipped: W. shipped - landed: W. landed - requiresInspection: Requires inspection - BIP: Boder Inspection Point - notes: Notes -es: - searchExtraCommunity: Buscar por envío extra comunitario - kg: KG Bloq. - physicKg: KG físico - shipped: F. envío - landed: F. llegada - notes: Notas - Open as PDF: Abrir como PDF - requiresInspection: Requiere inspección - BIP: Punto de Inspección Fronteriza -</i18n> diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index b22574632..29d342334 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -79,7 +79,7 @@ warehouses(); <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`extraCommunity.filter.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> @@ -92,7 +92,7 @@ warehouses(); <QItem> <QItemSection> <VnInput - :label="t('params.reference')" + :label="t('extraCommunity.filter.reference')" v-model="params.reference" is-outlined /> @@ -103,7 +103,7 @@ warehouses(); <QInput v-model="params.totalEntries" type="number" - :label="t('params.totalEntries')" + :label="t('extraCommunity.filter.totalEntries')" dense outlined rounded @@ -133,10 +133,10 @@ warehouses(); <QItem> <QItemSection> <VnSelect - :label="t('params.agencyModeFk')" + :label="t('extraCommunity.filter.agencyModeFk')" v-model="params.agencyModeFk" :options="agenciesOptions" - option-value="agencyFk" + option-value="id" option-label="name" hide-selected dense @@ -148,7 +148,7 @@ warehouses(); <QItem> <QItemSection> <VnInputDate - :label="t('params.shippedFrom')" + :label="t('extraCommunity.filter.shippedFrom')" v-model="params.shippedFrom" @update:model-value="searchFn()" is-outlined @@ -158,7 +158,7 @@ warehouses(); <QItem> <QItemSection> <VnInputDate - :label="t('params.landedTo')" + :label="t('extraCommunity.filter.landedTo')" v-model="params.landedTo" @update:model-value="searchFn()" is-outlined @@ -168,7 +168,7 @@ warehouses(); <QItem v-if="warehousesByContinent[params.continent]"> <QItemSection> <VnSelect - :label="t('params.warehouseOutFk')" + :label="t('extraCommunity.filter.warehouseOutFk')" v-model="params.warehouseOutFk" :options="warehousesByContinent[params.continent]" option-value="id" @@ -183,7 +183,7 @@ warehouses(); <QItem v-else> <QItemSection> <VnSelect - :label="t('params.warehouseOutFk')" + :label="t('extraCommunity.filter.warehouseOutFk')" v-model="params.warehouseOutFk" :options="warehousesOptions" option-value="id" @@ -198,7 +198,7 @@ warehouses(); <QItem> <QItemSection> <VnSelect - :label="t('params.warehouseInFk')" + :label="t('extraCommunity.filter.warehouseInFk')" v-model="params.warehouseInFk" :options="warehousesOptions" option-value="id" @@ -213,6 +213,7 @@ warehouses(); <QItem> <QItemSection> <VnSelectSupplier + :label="t('extraCommunity.cargoShip')" v-model="params.cargoSupplierFk" hide-selected dense @@ -221,10 +222,21 @@ warehouses(); /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnSelectSupplier + v-model="params.entrySupplierFk" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <VnSelect - :label="t('params.continent')" + :label="t('extraCommunity.filter.continent')" v-model="params.continent" :options="continentsOptions" option-value="code" @@ -240,30 +252,3 @@ warehouses(); </template> </VnFilterPanel> </template> - -<i18n> -en: - params: - id: Id - reference: Reference - totalEntries: Total entries - agencyModeFk: Agency - warehouseInFk: Warehouse In - warehouseOutFk: Warehouse Out - shippedFrom: Shipped from - landedTo: Landed to - cargoSupplierFk: Supplier - continent: Continent out -es: - params: - id: Id - reference: Referencia - totalEntries: Ent. totales - agencyModeFk: Agencia - warehouseInFk: Alm. entrada - warehouseOutFk: Alm. salida - shippedFrom: Llegada desde - landedTo: Llegada hasta - cargoSupplierFk: Proveedor - continent: Cont. Salida -</i18n> diff --git a/src/pages/Travel/locale/en.yml b/src/pages/Travel/locale/en.yml new file mode 100644 index 000000000..ddef66f2f --- /dev/null +++ b/src/pages/Travel/locale/en.yml @@ -0,0 +1,22 @@ +extraCommunity: + cargoShip: Cargo ship + searchExtraCommunity: Search for extra community shipping + kg: BI. KG + physicKg: Phy. KG + shipped: W. shipped + landed: W. landed + requiresInspection: Requires inspection + BIP: Boder Inspection Point + notes: Notes + filter: + id: Id + reference: Reference + totalEntries: Total entries + agencyModeFk: Agency + warehouseInFk: Warehouse In + warehouseOutFk: Warehouse Out + shippedFrom: Shipped from + landedTo: Landed to + cargoSupplierFk: Cargo supplier + continent: Continent out + entrySupplierFk: Supplier diff --git a/src/pages/Travel/locale/es.yml b/src/pages/Travel/locale/es.yml new file mode 100644 index 000000000..1542d8892 --- /dev/null +++ b/src/pages/Travel/locale/es.yml @@ -0,0 +1,23 @@ +extraCommunity: + cargoShip: Carguera + searchExtraCommunity: Buscar por envío extra comunitario + kg: KG Bloq. + physicKg: KG físico + shipped: F. envío + landed: F. llegada + notes: Notas + Open as PDF: Abrir como PDF + requiresInspection: Requiere inspección + BIP: Punto de Inspección Fronteriza + filter: + id: Id + reference: Referencia + totalEntries: Ent. totales + agencyModeFk: Agencia + warehouseInFk: Alm. entrada + warehouseOutFk: Alm. salida + shippedFrom: Llegada desde + landedTo: Llegada hasta + cargoSupplierFk: Carguera + continent: Cont. Salida + entrySupplierFk: Proveedor diff --git a/src/pages/Worker/Card/WorkerFormation.vue b/src/pages/Worker/Card/WorkerFormation.vue index e05eca7f8..e8680f7dd 100644 --- a/src/pages/Worker/Card/WorkerFormation.vue +++ b/src/pages/Worker/Card/WorkerFormation.vue @@ -119,7 +119,7 @@ const columns = computed(() => [ :url="`Workers/${entityId}/trainingCourse`" :url-create="`Workers/${entityId}/trainingCourse`" save-url="TrainingCourses/crud" - :filter="courseFilter" + :user-filter="courseFilter" :create="{ urlCreate: 'trainingCourses', title: t('Create training course'), diff --git a/src/pages/Worker/Card/WorkerMedical.vue b/src/pages/Worker/Card/WorkerMedical.vue index c220df76a..b3a599af7 100644 --- a/src/pages/Worker/Card/WorkerMedical.vue +++ b/src/pages/Worker/Card/WorkerMedical.vue @@ -8,6 +8,17 @@ const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => route.params.id); +const centerFilter = { + include: [ + { + relation: 'center', + scope: { + fields: ['id', 'name'], + }, + }, + ], +}; + const columns = [ { align: 'left', @@ -33,7 +44,7 @@ const columns = [ create: true, component: 'select', attrs: { - url: 'medicalCenters', + url: 'centers', fields: ['id', 'name'], }, }, @@ -84,6 +95,7 @@ const columns = [ ref="tableRef" data-key="WorkerMedical" :url="`Workers/${entityId}/medicalReview`" + :user-filter="centerFilter" save-url="MedicalReviews/crud" :create="{ urlCreate: 'medicalReviews', diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index b38d2749b..03013f011 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -25,7 +25,7 @@ const setFilteredAddresses = (data) => { @on-fetch="(data) => (validAddresses = data)" /> <FetchData url="Addresses" auto-load @on-fetch="setFilteredAddresses" /> - <FormModel auto-load model="zone"> + <FormModel auto-load model="Zone"> <template #form="{ data, validate }"> <VnRow> <VnInput @@ -33,6 +33,7 @@ const setFilteredAddresses = (data) => { :label="t('Name')" clearable v-model="data.name" + :required="true" /> </VnRow> <VnRow> @@ -83,7 +84,7 @@ const setFilteredAddresses = (data) => { type="number" min="0" /> - <VnInputTime v-model="data.hour" :label="t('Closing')" /> + <VnInputTime v-model="data.hour" :label="t('Closing')" :required="true" /> </VnRow> <VnRow> @@ -92,7 +93,7 @@ const setFilteredAddresses = (data) => { :label="t('Price')" type="number" min="0" - required="true" + :required="true" clearable /> <VnInput @@ -100,7 +101,7 @@ const setFilteredAddresses = (data) => { :label="t('Price optimum')" type="number" min="0" - required="true" + :required="true" clearable /> </VnRow> diff --git a/src/pages/Zone/Card/ZoneCalendar.vue b/src/pages/Zone/Card/ZoneCalendar.vue deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/pages/Zone/ZoneFilterPanel.vue b/src/pages/Zone/ZoneFilterPanel.vue index 3a35527ab..bbe12189a 100644 --- a/src/pages/Zone/ZoneFilterPanel.vue +++ b/src/pages/Zone/ZoneFilterPanel.vue @@ -38,7 +38,12 @@ const agencies = ref([]); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput :label="t('list.name')" v-model="params.name" is-outlined /> + <VnInput + :label="t('list.name')" + v-model="params.name" + is-outlined + data-cy="zoneFilterPanelNameInput" + /> </QItemSection> </QItem> <QItem> @@ -53,6 +58,7 @@ const agencies = ref([]); dense outlined rounded + data-cy="zoneFilterPanelAgencySelect" > </VnSelect> </QItemSection> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index 1fa539c91..a82bbb285 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -65,7 +65,6 @@ const tableFilter = { const columns = computed(() => [ { - align: 'left', name: 'id', label: t('list.id'), chip: { @@ -75,6 +74,8 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + columnClass: 'shrink-column', + component: 'number', }, { align: 'left', @@ -106,7 +107,6 @@ const columns = computed(() => [ format: (row, dashIfEmpty) => dashIfEmpty(row?.agencyMode?.name), }, { - align: 'left', name: 'price', label: t('list.price'), cardVisible: true, @@ -114,9 +114,11 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + columnClass: 'shrink-column', + component: 'number', }, { - align: 'left', + align: 'center', name: 'hour', label: t('list.close'), cardVisible: true, @@ -129,6 +131,7 @@ const columns = computed(() => [ label: t('list.addressFk'), cardVisible: true, columnFilter: false, + columnClass: 'expand', }, { align: 'right', @@ -177,67 +180,73 @@ function formatRow(row) { <ZoneFilterPanel data-key="ZonesList" /> </template> </RightMenu> - <VnTable - ref="tableRef" - data-key="ZonesList" - url="Zones" - :create="{ - urlCreate: 'Zones', - title: t('list.createZone'), - onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`), - formInitialData: {}, - }" - :user-filter="tableFilter" - :columns="columns" - redirect="zone" - :right-search="false" - > - <template #column-addressFk="{ row }"> - {{ dashIfEmpty(formatRow(row)) }} - </template> - <template #more-create-dialog="{ data }"> - <VnSelect - url="AgencyModes" - v-model="data.agencyModeFk" - option-value="id" - option-label="name" - :label="t('list.agency')" - /> - <VnInput - v-model="data.price" - :label="t('list.price')" - min="0" - type="number" - required="true" - /> - <VnInput - v-model="data.bonus" - :label="t('zone.bonus')" - min="0" - type="number" - /> - <VnInput - v-model="data.travelingDays" - :label="t('zone.travelingDays')" - type="number" - min="0" - /> - <VnInputTime v-model="data.hour" :label="t('list.close')" /> - <VnSelect - url="Warehouses" - v-model="data.warehouseFK" - option-value="id" - option-label="name" - :label="t('list.warehouse')" - :options="warehouseOptions" - /> - <QCheckbox - v-model="data.isVolumetric" - :label="t('list.isVolumetric')" - :toggle-indeterminate="false" - /> - </template> - </VnTable> + <div class="table-container"> + <div class="column items-center"> + <VnTable + ref="tableRef" + data-key="ZonesList" + url="Zones" + :create="{ + urlCreate: 'Zones', + title: t('list.createZone'), + onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`), + formInitialData: {}, + }" + :user-filter="tableFilter" + :columns="columns" + redirect="zone" + :right-search="false" + table-height="85vh" + order="id ASC" + > + <template #column-addressFk="{ row }"> + {{ dashIfEmpty(formatRow(row)) }} + </template> + <template #more-create-dialog="{ data }"> + <VnSelect + url="AgencyModes" + v-model="data.agencyModeFk" + option-value="id" + option-label="name" + :label="t('list.agency')" + /> + <VnInput + v-model="data.price" + :label="t('list.price')" + min="0" + type="number" + required="true" + /> + <VnInput + v-model="data.bonus" + :label="t('zone.bonus')" + min="0" + type="number" + /> + <VnInput + v-model="data.travelingDays" + :label="t('zone.travelingDays')" + type="number" + min="0" + /> + <VnInputTime v-model="data.hour" :label="t('list.close')" /> + <VnSelect + url="Warehouses" + v-model="data.warehouseFK" + option-value="id" + option-label="name" + :label="t('list.warehouse')" + :options="warehouseOptions" + /> + <QCheckbox + v-model="data.isVolumetric" + :label="t('list.isVolumetric')" + :toggle-indeterminate="false" + /> + </template> + </VnTable> + </div> + </div> </template> <i18n> @@ -245,3 +254,20 @@ es: Search zone: Buscar zona You can search zones by id or name: Puedes buscar zonas por id o nombre </i18n> + +<style lang="scss" scoped> +.table-container { + display: flex; + justify-content: center; +} +.column { + display: flex; + flex-direction: column; + align-items: center; + min-width: 70%; +} + +:deep(.shrink-column) { + width: 8%; +} +</style> diff --git a/src/pages/Zone/ZoneUpcoming.vue b/src/pages/Zone/ZoneUpcoming.vue index c74ae6078..adcdfbc04 100644 --- a/src/pages/Zone/ZoneUpcoming.vue +++ b/src/pages/Zone/ZoneUpcoming.vue @@ -56,7 +56,7 @@ onMounted(() => weekdayStore.initStore()); <ZoneSearchbar /> <VnSubToolbar /> <QPage class="column items-center q-pa-md"> - <QCard class="full-width q-pa-md"> + <QCard class="containerShrinked q-pa-md"> <div v-for="(detail, index) in details" :key="index" diff --git a/src/pages/Zone/locale/en.yml b/src/pages/Zone/locale/en.yml index 5fd1a3ea7..e53e7b560 100644 --- a/src/pages/Zone/locale/en.yml +++ b/src/pages/Zone/locale/en.yml @@ -44,6 +44,8 @@ summary: filterPanel: name: Name agencyModeFk: Agency + id: ID + price: Price deliveryPanel: pickup: Pick up delivery: Delivery diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml index 575b12f7a..bc31e74a9 100644 --- a/src/pages/Zone/locale/es.yml +++ b/src/pages/Zone/locale/es.yml @@ -45,6 +45,8 @@ summary: filterPanel: name: Nombre agencyModeFk: Agencia + id: ID + price: Precio deliveryPanel: pickup: Recogida delivery: Entrega diff --git a/test/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/myEntry.spec.js index 49d75cf39..ed469d9e2 100644 --- a/test/cypress/integration/entry/myEntry.spec.js +++ b/test/cypress/integration/entry/myEntry.spec.js @@ -8,11 +8,9 @@ describe('EntryMy when is supplier', () => { }, }); }); - + it('should open buyLabel when is supplier', () => { - cy.get( - '[to="/null/3"] > .q-card > :nth-child(2) > .q-btn > .q-btn__content > .q-icon' - ).click(); + cy.dataCy('cardBtn').eq(2).click(); cy.dataCy('printLabelsBtn').click(); cy.window().its('open').should('be.called'); }); diff --git a/test/cypress/integration/ticket/ticketSale.spec.js b/test/cypress/integration/ticket/ticketSale.spec.js index aed8dc85a..63562bd26 100644 --- a/test/cypress/integration/ticket/ticketSale.spec.js +++ b/test/cypress/integration/ticket/ticketSale.spec.js @@ -1,122 +1,208 @@ /// <reference types="cypress" /> describe('TicketSale', () => { - beforeEach(() => { - cy.login('developer'); - cy.viewport(1920, 1080); - cy.visit('/#/ticket/31/sale'); - }); - - const firstRow = 'tbody > :nth-child(1)'; - - const selectFirstRow = () => { - cy.waitForElement(firstRow); - cy.get(firstRow).find('.q-checkbox__inner').click(); - }; - - it('it should add item to basket', () => { - cy.window().then((win) => { - cy.stub(win, 'open').as('windowOpen'); + describe('Free ticket #31', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/sale'); }); - cy.dataCy('ticketSaleAddToBasketBtn').should('exist'); - cy.dataCy('ticketSaleAddToBasketBtn').click(); - cy.get('@windowOpen').should('be.calledWithMatch', /\/order\/\d+\/catalog/); - }); - it('should send SMS', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="sendShortageSMSItem"]'); - cy.dataCy('sendShortageSMSItem').should('exist'); - cy.dataCy('sendShortageSMSItem').click(); - cy.dataCy('vnSmsDialog').should('exist'); - cy.dataCy('sendSmsBtn').click(); - cy.checkNotification('SMS sent'); - }); + const firstRow = 'tbody > :nth-child(1)'; - it('should recalculate price when "Recalculate price" is clicked', () => { - cy.intercept('POST', '**/recalculatePrice').as('recalculatePrice'); - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="recalculatePriceItem"]'); - cy.dataCy('recalculatePriceItem').should('exist'); - cy.dataCy('recalculatePriceItem').click(); - cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200); - cy.checkNotification('Data saved'); - }); + const selectFirstRow = () => { + cy.waitForElement(firstRow); + cy.get(firstRow).find('.q-checkbox__inner').click(); + }; - it('should update discount when "Update discount" is clicked', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="updateDiscountItem"]'); - cy.dataCy('updateDiscountItem').should('exist'); - cy.dataCy('updateDiscountItem').click(); - cy.waitForElement('[data-cy="ticketSaleDiscountInput"]'); - cy.dataCy('ticketSaleDiscountInput').find('input').focus(); - cy.dataCy('ticketSaleDiscountInput').find('input').type('10'); - cy.dataCy('saveManaBtn').click(); - cy.waitForElement('.q-notification__message'); - cy.checkNotification('Data saved'); - }); + it('it should add item to basket', () => { + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen'); + }); + cy.dataCy('ticketSaleAddToBasketBtn').should('exist'); + cy.dataCy('ticketSaleAddToBasketBtn').click(); + cy.get('@windowOpen').should('be.calledWithMatch', /\/order\/\d+\/catalog/); + }); - it('adds claim', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.dataCy('createClaimItem').click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.url().should('contain', 'claim/'); - // Delete created claim to avoid cluttering the database - cy.dataCy('descriptor-more-opts').click(); - cy.dataCy('deleteClaim').click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.checkNotification('Data deleted'); - }); + it('should send SMS', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="sendShortageSMSItem"]'); + cy.dataCy('sendShortageSMSItem').should('exist'); + cy.dataCy('sendShortageSMSItem').click(); + cy.dataCy('vnSmsDialog').should('exist'); + cy.dataCy('sendSmsBtn').click(); + cy.checkNotification('SMS sent'); + }); - it('marks row as reserved', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="markAsReservedItem"]'); - cy.dataCy('markAsReservedItem').click(); - cy.dataCy('ticketSaleReservedIcon').should('exist'); - }); + it('should recalculate price when "Recalculate price" is clicked', () => { + cy.intercept('POST', '**/recalculatePrice').as('recalculatePrice'); + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="recalculatePriceItem"]'); + cy.dataCy('recalculatePriceItem').should('exist'); + cy.dataCy('recalculatePriceItem').click(); + cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200); + cy.checkNotification('Data saved'); + }); - it('unmarks row as reserved', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="unmarkAsReservedItem"]'); - cy.dataCy('unmarkAsReservedItem').click(); - cy.dataCy('ticketSaleReservedIcon').should('not.exist'); - }); + it('should update discount when "Update discount" is clicked', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="updateDiscountItem"]'); + cy.dataCy('updateDiscountItem').should('exist'); + cy.dataCy('updateDiscountItem').click(); + cy.waitForElement('[data-cy="ticketSaleDiscountInput"]'); + cy.dataCy('ticketSaleDiscountInput').find('input').focus(); + cy.dataCy('ticketSaleDiscountInput').find('input').type('10'); + cy.dataCy('saveManaBtn').click(); + cy.waitForElement('.q-notification__message'); + cy.checkNotification('Data saved'); + }); - it('refunds row with warehouse', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.dataCy('ticketSaleRefundItem').click(); - cy.dataCy('ticketSaleRefundWithWarehouse').click(); - cy.checkNotification('The following refund ticket have been created'); - }); + it('adds claim', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('createClaimItem').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.url().should('contain', 'claim/'); + // Delete created claim to avoid cluttering the database + cy.dataCy('descriptor-more-opts').click(); + cy.dataCy('deleteClaim').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.checkNotification('Data deleted'); + }); - it('refunds row without warehouse', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.dataCy('ticketSaleRefundItem').click(); - cy.dataCy('ticketSaleRefundWithoutWarehouse').click(); - cy.checkNotification('The following refund ticket have been created'); - }); + it('marks row as reserved', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="markAsReservedItem"]'); + cy.dataCy('markAsReservedItem').click(); + cy.dataCy('ticketSaleReservedIcon').should('exist'); + }); - it('transfer sale to a new ticket', () => { - cy.visit('/#/ticket/32/sale'); - cy.get('.q-item > .q-item__label').should('have.text', ' #32'); - selectFirstRow(); - cy.dataCy('ticketSaleTransferBtn').click(); - cy.dataCy('ticketTransferPopup').should('exist'); - cy.dataCy('ticketTransferNewTicketBtn').click(); - //check the new ticket has been created succesfully - cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); - }); + it('unmarks row as reserved', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="unmarkAsReservedItem"]'); + cy.dataCy('unmarkAsReservedItem').click(); + cy.dataCy('ticketSaleReservedIcon').should('not.exist'); + }); - it('should redirect to ticket logs', () => { - cy.get(firstRow).find('.q-btn:last').click(); - cy.url().should('match', /\/ticket\/31\/log/); + it('refunds row with warehouse', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('ticketSaleRefundItem').click(); + cy.dataCy('ticketSaleRefundWithWarehouse').click(); + cy.checkNotification('The following refund ticket have been created'); + }); + + it('refunds row without warehouse', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('ticketSaleRefundItem').click(); + cy.dataCy('ticketSaleRefundWithoutWarehouse').click(); + cy.checkNotification('The following refund ticket have been created'); + }); + + it('transfer sale to a new ticket', () => { + cy.visit('/#/ticket/32/sale'); + cy.get('.q-item > .q-item__label').should('have.text', ' #32'); + selectFirstRow(); + cy.dataCy('ticketSaleTransferBtn').click(); + cy.dataCy('ticketTransferPopup').should('exist'); + cy.dataCy('ticketTransferNewTicketBtn').click(); + //check the new ticket has been created succesfully + cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); + }); + + it('should redirect to ticket logs', () => { + cy.get(firstRow).find('.q-btn:last').click(); + cy.url().should('match', /\/ticket\/31\/log/); + }); + }); + describe('Ticket prepared #23', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/23/sale'); + }); + + const firstRow = 'tbody > :nth-child(1)'; + + const selectFirstRow = () => { + cy.waitForElement(firstRow); + cy.get(firstRow).find('.q-checkbox__inner').click(); + }; + + it('update price', () => { + const price = Number((Math.random() * 99 + 1).toFixed(2)); + cy.waitForElement(firstRow); + cy.get(':nth-child(10) > .q-btn').click(); + cy.waitForElement('[data-cy="ticketEditManaProxy"]'); + cy.dataCy('ticketEditManaProxy').should('exist'); + cy.waitForElement('[data-cy="Price_input"]'); + cy.dataCy('Price_input').clear(); + cy.dataCy('Price_input').type(price); + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + + cy.get(':nth-child(10) > .q-btn > .q-btn__content').should( + 'have.text', + `€${price}`, + ); + }); + it('update dicount', () => { + const discount = Math.floor(Math.random() * 100) + 1; + selectFirstRow(); + cy.get(':nth-child(11) > .q-btn').click(); + cy.waitForElement('[data-cy="ticketEditManaProxy"]'); + cy.dataCy('ticketEditManaProxy').should('exist'); + cy.waitForElement('[data-cy="Disc_input"]'); + cy.dataCy('Disc_input').clear(); + cy.dataCy('Disc_input').type(discount); + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + + cy.get(':nth-child(11) > .q-btn > .q-btn__content').should( + 'have.text', + `${discount}.00%`, + ); + }); + + it('change concept', () => { + const quantity = Math.floor(Math.random() * 100) + 1; + cy.waitForElement(firstRow); + cy.get(':nth-child(8) > .row').click(); + cy.get( + '.q-menu > [data-v-ca3f07a4=""] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="undefined_input"]', + ) + .type(quantity) + .type('{enter}'); + handleVnConfirm(); + + cy.get(':nth-child(8) >.row').should('contain.text', `${quantity}`); + }); + it('changequantity ', () => { + const quantity = Math.floor(Math.random() * 100) + 1; + cy.waitForElement(firstRow); + cy.dataCy('ticketSaleQuantityInput').clear(); + cy.dataCy('ticketSaleQuantityInput').type(quantity).trigger('tab'); + cy.get('.q-page > :nth-child(6)').click(); + + handleVnConfirm(); + + cy.get('[data-cy="ticketSaleQuantityInput"]') + .find('[data-cy="undefined_input"]') + .should('have.value', `${quantity}`); + }); }); }); + +function handleVnConfirm() { + cy.get('[data-cy="VnConfirm_confirm"] > .q-btn__content > .block').click(); + cy.waitForElement('.q-notification__message'); + + cy.get('.q-notification__message').should('be.visible'); + cy.checkNotification('Data saved'); +} diff --git a/test/cypress/integration/zone/zoneList.spec.js b/test/cypress/integration/zone/zoneList.spec.js index 8d01d4e4e..68e924635 100644 --- a/test/cypress/integration/zone/zoneList.spec.js +++ b/test/cypress/integration/zone/zoneList.spec.js @@ -1,4 +1,5 @@ describe('ZoneList', () => { + const agency = 'inhouse pickup'; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -6,11 +7,15 @@ describe('ZoneList', () => { }); it('should filter by agency', () => { - cy.get('input[aria-label="Agency"]').type('{downArrow}{enter}'); + cy.dataCy('zoneFilterPanelNameInput').type('{downArrow}{enter}'); }); it('should open the zone summary', () => { - cy.get('input[aria-label="Name"]').type('zone refund'); - cy.get('.q-scrollarea__content > .q-btn--standard > .q-btn__content').click(); + cy.dataCy('zoneFilterPanelAgencySelect').type(agency); + cy.get('.q-menu .q-item').contains(agency).click(); + cy.get(':nth-child(1) > [data-col-field="agencyModeFk"]').should( + 'include.text', + agency, + ); }); });