diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 6a547d95dbc..c6421719833 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -140,7 +140,7 @@ const $props = defineProps({ }, dataCy: { type: String, - default: 'vn-table', + default: 'vnTable', }, }); @@ -684,7 +684,7 @@ const rowCtrlClickFunction = computed(() => { @update:selected="emit('update:selected', $event)" @selection="(details) => handleSelection(details, rows)" :hide-selected-banner="true" - :data-cy="$props.dataCy ?? 'vnTable'" + :data-cy > <template #top-left v-if="!$props.withoutHeader"> <slot name="top-left"> </slot> @@ -781,6 +781,7 @@ const rowCtrlClickFunction = computed(() => { text-overflow: ellipsis; white-space: nowrap; " + :data-cy="`vnTableCell_${col.name}`" > <slot :name="`column-${col.name}`" diff --git a/src/components/common/VnCheckbox.vue b/src/components/common/VnCheckbox.vue index 94e91328b6c..daaf891dce2 100644 --- a/src/components/common/VnCheckbox.vue +++ b/src/components/common/VnCheckbox.vue @@ -27,7 +27,11 @@ const checkboxModel = computed({ </script> <template> <div> - <QCheckbox v-bind="$attrs" v-model="checkboxModel" /> + <QCheckbox + v-bind="$attrs" + v-model="checkboxModel" + :data-cy="$attrs['data-cy'] ?? `vnCheckbox${$attrs['label'] ?? ''}`" + /> <QIcon v-if="info" v-bind="$attrs" diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 1f4705faa15..343130f1d92 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -107,7 +107,7 @@ const manageDate = (date) => { @click="isPopupOpen = !isPopupOpen" @keydown="isPopupOpen = false" hide-bottom-space - :data-cy="$attrs.dataCy ?? $attrs.label + '_inputDate'" + :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'" > <template #append> <QIcon diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 57d8b042139..cf74e4485e1 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -151,7 +151,7 @@ const toModule = computed(() => { </script> <template> - <div class="descriptor"> + <div class="descriptor" data-cy="cardDescriptor"> <template v-if="entity && !isLoading"> <div class="header bg-primary q-pa-sm justify-between"> <slot name="header-extra-action"> @@ -213,18 +213,27 @@ const toModule = computed(() => { <QList dense> <QItemLabel header class="ellipsis text-h5" :lines="1"> <div class="title"> - <span v-if="$props.title" :title="getValueFromPath(title)"> + <span + v-if="$props.title" + :title="getValueFromPath(title)" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_title`" + > {{ getValueFromPath(title) ?? $props.title }} </span> <slot v-else name="description" :entity="entity"> - <span :title="entity.name"> - {{ entity.name }} - </span> + <span + :title="entity.name" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_description`" + v-text="entity.name" + /> </slot> </div> </QItemLabel> <QItem> - <QItemLabel class="subtitle" data-cy="descriptor_id"> + <QItemLabel + class="subtitle" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_subtitle`" + > #{{ getValueFromPath(subtitle) ?? entity.id }} </QItemLabel> <QBtn @@ -242,7 +251,10 @@ const toModule = computed(() => { </QBtn> </QItem> </QList> - <div class="list-box q-mt-xs"> + <div + class="list-box q-mt-xs" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_listbox`" + > <slot name="body" :entity="entity" /> </div> </div> diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 93e3a57f21f..85cc8cde2f8 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -249,7 +249,7 @@ const getLocale = (label) => { :key="chip.label" :removable="!unremovableParams?.includes(chip.label)" @remove="remove(chip.label)" - data-cy="vnFilterPanelChip" + :data-cy="`vnFilterPanelChip_${chip.label}`" > <slot name="tags" diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index a198c9c05c3..50da8a14367 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -28,7 +28,7 @@ function copyValueText() { const val = computed(() => $props.value); </script> <template> - <div class="vn-label-value"> + <div class="vn-label-value" :data-cy="`${$attrs['data-cy'] ?? 'vnLv'}${label ?? ''}`"> <QCheckbox v-if="typeof value === 'boolean'" v-model="val" diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue index 8a1c7a0f2ec..984e2b64f62 100644 --- a/src/components/ui/VnMoreOptions.vue +++ b/src/components/ui/VnMoreOptions.vue @@ -12,7 +12,7 @@ {{ $t('components.cardDescriptor.moreOptions') }} </QTooltip> <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> - <QList> + <QList data-cy="descriptor-more-opts_list"> <slot name="menu" :menu-ref="$refs.menuRef" /> </QList> </QMenu> diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index 905ddebb2b0..dc963a91b7f 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -121,25 +121,40 @@ function deleteFile(dmsFk) { hide-selected :is-clearable="false" :required="true" + data-cy="invoiceInBasicDataSupplier" /> <VnInput clearable clear-icon="close" :label="t('invoiceIn.supplierRef')" v-model="data.supplierRef" + data-cy="invoiceInBasicDataSupplierRef" /> </VnRow> <VnRow> - <VnInputDate :label="t('Expedition date')" v-model="data.issued" /> + <VnInputDate + :label="t('Expedition date')" + v-model="data.issued" + data-cy="invoiceInBasicDataIssued" + /> <VnInputDate :label="t('Operation date')" v-model="data.operated" autofocus + data-cy="invoiceInBasicDataOperated" /> </VnRow> <VnRow> - <VnInputDate :label="t('Entry date')" v-model="data.bookEntried" /> - <VnInputDate :label="t('Accounted date')" v-model="data.booked" /> + <VnInputDate + :label="t('Entry date')" + v-model="data.bookEntried" + data-cy="invoiceInBasicDatabookEntried" + /> + <VnInputDate + :label="t('Accounted date')" + v-model="data.booked" + data-cy="invoiceInBasicDataBooked" + /> </VnRow> <VnRow> <VnSelect @@ -149,7 +164,7 @@ function deleteFile(dmsFk) { option-value="id" option-label="id" :filter-options="['id', 'name']" - data-cy="UnDeductibleVatSelect" + data-cy="invoiceInBasicDataDeductibleExpenseFk" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -182,6 +197,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="downloadFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDownload" /> <QBtn :class="{ @@ -197,6 +213,7 @@ function deleteFile(dmsFk) { documentDialogRef.dms = data.dms; } " + data-cy="invoiceInBasicDataDmsEdit" > <QTooltip>{{ t('Edit document') }}</QTooltip> </QBtn> @@ -210,6 +227,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="deleteFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDelete" /> </div> <QBtn @@ -224,7 +242,7 @@ function deleteFile(dmsFk) { delete documentDialogRef.dms; } " - data-cy="dms-create" + data-cy="invoiceInBasicDataDmsAdd" > <QTooltip>{{ t('Create document') }}</QTooltip> </QBtn> @@ -237,9 +255,9 @@ function deleteFile(dmsFk) { :label="t('Currency')" v-model="data.currencyFk" :options="currencies" - option-value="id" option-label="code" sort-by="id" + data-cy="invoiceInBasicDataCurrencyFk" /> <VnSelect @@ -249,8 +267,8 @@ function deleteFile(dmsFk) { :label="t('Company')" v-model="data.companyFk" :options="companies" - option-value="id" option-label="code" + data-cy="invoiceInBasicDataCompanyFk" /> </VnRow> <VnRow> @@ -260,6 +278,7 @@ function deleteFile(dmsFk) { :options="sageWithholdings" option-value="id" option-label="withholding" + data-cy="invoiceInBasicDataWithholdingSageFk" /> </VnRow> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue index 1d0a8d078d0..775a2a72b6c 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue @@ -1,22 +1,16 @@ <script setup> import { ref, computed, capitalize } from 'vue'; -import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; import CrudModel from 'src/components/CrudModel.vue'; import FetchData from 'src/components/FetchData.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -const route = useRoute(); const { t } = useI18n(); const arrayData = useArrayData(); const invoiceIn = computed(() => arrayData.store.data); const invoiceInCorrectionRef = ref(); -const filter = { - include: { relation: 'invoiceIn' }, - where: { correctingFk: route.params.id }, -}; const columns = computed(() => [ { name: 'origin', @@ -92,7 +86,8 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); v-if="invoiceIn" data-key="InvoiceInCorrection" url="InvoiceInCorrections" - :filter="filter" + :user-filter="{ include: { relation: 'invoiceIn' } }" + :filter="{ where: { correctingFk: $route.params.id } }" auto-load primary-key="correctingFk" :default-remove="false" @@ -115,6 +110,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); :option-label="col.optionLabel" :disable="row.invoiceIn.isBooked" :filter-options="['description']" + data-cy="invoiceInCorrective_type" > <template #option="{ opt, itemProps }"> <QItem v-bind="itemProps"> @@ -137,6 +133,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); :rules="[requiredFieldRule]" :filter-options="['code', 'description']" :disable="row.invoiceIn.isBooked" + data-cy="invoiceInCorrective_class" > <template #option="{ opt, itemProps }"> <QItem v-bind="itemProps"> @@ -161,6 +158,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); :option-label="col.optionLabel" :rules="[requiredFieldRule]" :disable="row.invoiceIn.isBooked" + data-cy="invoiceInCorrective_reason" /> </QTd> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index 3843f5bf713..eb673c546cb 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -17,10 +17,6 @@ const { t } = useI18n(); const cardDescriptorRef = ref(); const entityId = computed(() => $props.id || +currentRoute.value.params.id); const totalAmount = ref(); -const config = ref(); -const cplusRectificationTypes = ref([]); -const siiTypeInvoiceIns = ref([]); -const invoiceCorrectionTypes = ref([]); const invoiceInCorrection = reactive({ correcting: [], corrected: null }); const routes = reactive({ getSupplier: (id) => { @@ -30,7 +26,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ supplierFk: id }), + table: JSON.stringify({ supplierFk: id }), }, }; }, @@ -39,7 +35,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ correctedFk: entityId.value }), + table: JSON.stringify({ correctedFk: entityId.value }), }, }; } @@ -108,7 +104,7 @@ async function setInvoiceCorrection(id) { <VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" /> <VnLv :label="t('invoiceIn.list.supplier')"> <template #value> - <span class="link"> + <span class="link" data-cy="invoiceInDescriptor_supplier"> {{ entity?.supplier?.nickname }} <SupplierDescriptorProxy :id="entity?.supplierFk" /> </span> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue index 8b039ec2726..058f17d31b4 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, toRefs, reactive } from 'vue'; +import { ref, computed, toRefs, reactive, onBeforeMount } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -111,10 +111,9 @@ async function cloneInvoice() { } const isAgricultural = () => { - if (!config.value) return false; return ( - invoiceIn.value?.supplier?.sageFarmerWithholdingFk === - config?.value[0]?.sageWithholdingFk + invoiceIn.value?.supplier?.sageWithholdingFk == + config.value?.sageFarmerWithholdingFk ); }; function showPdfInvoice() { @@ -153,162 +152,183 @@ const createInvoiceInCorrection = async () => { ); push({ path: `/invoice-in/${correctingId}/summary` }); }; + +onBeforeMount(async () => { + config.value = ( + await axios.get('invoiceinConfigs/findOne', { + params: { fields: ['sageFarmerWithholdingFk'] }, + }) + ).data; +}); </script> - <template> - <FetchData - url="InvoiceCorrectionTypes" - @on-fetch="(data) => (invoiceCorrectionTypes = data)" - auto-load - /> - <FetchData - url="CplusRectificationTypes" - @on-fetch="(data) => (cplusRectificationTypes = data)" - auto-load - /> - <FetchData - url="SiiTypeInvoiceIns" - :where="{ code: { like: 'R%' } }" - @on-fetch="(data) => (siiTypeInvoiceIns = data)" - auto-load - /> - <FetchData - url="InvoiceInConfigs" - :where="{ fields: ['sageWithholdingFk'] }" - auto-load - @on-fetch="(data) => (config = data)" - /> - <InvoiceInToBook> - <template #content="{ book }"> - <QItem - v-if="!invoice?.isBooked && canEditProp('toBook')" - v-ripple - clickable - @click="book(entityId)" + <template v-if="config"> + <FetchData + url="InvoiceCorrectionTypes" + @on-fetch="(data) => (invoiceCorrectionTypes = data)" + auto-load + /> + <FetchData + url="CplusRectificationTypes" + @on-fetch="(data) => (cplusRectificationTypes = data)" + auto-load + /> + <FetchData + url="SiiTypeInvoiceIns" + :where="{ code: { like: 'R%' } }" + @on-fetch="(data) => (siiTypeInvoiceIns = data)" + auto-load + /> + <InvoiceInToBook> + <template #content="{ book }"> + <QItem + v-if="!invoice?.isBooked && canEditProp('toBook')" + v-ripple + clickable + @click="book(entityId)" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> + </QItem> + </template> + </InvoiceInToBook> + <QItem + v-if="invoice?.isBooked && canEditProp('toUnbook')" + v-ripple + clickable + @click="triggerMenu('unbook')" + > + <QItemSection> + {{ t('invoiceIn.descriptorMenu.unbook') }} + </QItemSection> + </QItem> + <QItem + v-if="canEditProp('deleteById')" + v-ripple + clickable + @click="triggerMenu('delete')" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection> + </QItem> + <QItem + v-if="canEditProp('clone')" + v-ripple + clickable + @click="triggerMenu('clone')" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> + <QItemSection>{{ + t('invoiceIn.descriptorMenu.showAgriculturalPdf') + }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> + <QItemSection + >{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection > - <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> - </QItem> - </template> - </InvoiceInToBook> - <QItem - v-if="invoice?.isBooked && canEditProp('toUnbook')" - v-ripple - clickable - @click="triggerMenu('unbook')" - > - <QItemSection> - {{ t('invoiceIn.descriptorMenu.unbook') }} - </QItemSection> - </QItem> - <QItem - v-if="canEditProp('deleteById')" - v-ripple - clickable - @click="triggerMenu('delete')" - > - <QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection> - </QItem> - <QItem v-if="canEditProp('clone')" v-ripple clickable @click="triggerMenu('clone')"> - <QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection> - </QItem> - <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> - <QItemSection>{{ - t('invoiceIn.descriptorMenu.showAgriculturalPdf') - }}</QItemSection> - </QItem> - <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> - <QItemSection - >{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection + </QItem> + <QItem + v-if="!invoiceInCorrection.corrected" + v-ripple + clickable + @click="triggerMenu('correct')" + data-cy="createCorrectiveItem" > - </QItem> - <QItem - v-if="!invoiceInCorrection.corrected" - v-ripple - clickable - @click="triggerMenu('correct')" - data-cy="createCorrectiveItem" - > - <QItemSection - >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection + <QItemSection + >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection + > + </QItem> + <QItem + v-if="invoice.dmsFk" + v-ripple + clickable + @click="downloadFile(invoice.dmsFk)" > - </QItem> - <QItem v-if="invoice.dmsFk" v-ripple clickable @click="downloadFile(invoice.dmsFk)"> - <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> - </QItem> - <QDialog ref="correctionDialogRef"> - <QCard> - <QCardSection> - <QItem class="q-px-none"> - <span class="text-primary text-h6 full-width"> - {{ t('Create rectificative invoice') }} - </span> - <QBtn icon="close" flat round dense v-close-popup /> - </QItem> - </QCardSection> - <QCardSection> - <QItem> - <QItemSection> - <QInput - :label="t('Original invoice')" - v-model="entityId" - readonly - /> - <VnSelect - :label="`${useCapitalize(t('globals.class'))}`" - v-model="correctionFormData.invoiceClass" - :options="siiTypeInvoiceIns" - option-value="id" - option-label="code" - :required="true" - /> - </QItemSection> - <QItemSection> - <VnSelect - :label="`${useCapitalize(t('globals.type'))}`" - v-model="correctionFormData.invoiceType" - :options="cplusRectificationTypes" - option-value="id" - option-label="description" - :required="true" - > - <template #option="{ itemProps, opt }"> - <QItem v-bind="itemProps"> - <QItemSection> - <QItemLabel - >{{ opt.id }} - - {{ opt.description }}</QItemLabel - > - </QItemSection> - </QItem> - <div></div> - </template> - </VnSelect> + <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> + </QItem> + <QDialog ref="correctionDialogRef"> + <QCard data-cy="correctiveInvoiceDialog"> + <QCardSection> + <QItem class="q-px-none"> + <span class="text-primary text-h6 full-width"> + {{ t('Create rectificative invoice') }} + </span> + <QBtn icon="close" flat round dense v-close-popup /> + </QItem> + </QCardSection> + <QCardSection> + <QItem> + <QItemSection> + <QInput + :label="t('Original invoice')" + v-model="entityId" + readonly + /> + <VnSelect + :label="`${useCapitalize(t('globals.class'))}`" + v-model="correctionFormData.invoiceClass" + :options="siiTypeInvoiceIns" + option-value="id" + option-label="code" + :required="true" + data-cy="invoiceInDescriptorMenu_class" + /> + </QItemSection> + <QItemSection> + <VnSelect + :label="`${useCapitalize(t('globals.type'))}`" + v-model="correctionFormData.invoiceType" + :options="cplusRectificationTypes" + option-value="id" + option-label="description" + :required="true" + data-cy="invoiceInDescriptorMenu_type" + > + <template #option="{ itemProps, opt }"> + <QItem v-bind="itemProps"> + <QItemSection> + <QItemLabel + >{{ opt.id }} - + {{ opt.description }}</QItemLabel + > + </QItemSection> + </QItem> + <div></div> + </template> + </VnSelect> - <VnSelect - :label="`${useCapitalize(t('globals.reason'))}`" - v-model="correctionFormData.invoiceReason" - :options="invoiceCorrectionTypes" - option-value="id" - option-label="description" - :required="true" - /> - </QItemSection> - </QItem> - </QCardSection> - <QCardActions class="justify-end q-mr-sm"> - <QBtn flat :label="t('globals.close')" color="primary" v-close-popup /> - <QBtn - :label="t('globals.save')" - color="primary" - v-close-popup - @click="createInvoiceInCorrection" - :disable="isNotFilled" - /> - </QCardActions> - </QCard> - </QDialog> + <VnSelect + :label="`${useCapitalize(t('globals.reason'))}`" + v-model="correctionFormData.invoiceReason" + :options="invoiceCorrectionTypes" + option-value="id" + option-label="description" + :required="true" + data-cy="invoiceInDescriptorMenu_reason" + /> + </QItemSection> + </QItem> + </QCardSection> + <QCardActions class="justify-end q-mr-sm"> + <QBtn + flat + :label="t('globals.close')" + color="primary" + v-close-popup + /> + <QBtn + :label="t('globals.save')" + color="primary" + v-close-popup + @click="createInvoiceInCorrection" + :disable="isNotFilled" + data-cy="saveCorrectiveInvoice" + /> + </QCardActions> + </QCard> + </QDialog> + </template> </template> - <i18n> en: isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 18602f04324..f6beecd3d4a 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -198,6 +198,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; color="orange-11" text-color="black" @click="book(entityId)" + data-cy="invoiceInSummary_book" /> </template> </InvoiceIntoBook> @@ -219,7 +220,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :value="entity.supplier?.name" > <template #value> - <span class="link"> + <span class="link" data-cy="invoiceInSummary_supplier"> {{ entity.supplier?.name }} <SupplierDescriptorProxy :id="entity.supplierFk" /> </span> diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index eae255120d9..e37cf5b7e57 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -202,6 +202,9 @@ function setCursor(ref) { :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" + :acls="[ + { model: 'Expense', props: '*', accessType: 'WRITE' }, + ]" @keydown.tab.prevent=" autocompleteExpense( $event, diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index e010a1edb39..a4fb0d653a8 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -7,6 +7,7 @@ import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import { dateRange } from 'src/filters'; import { date } from 'quasar'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; defineProps({ dataKey: { type: String, required: true } }); const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; @@ -147,13 +148,13 @@ function handleDaysAgo(params, daysAgo) { </QItem> <QItem> <QItemSection> - <QCheckbox + <VnCheckbox :label="$t('invoiceIn.isBooked')" v-model="params.isBooked" @update:model-value="searchFn()" toggle-indeterminate /> - <QCheckbox + <VnCheckbox :label="getLocale('params.correctingFk')" v-model="params.correctingFk" @update:model-value="searchFn()" diff --git a/src/pages/InvoiceIn/InvoiceInToBook.vue b/src/pages/InvoiceIn/InvoiceInToBook.vue index 5bdbe197b99..23175f2e735 100644 --- a/src/pages/InvoiceIn/InvoiceInToBook.vue +++ b/src/pages/InvoiceIn/InvoiceInToBook.vue @@ -4,7 +4,7 @@ import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import { useArrayData } from 'src/composables/useArrayData'; -import qs from 'qs'; + const { notify, dialog } = useQuasar(); const { t } = useI18n(); @@ -61,17 +61,15 @@ async function checkToBook(id) { } async function toBook(id) { - let type = 'positive'; - let message = t('globals.dataSaved'); - + let err = false; try { await axios.post(`InvoiceIns/${id}/toBook`); store.data.isBooked = true; } catch (e) { - type = 'negative'; - message = t('It was not able to book the invoice'); + err = true; + throw e; } finally { - notify({ type, message }); + if (!err) notify({ type: 'positive', message: t('globals.dataSaved') }); } } </script> diff --git a/src/pages/Zone/Card/ZoneEventExclusionForm.vue b/src/pages/Zone/Card/ZoneEventExclusionForm.vue index 3828c998f2f..582a8bbadd7 100644 --- a/src/pages/Zone/Card/ZoneEventExclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventExclusionForm.vue @@ -1,16 +1,18 @@ <script setup> -import { ref, computed, onMounted, reactive } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; +import { useQuasar } from 'quasar'; +import axios from 'axios'; +import moment from 'moment'; import VnRow from 'components/ui/VnRow.vue'; import FormPopup from 'components/FormPopup.vue'; import ZoneLocationsTree from './ZoneLocationsTree.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; - import { useArrayData } from 'src/composables/useArrayData'; import { useVnConfirm } from 'composables/useVnConfirm'; -import axios from 'axios'; +import { toDateFormat } from 'src/filters/date'; const props = defineProps({ date: { @@ -34,18 +36,25 @@ const props = defineProps({ type: Array, default: () => [], }, + isMasiveEdit: { + type: Boolean, + default: false, + }, + zoneIds: { + type: Array, + default: () => [], + }, }); const emit = defineEmits(['onSubmit', 'closeForm']); - +const quasar = useQuasar(); const route = useRoute(); const { t } = useI18n(); const { openConfirmationModal } = useVnConfirm(); const isNew = computed(() => props.isNewMode); -const dated = reactive(props.date); +const dated = ref(props.date || Date.vnNew()); const tickedNodes = ref(); - const _excludeType = ref('all'); const excludeType = computed({ get: () => _excludeType.value, @@ -63,16 +72,46 @@ const exclusionGeoCreate = async () => { geoIds: tickedNodes.value, }; await axios.post('Zones/exclusionGeo', params); + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); await refetchEvents(); }; const exclusionCreate = async () => { - const url = `Zones/${route.params.id}/exclusions`; + const defaultMonths = await axios.get('ZoneConfigs'); + const nMonths = defaultMonths.data[0].defaultMonths; const body = { - dated, + dated: dated.value, }; - if (isNew.value || props.event?.type) await axios.post(`${url}`, [body]); - else await axios.put(`${url}/${props.event?.id}`, body); + const zoneIds = props.zoneIds?.length ? props.zoneIds : [route.params.id]; + for (const id of zoneIds) { + const url = `Zones/${id}/exclusions`; + let today = moment(dated.value); + let lastDay = today.clone().add(nMonths, 'months').endOf('month'); + + const { data } = await axios.get(`Zones/getEventsFiltered`, { + params: { + zoneFk: id, + started: today, + ended: lastDay, + }, + }); + const existsEvent = data.events.find( + (event) => toDateFormat(event.dated) === toDateFormat(dated.value), + ); + if (existsEvent) { + await axios.delete(`Zones/${existsEvent?.zoneFk}/events/${existsEvent?.id}`); + } + + if (isNew.value || props.event?.type) await axios.post(`${url}`, [body]); + else await axios.put(`${url}/${props.event?.id}`, body); + } + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); await refetchEvents(); }; @@ -129,6 +168,7 @@ onMounted(() => { :label="t('eventsExclusionForm.all')" /> <QRadio + v-if="!props.isMasiveEdit" v-model="excludeType" dense val="specificLocations" diff --git a/src/pages/Zone/Card/ZoneEventInclusionForm.vue b/src/pages/Zone/Card/ZoneEventInclusionForm.vue index fb552bb9386..8b02c2d841f 100644 --- a/src/pages/Zone/Card/ZoneEventInclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventInclusionForm.vue @@ -2,6 +2,13 @@ import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; +import { useQuasar } from 'quasar'; +import axios from 'axios'; +import moment from 'moment'; + +import { useArrayData } from 'src/composables/useArrayData'; +import { useWeekdayStore } from 'src/stores/useWeekdayStore'; +import { useVnConfirm } from 'composables/useVnConfirm'; import VnRow from 'components/ui/VnRow.vue'; import FormPopup from 'components/FormPopup.vue'; @@ -9,11 +16,7 @@ import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnWeekdayPicker from 'src/components/common/VnWeekdayPicker.vue'; import VnInputTime from 'components/common/VnInputTime.vue'; import VnInput from 'src/components/common/VnInput.vue'; - -import { useArrayData } from 'src/composables/useArrayData'; -import { useWeekdayStore } from 'src/stores/useWeekdayStore'; -import { useVnConfirm } from 'composables/useVnConfirm'; -import axios from 'axios'; +import { toDateFormat } from 'src/filters/date'; const props = defineProps({ date: { @@ -32,6 +35,14 @@ const props = defineProps({ type: Boolean, default: true, }, + isMasiveEdit: { + type: Boolean, + default: false, + }, + zoneIds: { + type: Array, + default: () => [], + }, }); const emit = defineEmits(['onSubmit', 'closeForm']); @@ -40,10 +51,10 @@ const route = useRoute(); const { t } = useI18n(); const weekdayStore = useWeekdayStore(); const { openConfirmationModal } = useVnConfirm(); - +const quasar = useQuasar(); const isNew = computed(() => props.isNewMode); const eventInclusionFormData = ref({ wdays: [] }); - +const dated = ref(props.date || Date.vnNew()); const _inclusionType = ref('indefinitely'); const inclusionType = computed({ get: () => _inclusionType.value, @@ -56,8 +67,12 @@ const inclusionType = computed({ const arrayData = useArrayData('ZoneEvents'); const createEvent = async () => { + const defaultMonths = await axios.get('ZoneConfigs'); + const nMonths = defaultMonths.data[0].defaultMonths; + eventInclusionFormData.value.weekDays = weekdayStore.toSet( eventInclusionFormData.value.wdays, + eventInclusionFormData.value.wdays, ); if (inclusionType.value == 'day') eventInclusionFormData.value.weekDays = ''; @@ -68,14 +83,43 @@ const createEvent = async () => { eventInclusionFormData.value.ended = null; } - if (isNew.value) - await axios.post(`Zones/${route.params.id}/events`, eventInclusionFormData.value); - else - await axios.put( - `Zones/${route.params.id}/events/${props.event?.id}`, - eventInclusionFormData.value, - ); + const zoneIds = props.zoneIds?.length ? props.zoneIds : [route.params.id]; + for (const id of zoneIds) { + let today = eventInclusionFormData.value.dated + ? moment(eventInclusionFormData.value.dated) + : moment(dated.value); + let lastDay = today.clone().add(nMonths, 'months').endOf('month'); + const { data } = await axios.get(`Zones/getEventsFiltered`, { + params: { + zoneFk: id, + started: today, + ended: lastDay, + }, + }); + const existsExclusion = data.exclusions.find( + (exclusion) => + toDateFormat(exclusion.dated) === + toDateFormat(eventInclusionFormData.value.dated), + ); + if (existsExclusion) { + await axios.delete( + `Zones/${existsExclusion?.zoneFk}/exclusions/${existsExclusion?.id}`, + ); + } + + if (isNew.value) + await axios.post(`Zones/${id}/events`, eventInclusionFormData.value); + else + await axios.put( + `Zones/${id}/events/${props.event?.id}`, + eventInclusionFormData.value, + ); + } + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); await refetchEvents(); emit('onSubmit'); }; @@ -97,9 +141,11 @@ const refetchEvents = async () => { onMounted(() => { if (props.event) { + dated.value = props.event?.dated; eventInclusionFormData.value = { ...props.event }; inclusionType.value = props.event?.type || 'day'; } else if (props.date) { + dated.value = props.date; eventInclusionFormData.value.dated = props.date; inclusionType.value = 'day'; } else inclusionType.value = 'indefinitely'; @@ -125,6 +171,7 @@ onMounted(() => { data-cy="ZoneEventInclusionDayRadio" /> <QRadio + v-if="!props.isMasiveEdit" v-model="inclusionType" dense val="indefinitely" @@ -132,6 +179,7 @@ onMounted(() => { data-cy="ZoneEventInclusionIndefinitelyRadio" /> <QRadio + v-if="!props.isMasiveEdit" v-model="inclusionType" dense val="range" diff --git a/src/pages/Zone/Card/ZoneLocations.vue b/src/pages/Zone/Card/ZoneLocations.vue index 08b99df60b4..add9f6f5bba 100644 --- a/src/pages/Zone/Card/ZoneLocations.vue +++ b/src/pages/Zone/Card/ZoneLocations.vue @@ -34,9 +34,10 @@ const onSelected = async (val, node) => { node.selected ? '--checked' : node.selected == false - ? '--unchecked' - : '--indeterminate', + ? '--unchecked' + : '--indeterminate', ]" + data-cy="ZoneLocationTreeCheckbox" /> </template> </ZoneLocationsTree> diff --git a/src/pages/Zone/ZoneCalendarGrid.vue b/src/pages/Zone/ZoneCalendarGrid.vue index 91d2cc7ebc8..1ef687b3f19 100644 --- a/src/pages/Zone/ZoneCalendarGrid.vue +++ b/src/pages/Zone/ZoneCalendarGrid.vue @@ -42,7 +42,7 @@ const refreshEvents = () => { days.value = {}; if (!data.value) return; - let day = new Date(firstDay.value.getTime()); + let day = new Date(firstDay?.value?.getTime()); while (day <= lastDay.value) { let stamp = day.getTime(); @@ -156,7 +156,7 @@ watch( (value) => { data.value = value; }, - { immediate: true } + { immediate: true }, ); const getMonthNameAndYear = (date) => { diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index 869b0c12c94..8d7c4a165ac 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -14,7 +14,11 @@ import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputTime from 'src/components/common/VnInputTime.vue'; + import VnSection from 'src/components/common/VnSection.vue'; +import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; +import ZoneEventInclusionForm from './Card/ZoneEventInclusionForm.vue'; +import ZoneEventExclusionForm from './Card/ZoneEventExclusionForm.vue'; const { t } = useI18n(); const router = useRouter(); @@ -24,6 +28,11 @@ const { openConfirmationModal } = useVnConfirm(); const tableRef = ref(); const warehouseOptions = ref([]); const dataKey = 'ZoneList'; +const selectedRows = ref([]); +const hasSelectedRows = computed(() => selectedRows.value.length > 0); +const openInclusionForm = ref(); +const showZoneEventForm = ref(false); +const zoneIds = ref({}); const tableFilter = { include: [ { @@ -191,6 +200,16 @@ const exprBuilder = (param, value) => { }; } }; + +function openForm(value, rows) { + zoneIds.value = rows.map((row) => row.id); + openInclusionForm.value = value; + showZoneEventForm.value = true; +} + +const closeEventForm = () => { + showZoneEventForm.value = false; +}; </script> <template> @@ -206,6 +225,28 @@ const exprBuilder = (param, value) => { }" > <template #body> + <VnSubToolbar> + <template #st-actions> + <QBtnGroup style="column-gap: 10px"> + <QBtn + color="primary" + icon-right="event_available" + :disable="!hasSelectedRows" + @click="openForm(true, selectedRows)" + > + <QTooltip>{{ t('list.includeEvent') }}</QTooltip> + </QBtn> + <QBtn + color="primary" + icon-right="event_busy" + :disable="!hasSelectedRows" + @click="openForm(false, selectedRows)" + > + <QTooltip>{{ t('list.excludeEvent') }}</QTooltip> + </QBtn> + </QBtnGroup> + </template> + </VnSubToolbar> <div class="table-container"> <div class="column items-center"> <VnTable @@ -220,6 +261,11 @@ const exprBuilder = (param, value) => { formInitialData: {}, }" table-height="85vh" + v-model:selected="selectedRows" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" > <template #column-addressFk="{ row }"> {{ dashIfEmpty(formatRow(row)) }} @@ -271,6 +317,21 @@ const exprBuilder = (param, value) => { </div> </template> </VnSection> + <QDialog v-model="showZoneEventForm" @hide="closeEventForm()"> + <ZoneEventInclusionForm + v-if="openInclusionForm" + :event="'event'" + :is-masive-edit="true" + :zone-ids="zoneIds" + @close-form="closeEventForm" + /> + <ZoneEventExclusionForm + v-else + :zone-ids="zoneIds" + :is-masive-edit="true" + @close-form="closeEventForm" + /> + </QDialog> </template> <style lang="scss" scoped> diff --git a/src/pages/Zone/locale/en.yml b/src/pages/Zone/locale/en.yml index 22f4b1ae6ec..f46a98ee63d 100644 --- a/src/pages/Zone/locale/en.yml +++ b/src/pages/Zone/locale/en.yml @@ -25,6 +25,7 @@ list: agency: Agency close: Close price: Price + priceOptimum: Optimal price create: Create zone openSummary: Details searchZone: Search zones @@ -37,6 +38,8 @@ list: createZone: Create zone zoneSummary: Summary addressFk: Address + includeEvent: Include event + excludeEvent: Exclude event create: name: Name closingHour: Closing hour diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml index 777bc1c031f..7a23bdc0227 100644 --- a/src/pages/Zone/locale/es.yml +++ b/src/pages/Zone/locale/es.yml @@ -39,6 +39,8 @@ list: createZone: Crear zona zoneSummary: Resumen addressFk: Consignatario + includeEvent: Incluir evento + excludeEvent: Excluir evento create: closingHour: Hora de cierre itemMaxSize: Medida máxima diff --git a/src/stores/useWeekdayStore.js b/src/stores/useWeekdayStore.js index 57a302dc105..bf6b2704d4c 100644 --- a/src/stores/useWeekdayStore.js +++ b/src/stores/useWeekdayStore.js @@ -77,14 +77,14 @@ export const useWeekdayStore = defineStore('weekdayStore', () => { const locales = {}; for (let code of localeOrder.es) { const weekDay = weekdaysMap[code]; - const locale = t(`weekdays.${weekdaysMap[code].code}`); + const locale = t(`weekdays.${weekDay?.code}`); const obj = { ...weekDay, locale, localeChar: locale.substr(0, 1), localeAbr: locale.substr(0, 3), }; - locales[weekDay.code] = obj; + locales[weekDay?.code] = obj; } return locales; }); diff --git a/test/cypress/integration/Order/orderCatalog.spec.js b/test/cypress/integration/Order/orderCatalog.spec.js index 386e3b2aae1..050dd396cac 100644 --- a/test/cypress/integration/Order/orderCatalog.spec.js +++ b/test/cypress/integration/Order/orderCatalog.spec.js @@ -55,9 +55,9 @@ describe('OrderCatalog', () => { it('removes a secondary tag', () => { cy.get(':nth-child(1) > [data-cy="catalogFilterCategory"]').click(); cy.selectOption('[data-cy="catalogFilterType"]', 'Anthurium'); - cy.dataCy('vnFilterPanelChip').should('exist'); + cy.dataCy('vnFilterPanelChip_typeFk').should('exist'); cy.get('[data-cy="catalogFilterCustomTag"] > .q-chip__icon--remove').click(); - cy.dataCy('vnFilterPanelChip').should('not.exist'); + cy.dataCy('vnFilterPanelChip_typeFk').should('not.exist'); }); it('Removes category tag', () => { diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js index 467c6c37d8b..caf94b8bd58 100644 --- a/test/cypress/integration/client/clientList.spec.js +++ b/test/cypress/integration/client/clientList.spec.js @@ -53,7 +53,7 @@ describe.skip('Client list', () => { it('Client founded create ticket', () => { const search = 'Jessica Jones'; cy.searchByLabel('Name', search); - cy.openActionDescriptor('Create ticket'); + cy.selectDescriptorOption(); cy.waitForElement('#formModel'); cy.waitForElement('.q-form'); cy.checkValueForm(1, search); diff --git a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js index 11ca1bb59be..ee4d9fb749b 100644 --- a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js @@ -1,26 +1,40 @@ /// <reference types="cypress" /> +import moment from 'moment'; describe('InvoiceInBasicData', () => { - const firstFormSelect = '.q-card > .vn-row:nth-child(1) > .q-select'; const dialogInputs = '.q-dialog input'; - const resetBtn = '.q-btn-group--push > .q-btn--flat'; const getDocumentBtns = (opt) => `[data-cy="dms-buttons"] > :nth-child(${opt})`; + const futureDate = moment().add(1, 'days').format('DD-MM-YYYY'); + const mock = { + invoiceInBasicDataSupplier: { val: 'Bros nick', type: 'select' }, + invoiceInBasicDataSupplierRef_input: 'mockInvoice41', + invoiceInBasicDataIssued: { val: futureDate, type: 'date' }, + invoiceInBasicDataOperated: { val: futureDate, type: 'date' }, + invoiceInBasicDatabookEntried: { val: futureDate, type: 'date' }, + invoiceInBasicDataBooked: { + val: moment().add(5, 'days').format('DD-MM-YYYY'), + type: 'date', + }, + invoiceInBasicDataDeductibleExpenseFk: { + val: '4751000000', + type: 'select', + }, + invoiceInBasicDataCurrencyFk: { val: 'USD', type: 'select' }, + invoiceInBasicDataCompanyFk: { val: 'CCs', type: 'select' }, + invoiceInBasicDataWithholdingSageFk: { + val: 'Arrendamiento y subarrendamiento', + type: 'select', + }, + }; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/basic-data`); }); - it('should edit the provideer and supplier ref', () => { - cy.dataCy('UnDeductibleVatSelect').type('4751000000'); - cy.get('.q-menu .q-item').contains('4751000000').click(); - cy.get(resetBtn).click(); - - cy.waitForElement('#formModel').within(() => { - cy.dataCy('vnSupplierSelect').type('Bros nick'); - }) - cy.get('.q-menu .q-item').contains('Bros nick').click(); + it('should edit every field', () => { + cy.fillInForm(mock, { attr: 'data-cy' }); cy.saveCard(); - cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Bros nick'); + cy.validateForm(mock, { attr: 'data-cy' }); }); it('should edit, remove and create the dms data', () => { @@ -44,7 +58,7 @@ describe('InvoiceInBasicData', () => { cy.checkNotification('Data saved'); //create - cy.get('[data-cy="dms-create"]').eq(0).click(); + cy.get('[data-cy="invoiceInBasicDataDmsAdd"]').eq(0).click(); cy.get('[data-cy="VnDms_inputFile"').selectFile( 'test/cypress/fixtures/image.jpg', { diff --git a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js index 73117404015..275fa135879 100644 --- a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js @@ -1,22 +1,59 @@ -/// <reference types="cypress" /> -describe('InvoiceInCorrective', () => { - const saveDialog = '.q-card > .q-card__actions > .q-btn--standard '; +describe('invoiceInCorrective', () => { + beforeEach(() => cy.login('administrative')); - it('should create a correcting invoice', () => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit(`/#/invoice-in/1/summary`); + it('should modify the invoice', () => { + cy.visit('/#/invoice-in/1/summary'); cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + cy.intercept('POST', '/api/InvoiceInCorrections/crud').as('crud'); + cy.intercept('GET', /InvoiceInCorrections\?filter=.+/).as('getCorrective'); - cy.openActionsDescriptor(); + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); - cy.dataCy('createCorrectiveItem').click(); - cy.get(saveDialog).click(); - cy.wait('@corrective').then((interception) => { - const correctingId = interception.response.body; - cy.url().should('include', `/invoice-in/${correctingId}/summary`); - cy.visit(`/#/invoice-in/${correctingId}/corrective`); + cy.wait('@corrective').then(({ response }) => { + const correctingFk = response.body; + cy.url().should('include', `/invoice-in/${correctingFk}/summary`); + cy.visit(`/#/invoice-in/${correctingFk}/corrective`); + cy.selectOption('[data-cy="invoiceInCorrective_class"]', 'r4'); + cy.selectOption('[data-cy="invoiceInCorrective_type"]', 'sustitución'); + cy.selectOption('[data-cy="invoiceInCorrective_reason"]', 'vat'); + cy.dataCy('crudModelDefaultSaveBtn').click(); + + cy.wait('@crud'); + cy.reload(); + cy.wait('@getCorrective'); + cy.validateRow('tbody > :nth-of-type(1)', [ + , + 'S – Por sustitución', + 'R4', + 'Error in VAT calculation', + ]); }); - cy.get('tbody > tr:visible').should('have.length', 1); + }); + + it('should not be able to modify the invoice if the original invoice is booked', () => { + cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + cy.visit('/#/invoice-in/4/summary'); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); + + cy.wait('@corrective').then(({ response }) => { + const correctingFk = response.body; + cy.url().should('include', `/invoice-in/${correctingFk}/summary`); + cy.visit(`/#/invoice-in/${correctingFk}/corrective`); + + cy.dataCy('invoiceInCorrective_class').should('be.disabled'); + cy.dataCy('invoiceInCorrective_type').should('be.disabled'); + cy.dataCy('invoiceInCorrective_reason').should('be.disabled'); + }); + }); + + it('should show/hide the section if it is a corrective invoice', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.get('[data-cy="InvoiceInCorrective-menu-item"]').should('not.exist'); + cy.clicDescriptorAction(4); + cy.get('[data-cy="InvoiceInCorrective-menu-item"]').should('exist'); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js index 97a9fe976a2..7058e154ca7 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js @@ -1,21 +1,147 @@ describe('InvoiceInDescriptor', () => { - const book = '.summaryHeader > .no-wrap > .q-btn'; - const firstDescritorOpt = '.q-menu > .q-list > :nth-child(5) > .q-item__section'; - const checkbox = ':nth-child(5) > .q-checkbox'; + beforeEach(() => cy.login('administrative')); - it('should booking and unbooking the invoice properly', () => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit('/#/invoice-in/1/summary'); - cy.waitForElement('.q-page'); + describe('more options', () => { + it('should booking and unbooking the invoice properly', () => { + const checkbox = '[data-cy="vnLvIs booked"] > .q-checkbox'; + cy.visit('/#/invoice-in/2/summary'); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox, false); + }); - cy.get(book).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.get(checkbox).invoke('attr', 'aria-checked').should('eq', 'true'); + it('should delete the invoice properly', () => { + cy.visit('/#/invoice-in/2/summary'); + cy.selectDescriptorOption(2); + cy.clickConfirm(); + cy.checkNotification('invoice deleted'); + }); - cy.dataCy('descriptor-more-opts').first().click(); - cy.get(firstDescritorOpt).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.get(checkbox).invoke('attr', 'aria-checked').should('eq', 'false'); + it('should clone the invoice properly', () => { + cy.visit('/#/invoice-in/3/summary'); + cy.selectDescriptorOption(3); + cy.clickConfirm(); + cy.checkNotification('Invoice cloned'); + }); + + it('should show the agricultural PDF properly', () => { + cy.visit('/#/invoice-in/6/summary'); + cy.validatePdfDownload( + /api\/InvoiceIns\/6\/invoice-in-pdf\?access_token=.*/, + () => cy.selectDescriptorOption(4), + ); + }); + + it('should send the agricultural PDF properly', () => { + cy.intercept('POST', 'api/InvoiceIns/6/invoice-in-email').as('sendEmail'); + cy.visit('/#/invoice-in/6/summary'); + cy.selectDescriptorOption(5); + + cy.dataCy('SendEmailNotifiactionDialogInput_input').type( + '{selectall}jorgito@gmail.mx', + ); + cy.clickConfirm(); + cy.checkNotification('Notification sent'); + cy.wait('@sendEmail').then(({ request, response }) => { + expect(request.body).to.deep.equal({ + recipientId: 2, + recipient: 'jorgito@gmail.mx', + }); + expect(response.statusCode).to.equal(200); + }); + }); + // https://redmine.verdnatura.es/issues/8767 + it.skip('should download the file properly', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.validateDownload(() => cy.selectDescriptorOption(5)); + }); + }); + + describe('buttons', () => { + beforeEach(() => cy.visit('/#/invoice-in/1/summary')); + + it('should navigate to the supplier summary', () => { + cy.clicDescriptorAction(1); + cy.url().should('to.match', /supplier\/\d+\/summary/); + }); + + it('should navigate to the entry summary', () => { + cy.clicDescriptorAction(2); + cy.url().should('to.match', /entry\/\d+\/summary/); + }); + + it('should navigate to the invoiceIn list', () => { + cy.clicDescriptorAction(3); + cy.url().should('to.match', /invoice-in\/list\?table=\{.*supplierFk.+\}/); + }); + }); + + describe('corrective', () => { + const originalId = 4; + + beforeEach(() => cy.visit(`/#/invoice-in/${originalId}/summary`)); + + it('should create a correcting invoice and redirect to original invoice', () => { + createCorrective(); + redirect(originalId); + }); + + it('should create a correcting invoice and navigate to list filtered by corrective', () => { + createCorrective(); + redirect(originalId); + + cy.clicDescriptorAction(4); + cy.validateVnTableRows({ + cols: [ + { + name: 'supplierRef', + val: '1237', + operation: 'include', + }, + ], + }); + }); + }); + + describe('link', () => { + it('should open the supplier descriptor popup', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.intercept('GET', /Suppliers\/\d+/).as('getSupplier'); + + cy.dataCy('invoiceInDescriptor_supplier').then(($el) => { + const alias = $el.text().trim(); + $el.click(); + cy.wait('@getSupplier').then(() => + cy.validateDescriptor({ listbox: { 1: alias }, popup: true }), + ); + }); + }); }); }); + +function createCorrective() { + cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); + + cy.wait('@corrective').then(({ response }) => { + const correctingId = response.body; + cy.url().should('include', `/invoice-in/${correctingId}/summary`); + cy.visit(`/#/invoice-in/${correctingId}/corrective`); + cy.dataCy('invoiceInCorrective_class').should('contain.value', 'R2'); + cy.dataCy('invoiceInCorrective_type').should('contain.value', 'diferencias'); + cy.dataCy('invoiceInCorrective_reason').should('contain.value', 'sales details'); + }); +} + +function redirect(subtitle) { + const regex = new RegExp(`InvoiceIns/${subtitle}\\?filter=.*`); + cy.intercept('GET', regex).as('getOriginal'); + cy.clicDescriptorAction(4); + cy.wait('@getOriginal'); + cy.validateDescriptor({ subtitle }); +} diff --git a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js index 5a5becd222d..2fc34a7aec2 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js @@ -4,7 +4,7 @@ describe('InvoiceInDueDay', () => { const addBtn = '.q-page-sticky > div > .q-btn > .q-btn__content'; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/6/due-day`); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js index 4c255054843..6a1c187858f 100644 --- a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js @@ -6,7 +6,7 @@ describe('InvoiceInIntrastat', () => { const firstRowAmount = `${firstRow} > :nth-child(3)`; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/intrastat`); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js index d9ab3f7e790..44a61609e5f 100644 --- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -1,13 +1,21 @@ /// <reference types="cypress" /> + describe('InvoiceInList', () => { const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; const firstId = `${firstRow} > td:nth-child(2) span`; const firstDetailBtn = `${firstRow} .q-btn:nth-child(1)`; const summaryHeaders = '.summaryBody .header-link'; + const mockInvoiceRef = `createMockInvoice${Math.floor(Math.random() * 100)}`; + const mock = { + vnSupplierSelect: { val: 'farmer king', type: 'select' }, + 'Invoice nº_input': mockInvoiceRef, + Company_select: { val: 'orn', type: 'select' }, + 'Expedition date_inputDate': '16-11-2001', + }; beforeEach(() => { cy.viewport(1920, 1080); - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/list`); cy.get('#searchbar input').type('{enter}'); }); @@ -27,4 +35,18 @@ describe('InvoiceInList', () => { cy.get(summaryHeaders).eq(1).contains('Basic data'); cy.get(summaryHeaders).eq(4).contains('Vat'); }); + + it('should create a new Invoice', () => { + cy.dataCy('vnTableCreateBtn').click(); + cy.fillInForm({ ...mock }, { attr: 'data-cy' }); + cy.dataCy('FormModelPopup_save').click(); + cy.intercept('GET', /\/api\/InvoiceIns\/\d+\/getTotals$/).as('invoice'); + cy.wait('@invoice').then(() => + cy.validateDescriptor({ + title: mockInvoiceRef, + listBox: { 0: '11/16/2001', 3: 'The farmer' }, + }), + ); + cy.get('[data-cy="vnLvCompany"]').should('contain.text', 'ORN'); + }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js new file mode 100644 index 00000000000..faad22f1254 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js @@ -0,0 +1,23 @@ +describe('InvoiceInSerial', () => { + beforeEach(() => { + cy.login('administrative'); + cy.visit('#/invoice-in/serial'); + }); + + it('should filter by serial number', () => { + cy.dataCy('serial_input').type('R{enter}'); + cy.validateVnTableRows({ cols: [{ name: 'serial', val: 'r' }] }); + }); + + it('should filter by last days ', () => { + let before; + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((total) => (before = +total)); + + cy.dataCy('Last days_input').type('{selectall}1{enter}'); + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((total) => expect(+total).to.be.lessThan(before)); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js b/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js new file mode 100644 index 00000000000..72dbdd9a843 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js @@ -0,0 +1,24 @@ +describe('InvoiceInSummary', () => { + beforeEach(() => { + cy.login('administrative'); + cy.visit('/#/invoice-in/3/summary'); + }); + + it('should booking and unbooking the invoice properly', () => { + const checkbox = '[data-cy="vnLvIs booked"] > .q-checkbox'; + cy.dataCy('invoiceInSummary_book').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox); + }); + + it('should open the supplier descriptor popup', () => { + cy.intercept('GET', /Suppliers\/\d+/).as('getSupplier'); + cy.dataCy('invoiceInSummary_supplier').then(($el) => { + const description = $el.text().trim(); + $el.click(); + cy.wait('@getSupplier').then(() => + cy.validateDescriptor({ description, popup: true }), + ); + }); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js index 3e9997a74ff..e9412244f11 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -8,7 +8,7 @@ describe('InvoiceInVat', () => { const randomInt = Math.floor(Math.random() * 100); beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/vat`); }); diff --git a/test/cypress/integration/vnComponent/VnLog.spec.js b/test/cypress/integration/vnComponent/VnLog.spec.js index 496b1cd12d9..88edabb2e9c 100644 --- a/test/cypress/integration/vnComponent/VnLog.spec.js +++ b/test/cypress/integration/vnComponent/VnLog.spec.js @@ -25,7 +25,7 @@ describe('VnLog', () => { it('should show claimDescriptor', () => { cy.dataCy('iconLaunch-claimFk').first().click(); - cy.dataCy('descriptor_id').contains('1'); + cy.dataCy('cardDescriptor_subtitle').contains('1'); cy.dataCy('iconLaunch-claimFk').first().click(); }); }); diff --git a/test/cypress/integration/zone/zoneCalendar.spec.js b/test/cypress/integration/zone/zoneCalendar.spec.js index 07661a17de2..68b85d1d21f 100644 --- a/test/cypress/integration/zone/zoneCalendar.spec.js +++ b/test/cypress/integration/zone/zoneCalendar.spec.js @@ -1,11 +1,10 @@ describe('ZoneCalendar', () => { const addEventBtn = '.q-page-sticky > div > .q-btn'; const submitBtn = '.q-mt-lg > .q-btn--standard'; - const deleteBtn = '[data-cy="ZoneEventsPanelDeleteBtn"]'; + const deleteBtn = 'ZoneEventsPanelDeleteBtn'; beforeEach(() => { cy.login('developer'); - cy.viewport(1920, 1080); cy.visit(`/#/zone/13/events`); }); @@ -14,7 +13,7 @@ describe('ZoneCalendar', () => { cy.dataCy('ZoneEventInclusionDayRadio').click(); cy.get('.q-card > :nth-child(5)').type('01/01/2001'); cy.get(submitBtn).click(); - cy.get(deleteBtn).click(); + cy.dataCy(deleteBtn).click(); cy.dataCy('VnConfirm_confirm').click(); }); @@ -23,7 +22,7 @@ describe('ZoneCalendar', () => { cy.get('.flex > .q-gutter-x-sm > :nth-child(1)').click(); cy.get('.flex > .q-gutter-x-sm > :nth-child(2)').click(); cy.get(submitBtn).click(); - cy.get(deleteBtn).click(); + cy.dataCy(deleteBtn).click(); cy.dataCy('VnConfirm_confirm').click(); }); @@ -34,7 +33,7 @@ describe('ZoneCalendar', () => { cy.dataCy('From_inputDate').type('01/01/2001'); cy.dataCy('To_inputDate').type('31/01/2001'); cy.get(submitBtn).click(); - cy.get(deleteBtn).click(); + cy.dataCy(deleteBtn).click(); cy.dataCy('VnConfirm_confirm').click(); }); diff --git a/test/cypress/integration/zone/zoneDeliveryDays.spec.js b/test/cypress/integration/zone/zoneDeliveryDays.spec.js index 291c20ce320..a89def12d31 100644 --- a/test/cypress/integration/zone/zoneDeliveryDays.spec.js +++ b/test/cypress/integration/zone/zoneDeliveryDays.spec.js @@ -37,7 +37,6 @@ describe('ZoneDeliveryDays', () => { cy.get('@focusedElement').blur(); } }); - cy.get('.q-menu').should('not.exist'); cy.dataCy('ZoneDeliveryDaysAgencySelect').type(agency); cy.get('.q-menu .q-item').contains(agency).click(); @@ -49,7 +48,6 @@ describe('ZoneDeliveryDays', () => { cy.get('@focusedElement').blur(); } }); - cy.get('.q-menu').should('not.exist'); cy.get(submitForm).click(); cy.wait('@events').then((interception) => { diff --git a/test/cypress/integration/zone/zoneLocations.spec.js b/test/cypress/integration/zone/zoneLocations.spec.js index 3a52d276c54..dabd3eb2bf8 100644 --- a/test/cypress/integration/zone/zoneLocations.spec.js +++ b/test/cypress/integration/zone/zoneLocations.spec.js @@ -1,26 +1,59 @@ -describe.skip('ZoneLocations', () => { - const data = { - Warehouse: { val: 'Warehouse One', type: 'select' }, - }; - - const postalCode = - '[style=""] > :nth-child(1) > :nth-child(1) > :nth-child(2) > :nth-child(1) > :nth-child(1) > :nth-child(2) > :nth-child(1) > .q-tree__node--parent > .q-tree__node-collapsible > .q-tree__children'; - +describe('ZoneLocations', () => { + const cp = 46680; + const searchIcon = '.router-link-active > .q-icon'; beforeEach(() => { - cy.viewport(1280, 720); cy.login('developer'); cy.visit(`/#/zone/2/location`); }); - it('should show all locations on entry', () => { + it('should be able to search by postal code', () => { cy.get('.q-tree > :nth-child(1) > :nth-child(2) > :nth-child(1)') - .children() - .should('have.length', 9); + .should('exist') + .should('be.visible'); + + cy.intercept('GET', '**/api/Zones/2/getLeaves*', (req) => { + req.headers['cache-control'] = 'no-cache'; + req.headers['pragma'] = 'no-cache'; + req.headers['expires'] = '0'; + + req.on('response', (res) => { + delete res.headers['if-none-match']; + delete res.headers['if-modified-since']; + }); + }).as('location'); + cy.get('#searchbarForm').type(cp); + cy.get(searchIcon).click(); + cy.wait('@location').then((interception) => { + const data = interception.response.body; + expect(data).to.include(cp); + }); }); - it('should be able to search by postal code', () => { - cy.get('#searchbarForm').type('46680'); - cy.get('.router-link-active > .q-icon').click(); - cy.get(postalCode).should('include.text', '46680'); + it('should check, uncheck, and set a location to mixed state', () => { + cy.get('#searchbarForm').type(cp); + cy.get(searchIcon).click(); + + cy.get('.q-tree > :nth-child(1) > :nth-child(2) > :nth-child(1)') + .as('tree') + .within(() => { + cy.get('[data-cy="ZoneLocationTreeCheckbox"] > .q-checkbox__inner') + .last() + .as('lastCheckbox'); + + const verifyCheckboxState = (state) => { + cy.get('@lastCheckbox') + .parents('.q-checkbox') + .should('have.attr', 'aria-checked', state); + }; + + cy.get('@lastCheckbox').click(); + verifyCheckboxState('true'); + + cy.get('@lastCheckbox').click(); + verifyCheckboxState('false'); + + cy.get('@lastCheckbox').click(); + verifyCheckboxState('mixed'); + }); }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 45e17654e06..de25959b2ca 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -27,8 +27,9 @@ // DO NOT REMOVE // Imports Quasar Cypress AE predefined commands // import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress'; - +import moment from 'moment'; import waitUntil from './waitUntil'; + Cypress.Commands.add('waitUntil', { prevSubject: 'optional' }, waitUntil); Cypress.Commands.add('resetDB', () => { @@ -122,9 +123,10 @@ function selectItem(selector, option, ariaControl, hasWrite = true) { cy.waitSpinner(); getItems(ariaControl).then((items) => { - const matchingItem = items - .toArray() - .find((item) => item.innerText.includes(option)); + const matchingItem = items.toArray().find((item) => { + const val = typeof option == 'string' ? option.toLowerCase() : option; + return item.innerText.toLowerCase().includes(val); + }); if (matchingItem) return cy.wrap(matchingItem).click(); if (hasWrite) cy.get(selector).clear().type(option); @@ -160,14 +162,20 @@ Cypress.Commands.add('countSelectOptions', (selector, option) => { cy.get('.q-menu .q-item').should('have.length', option); }); -Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { +Cypress.Commands.add('fillInForm', (obj, opts = {}) => { + cy.waitSpinner(); + const { form = '.q-form > .q-card', attr = 'aria-label' } = opts; cy.waitForElement(form); cy.get(`${form} input`).each(([el]) => { cy.wrap(el) - .invoke('attr', 'aria-label') - .then((ariaLabel) => { - const field = obj[ariaLabel]; + .invoke('attr', attr) + .then((key) => { + const field = obj[key]; if (!field) return; + if (typeof field == 'string') + return cy + .wrap(el) + .type(`{selectall}{backspace}${field}`, { delay: 0 }); const { type, val } = field; switch (type) { @@ -175,7 +183,9 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { cy.selectOption(el, val); break; case 'date': - cy.get(el).type(val.split('-').join('')); + cy.get(el).type( + `{selectall}{backspace}${val.split('-').join('')}`, + ); break; case 'time': cy.get(el).click(); @@ -191,6 +201,40 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { }); }); +Cypress.Commands.add('validateForm', (obj, opts = {}) => { + const { form = '.q-form > .q-card', attr = 'data-cy' } = opts; + cy.waitForElement(form); + cy.get(`${form} input`).each(([el]) => { + cy.wrap(el) + .invoke('attr', attr) + .then((key) => { + const field = obj[key]; + if (!field) return; + + const { type, val } = field; + cy.get(el) + .invoke('val') + .then((elVal) => { + if (typeof field == 'string') + expect(elVal.toLowerCase()).to.equal(field.toLowerCase()); + else + switch (type) { + case 'date': + const elDate = moment(elVal, 'DD-MM-YYYY'); + const mockDate = moment(val, 'DD-MM-YYYY'); + expect(elDate.isSame(mockDate, 'day')).to.be.true; + break; + default: + expect(elVal.toLowerCase()).to.equal( + val.toLowerCase(), + ); + break; + } + }); + }); + }); +}); + Cypress.Commands.add('checkOption', (selector) => { cy.get(selector).find('.q-checkbox__inner').click(); }); @@ -207,14 +251,17 @@ Cypress.Commands.add('saveCard', () => { Cypress.Commands.add('resetCard', () => { cy.get('[title="Reset"]').click(); }); + Cypress.Commands.add('removeCard', () => { cy.get('[title="Remove"]').click(); }); + Cypress.Commands.add('addCard', () => { cy.waitForElement('tbody'); cy.waitForElement('.q-page-sticky > div > .q-btn'); cy.get('.q-page-sticky > div > .q-btn').click(); }); + Cypress.Commands.add('clickConfirm', () => { cy.waitForElement('.q-dialog__inner > .q-card'); cy.get('.q-card__actions > .q-btn--unelevated > .q-btn__content > .block').click(); @@ -295,6 +342,7 @@ Cypress.Commands.add('removeRow', (rowIndex) => { }); }); }); + Cypress.Commands.add('openListSummary', (row) => { cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click(); }); @@ -322,20 +370,8 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => { cy.get(selector).should('have.text', expectedValue); }); -Cypress.Commands.add('openActionDescriptor', (opt) => { - cy.openActionsDescriptor(); - const listItem = '[role="menu"] .q-list .q-item'; - cy.contains(listItem, opt).click(); -}); - Cypress.Commands.add('openActionsDescriptor', () => { - cy.get('[data-cy="descriptor-more-opts"]').click(); -}); - -Cypress.Commands.add('clickButtonDescriptor', (id) => { - cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`) - .invoke('removeAttr', 'target') - .click(); + cy.get('[data-cy="cardDescriptor"] [data-cy="descriptor-more-opts"]').click(); }); Cypress.Commands.add('openUserPanel', () => { @@ -424,3 +460,137 @@ Cypress.Commands.add('searchBtnFilterPanel', () => { Cypress.Commands.add('waitRequest', (alias, cb) => { cy.wait(alias).then(cb); }); + +Cypress.Commands.add('validateDescriptor', (toCheck = {}) => { + const { title, description, subtitle, listbox = {}, popup = false } = toCheck; + + const popupSelector = popup ? '[role="menu"] ' : ''; + + if (title) cy.get(`${popupSelector}[data-cy="cardDescriptor_title"]`).contains(title); + if (description) + cy.get(`${popupSelector}[data-cy="cardDescriptor_description"]`).contains( + description, + ); + if (subtitle) + cy.get(`${popupSelector}[data-cy="cardDescriptor_subtitle"]`).contains(subtitle); + + for (const index in listbox) + cy.get(`${popupSelector}[data-cy="cardDescriptor_listbox"] > *`) + .eq(index) + .should('contain.text', listbox[index]); +}); + +Cypress.Commands.add('validateVnTableRows', (opts = {}) => { + let { cols = [], rows = [] } = opts; + if (!Array.isArray(cols)) cols = [cols]; + const rowSelector = rows.length + ? rows.map((row) => `> :nth-child(${row})`).join(', ') + : '> *'; + cy.get(`[data-cy="vnTable"] .q-virtual-scroll__content`).within(() => { + cy.get(`${rowSelector}`).each(($el) => { + for (const { name, type = 'string', val, operation = 'equal' } of cols) { + cy.wrap($el) + .find(`[data-cy="vnTableCell_${name}"]`) + .invoke('text') + .then((text) => { + if (type === 'string') + expect(text.trim().toLowerCase()).to[operation]( + val.toLowerCase(), + ); + if (type === 'number') cy.checkNumber(text, val, operation); + if (type === 'date') cy.checkDate(text, val, operation); + }); + } + }); + }); +}); + +Cypress.Commands.add('checkDate', (rawDate, expectedVal, operation) => { + const date = moment(rawDate.trim(), 'MM/DD/YYYY'); + const compareDate = moment(expectedVal, 'DD/MM/YYYY'); + + switch (operation) { + case 'equal': + expect(text.trim()).to.equal(compareDate); + break; + case 'before': + expect(date.isBefore(compareDate)).to.be.true; + break; + case 'after': + expect(date.isAfter(compareDate)).to.be.true; + } +}); + +Cypress.Commands.add('selectDescriptorOption', (opt = 1) => { + const listItem = '[data-cy="descriptor-more-opts_list"]'; + cy.get('body').then(($body) => { + if (!$body.find(listItem).length) cy.openActionsDescriptor(); + }); + + cy.waitForElement(listItem); + cy.get(`${listItem} > :not(template):nth-of-type(${opt})`).click(); +}); + +Cypress.Commands.add('validateCheckbox', (selector, expectedVal = 'true') => { + cy.get(selector).should('have.attr', 'aria-checked', expectedVal.toString()); +}); + +Cypress.Commands.add('validateDownload', (trigger, opts = {}) => { + const { + url = /api\/dms\/\d+\/downloadFile\?access_token=.+/, + types = ['text/plain', 'image/jpeg'], + alias = 'download', + } = opts; + cy.intercept('GET', url).as(alias); + trigger().then(() => { + cy.wait(`@${alias}`).then(({ response }) => { + expect(response.statusCode).to.equal(200); + const isValidType = types.some((type) => + response.headers['content-type'].includes(type), + ); + expect(isValidType).to.be.true; + }); + }); +}); + +Cypress.Commands.add('validatePdfDownload', (match, trigger) => { + cy.window().then((win) => { + cy.stub(win, 'open') + .callsFake(() => null) + .as('pdf'); + }); + trigger(); + cy.get('@pdf') + .should('be.calledOnce') + .then((stub) => { + const [url] = stub.getCall(0).args; + expect(url).to.match(match); + cy.request(url).then((response) => + expect(response.headers['content-type']).to.include('application/pdf'), + ); + }); +}); + +Cypress.Commands.add('clicDescriptorAction', (index = 1) => { + cy.get(`[data-cy="descriptor_actions"] .q-btn:nth-of-type(${index})`).click(); +}); + +Cypress.Commands.add('checkQueryParams', (expectedParams = {}) => { + cy.url().then((url) => { + const urlParams = new URLSearchParams(url.split('?')[1]); + + for (const key in expectedParams) { + const expected = expectedParams[key]; + const param = JSON.parse(decodeURIComponent(urlParams.get(key))); + + if (typeof expected === 'object') { + const { subkey, val } = expected; + expect(param[subkey]).to.equal(val); + } else expect(param).to.equal(expected); + } + }); +}); + +Cypress.Commands.add('waitTableScrollLoad', () => + cy.waitForElement('[data-q-vs-anchor]'), +);