diff --git a/CHANGELOG.md b/CHANGELOG.md index dd75a00a4..3b654a1ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,214 @@ +# Version 25.12 - 2025-03-25 + +### Added 🆕 + +- chore: add junit-merge dependency to package.json by:alexm +- chore: downgrade version from 25.14.0 to 25.12.0 in package.json by:alexm +- chore: reduce page load timeout in Cypress configuration by:alexm +- chore: refs #6695 update Cypress to version 14.1.0 and simplify test execution in Jenkinsfile by:alexm +- chore: refs #8602 add comments for clarity in Cypress commands file by:pablone +- chore: refs #8602 enhance Cypress support files with detailed comments and organization by:pablone +- ci: refs #6695 feat jenkins parallel e2e by:alexm +- feat: integrate vue-router to enhance routing capabilities in ZoneCard component by:alexm +- feat: refs #6695 implement parallel Cypress testing and enhance timeout configurations by:alexm +- feat: refs #6695 update Cypress parallel test execution to use 3 instances by:alexm +- feat: refs #6802 add dash placeholder for empty department names in InvoiceOut list by:jgallego +- feat: refs #6802 add DepartmentDescriptorProxy to InvoiceOutList and update translations by:jgallego +- feat: refs #6802 add DepartmentDescriptorProxy to various components and update department handling by:jgallego +- feat: refs #7587 add 'ticketClaimed' translation and implement claims retrieval in TicketDescriptor by:jtubau +- feat: refs #7949 show new field in ticket sales by:Jon +- feat: refs #8045 added new logic to show the correct icon and the correct path to redirect by:Jon +- feat: refs #8045 modified icon and route to redirect from CardDescriptor by:Jon +- feat: refs #8074 modified spinner size by:Jon +- feat: refs #8600 added calendar e2e and modified basic data by:Jon +- feat: refs #8600 added deliveryDays and modified warehouse E2Es by:Jon +- feat: refs #8600 added new tests for zoneSummary & zoneLocations by:provira +- feat: refs #8602 add custom Cypress commands for improved element interaction and request handling by:pablone +- feat: refs #8602 add new Cypress command for clicking buttons with icons by:pablone +- feat: refs #8602 add remove functionality for tag filters in EntryBuys component by:pablone +- feat: refs #8602 add sorting options for select fields and update locale files with supplier name by:pablone +- feat: refs #8602 refactor EntryBuys component and enhance observation tests by:pablone +- feat: refs #8602 remove unused state property from useArrayDataStore by:pablone +- feat: refs #8602 remove unused URL property from VnTable in ClaimList component by:pablone +- feat: refs #8602 skip warehouse creation and removal test in ZoneWarehouse spec by:pablone +- feat: refs #8602 streamline beforeSaveFn execution in CrudModel component by:pablone +- feat: refs #8602 streamline beforeSaveFn execution in VnTable component by:pablone +- feat: refs #8602 streamline filter logic in EntryBuys component by:pablone +- feat: refs #8602 update entry components and tests, add data-cy attributes for Cypress integration by:pablone +- feat: refs #8602 update localization for purchased spaces and enhance Entry components with new labels by:pablone +- feat: refs #8606 adapt module to VnCatdBeta by:Jon +- feat: refs #8612 added summary button & changed e2e tests by:provira +- feat: refs #8612 changed shelving to VnTable & created e2e tests by:provira +- feat: refs #8616 add summary prop to CardDescriptor in RoadmapDescriptor and WorkerDescriptor by:jtubau +- feat: refs #8616 add VnCheckbox component to VnFilter and update prop types in VnFilterPanel and VnSearchbar by:jtubau +- feat: refs #8630 add Agency and Vehicle descriptor components with summary props by:jtubau +- feat: refs #8638 add AWB field to travel and entry forms, update translations and styles by:pablone +- feat: refs #8638 add data attributes for transfer buys functionality in EntryBuys.vue and corresponding tests by:pablone +- feat: refs #8648 enhance roadmapList tests with improved selectors and additional scenarios by:jtubau +- feat: refs #8664 add CmrFilter component and integrate it into CmrList for enhanced filtering options by:jtubau +- feat: refs #8721 add ticket navigation and update route columns by:jtubau +- feat: run.sh build neccessary images by:alexm +- feat: update Jenkinsfile to pull Docker images for back and db services by:alexm +- feat: update labels and add department selection in InvoiceOut filter and list by:jgallego +- refactor: refs #8626 update button styles and improve route redirection logic by:jtubau +- style: refs #8041 new variable by:benjaminedc + +### Changed 📦 + +- feat: refs #8602 refactor EntryBuys component and enhance observation tests by:pablone +- refactor(cypress): refs #6695 simplify parallel test execution script by:alexm +- refactor: deleted useless (origin/Warmfix-DepartmentIcon) by:Jon +- refactor: refs #6695 enable ClaimNotes test suite by:alexm +- refactor: refs #6695 fix invoiceOutSummary by:alexm +- refactor: refs #6695 improve notification check and extend waitForElement timeout by:alexm +- refactor: refs #6695 remove mocha dependency and optimize Cypress command execution by:alexm +- refactor: refs #6695 skips by:alexm +- refactor: refs #6695 skip zoneWarehouse by:alexm +- refactor: refs #6695 streamline Cypress test execution and remove deprecated configurations by:alexm +- refactor: refs #6802 replace salesPerson references with department in claims and tickets by:jgallego +- refactor: refs #6802 replace 'salesPerson' terminology with 'team' across multiple locales and components by:jgallego +- refactor: refs #6802 update import paths for DepartmentDescriptorProxy to use Worker directory by:jgallego +- refactor: refs #6802 update InvoiceOutNegativeBases to use Department instead of Worker by:jgallego +- refactor: refs #6802 update TicketFilter and TicketSale components to use departmentFk and adjust API endpoints by:jgallego +- refactor: refs #8041 unify class link and unify titles to VnTitles by:benjaminedc +- refactor: refs #8045 modified icon and module const by:Jon +- refactor: refs #8197 rename VnCardBeta to VnCard by:alexm +- refactor: refs #8197 simplify menu retrieval logic in LeftMenu component by:alexm +- refactor: refs #8322 changed Wagon component to use VnSection/VnCardBeta by:provira +- refactor: refs #8322 remove keyBinding from Wagon router module by:alexm +- refactor: refs #8322 update WagonCard component and routing structure by:alexm +- refactor: refs #8370 modified function to get the correct date by:Jon +- refactor: refs #8472 remove added div and add class to VnInput by:jtubau +- refactor: refs #8472 unified styling for the more-create-dialog slot to ensure consistency across all scenarios by:jtubau +- refactor: refs #8472 update class names from q-span-2 to col-span-2 for consistency in layout by:jtubau +- refactor: refs #8600 changed test case description by:provira +- refactor: refs #8600 modified make invoice and send dialog e2es by:Jon +- refactor: refs #8600 modified upcomingDeliveries e2e and created deliveryDays by:Jon +- refactor: refs #8600 modified zoneSummary e2e by:Jon +- refactor: refs #8602 remove redundant date input test from entryList.spec.js by:pablone +- refactor: refs #8606 clear some warnings by:Jon +- refactor: refs #8606 deleted code and fixed translation by:Jon +- refactor: refs #8606 merged previous and e2e changes and corrected minor errors by:Jon +- refactor: refs #8606 requested changes by:Jon +- refactor: refs #8616 integrate summary dialog and update navigation in Agency and Vehicle components by:jtubau +- refactor: refs #8616 integrate VnSelectWorker component in RouteList and optimize format functions by:jtubau +- refactor: refs #8616 simplify template bindings and improve link generation in VehicleSummary by:jtubau +- refactor: refs #8616 update routing components for AgencyList and RouteRoadmap in route.js by:jtubau +- refactor: refs #8619 simplify empty data check in RouteDescriptor component by:jtubau +- refactor: refs #8626 add cardVisible property to RouteList columns by:jtubau +- refactor: refs #8626 add formatting for agency and vehicle columns in RouteList by:jtubau +- refactor: refs #8626 enhance Worker and Agency components with data attributes and improved routing by:jtubau +- refactor: refs #8626 improve test messages and selectors in route tests by:jtubau +- refactor: refs #8626 update button styles and improve route redirection logic by:jtubau +- refactor: refs #8626 update RouteList columns and enable AgencyWorkCenter tests by:jtubau +- refactor: refs #8630 add vehicle translations and enhance route list columns by:jtubau +- refactor: refs #8648 update roadmap deletion test to use current element text by:jtubau +- refactor: refs #8664 enhance CmrList component with query initialization and user parameters by:jtubau +- refactor: refs #8664 localization files by:jtubau +- refactor: refs #8664 remove CmrFilter and replace with VnSearchbar in CmrList by:jtubau +- refactor: remove unnecessary login and reload calls in ClaimDevelopment tests by:alexm +- refactor: simplify client selection in order creation test by:alexm +- refactor: update client ID input selector and remove viewport setting by:alexm +- test: refs #8197 comment out ticket list tests for refactoring by:alexm +- test: refs #8626 refactor notification check in routeList.spec.js by:jtubau +- test: refs #8626 refactor routeList.spec.js to use a constant for summary URL by:jtubau +- test: refs #8626 refactor routeList.spec.js to use selectors and improve readability by:jtubau + +### Fixed 🛠️ + +- fix: add --init flag to Docker container for Cypress tests by:alexm +- fix: agency list filters by:jtubau +- fix: align Article label to the left in EntryBuys component by:alexm +- fix: card descriptor imports by:Jon +- fix: card descriptor merge by:Jon +- fix(ClaimAction): update shelving options to use URL instead of static data by:jgallego +- fix(ClaimSummary): clean url by:alexm +- fix(cypress.config.js): refs #6695 update reporter to junit and remove unused dependencies by:alexm +- fix(cypressParallel.sh): refs #6695 improve script readability by:alexm +- fix(cypressParallel.sh): refs #6695 improve test execution output for clarity by:alexm +- fix(cypressParallel.sh): refs #6695 simplify test execution output format by:alexm +- fix(cypress scripts): refs #6695 improve cleanup process and adjust output redirection by:alexm +- fix: fixed department descriptor icon by:Jon +- fix: fixed submodule descriptors icons by:Jon +- fix(invoiceOutSummary.spec.js): refs #6695 remove unnecessary visibility check for descriptor by:alexm +- fix(Jenkinsfile): reduce parallel Cypress test execution from 3 to 2 by:alexm +- fix(Jenkinsfile): refs #6695 add credentials for Docker login in E2E stage by:alexm +- fix(Jenkinsfile): refs #6695 change parallel test execution from 4 to 2 by:alexm +- fix(Jenkinsfile): refs #6695 increase parallel test execution from 2 to 4 by:alexm +- fix(Jenkinsfile): refs #6695 update parallel test execution to 4 by:alexm +- fix(LeftMenu): refs #8197 handle missing children in findRoute and update menu structure by:alexm +- fix: refs #6695 update Cypress configuration and test result paths by:alexm +- fix: refs #6695 update Jenkinsfile to build Docker image correctly and modify logout test visit method by:alexm +- fix: refs #6695 update Jenkinsfile to remove specific e2e XML files and adjust Cypress parallel execution by:alexm +- fix: refs #6695 update Jenkinsfile to source cypressParallel.sh correctly by:alexm +- fix: refs #6695 update visit method in TicketLackDetail.spec.js to prevent page reload by:alexm +- fix: refs #6802 update import path for DepartmentDescriptorProxy in OrderList.vue by:jgallego +- fix: refs #6802 update OrderFilter to use department relation instead of salesPerson by:jgallego +- fix: refs #8041 update redirection from preview to summary in ShelvingList tests by:benjaminedc +- fix: refs #8041 update selector for summary header in ParkingList tests by:benjaminedc +- fix: refs #8041 update summaryHeader selector in ParkingList test by:benjaminedc +- fix: refs #8322 update order property for WagonList component by:alexm +- fix: refs #8370 change param rely on month by:Jon +- fix: refs #8417 added data-cy to all files and fixed test by:provira +- fix: refs #8417 added data-cy to delete button by:provira +- fix: refs #8417 fixed claimPhoto e2e by:provira +- fix: refs #8417 fixed claimPhoto e2e test by:provira +- fix: refs #8417 fixed e2e test by:provira +- fix: refs #8417 fixed e2e test case by:provira +- fix: refs #8417 fixed failing test case by:provira +- fix: refs #8417 fixed invoiceOutSummary e2e test by:provira +- fix: refs #8417 removed .only by:provira +- fix: refs #8583 basicData, business, summary by:carlossa +- fix: refs #8583 basicData e2e by:carlossa +- fix: refs #8583 basicData timeControl by:carlossa +- fix: refs #8583 cypressconf by:carlossa +- fix: refs #8583 dataCy operator by:carlossa +- fix: refs #8583 fix AddCard by:carlossa +- fix: refs #8583 mutual create by:carlossa +- fix: refs #8583 operator by:carlossa +- fix: refs #8583 remove workerTimeControl by:carlossa +- fix: refs #8583 tMutual, tNotes, TOperator by:carlossa +- fix: refs #8583 wBusiness by:carlossa +- fix: refs #8583 wBusiness e2e by:carlossa +- fix: refs #8583 workerBasicData & workerTimeControl by:carlossa +- fix: refs #8583 workerBusiness by:carlossa +- fix: refs #8583 workerBusiness e2e by:carlossa +- fix: refs #8583 workerBusiness test by:carlossa +- fix: refs #8583 workerE2E by:carlossa +- fix: refs #8583 worker mutual e2e by:carlossa +- fix: refs #8583 workerSummary test by:carlossa +- fix: refs #8583 workerTimeControl by:carlossa +- fix: refs #8583 workerTimeControl e2e by:carlossa +- fix: refs #8600 e2e by:Jon +- fix: refs #8600 fixed calendar e2e by:Jon +- fix: refs #8600 fixed e2e and skip client ones by:Jon +- fix: refs #8600 fixed e2e by:Jon +- fix: refs #8600 fixed invoiceOut summary e2e by:Jon +- fix: refs #8600 fixed zoneList & added test case to zoneSummary by:provira +- fix: refs #8600 zone basic data e2e and skip intermitent invoice out summary it by:Jon +- fix: refs #8602 delete unused entryDms and stockBought test files (origin/8581-e2eInvoiceIn) by:pablone +- fix: refs #8606 deleted code by:Jon +- fix: refs #8612 changed QCheckbox for VnCheckbox by:provira +- fix: refs #8612 fixed shelving e2e tests by:provira +- fix: refs #8616 add conditional for SupplierDescriptorProxy and bind attributes in CardDescriptor by:jtubau +- fix: refs #8616 remove redundant v-on binding from QCheckbox in VnCheckbox.vue by:jtubau +- fix: refs #8616 update binding syntax for is-editable prop in AgencyList.vue by:jtubau +- fix: refs #8616 update FormModel prop from 'update-url' to 'url-update' in Agency and RoadMap BasicData by:jtubau +- fix: refs #8619 handle empty ticket records in RouteDescriptor component by:jtubau +- fix: refs #8619 update route descriptor to handle empty ticket records and adjust test cases by:jtubau +- fix: refs #8626 remove duplicate ref attribute from RouteList.vue by:jtubau +- fix: refs #8630 remove duplicated locations by:jtubau +- fix: refs #8638 restore invoiceInBasicData by:pablone +- fix: refs #8638 update comment formatting in VnTable.vue by:pablone +- fix: refs #8638 update null check for maxlength validation in VnInput.vue by:pablone +- fix: simplify menu structure in monitor router module (origin/fix_monitor_leftMenu) by:Javier Segarra +- refactor: refs #6695 fix invoiceOutSummary by:alexm +- refactor: refs #8606 deleted code and fixed translation by:Jon +- test: fix intermitent e2e by:alexm +- test: fix orderList e2e, unestables by:alexm +- test(OrderList): fix inconsistency by:alexm +- test(TicketList): fix inconsistency by:alexm + # Version 25.10 - 2025-03-11 ### Added 🆕 diff --git a/Jenkinsfile b/Jenkinsfile index 2f11556b5..05ef34791 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -115,6 +115,7 @@ pipeline { steps { script { sh 'rm -f junit/e2e-*.xml' + sh 'rm -rf test/cypress/screenshots' env.COMPOSE_TAG = PROTECTED_BRANCH.contains(env.CHANGE_TARGET) ? env.CHANGE_TARGET : 'dev' def image = docker.build('lilium-dev', '-f docs/Dockerfile.dev docs') @@ -125,13 +126,14 @@ pipeline { sh "docker-compose ${env.COMPOSE_PARAMS} up -d" image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") { - sh 'sh test/cypress/cypressParallel.sh 2' + sh 'sh test/cypress/cypressParallel.sh 1' } } } post { always { sh "docker-compose ${env.COMPOSE_PARAMS} down -v" + archiveArtifacts artifacts: 'test/cypress/screenshots/**/*', allowEmptyArchive: true junit( testResults: 'junit/e2e-*.xml', allowEmptyResults: true diff --git a/package.json b/package.json index 076cbbb14..017412ef2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "25.12.0", + "version": "25.14.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 8c4f70f3b..6303f48ae 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -181,9 +181,8 @@ async function saveChanges(data) { return; } let changes = data || getChanges(); - if ($props.beforeSaveFn) { - changes = await $props.beforeSaveFn(changes, getChanges); - } + if ($props.beforeSaveFn) changes = await $props.beforeSaveFn(changes, getChanges); + try { if (changes?.creates?.length === 0 && changes?.updates?.length === 0) { return; @@ -194,7 +193,7 @@ async function saveChanges(data) { isLoading.value = false; } originalData.value = JSON.parse(JSON.stringify(formData.value)); - if (changes.creates?.length) await vnPaginateRef.value.fetch(); + if (changes?.creates?.length) await vnPaginateRef.value.fetch(); hasChanges.value = false; emit('saveChanges', data); diff --git a/src/components/ItemsFilterPanel.vue b/src/components/ItemsFilterPanel.vue index f73753a6b..0f1e3f1eb 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -198,8 +198,7 @@ const setCategoryList = (data) => { v-model="params.typeFk" :options="itemTypesOptions" dense - outlined - rounded + filled use-input :disable="!selectedCategoryFk" @update:model-value=" @@ -235,8 +234,7 @@ const setCategoryList = (data) => { v-model="value.selectedTag" :options="tagOptions" dense - outlined - rounded + filled :emit-value="false" use-input :is-clearable="false" @@ -252,8 +250,7 @@ const setCategoryList = (data) => { option-value="value" option-label="value" dense - outlined - rounded + filled emit-value use-input :disable="!value" @@ -265,7 +262,6 @@ const setCategoryList = (data) => { v-model="value.value" :label="t('components.itemsFilterPanel.value')" :disable="!value" - is-outlined :is-clearable="false" @keyup.enter="applyTags(params, searchFn)" /> diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 78b00d041..e214770d2 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', }, }); @@ -595,18 +595,17 @@ function cardClick(_, row) { function removeTextValue(data, getChanges) { let changes = data.updates; - if (!changes) return data; - - for (const change of changes) { - for (const key in change.data) { - if (key.endsWith('VnTableTextValue')) { - delete change.data[key]; + if (changes) { + for (const change of changes) { + for (const key in change.data) { + if (key.endsWith('VnTableTextValue')) { + delete change.data[key]; + } } } + + data.updates = changes.filter((change) => Object.keys(change.data).length > 0); } - - data.updates = changes.filter((change) => Object.keys(change.data).length > 0); - if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges); return data; @@ -634,6 +633,7 @@ const rowCtrlClickFunction = computed(() => { :data-key="$attrs['data-key']" :columns="columns" :redirect="redirect" + v-bind="$attrs?.['table-filter']" > <template v-for="(_, slotName) in $slots" @@ -685,7 +685,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> @@ -776,12 +776,13 @@ const rowCtrlClickFunction = computed(() => { :data-col-field="col?.name" > <div - class="no-padding no-margin peter" + class="no-padding no-margin" style=" overflow: hidden; text-overflow: ellipsis; white-space: nowrap; " + :data-cy="`vnTableCell_${col.name}`" > <slot :name="`column-${col.name}`" @@ -896,7 +897,7 @@ const rowCtrlClickFunction = computed(() => { {{ row[splittedColumns.title.name] }} </span> </QCardSection> - <!-- Fields --> + <!-- Fields --> <QCardSection class="q-pl-sm q-py-xs" :class="$props.cardClass" @@ -978,6 +979,8 @@ const rowCtrlClickFunction = computed(() => { v-for="col of cols.filter((cols) => cols.visible ?? true)" :key="col?.id" :class="getColAlign(col)" + :style="col?.width ? `max-width: ${col?.width}` : ''" + style="font-size: small" > <slot :name="`column-footer-${col.name}`" @@ -1040,38 +1043,43 @@ const rowCtrlClickFunction = computed(() => { @on-data-saved="(_, res) => createForm.onDataSaved(res)" > <template #form-inputs="{ data }"> - <div :style="createComplement?.containerStyle"> - <div - :style="createComplement?.previousStyle" - v-if="!quasar.screen.xs" - > - <slot name="previous-create-dialog" :data="data" /> - </div> - <div class="grid-create" :style="createComplement?.columnGridStyle"> - <slot - v-for="column of splittedColumns.create" - :key="column.name" - :name="`column-create-${column.name}`" - :data="data" - :column-name="column.name" - :label="column.label" + <slot name="alter-create" :data="data"> + <div :style="createComplement?.containerStyle"> + <div + :style="createComplement?.previousStyle" + v-if="!quasar.screen.xs" > - <VnColumn - :column="{ - ...column, - ...{ disable: column?.createDisable ?? false }, - }" - :row="{}" - default="input" - v-model="data[column.name]" - :show-label="true" - component-prop="columnCreate" - :data-cy="`${column.name}-create-popup`" - /> - </slot> - <slot name="more-create-dialog" :data="data" /> + <slot name="previous-create-dialog" :data="data" /> + </div> + <div + class="grid-create" + :style="createComplement?.columnGridStyle" + > + <slot + v-for="column of splittedColumns.create" + :key="column.name" + :name="`column-create-${column.name}`" + :data="data" + :column-name="column.name" + :label="column.label" + > + <VnColumn + :column="{ + ...column, + ...column?.createAttrs, + }" + :row="{}" + default="input" + v-model="data[column.name]" + :show-label="true" + component-prop="columnCreate" + :data-cy="`${column.name}-create-popup`" + /> + </slot> + <slot name="more-create-dialog" :data="data" /> + </div> </div> - </div> + </slot> </template> </FormModelPopup> </QDialog> @@ -1148,7 +1156,7 @@ es: .grid-create { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); max-width: 100%; grid-gap: 20px; margin: 0 auto; diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 620dc2ad2..21cdc9df5 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -1,12 +1,15 @@ <script setup> -import { onBeforeMount } from 'vue'; -import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'; +import { onBeforeMount, computed } from 'vue'; +import { useRoute, useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; import VnSubToolbar from '../ui/VnSubToolbar.vue'; +const emit = defineEmits(['onFetch']); + const props = defineProps({ + id: { type: Number, required: false, default: null }, dataKey: { type: String, required: true }, url: { type: String, default: undefined }, idInWhere: { type: Boolean, default: false }, @@ -16,10 +19,13 @@ const props = defineProps({ searchDataKey: { type: String, default: undefined }, searchbarProps: { type: Object, default: undefined }, redirectOnError: { type: Boolean, default: false }, + visual: { type: Boolean, default: true }, }); +const route = useRoute(); const stateStore = useStateStore(); const router = useRouter(); +const entityId = computed(() => props.id || route?.params?.id); const arrayData = useArrayData(props.dataKey, { url: props.url, userFilter: props.filter, @@ -35,7 +41,7 @@ onBeforeMount(async () => { const route = router.currentRoute.value; try { - await fetch(route.params.id); + await fetch(entityId.value); } catch { const { matched: matches } = route; const { path } = matches.at(-1); @@ -51,8 +57,7 @@ onBeforeRouteUpdate(async (to, from) => { router.push({ name, params: to.params }); } } - const id = to.params.id; - if (id !== from.params.id) await fetch(id, true); + if (entityId.value !== to.params.id) await fetch(to.params.id, true); }); async function fetch(id, append = false) { @@ -61,14 +66,17 @@ async function fetch(id, append = false) { else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; else arrayData.store.url = props.url.replace(regex, `/${id}`); await arrayData.fetch({ append, updateRouter: false }); + emit('onFetch', arrayData.store.data); } function hasRouteParam(params, valueToCheck = ':addressId') { return Object.values(params).includes(valueToCheck); } </script> <template> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="$route.path" /> - </div> + <template v-if="visual"> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="$route.path" /> + </div> + </template> </template> diff --git a/src/components/common/VnCheckbox.vue b/src/components/common/VnCheckbox.vue index 94e91328b..daaf891dc 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/VnDms.vue b/src/components/common/VnDms.vue index 35308c2c4..bee300f4e 100644 --- a/src/components/common/VnDms.vue +++ b/src/components/common/VnDms.vue @@ -177,6 +177,7 @@ function addDefaultData(data) { name="vn:attach" class="cursor-pointer" @click="inputFileRef.pickFiles()" + data-cy="attachFile" > <QTooltip>{{ t('globals.selectFile') }}</QTooltip> </QIcon> diff --git a/src/components/common/VnDmsInput.vue b/src/components/common/VnDmsInput.vue new file mode 100644 index 000000000..25d625d5d --- /dev/null +++ b/src/components/common/VnDmsInput.vue @@ -0,0 +1,166 @@ +<script setup> +import VnConfirm from '../ui/VnConfirm.vue'; +import VnInput from './VnInput.vue'; +import VnDms from './VnDms.vue'; +import axios from 'axios'; +import { useQuasar } from 'quasar'; +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { downloadFile } from 'src/composables/downloadFile'; + +const { t } = useI18n(); +const quasar = useQuasar(); +const documentDialogRef = ref({}); +const editDownloadDisabled = ref(false); +const $props = defineProps({ + defaultDmsCode: { + type: String, + default: 'InvoiceIn', + }, + disable: { + type: Boolean, + default: true, + }, + data: { + type: Object, + default: null, + }, + formRef: { + type: Object, + default: null, + }, +}); + +function deleteFile(dmsFk) { + quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('globals.confirmDeletion'), + message: t('globals.confirmDeletionMessage'), + }, + }) + .onOk(async () => { + await axios.post(`dms/${dmsFk}/removeFile`); + $props.formRef.formData.dmsFk = null; + $props.formRef.formData.dms = undefined; + $props.formRef.hasChanges = true; + $props.formRef.save(); + }); +} +</script> +<template> + <div class="row no-wrap"> + <VnInput + :label="t('Document')" + v-model="data.dmsFk" + clearable + clear-icon="close" + class="full-width" + :disable="disable" + /> + <div + v-if="data.dmsFk" + class="row no-wrap q-pa-xs q-gutter-x-xs" + data-cy="dms-buttons" + > + <QBtn + :disable="editDownloadDisabled" + @click="downloadFile(data.dmsFk)" + icon="cloud_download" + color="primary" + flat + :class="{ + 'no-pointer-events': editDownloadDisabled, + }" + padding="xs" + round + > + <QTooltip>{{ t('Download file') }}</QTooltip> + </QBtn> + <QBtn + :disable="editDownloadDisabled" + @click=" + () => { + documentDialogRef.show = true; + documentDialogRef.dms = data.dms; + } + " + icon="edit" + color="primary" + flat + :class="{ + 'no-pointer-events': editDownloadDisabled, + }" + padding="xs" + round + > + <QTooltip>{{ t('Edit document') }}</QTooltip> + </QBtn> + <QBtn + :disable="editDownloadDisabled" + @click="deleteFile(data.dmsFk)" + icon="delete" + color="primary" + flat + round + :class="{ + 'no-pointer-events': editDownloadDisabled, + }" + padding="xs" + > + <QTooltip>{{ t('Delete file') }}</QTooltip> + </QBtn> + </div> + <QBtn + v-else + icon="add_circle" + color="primary" + flat + round + v-shortcut="'+'" + padding="xs" + @click=" + () => { + documentDialogRef.show = true; + delete documentDialogRef.dms; + } + " + data-cy="dms-create" + > + <QTooltip>{{ t('Create document') }}</QTooltip> + </QBtn> + </div> + <QDialog v-model="documentDialogRef.show"> + <VnDms + model="dms" + :default-dms-code="defaultDmsCode" + :form-initial-data="documentDialogRef.dms" + :url=" + documentDialogRef.dms + ? `Dms/${documentDialogRef.dms.id}/updateFile` + : 'Dms/uploadFile' + " + :description="documentDialogRef.supplierName" + @on-data-saved=" + (_, { data }) => { + let dmsData = data; + if (Array.isArray(data)) dmsData = data[0]; + formRef.formData.dmsFk = dmsData.id; + formRef.formData.dms = dmsData; + formRef.hasChanges = true; + formRef.save(); + } + " + /> + </QDialog> +</template> +<i18n> +es: + Document: Documento + Download file: Descargar archivo + Edit document: Editar documento + Delete file: Eliminar archivo + Create document: Crear documento + +</i18n> diff --git a/src/components/common/VnDmsList.vue b/src/components/common/VnDmsList.vue index 424781a26..aafa9f4ba 100644 --- a/src/components/common/VnDmsList.vue +++ b/src/components/common/VnDmsList.vue @@ -389,10 +389,7 @@ defineExpose({ </div> </template> </QTable> - <div - v-else - class="info-row q-pa-md text-center" - > + <div v-else class="info-row q-pa-md text-center"> <h5> {{ t('No data to display') }} </h5> @@ -416,6 +413,7 @@ defineExpose({ v-shortcut @click="showFormDialog()" class="fill-icon" + data-cy="addButton" > <QTooltip> {{ t('Upload file') }} diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 9821992cb..474d68116 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -84,7 +84,7 @@ const mixinRules = [ ...($attrs.rules ?? []), (val) => { const maxlength = $props.maxlength; - if (maxlength && +val.length > maxlength) + if (maxlength && +val?.length > maxlength) return t(`maxLength`, { value: maxlength }); const { min, max } = vnInputRef.value.$attrs; if (!min) return null; diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 1f4705faa..343130f1d 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/common/VnLog.vue b/src/components/common/VnLog.vue index 8f106a9f1..804147539 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -10,7 +10,7 @@ import { useColor } from 'src/composables/useColor'; import { useCapitalize } from 'src/composables/useCapitalize'; import { useValidator } from 'src/composables/useValidator'; import VnAvatar from '../ui/VnAvatar.vue'; -import VnJsonValue from '../common/VnJsonValue.vue'; +import VnLogValue from './VnLogValue.vue'; import FetchData from '../FetchData.vue'; import VnSelect from './VnSelect.vue'; import VnUserLink from '../ui/VnUserLink.vue'; @@ -560,10 +560,11 @@ watch( value.nameI18n }}: </span> - <VnJsonValue + <VnLogValue :value=" value.val.val " + :name="value.name" /> </QItem> </QCardSection> @@ -614,7 +615,10 @@ watch( > {{ prop.nameI18n }}: </span> - <VnJsonValue :value="prop.val.val" /> + <VnLogValue + :value="prop.val.val" + :name="prop.name" + /> <span v-if=" propIndex < @@ -642,8 +646,9 @@ watch( {{ prop.nameI18n }}: </span> <span v-if="log.action == 'update'"> - <VnJsonValue + <VnLogValue :value="prop.old.val" + :name="prop.name" /> <span v-if="prop.old.id" @@ -652,8 +657,9 @@ watch( #{{ prop.old.id }} </span> → - <VnJsonValue + <VnLogValue :value="prop.val.val" + :name="prop.name" /> <span v-if="prop.val.id" @@ -663,8 +669,9 @@ watch( </span> </span> <span v-else="prop.old.val"> - <VnJsonValue + <VnLogValue :value="prop.val.val" + :name="prop.name" /> <span v-if="prop.old.id" @@ -700,6 +707,7 @@ watch( v-model="searchInput" class="full-width" clearable + filled clear-icon="close" @keyup.enter="() => selectFilter('search')" @focusout="() => selectFilter('search')" @@ -719,6 +727,7 @@ watch( v-model="selectedFilters.changedModel" option-label="locale" option-value="value" + filled :options="actions" @update:model-value="selectFilter('action')" hide-selected @@ -744,8 +753,7 @@ watch( class="full-width" :label="t('globals.user')" v-model="userSelect" - option-label="name" - option-value="id" + filled :url="`${model}Logs/${route.params.id}/editors`" :fields="['id', 'nickname', 'name', 'image']" sort-by="nickname" @@ -774,6 +782,7 @@ watch( :label="t('globals.changes')" v-model="changeInput" class="full-width" + filled clearable clear-icon="close" @keyup.enter="selectFilter('change')" @@ -810,6 +819,7 @@ watch( @clear="selectFilter('date', 'to')" v-model="dateFrom" clearable + filled clear-icon="close" /> </QItem> @@ -822,6 +832,7 @@ watch( @clear="selectFilter('date', 'from')" v-model="dateTo" clearable + filled clear-icon="close" /> </QItem> @@ -835,6 +846,7 @@ watch( dense flat minimal + filled @update:model-value=" (value) => { dateFromDialog = false; diff --git a/src/components/common/VnLogValue.vue b/src/components/common/VnLogValue.vue new file mode 100644 index 000000000..df0be4011 --- /dev/null +++ b/src/components/common/VnLogValue.vue @@ -0,0 +1,22 @@ +<script setup> +import { useDescriptorStore } from 'src/stores/useDescriptorStore'; +import VnJsonValue from './VnJsonValue.vue'; +import { computed } from 'vue'; +const descriptorStore = useDescriptorStore(); + +const $props = defineProps({ + name: { type: [String], default: undefined }, +}); + +const descriptor = computed(() => descriptorStore.has($props.name)); +</script> +<template> + <VnJsonValue v-bind="$attrs" /> + <QIcon + name="launch" + class="link" + v-if="$attrs.value && descriptor" + :data-cy="'iconLaunch-' + $props.name" + /> + <component :is="descriptor" :id="$attrs.value" v-if="$attrs.value && descriptor" /> +</template> diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 339f90e0e..6eda03891 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue'; +import { ref, toRefs, computed, watch, onMounted, useAttrs, nextTick } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; import { useRequired } from 'src/composables/useRequired'; @@ -247,6 +247,7 @@ async function fetchFilter(val) { } async function filterHandler(val, update) { + if (isLoading.value) return update(); if (!val && lastVal.value === val) { lastVal.value = val; return update(); @@ -294,6 +295,7 @@ async function onScroll({ to, direction, from, index }) { await arrayData.loadMore(); setOptions(arrayData.store.data); vnSelectRef.value.scrollTo(lastIndex); + await nextTick(); isLoading.value = false; } } diff --git a/src/components/common/__tests__/VnDmsList.spec.js b/src/components/common/__tests__/VnDmsList.spec.js index 9649943a2..22101239e 100644 --- a/src/components/common/__tests__/VnDmsList.spec.js +++ b/src/components/common/__tests__/VnDmsList.spec.js @@ -4,12 +4,15 @@ import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; describe('VnDmsList', () => { let vm; - const dms = { - userFk: 1, - name: 'DMS 1' + const dms = { + userFk: 1, + name: 'DMS 1', }; - + beforeAll(() => { + vi.mock('src/composables/getUrl', () => ({ + getUrl: vi.fn().mockResolvedValue(''), + })); vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); vm = createWrapper(VnDmsList, { props: { @@ -18,8 +21,8 @@ describe('VnDmsList', () => { filter: 'wd.workerFk', updateModel: 'Workers', deleteModel: 'WorkerDms', - downloadModel: 'WorkerDms' - } + downloadModel: 'WorkerDms', + }, }).vm; }); @@ -29,46 +32,45 @@ describe('VnDmsList', () => { describe('setData()', () => { const data = [ - { - userFk: 1, + { + userFk: 1, name: 'Jessica', lastName: 'Jones', file: '4.jpg', - created: '2021-07-28 21:00:00' + created: '2021-07-28 21:00:00', }, - { - userFk: 2, + { + userFk: 2, name: 'Bruce', lastName: 'Banner', created: '2022-07-28 21:00:00', dms: { - userFk: 2, + userFk: 2, name: 'Bruce', lastName: 'BannerDMS', created: '2022-07-28 21:00:00', file: '4.jpg', - } + }, }, { userFk: 3, name: 'Natasha', lastName: 'Romanoff', file: '4.jpg', - created: '2021-10-28 21:00:00' - } - ] + created: '2021-10-28 21:00:00', + }, + ]; it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => { vm.setData(data); expect([vm.rows][0][0].lastName).toEqual('BannerDMS'); expect([vm.rows][0][1].lastName).toEqual('Romanoff'); - }); }); describe('parseDms()', () => { - const resultDms = { ...dms, userId:1}; - + const resultDms = { ...dms, userId: 1 }; + it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { const parsedDms = vm.parseDms(dms); expect(parsedDms).toEqual(resultDms); @@ -76,12 +78,12 @@ describe('VnDmsList', () => { }); describe('showFormDialog()', () => { - const resultDms = { ...dms, userId:1}; - + const resultDms = { ...dms, userId: 1 }; + it('should call fn parseDms() and set show true if dms is defined', () => { vm.showFormDialog(dms); expect(vm.formDialog.show).toEqual(true); expect(vm.formDialog.dms).toEqual(resultDms); }); }); -}); \ No newline at end of file +}); diff --git a/src/components/common/__tests__/VnLogValue.spec.js b/src/components/common/__tests__/VnLogValue.spec.js new file mode 100644 index 000000000..c23743a02 --- /dev/null +++ b/src/components/common/__tests__/VnLogValue.spec.js @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import VnLogValue from 'src/components/common/VnLogValue.vue'; +import { createWrapper } from 'app/test/vitest/helper'; + +const buildComponent = (props) => { + return createWrapper(VnLogValue, { + props, + global: {}, + }).wrapper; +}; + +describe('VnLogValue', () => { + const id = 1; + it('renders without descriptor', async () => { + expect(getIcon('inventFk').exists()).toBe(false); + }); + + it('renders with descriptor', async () => { + expect(getIcon('claimFk').text()).toBe('launch'); + }); + + function getIcon(name) { + const wrapper = buildComponent({ value: { val: id }, name }); + return wrapper.find('.q-icon'); + } +}); diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 744f84e6d..5f9a89d64 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -1,353 +1,38 @@ <script setup> -import { onBeforeMount, watch, computed, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; -import { useArrayData } from 'composables/useArrayData'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import { useState } from 'src/composables/useState'; -import { useRoute, useRouter } from 'vue-router'; -import { useClipboard } from 'src/composables/useClipboard'; -import VnMoreOptions from './VnMoreOptions.vue'; +import { ref } from 'vue'; +import VnDescriptor from './VnDescriptor.vue'; const $props = defineProps({ - url: { - type: String, - default: '', - }, - filter: { - type: Object, - default: null, - }, - title: { - type: String, - default: '', - }, - subtitle: { + id: { type: Number, - default: null, + default: false, }, - dataKey: { - type: String, - default: null, - }, - summary: { + card: { type: Object, default: null, }, - width: { - type: String, - default: 'md-width', - }, }); -const state = useState(); -const route = useRoute(); -const router = useRouter(); -const { t } = useI18n(); -const { copyText } = useClipboard(); -const { viewSummary } = useSummaryDialog(); -let arrayData; -let store; -let entity; -const isLoading = ref(false); -const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); -const DESCRIPTOR_PROXY = 'DescriptorProxy'; -const moduleName = ref(); -const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value; -defineExpose({ getData }); - -onBeforeMount(async () => { - arrayData = useArrayData($props.dataKey, { - url: $props.url, - userFilter: $props.filter, - skip: 0, - oneRecord: true, - }); - store = arrayData.store; - entity = computed(() => { - const data = store.data ?? {}; - if (data) emit('onFetch', data); - return data; - }); - - // It enables to load data only once if the module is the same as the dataKey - if (!isSameDataKey.value || !route.params.id) await getData(); - watch( - () => [$props.url, $props.filter], - async () => { - if (!isSameDataKey.value) await getData(); - }, - ); -}); - -function getName() { - let name = $props.dataKey; - if ($props.dataKey.includes(DESCRIPTOR_PROXY)) { - name = name.split(DESCRIPTOR_PROXY)[0]; - } - return name; -} -const routeName = computed(() => { - let routeName = getName(); - return `${routeName}Summary`; -}); - -async function getData() { - store.url = $props.url; - store.filter = $props.filter ?? {}; - isLoading.value = true; - try { - const { data } = await arrayData.fetch({ append: false, updateRouter: false }); - state.set($props.dataKey, data); - emit('onFetch', data); - } finally { - isLoading.value = false; - } -} - -function getValueFromPath(path) { - if (!path) return; - const keys = path.toString().split('.'); - let current = entity.value; - - for (const key of keys) { - if (current[key] === undefined) return undefined; - else current = current[key]; - } - return current; -} - -function copyIdText(id) { - copyText(id, { - component: { - copyValue: id, - }, - }); -} - const emit = defineEmits(['onFetch']); - -const iconModule = computed(() => { - moduleName.value = getName(); - if (isSameModuleName) { - return router.options.routes[1].children.find((r) => r.name === moduleName.value) - ?.meta?.icon; - } else { - return route.matched[1].meta.icon; - } -}); - -const toModule = computed(() => { - moduleName.value = getName(); - if (isSameModuleName) { - return router.options.routes[1].children.find((r) => r.name === moduleName.value) - ?.children[0]?.redirect; - } else { - return route.matched[1].path.split('/').length > 2 - ? route.matched[1].redirect - : route.matched[1].children[0].redirect; - } -}); +const entity = ref(); </script> <template> - <div class="descriptor"> - <template v-if="entity && !isLoading"> - <div class="header bg-primary q-pa-sm justify-between"> - <slot name="header-extra-action"> - <QBtn - round - flat - dense - size="md" - :icon="iconModule" - color="white" - class="link" - :to="toModule" - > - <QTooltip> - {{ t('globals.goToModuleIndex') }} - </QTooltip> - </QBtn> - </slot> - <QBtn - @click.stop="viewSummary(entity.id, $props.summary, $props.width)" - round - flat - dense - size="md" - icon="preview" - color="white" - class="link" - v-if="summary" - > - <QTooltip> - {{ t('components.smartCard.openSummary') }} - </QTooltip> - </QBtn> - <RouterLink :to="{ name: routeName, params: { id: entity.id } }"> - <QBtn - class="link" - color="white" - dense - flat - icon="launch" - round - size="md" - > - <QTooltip> - {{ t('components.cardDescriptor.summary') }} - </QTooltip> - </QBtn> - </RouterLink> - <VnMoreOptions v-if="$slots.menu"> - <template #menu="{ menuRef }"> - <slot name="menu" :entity="entity" :menu-ref="menuRef" /> - </template> - </VnMoreOptions> - </div> - <slot name="before" /> - <div class="body q-py-sm"> - <QList dense> - <QItemLabel header class="ellipsis text-h5" :lines="1"> - <div class="title"> - <span v-if="$props.title" :title="getValueFromPath(title)"> - {{ getValueFromPath(title) ?? $props.title }} - </span> - <slot v-else name="description" :entity="entity"> - <span :title="entity.name"> - {{ entity.name }} - </span> - </slot> - </div> - </QItemLabel> - <QItem> - <QItemLabel class="subtitle"> - #{{ getValueFromPath(subtitle) ?? entity.id }} - </QItemLabel> - <QBtn - round - flat - dense - size="sm" - icon="content_copy" - color="primary" - @click.stop="copyIdText(entity.id)" - > - <QTooltip> - {{ t('globals.copyId') }} - </QTooltip> - </QBtn> - </QItem> - </QList> - <div class="list-box q-mt-xs"> - <slot name="body" :entity="entity" /> - </div> - </div> - <div class="icons"> - <slot name="icons" :entity="entity" /> - </div> - <div class="actions justify-center" data-cy="descriptor_actions"> - <slot name="actions" :entity="entity" /> - </div> - <slot name="after" /> - </template> - <SkeletonDescriptor v-if="!entity || isLoading" /> - </div> - <QInnerLoading - :label="t('globals.pleaseWait')" - :showing="isLoading" - color="primary" - /> -</template> - -<style lang="scss"> -.body { - background-color: var(--vn-section-color); - .text-h5 { - font-size: 20px; - padding-top: 5px; - padding-bottom: 0px; - } - .q-item { - min-height: 20px; - - .link { - margin-left: 10px; - } - } - .vn-label-value { - display: flex; - padding: 0px 16px; - .label { - color: var(--vn-label-color); - font-size: 14px; - - &:not(:has(a))::after { - content: ':'; + <component + :is="card" + :id + :visual="false" + v-bind="$attrs" + @on-fetch=" + (data) => { + entity = data; + emit('onFetch', data); } - } - .value { - color: var(--vn-text-color); - font-size: 14px; - margin-left: 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: left; - } - .info { - margin-left: 5px; - } - } -} -</style> - -<style lang="scss" scoped> -.title { - overflow: hidden; - text-overflow: ellipsis; - span { - color: var(--vn-text-color); - font-weight: bold; - } -} -.subtitle { - color: var(--vn-text-color); - font-size: 16px; - margin-bottom: 2px; -} -.list-box { - .q-item__label { - color: var(--vn-label-color); - padding-bottom: 0%; - } -} -.descriptor { - width: 256px; - .header { - display: flex; - align-items: center; - } - .icons { - margin: 0 10px; - display: flex; - justify-content: center; - .q-icon { - margin-right: 5px; - } - } - .actions { - margin: 0 5px; - justify-content: center !important; - } -} -</style> -<i18n> - en: - globals: - copyId: Copy ID - es: - globals: - copyId: Copiar ID -</i18n> + " + /> + <VnDescriptor v-model="entity" v-bind="$attrs"> + <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> + <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> + </template> + </VnDescriptor> +</template> diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 6a61994c1..05bfed998 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -81,6 +81,7 @@ async function fetch() { name: `${moduleName ?? route.meta.moduleName}Summary`, params: { id: entityId || entity.id }, }" + data-cy="goToSummaryBtn" > <QIcon name="open_in_new" color="white" size="sm" /> </router-link> diff --git a/src/components/ui/EntityDescriptor.vue b/src/components/ui/EntityDescriptor.vue new file mode 100644 index 000000000..a5dced551 --- /dev/null +++ b/src/components/ui/EntityDescriptor.vue @@ -0,0 +1,78 @@ +<script setup> +import { onBeforeMount, watch, computed, ref } from 'vue'; +import { useArrayData } from 'composables/useArrayData'; +import { useState } from 'src/composables/useState'; +import { useRoute } from 'vue-router'; +import VnDescriptor from './VnDescriptor.vue'; + +const $props = defineProps({ + url: { + type: String, + default: '', + }, + filter: { + type: Object, + default: null, + }, + dataKey: { + type: String, + default: null, + }, +}); + +const state = useState(); +const route = useRoute(); +let arrayData; +let store; +let entity; +const isLoading = ref(false); +const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); +defineExpose({ getData }); + +onBeforeMount(async () => { + arrayData = useArrayData($props.dataKey, { + url: $props.url, + userFilter: $props.filter, + skip: 0, + oneRecord: true, + }); + store = arrayData.store; + entity = computed(() => { + const data = store.data ?? {}; + if (data) emit('onFetch', data); + return data; + }); + + // It enables to load data only once if the module is the same as the dataKey + if (!isSameDataKey.value || !route.params.id) await getData(); + watch( + () => [$props.url, $props.filter], + async () => { + if (!isSameDataKey.value) await getData(); + }, + ); +}); + +async function getData() { + store.url = $props.url; + store.filter = $props.filter ?? {}; + isLoading.value = true; + try { + const { data } = await arrayData.fetch({ append: false, updateRouter: false }); + state.set($props.dataKey, data); + emit('onFetch', data); + } finally { + isLoading.value = false; + } +} + +const emit = defineEmits(['onFetch']); +</script> + +<template> + <VnDescriptor v-model="entity" v-bind="$attrs" :module="dataKey"> + <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> + <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> + </template> + </VnDescriptor> +</template> diff --git a/src/components/ui/VnDescriptor.vue b/src/components/ui/VnDescriptor.vue new file mode 100644 index 000000000..47da98d74 --- /dev/null +++ b/src/components/ui/VnDescriptor.vue @@ -0,0 +1,318 @@ +<script setup> +import { computed, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import { useRoute, useRouter } from 'vue-router'; +import { useClipboard } from 'src/composables/useClipboard'; +import VnMoreOptions from './VnMoreOptions.vue'; + +const entity = defineModel({ type: Object, default: null }); +const $props = defineProps({ + title: { + type: String, + default: '', + }, + subtitle: { + type: Number, + default: null, + }, + summary: { + type: Object, + default: null, + }, + width: { + type: String, + default: 'md-width', + }, + module: { + type: String, + default: null, + }, + toModule: { + type: String, + default: null, + }, +}); + +const route = useRoute(); +const router = useRouter(); +const { t } = useI18n(); +const { copyText } = useClipboard(); +const { viewSummary } = useSummaryDialog(); +const DESCRIPTOR_PROXY = 'DescriptorProxy'; +const moduleName = ref(); +const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value; + +function getName() { + let name = $props.module; + if ($props.module.includes(DESCRIPTOR_PROXY)) { + name = name.split(DESCRIPTOR_PROXY)[0]; + } + return name; +} +const routeName = computed(() => { + let routeName = getName(); + return `${routeName}Summary`; +}); + +function getValueFromPath(path) { + if (!path) return; + const keys = path.toString().split('.'); + let current = entity.value; + + for (const key of keys) { + if (current[key] === undefined) return undefined; + else current = current[key]; + } + return current; +} + +function copyIdText(id) { + copyText(id, { + component: { + copyValue: id, + }, + }); +} + +const emit = defineEmits(['onFetch']); + +const iconModule = computed(() => { + moduleName.value = getName(); + if ($props.toModule) { + return router.getRoutes().find((r) => r.name === $props.toModule.name).meta.icon; + } + if (isSameModuleName) { + return router.options.routes[1].children.find((r) => r.name === moduleName.value) + ?.meta?.icon; + } else { + return route.matched[1].meta.icon; + } +}); + +const toModule = computed(() => { + moduleName.value = getName(); + if ($props.toModule) return $props.toModule; + if (isSameModuleName) { + return router.options.routes[1].children.find((r) => r.name === moduleName.value) + ?.redirect; + } else { + return route.matched[1].path.split('/').length > 2 + ? route.matched[1].redirect + : route.matched[1].children[0].redirect; + } +}); +</script> + +<template> + <div class="descriptor" data-cy="vnDescriptor"> + <template v-if="entity && entity?.id"> + <div class="header bg-primary q-pa-sm justify-between"> + <slot name="header-extra-action"> + <QBtn + round + flat + dense + size="md" + :icon="iconModule" + color="white" + class="link" + :to="toModule" + > + <QTooltip> + {{ t('globals.goToModuleIndex') }} + </QTooltip> + </QBtn> + </slot> + <QBtn + @click.stop="viewSummary(entity.id, summary, width)" + round + flat + dense + size="md" + icon="preview" + color="white" + class="link" + v-if="summary" + data-cy="openSummaryBtn" + > + <QTooltip> + {{ t('components.smartCard.openSummary') }} + </QTooltip> + </QBtn> + <RouterLink :to="{ name: routeName, params: { id: entity.id } }"> + <QBtn + class="link" + color="white" + dense + flat + icon="launch" + round + size="md" + data-cy="goToSummaryBtn" + > + <QTooltip> + {{ t('components.vnDescriptor.summary') }} + </QTooltip> + </QBtn> + </RouterLink> + <VnMoreOptions v-if="$slots.menu"> + <template #menu="{ menuRef }"> + <slot name="menu" :entity="entity" :menu-ref="menuRef" /> + </template> + </VnMoreOptions> + </div> + <slot name="before" /> + <div class="body q-py-sm"> + <QList dense> + <QItemLabel header class="ellipsis text-h5" :lines="1"> + <div class="title"> + <span + v-if="title" + :title="getValueFromPath(title)" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_title`" + > + {{ getValueFromPath(title) ?? title }} + </span> + <slot v-else name="description" :entity="entity"> + <span + :title="entity.name" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_description`" + v-text="entity.name" + /> + </slot> + </div> + </QItemLabel> + <QItem> + <QItemLabel + class="subtitle" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_subtitle`" + > + #{{ getValueFromPath(subtitle) ?? entity.id }} + </QItemLabel> + <QBtn + round + flat + dense + size="sm" + icon="content_copy" + color="primary" + @click.stop="copyIdText(entity.id)" + > + <QTooltip> + {{ t('globals.copyId') }} + </QTooltip> + </QBtn> + </QItem> + </QList> + <div + class="list-box q-mt-xs" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_listbox`" + > + <slot name="body" :entity="entity" /> + </div> + </div> + <div class="icons"> + <slot name="icons" :entity="entity" /> + </div> + <div class="actions justify-center" data-cy="descriptor_actions"> + <slot name="actions" :entity="entity" /> + </div> + <slot name="after" /> + </template> + <SkeletonDescriptor v-if="!entity" /> + </div> + <QInnerLoading :label="t('globals.pleaseWait')" :showing="!entity" color="primary" /> +</template> + +<style lang="scss"> +.body { + background-color: var(--vn-section-color); + .text-h5 { + font-size: 20px; + padding-top: 5px; + padding-bottom: 0px; + } + .q-item { + min-height: 20px; + + .link { + margin-left: 10px; + } + } + .vn-label-value { + display: flex; + padding: 0px 16px; + .label { + color: var(--vn-label-color); + font-size: 14px; + + &:not(:has(a))::after { + content: ':'; + } + } + .value { + color: var(--vn-text-color); + font-size: 14px; + margin-left: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + } + .info { + margin-left: 5px; + } + } +} +</style> + +<style lang="scss" scoped> +.title { + overflow: hidden; + text-overflow: ellipsis; + span { + color: var(--vn-text-color); + font-weight: bold; + } +} +.subtitle { + color: var(--vn-text-color); + font-size: 16px; + margin-bottom: 2px; +} +.list-box { + .q-item__label { + color: var(--vn-label-color); + padding-bottom: 0%; + } +} +.descriptor { + width: 256px; + .header { + display: flex; + align-items: center; + } + .icons { + margin: 0 10px; + display: flex; + justify-content: center; + .q-icon { + margin-right: 5px; + } + } + .actions { + margin: 0 5px; + justify-content: center !important; + } +} +</style> +<i18n> + en: + globals: + copyId: Copy ID + es: + globals: + copyId: Copiar ID +</i18n> diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 93e3a57f2..85cc8cde2 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 6f7d42a89..55c65d58e 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 8a1c7a0f2..984e2b64f 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/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index 6ce28254d..b7e6ccbec 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -40,6 +40,11 @@ const quasar = useQuasar(); const newNote = reactive({ text: null, observationTypeFk: null }); const observationTypes = ref([]); const vnPaginateRef = ref(); + +const defaultObservationType = computed(() => + observationTypes.value.find(ot => ot.code === 'salesPerson')?.id +); + let originalText; function handleClick(e) { @@ -111,14 +116,22 @@ function fetchData([data]) { originalText = data?.notes; emit('onFetch', data); } + +const handleObservationTypes = (data) => { + observationTypes.value = data; + if(defaultObservationType.value) { + newNote.observationTypeFk = defaultObservationType.value; + } +}; + </script> <template> <FetchData v-if="selectType" url="ObservationTypes" - :filter="{ fields: ['id', 'description'] }" + :filter="{ fields: ['id', 'description', 'code'] }" auto-load - @on-fetch="(data) => (observationTypes = data)" + @on-fetch="handleObservationTypes" /> <FetchData v-if="justInput" @@ -154,6 +167,7 @@ function fetchData([data]) { filled size="lg" autogrow + autofocus @keyup.enter.stop="handleClick" :required="isRequired" clearable @@ -189,7 +203,6 @@ function fetchData([data]) { :search-url="false" @on-fetch=" newNote.text = ''; - newNote.observationTypeFk = null; " > <template #body="{ rows }"> diff --git a/src/components/ui/VnToSummary.vue b/src/components/ui/VnToSummary.vue index 305d65e02..853d26230 100644 --- a/src/components/ui/VnToSummary.vue +++ b/src/components/ui/VnToSummary.vue @@ -26,6 +26,7 @@ const id = props.entityId; :to="{ name: routeName, params: { id: id } }" class="header link" :href="url" + data-cy="goToSummaryBtn" > <QIcon name="open_in_new" color="white" size="sm" /> </router-link> diff --git a/src/composables/__tests__/downloadFile.spec.js b/src/composables/__tests__/downloadFile.spec.js index f53b56b3e..f83a973b0 100644 --- a/src/composables/__tests__/downloadFile.spec.js +++ b/src/composables/__tests__/downloadFile.spec.js @@ -6,10 +6,12 @@ const session = useSession(); const token = session.getToken(); describe('downloadFile', () => { - const baseUrl = 'http://localhost:9000'; let defaulCreateObjectURL; beforeAll(() => { + vi.mock('src/composables/getUrl', () => ({ + getUrl: vi.fn().mockResolvedValue(''), + })); defaulCreateObjectURL = window.URL.createObjectURL; window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); }); @@ -22,15 +24,14 @@ describe('downloadFile', () => { headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, }; vi.spyOn(axios, 'get').mockImplementation((url) => { - if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); - else if (url.includes('downloadFile')) return Promise.resolve(res); + if (url.includes('downloadFile')) return Promise.resolve(res); }); await downloadFile(1); expect(axios.get).toHaveBeenCalledWith( - `${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, - { responseType: 'blob' } + `/api/dms/1/downloadFile?access_token=${token}`, + { responseType: 'blob' }, ); }); }); diff --git a/src/composables/downloadFile.js b/src/composables/downloadFile.js index 4588265a2..0c4e8edb6 100644 --- a/src/composables/downloadFile.js +++ b/src/composables/downloadFile.js @@ -7,18 +7,33 @@ const { getTokenMultimedia } = useSession(); const token = getTokenMultimedia(); export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) { - const appUrl = (await getUrl('', 'lilium')).replace('/#/', ''); + const appUrl = await getAppUrl(); const response = await axios.get( url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`, - { responseType: 'blob' } + { responseType: 'blob' }, ); + download(response); +} + +export async function downloadDocuware(url, params) { + const appUrl = await getAppUrl(); + const response = await axios.get(`${appUrl}/api/` + url, { + responseType: 'blob', + params, + }); + + download(response); +} + +function download(response) { const contentDisposition = response.headers['content-disposition']; const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); - const filename = - matches != null && matches[1] - ? matches[1].replace(/['"]/g, '') - : 'downloaded-file'; + const filename = matches?.[1] ? matches[1].replace(/['"]/g, '') : 'downloaded-file'; exportFile(filename, response.data); } + +async function getAppUrl() { + return (await getUrl('', 'lilium')).replace('/#/', ''); +} diff --git a/src/css/app.scss b/src/css/app.scss index 5befd150b..b299973d1 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -325,7 +325,6 @@ input::-webkit-inner-spin-button { min-height: auto !important; display: flex; align-items: flex-end; - padding-bottom: 2px; .q-field__native.row { min-height: auto !important; } diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index c1286267c..c62305f95 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -646,6 +646,7 @@ worker: model: Model serialNumber: Serial number removePDA: Deallocate PDA + sendToTablet: Send to tablet create: lastName: Last name birth: Birth @@ -816,6 +817,7 @@ travel: search: Search travel searchInfo: You can search by travel id or name id: Id + awbFk: AWB travelList: tableVisibleColumns: ref: Reference @@ -892,6 +894,8 @@ components: VnLv: copyText: '{copyValue} has been copied to the clipboard' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' + VnNotes: + clientWithoutPhone: 'The following clients do not have a phone number and the message will not be sent to them: {clientWithoutPhone}' weekdays: sun: Sunday mon: Monday diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 681781d11..86d15e985 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -731,6 +731,7 @@ worker: model: Modelo serialNumber: NĂşmero de serie removePDA: Desasignar PDA + sendToTablet: Enviar a la tablet create: lastName: Apellido birth: Fecha de nacimiento @@ -899,6 +900,7 @@ travel: search: Buscar envĂo searchInfo: Buscar envĂo por id o nombre id: Id + awbFk: GuĂa aĂ©rea travelList: tableVisibleColumns: ref: Referencia @@ -976,6 +978,8 @@ components: VnLv: copyText: '{copyValue} se ha copiado al portapepeles' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' + VnNotes: + clientWithoutPhone: 'Estos clientes no tienen asociado nĂşmero de tĂ©lefono y el sms no les será enviado: {clientWithoutPhone}' weekdays: sun: Domingo mon: Lunes diff --git a/src/pages/Account/AccountFilter.vue b/src/pages/Account/AccountFilter.vue index 50c3ee1ac..732e92f77 100644 --- a/src/pages/Account/AccountFilter.vue +++ b/src/pages/Account/AccountFilter.vue @@ -47,7 +47,7 @@ const rolesOptions = ref([]); :label="t('globals.name')" v-model="params.name" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -57,7 +57,7 @@ const rolesOptions = ref([]); :label="t('account.card.alias')" v-model="params.nickname" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -75,8 +75,7 @@ const rolesOptions = ref([]); use-input hide-selected dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> diff --git a/src/pages/Account/Acls/AclFilter.vue b/src/pages/Account/Acls/AclFilter.vue index 8035f92b8..222fe5b77 100644 --- a/src/pages/Account/Acls/AclFilter.vue +++ b/src/pages/Account/Acls/AclFilter.vue @@ -56,8 +56,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -72,8 +71,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -83,7 +81,7 @@ onBeforeMount(() => { :label="t('acls.aclFilter.property')" v-model="params.property" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -98,8 +96,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -114,8 +111,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Account/Alias/Card/AliasDescriptor.vue b/src/pages/Account/Alias/Card/AliasDescriptor.vue index 671ef7fbc..957047cc3 100644 --- a/src/pages/Account/Alias/Card/AliasDescriptor.vue +++ b/src/pages/Account/Alias/Card/AliasDescriptor.vue @@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import axios from 'axios'; @@ -48,11 +48,12 @@ const removeAlias = () => { </script> <template> - <CardDescriptor + <EntityDescriptor ref="descriptor" :url="`MailAliases/${entityId}`" data-key="Alias" title="alias" + :to-module="{ name: 'AccountAlias' }" > <template #menu> <QItem v-ripple clickable @click="removeAlias()"> @@ -62,7 +63,7 @@ const removeAlias = () => { <template #body="{ entity }"> <VnLv :label="t('role.description')" :value="entity.description" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 49328fe87..eb0a9013c 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -1,7 +1,7 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; import VnImg from 'src/components/ui/VnImg.vue'; @@ -20,7 +20,7 @@ onMounted(async () => { </script> <template> - <CardDescriptor + <EntityDescriptor ref="descriptor" :url="`VnUsers/preview`" :filter="{ ...filter, where: { id: entityId } }" @@ -78,7 +78,7 @@ onMounted(async () => { </QIcon> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style scoped> .q-item__label { diff --git a/src/pages/Account/Card/AccountDescriptorProxy.vue b/src/pages/Account/Card/AccountDescriptorProxy.vue new file mode 100644 index 000000000..de3220fea --- /dev/null +++ b/src/pages/Account/Card/AccountDescriptorProxy.vue @@ -0,0 +1,14 @@ +<script setup> +import AccountDescriptor from './AccountDescriptor.vue'; +import AccountSummary from './AccountSummary.vue'; +</script> +<template> + <QPopupProxy style="max-width: 10px"> + <AccountDescriptor + v-if="$attrs.id" + v-bind="$attrs.id" + :summary="AccountSummary" + :proxy-render="true" + /> + </QPopupProxy> +</template> diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index 2172fec9a..a098f10ee 100644 --- a/src/pages/Account/Card/AccountSummary.vue +++ b/src/pages/Account/Card/AccountSummary.vue @@ -18,7 +18,7 @@ const entityId = computed(() => $props.id || route.params.id); data-key="Account" ref="AccountSummary" url="VnUsers/preview" - :filter="filter" + :filter="{ ...filter, where: { id: entityId } }" > <template #header="{ entity }">{{ entity.id }} - {{ entity.nickname }}</template> <template #menu> diff --git a/src/pages/Account/Role/AccountRolesFilter.vue b/src/pages/Account/Role/AccountRolesFilter.vue index cbe7a70c8..1358236c6 100644 --- a/src/pages/Account/Role/AccountRolesFilter.vue +++ b/src/pages/Account/Role/AccountRolesFilter.vue @@ -27,7 +27,7 @@ const props = defineProps({ :label="t('globals.name')" v-model="params.name" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -37,7 +37,7 @@ const props = defineProps({ :label="t('role.description')" v-model="params.description" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index 517517af0..698bea4fa 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -32,11 +32,12 @@ const removeRole = async () => { </script> <template> - <CardDescriptor + <EntityDescriptor url="VnRoles" :filter="{ where: { id: entityId } }" data-key="Role" :summary="$props.summary" + :to-module="{ name: 'AccountRoles' }" > <template #menu> <QItem v-ripple clickable @click="removeRole()"> @@ -46,7 +47,7 @@ const removeRole = async () => { <template #body="{ entity }"> <VnLv :label="t('role.description')" :value="entity.description" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style scoped> .q-item__label { diff --git a/src/pages/Claim/Card/ClaimBasicData.vue b/src/pages/Claim/Card/ClaimBasicData.vue index 43941d1dc..7e7d42ae8 100644 --- a/src/pages/Claim/Card/ClaimBasicData.vue +++ b/src/pages/Claim/Card/ClaimBasicData.vue @@ -2,6 +2,7 @@ import { ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; +import { getDifferences, getUpdatedValues } from 'src/filters'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import FetchData from 'components/FetchData.vue'; @@ -9,12 +10,18 @@ import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; - import VnAvatar from 'src/components/ui/VnAvatar.vue'; const route = useRoute(); const { t } = useI18n(); const workersOptions = ref([]); + +function onBeforeSave(formData, originalData) { + return getUpdatedValues( + Object.keys(getDifferences(formData, originalData)), + formData, + ); +} </script> <template> <FetchData @@ -27,6 +34,7 @@ const workersOptions = ref([]); <FormModel model="Claim" :url-update="`Claims/updateClaim/${route.params.id}`" + :mapper="onBeforeSave" auto-load > <template #form="{ data, validate }"> diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index d789b63d3..76ede81ed 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -6,7 +6,7 @@ import { toDateHourMinSec, toPercentage } from 'src/filters'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import { getUrl } from 'src/composables/getUrl'; @@ -44,7 +44,7 @@ onMounted(async () => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Claims/${entityId}`" :filter="filter" title="client.name" @@ -147,7 +147,7 @@ onMounted(async () => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style scoped> .q-item__label { diff --git a/src/pages/Claim/Card/ClaimDescriptorProxy.vue b/src/pages/Claim/Card/ClaimDescriptorProxy.vue new file mode 100644 index 000000000..78e686745 --- /dev/null +++ b/src/pages/Claim/Card/ClaimDescriptorProxy.vue @@ -0,0 +1,14 @@ +<script setup> +import ClaimDescriptor from './ClaimDescriptor.vue'; +import ClaimSummary from './ClaimSummary.vue'; +</script> +<template> + <QPopupProxy style="max-width: 10px"> + <ClaimDescriptor + v-if="$attrs.id" + v-bind="$attrs.id" + :summary="ClaimSummary" + :proxy-render="true" + /> + </QPopupProxy> +</template> diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index 37146865c..51460f7e4 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -33,7 +33,7 @@ const props = defineProps({ :label="t('claim.customerId')" v-model="params.clientFk" lazy-rules - is-outlined + filled > <template #prepend> <QIcon name="badge" size="xs" /></template> </VnInput> @@ -41,12 +41,11 @@ const props = defineProps({ :label="t('Client Name')" v-model="params.clientName" lazy-rules - is-outlined + filled /> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" option-value="id" @@ -61,8 +60,7 @@ const props = defineProps({ :use-like="false" option-filter="firstName" dense - outlined - rounded + filled /> <VnSelect :label="t('claim.state')" @@ -70,14 +68,12 @@ const props = defineProps({ :options="states" option-label="description" dense - outlined - rounded + filled /> <VnInputDate v-model="params.created" :label="t('claim.created')" - outlined - rounded + filled dense /> <VnSelect @@ -86,8 +82,7 @@ const props = defineProps({ url="Items/withName" :use-like="false" sort-by="id DESC" - outlined - rounded + filled dense /> <VnSelect @@ -98,15 +93,13 @@ const props = defineProps({ :use-like="false" option-filter="firstName" dense - outlined - rounded + filled /> <VnSelect :label="t('claim.zone')" v-model="params.zoneFk" url="Zones" - outlined - rounded + filled dense /> <QCheckbox diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 8978c00f1..c7461f890 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -7,7 +7,7 @@ import { toCurrency, toDate } from 'src/filters'; import useCardDescription from 'src/composables/useCardDescription'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; @@ -54,7 +54,7 @@ const debtWarning = computed(() => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Clients/${entityId}/getCard`" :summary="$props.summary" data-key="Customer" @@ -105,15 +105,6 @@ const debtWarning = computed(() => { > <QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip> </QIcon> - - <QIcon - v-if="entity?.substitutionAllowed" - name="help" - size="xs" - color="primary" - > - <QTooltip>{{ t('Allowed substitution') }}</QTooltip> - </QIcon> <QIcon v-if="!entity.account?.active" color="primary" @@ -232,7 +223,7 @@ const debtWarning = computed(() => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Customer/Card/CustomerDescriptorMenu.vue b/src/pages/Customer/Card/CustomerDescriptorMenu.vue index aea45721c..fb78eab69 100644 --- a/src/pages/Customer/Card/CustomerDescriptorMenu.vue +++ b/src/pages/Customer/Card/CustomerDescriptorMenu.vue @@ -61,16 +61,6 @@ const openCreateForm = (type) => { .join('&'); useOpenURL(`/#/${type}/list?${params}`); }; -const updateSubstitutionAllowed = async () => { - try { - await axios.patch(`Clients/${route.params.id}`, { - substitutionAllowed: !$props.customer.substitutionAllowed, - }); - notify('globals.notificationSent', 'positive'); - } catch (error) { - notify(error.message, 'positive'); - } -}; </script> <template> @@ -79,13 +69,6 @@ const updateSubstitutionAllowed = async () => { {{ t('globals.pageTitles.createTicket') }} </QItemSection> </QItem> - <QItem v-ripple clickable> - <QItemSection @click="updateSubstitutionAllowed()">{{ - $props.customer.substitutionAllowed - ? t('Disable substitution') - : t('Allow substitution') - }}</QItemSection> - </QItem> <QItem v-ripple clickable> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> </QItem> diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 2ace6dd02..55a7f565e 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -41,7 +41,7 @@ const exprBuilder = (param, value) => { <template #body="{ params, searchFn }"> <QItem class="q-my-sm"> <QItemSection> - <VnInput :label="t('FI')" v-model="params.fi" is-outlined> + <VnInput :label="t('FI')" v-model="params.fi" filled> <template #prepend> <QIcon name="badge" size="xs" /> </template> @@ -50,7 +50,7 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Name')" v-model="params.name" is-outlined /> + <VnInput :label="t('Name')" v-model="params.name" filled /> </QItemSection> </QItem> <QItem class="q-mb-sm"> @@ -58,16 +58,15 @@ const exprBuilder = (param, value) => { <VnInput :label="t('customer.summary.socialName')" v-model="params.socialName" - is-outlined + filled /> </QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" option-value="id" @@ -89,8 +88,7 @@ const exprBuilder = (param, value) => { map-options hide-selected dense - outlined - rounded + filled auto-load :input-debounce="0" /> @@ -98,12 +96,12 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('City')" v-model="params.city" is-outlined /> + <VnInput :label="t('City')" v-model="params.city" filled /> </QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Phone')" v-model="params.phone" is-outlined> + <VnInput :label="t('Phone')" v-model="params.phone" filled> <template #prepend> <QIcon name="phone" size="xs" /> </template> @@ -112,7 +110,7 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Email')" v-model="params.email" is-outlined> + <VnInput :label="t('Email')" v-model="params.email" filled> <template #prepend> <QIcon name="email" size="sm" /> </template> @@ -132,19 +130,14 @@ const exprBuilder = (param, value) => { map-options hide-selected dense - outlined - rounded + filled auto-load sortBy="name ASC" /></QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput - :label="t('Postcode')" - v-model="params.postcode" - is-outlined - /> + <VnInput :label="t('Postcode')" v-model="params.postcode" filled /> </QItemSection> </QItem> </template> diff --git a/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue b/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue index 0eab7b7c5..64e3baeb5 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue @@ -45,8 +45,7 @@ const departments = ref(); dense option-label="name" option-value="id" - outlined - rounded + filled emit-value hide-selected map-options @@ -67,8 +66,7 @@ const departments = ref(); map-options option-label="name" option-value="id" - outlined - rounded + filled use-input v-model="params.departmentFk" @update:model-value="searchFn()" @@ -91,8 +89,7 @@ const departments = ref(); map-options option-label="name" option-value="id" - outlined - rounded + filled use-input v-model="params.countryFk" @update:model-value="searchFn()" @@ -108,7 +105,7 @@ const departments = ref(); <VnInput :label="t('P. Method')" clearable - is-outlined + filled v-model="params.paymentMethod" /> </QItemSection> @@ -119,7 +116,7 @@ const departments = ref(); <VnInput :label="t('Balance D.')" clearable - is-outlined + filled v-model="params.balance" /> </QItemSection> @@ -137,8 +134,7 @@ const departments = ref(); map-options option-label="name" option-value="id" - outlined - rounded + filled use-input v-model="params.workerFk" @update:model-value="searchFn()" @@ -154,7 +150,7 @@ const departments = ref(); <VnInputDate :label="t('L. O. Date')" clearable - is-outlined + filled v-model="params.date" /> </QItemSection> @@ -165,7 +161,7 @@ const departments = ref(); <VnInput :label="t('Credit I.')" clearable - is-outlined + filled v-model="params.credit" /> </QItemSection> @@ -175,7 +171,7 @@ const departments = ref(); <QItemSection> <VnInputDate :label="t('From')" - is-outlined + filled v-model="params.defaulterSinced" /> </QItemSection> diff --git a/src/pages/Customer/Payments/CustomerPaymentsFilter.vue b/src/pages/Customer/Payments/CustomerPaymentsFilter.vue index 8982cba5a..ec20237b4 100644 --- a/src/pages/Customer/Payments/CustomerPaymentsFilter.vue +++ b/src/pages/Customer/Payments/CustomerPaymentsFilter.vue @@ -25,7 +25,7 @@ const props = defineProps({ <template #body="{ params }"> <QItem> <QItemSection> - <VnInput :label="t('Order ID')" v-model="params.orderFk" is-outlined> + <VnInput :label="t('Order ID')" v-model="params.orderFk" filled> <template #prepend> <QIcon name="vn:basket" size="xs" /> </template> @@ -34,11 +34,7 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnInput - :label="t('Customer ID')" - v-model="params.clientFk" - is-outlined - > + <VnInput :label="t('Customer ID')" v-model="params.clientFk" filled> <template #prepend> <QIcon name="vn:client" size="xs" /> </template> @@ -47,19 +43,15 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnInputNumber - :label="t('Amount')" - v-model="params.amount" - is-outlined - /> + <VnInputNumber :label="t('Amount')" v-model="params.amount" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate v-model="params.from" :label="t('From')" filled /> </QItemSection> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate v-model="params.to" :label="t('To')" filled /> </QItemSection> </QItem> </template> diff --git a/src/pages/Entry/Card/EntryBasicData.vue b/src/pages/Entry/Card/EntryBasicData.vue index 6462ed24a..f6d15a977 100644 --- a/src/pages/Entry/Card/EntryBasicData.vue +++ b/src/pages/Entry/Card/EntryBasicData.vue @@ -13,6 +13,9 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; +import VnDmsInput from 'src/components/common/VnDmsInput.vue'; const route = useRoute(); const { t } = useI18n(); @@ -23,6 +26,7 @@ const user = state.getUser().fn(); const companiesOptions = ref([]); const currenciesOptions = ref([]); +const entryRef = ref({}); onMounted(() => { checkEntryLock(route.params.id, user.id); @@ -47,13 +51,14 @@ onMounted(() => { auto-load /> <FormModel - :url-update="`Entries/${route.params.id}`" + ref="entryRef" model="Entry" - auto-load + :url-update="`Entries/${route.params.id}`" :clear-store-on-unmount="false" + auto-load > <template #form="{ data }"> - <VnRow> + <VnRow class="q-py-sm"> <VnSelectTravelExtended :data="data" v-model="data.travelFk" @@ -65,15 +70,22 @@ onMounted(() => { :required="true" /> </VnRow> - <VnRow> - <VnInput v-model="data.reference" :label="t('globals.reference')" /> - <VnInputNumber - v-model="data.invoiceAmount" - :label="t('entry.summary.invoiceAmount')" - :positive="false" + <VnRow class="q-py-sm"> + <VnInput + v-model="data.reference" + :label="t('entry.list.tableVisibleColumns.reference')" + /> + <VnSelect + v-model="data.typeFk" + url="entryTypes" + :fields="['code', 'description']" + option-value="code" + optionLabel="description" + sortBy="description" + :label="t('entry.list.tableVisibleColumns.entryTypeDescription')" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInput v-model="data.invoiceNumber" :label="t('entry.summary.invoiceNumber')" @@ -84,12 +96,13 @@ onMounted(() => { :options="companiesOptions" option-value="id" option-label="code" + sort-by="code" map-options hide-selected :required="true" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInputNumber :label="t('entry.summary.commission')" v-model="data.commission" @@ -102,15 +115,15 @@ onMounted(() => { :options="currenciesOptions" option-value="id" option-label="code" + sort-by="code" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInputNumber v-model="data.initialTemperature" name="initialTemperature" :label="t('entry.basicData.initialTemperature')" :step="0.5" - :decimal-places="2" :positive="false" /> <VnInputNumber @@ -118,12 +131,21 @@ onMounted(() => { name="finalTemperature" :label="t('entry.basicData.finalTemperature')" :step="0.5" - :decimal-places="2" :positive="false" /> </VnRow> - <VnRow> - <QInput + <VnRow class="q-py-sm"> + <VnInputNumber + v-model="data.invoiceAmount" + :label="t('entry.list.tableVisibleColumns.invoiceAmount')" + :positive="false" + @update:model-value="data.buyerFk = user.id" + /> + <VnSelectWorker v-model="data.buyerFk" hide-selected /> + <VnDmsInput :data="data" :formRef="entryRef" :disable="false" /> + </VnRow> + <VnRow class="q-py-sm"> + <VnInputNumber :label="t('entry.basicData.observation')" type="textarea" v-model="data.observation" @@ -132,14 +154,20 @@ onMounted(() => { fill-input /> </VnRow> - <VnRow> - <QCheckbox v-model="data.isOrdered" :label="t('entry.summary.ordered')" /> - <QCheckbox v-model="data.isConfirmed" :label="t('globals.confirmed')" /> - <QCheckbox - v-model="data.isExcludedFromAvailable" - :label="t('entry.summary.excludedFromAvailable')" + <VnRow class="q-py-sm"> + <VnCheckbox + v-model="data.isOrdered" + :label="t('entry.list.tableVisibleColumns.isOrdered')" /> - <QCheckbox + <VnCheckbox + v-model="data.isConfirmed" + :label="t('entry.list.tableVisibleColumns.isConfirmed')" + /> + <VnCheckbox + v-model="data.isExcludedFromAvailable" + :label="t('entry.list.tableVisibleColumns.isExcludedFromAvailable')" + /> + <VnCheckbox :disable="!isAdministrative()" v-model="data.isBooked" :label="t('entry.basicData.booked')" diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue index 684ed5f59..a93b0801b 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -2,7 +2,7 @@ import { useStateStore } from 'stores/useStateStore'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { onMounted, ref } from 'vue'; +import { onMounted, ref, computed } from 'vue'; import { useState } from 'src/composables/useState'; @@ -16,6 +16,9 @@ import ItemDescriptor from 'src/pages/Item/Card/ItemDescriptor.vue'; import axios from 'axios'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import { checkEntryLock } from 'src/composables/checkEntryLock'; +import VnRow from 'src/components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import VnInputNumber from 'src/components/common/VnInputNumber.vue'; const $props = defineProps({ id: { @@ -42,6 +45,8 @@ const entityId = ref($props.id ?? route.params.id); const entryBuysRef = ref(); const footerFetchDataRef = ref(); const footer = ref({}); +const dialogRef = ref(false); +const newEntryRef = ref(null); const columns = [ { align: 'center', @@ -57,31 +62,6 @@ const columns = [ createOrder: 12, width: '25px', }, - { - label: t('Buyer'), - name: 'workerFk', - component: 'select', - attrs: { - url: 'TicketRequests/getItemTypeWorker', - fields: ['id', 'nickname'], - optionLabel: 'nickname', - sortBy: 'nickname ASC', - optionValue: 'id', - }, - visible: false, - }, - { - label: t('Family'), - name: 'itemTypeFk', - component: 'select', - attrs: { - url: 'itemTypes', - fields: ['id', 'name'], - optionLabel: 'name', - optionValue: 'id', - }, - visible: false, - }, { name: 'id', isId: true, @@ -111,16 +91,10 @@ const columns = [ }, }, { - align: 'center', + align: 'left', label: t('Article'), + component: 'input', name: 'name', - component: 'select', - attrs: { - url: 'Items', - fields: ['id', 'name'], - optionLabel: 'name', - optionValue: 'id', - }, width: '85px', isEditable: false, }, @@ -212,7 +186,6 @@ const columns = [ }, }, { - align: 'center', labelAbbreviation: 'GM', label: t('Grouping selector'), toolTip: t('Grouping selector'), @@ -240,7 +213,6 @@ const columns = [ }, }, { - align: 'center', labelAbbreviation: 'G', label: 'Grouping', toolTip: 'Grouping', @@ -281,6 +253,7 @@ const columns = [ component: 'number', attrs: { positive: false, + decimalPlaces: 3, }, cellEvent: { 'update:modelValue': async (value, oldValue, row) => { @@ -294,7 +267,7 @@ const columns = [ align: 'center', label: t('Amount'), name: 'amount', - width: '45px', + width: '75px', component: 'number', attrs: { positive: false, @@ -310,7 +283,9 @@ const columns = [ toolTip: t('Package'), name: 'price2', component: 'number', - createDisable: true, + createAttrs: { + disable: true, + }, width: '35px', create: true, format: (row) => parseFloat(row['price2']).toFixed(2), @@ -320,7 +295,9 @@ const columns = [ label: t('Box'), name: 'price3', component: 'number', - createDisable: true, + createAttrs: { + disable: true, + }, cellEvent: { 'update:modelValue': async (value, oldValue, row) => { row['price2'] = row['price2'] * (value / oldValue); @@ -340,13 +317,6 @@ const columns = [ toggleIndeterminate: false, }, component: 'checkbox', - cellEvent: { - 'update:modelValue': async (value, oldValue, row) => { - await axios.patch(`Items/${row['itemFk']}`, { - hasMinPrice: value, - }); - }, - }, width: '25px', }, { @@ -356,13 +326,6 @@ const columns = [ toolTip: t('Minimum price'), name: 'minPrice', component: 'number', - cellEvent: { - 'update:modelValue': async (value, oldValue, row) => { - await axios.patch(`Items/${row['itemFk']}`, { - minPrice: value, - }); - }, - }, width: '35px', style: (row) => { if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' }; @@ -425,6 +388,23 @@ const columns = [ }, }, ]; +const buyerFk = ref(null); +const itemTypeFk = ref(null); +const inkFk = ref(null); +const tag1 = ref(null); +const tag2 = ref(null); +const tag1Filter = ref(null); +const tag2Filter = ref(null); +const filter = computed(() => { + const where = {}; + where.workerFk = buyerFk.value; + where.itemTypeFk = itemTypeFk.value; + where.inkFk = inkFk.value; + where.tag1 = tag1.value; + where.tag2 = tag2.value; + + return { where }; +}); function getQuantityStyle(row) { if (row?.quantity !== row?.stickers * row?.packing) @@ -521,6 +501,23 @@ async function setBuyUltimate(itemFk, data) { }); } +async function transferBuys(rows, newEntry) { + if (!newEntry) return; + + const promises = rows.map((row) => { + return axios.patch('Buys', { id: row.id, entryFk: newEntry }); + }); + + await Promise.all(promises); + + await axios.post(`Entries/${newEntry}/recalcEntryPrices`); + await axios.post(`Entries/${entityId.value}/recalcEntryPrices`); + + entryBuysRef.value.reload(); + newEntryRef.value = null; + dialogRef.value = false; +} + onMounted(() => { stateStore.rightDrawer = false; if ($props.editableMode) checkEntryLock(entityId.value, user.id); @@ -595,6 +592,47 @@ onMounted(() => { </QItem> </QList> </QBtnDropdown> + <QBtn + icon="move_group" + color="primary" + :title="t('Transfer buys')" + data-cy="transferBuys" + flat + @click="dialogRef = true" + :disable="!selectedRows.length" + /> + <QDialog v-model="dialogRef"> + <QCard> + <QCardSection> + <span>{{ t('Transfer buys') }}</span> + </QCardSection> + <QCardSection> + <VnInputNumber + v-model="newEntryRef" + :label="t('Entry')" + type="number" + data-cy="entryDestinyInput" + /> + </QCardSection> + <QCardSection> + <QCardActions> + <QBtn + label="Cancel" + flat + color="primary" + @click="dialogRef = false" + /> + <QBtn + label="Transfer" + data-cy="transferBuysBtn" + flat + color="primary" + @click="transferBuys(selectedRows, newEntryRef)" + /> + </QCardActions> + </QCardSection> + </QCard> + </QDialog> </QBtnGroup> </Teleport> <FetchData @@ -610,6 +648,7 @@ onMounted(() => { :url="`Entries/${entityId}/getBuyList`" search-url="EntryBuys" save-url="Buys/crud" + :filter="filter" :disable-option="{ card: true }" v-model:selected="selectedRows" @on-fetch="() => footerFetchDataRef.fetch()" @@ -643,7 +682,7 @@ onMounted(() => { }, columnGridStyle: { 'max-width': '50%', - 'margin-right': '30px', + 'margin-right': '5%', flex: 1, }, previousStyle: { @@ -655,7 +694,7 @@ onMounted(() => { :is-editable="editableMode" :without-header="!editableMode" :with-filters="editableMode" - :right-search="editableMode" + :right-search="false" :row-click="false" :columns="columns" :beforeSaveFn="beforeSave" @@ -666,6 +705,47 @@ onMounted(() => { data-cy="entry-buys" overlay > + <template #top-left> + <VnRow> + <VnSelect + :label="t('Buyer')" + v-model="buyerFk" + url="TicketRequests/getItemTypeWorker" + :fields="['id', 'nickname']" + option-label="nickname" + sort-by="nickname ASC" + :use-like="false" + /> + <VnSelect + :label="t('Family')" + v-model="itemTypeFk" + url="ItemTypes" + :fields="['id', 'name']" + option-label="name" + sort-by="name ASC" + /> + <VnSelect + :label="t('Color')" + v-model="inkFk" + url="Inks" + :fields="['id', 'name']" + option-label="name" + sort-by="name ASC" + /> + <VnInput + v-model="tag1Filter" + :label="t('Tag')" + @keyup.enter="tag1 = tag1Filter" + @remove="tag1 = null" + /> + <VnInput + v-model="tag2Filter" + :label="t('Tag')" + @keyup.enter="tag2 = tag2Filter" + @remove="tag2 = null" + /> + </VnRow> + </template> <template #column-hex="{ row }"> <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> </template> @@ -696,7 +776,7 @@ onMounted(() => { </div> </template> <template #column-footer-weight> - {{ footer?.weight }} + <span class="q-pr-xs">{{ footer?.weight }}</span> </template> <template #column-footer-quantity> <span :style="getQuantityStyle(footer)" data-cy="footer-quantity"> @@ -704,9 +784,8 @@ onMounted(() => { </span> </template> <template #column-footer-amount> - <span :style="getAmountStyle(footer)" data-cy="footer-amount"> - {{ footer?.amount }} - </span> + <span data-cy="footer-amount">{{ footer?.amount }} / </span> + <span style="color: var(--q-positive)">{{ footer?.checkedAmount }}</span> </template> <template #column-create-itemFk="{ data }"> <VnSelect @@ -767,6 +846,8 @@ onMounted(() => { </template> <i18n> es: + Buyer: Comprador + Family: Familia Article: ArtĂculo Siz.: Med. Size: Medida @@ -798,6 +879,8 @@ es: Create buy: Crear compra Invert quantity value: Invertir valor de cantidad Check buy amount: Marcar como correcta la cantidad de compra + Transfer buys: Transferir compras + Entry: Entrada </i18n> <style lang="scss" scoped> .centered-container { diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 313ed3d72..202f94997 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -6,7 +6,7 @@ import { toDate } from 'src/filters'; import { getUrl } from 'src/composables/getUrl'; import { useQuasar } from 'quasar'; import { usePrintService } from 'composables/usePrintService'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import axios from 'axios'; @@ -92,7 +92,7 @@ const getEntryRedirectionFilter = (entry) => { }; function showEntryReport() { - openReport(`Entries/${entityId.value}/entry-order-pdf`); + openReport(`Entries/${entityId.value}/entry-order-pdf`, {}, true); } function showNotification(type, message) { @@ -145,9 +145,9 @@ async function deleteEntry() { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Entries/${entityId}`" - :filter="entryFilter" + :user-filter="entryFilter" title="supplier.nickname" data-key="Entry" width="lg-width" @@ -264,7 +264,7 @@ async function deleteEntry() { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Entry/Card/EntryNotes.vue b/src/pages/Entry/Card/EntryNotes.vue index 459c3b069..4159ed5ca 100644 --- a/src/pages/Entry/Card/EntryNotes.vue +++ b/src/pages/Entry/Card/EntryNotes.vue @@ -2,153 +2,82 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; - -import FetchData from 'components/FetchData.vue'; -import CrudModel from 'components/CrudModel.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; const { params } = useRoute(); const { t } = useI18n(); - +const selectedRows = ref([]); const entryObservationsRef = ref(null); -const entryObservationsOptions = ref([]); -const selected = ref([]); - -const sortEntryObservationOptions = (data) => { - entryObservationsOptions.value = [...data].sort((a, b) => - a.description.localeCompare(b.description), - ); -}; - +const entityId = ref(params.id); const columns = computed(() => [ { - name: 'observationType', - label: t('entry.notes.observationType'), - field: (row) => row.observationTypeFk, - sortable: true, - options: entryObservationsOptions.value, - required: true, - model: 'observationTypeFk', - optionValue: 'id', - optionLabel: 'description', - tabIndex: 1, - align: 'left', + name: 'id', + isId: true, + visible: false, + isEditable: false, + columnFilter: false, }, { + name: 'observationTypeFk', + label: t('entry.notes.observationType'), + component: 'select', + columnFilter: { inWhere: true }, + attrs: { + inWhere: true, + url: 'ObservationTypes', + fields: ['id', 'description'], + optionValue: 'id', + optionLabel: 'description', + sortBy: 'description', + }, + width: '30px', + create: true, + }, + { + align: 'left', name: 'description', label: t('globals.description'), - field: (row) => row.description, - tabIndex: 2, - align: 'left', + component: 'input', + columnFilter: false, + attrs: { autogrow: true }, + create: true, }, ]); + +const filter = computed(() => ({ + fields: ['id', 'entryFk', 'observationTypeFk', 'description'], + include: ['observationType'], + where: { entryFk: entityId }, +})); </script> <template> - <FetchData - url="ObservationTypes" - @on-fetch="(data) => sortEntryObservationOptions(data)" + <VnTable + ref="entryObservationsRef" + data-key="EntryObservations" + :columns="columns" + url="EntryObservations" + :user-filter="filter" + order="id ASC" + :disable-option="{ card: true }" + :is-editable="true" + :right-search="true" + v-model:selected="selectedRows" + :create="{ + urlCreate: 'EntryObservations', + title: t('Create note'), + onDataSaved: () => { + entryObservationsRef.reload(); + }, + formInitialData: { entryFk: entityId }, + }" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" auto-load /> - <CrudModel - data-key="EntryAccount" - url="EntryObservations" - model="EntryAccount" - :filter="{ - fields: ['id', 'entryFk', 'observationTypeFk', 'description'], - where: { entryFk: params.id }, - }" - ref="entryObservationsRef" - :data-required="{ entryFk: params.id }" - v-model:selected="selected" - auto-load - > - <template #body="{ rows, validate }"> - <QTable - v-model:selected="selected" - :columns="columns" - :rows="rows" - :pagination="{ rowsPerPage: 0 }" - row-key="$index" - selection="multiple" - hide-pagination - :grid="$q.screen.lt.md" - table-header-class="text-left" - > - <template #body-cell-observationType="{ row, col }"> - <QTd> - <VnSelect - v-model="row[col.model]" - :options="col.options" - :option-value="col.optionValue" - :option-label="col.optionLabel" - :autofocus="col.tabIndex == 1" - input-debounce="0" - hide-selected - :required="true" - /> - </QTd> - </template> - <template #body-cell-description="{ row, col }"> - <QTd> - <VnInput - :label="t('globals.description')" - v-model="row[col.name]" - :rules="validate('EntryObservation.description')" - /> - </QTd> - </template> - <template #item="props"> - <div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition"> - <QCard bordered flat> - <QCardSection> - <QCheckbox v-model="props.selected" dense /> - </QCardSection> - <QSeparator /> - <QList dense> - <QItem> - <QItemSection> - <VnSelect - v-model="props.row.observationTypeFk" - :options="entryObservationsOptions" - option-value="id" - option-label="description" - input-debounce="0" - hide-selected - :required="true" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnInput - :label="t('globals.description')" - v-model="props.row.description" - :rules=" - validate('EntryObservation.description') - " - /> - </QItemSection> - </QItem> - </QList> - </QCard> - </div> - </template> - </QTable> - </template> - </CrudModel> - <QPageSticky position="bottom-right" :offset="[25, 25]"> - <QBtn - fab - color="primary" - icon="add" - v-shortcut="'+'" - @click="entryObservationsRef.insert()" - /> - </QPageSticky> </template> <i18n> es: - Add note: Añadir nota - Remove note: Quitar nota + Create note: Crear nota </i18n> diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index c40e2ba46..37a28968c 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -84,7 +84,10 @@ onMounted(async () => { :label="t('globals.company')" :value="entry?.company?.code" /> - <VnLv :label="t('globals.reference')" :value="entry?.reference" /> + <VnLv + :label="t('entry.list.tableVisibleColumns.reference')" + :value="entry?.reference" + /> <VnLv :label="t('entry.summary.invoiceNumber')" :value="entry?.invoiceNumber" @@ -92,13 +95,13 @@ onMounted(async () => { </div> <div class="card-content"> <VnCheckbox - :label="t('entry.summary.ordered')" + :label="t('entry.list.tableVisibleColumns.isOrdered')" v-model="entry.isOrdered" :disable="true" size="xs" /> <VnCheckbox - :label="t('globals.confirmed')" + :label="t('entry.list.tableVisibleColumns.isConfirmed')" v-model="entry.isConfirmed" :disable="true" size="xs" @@ -110,7 +113,11 @@ onMounted(async () => { size="xs" /> <VnCheckbox - :label="t('entry.summary.excludedFromAvailable')" + :label=" + t( + 'entry.list.tableVisibleColumns.isExcludedFromAvailable', + ) + " v-model="entry.isExcludedFromAvailable" :disable="true" size="xs" @@ -155,6 +162,7 @@ onMounted(async () => { /> </div> <div class="card-content"> + <VnLv :label="t('travel.awbFk')" :value="entry.travel.awbFk" /> <VnCheckbox :label="t('entry.summary.travelDelivered')" v-model="entry.travel.isDelivered" diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index c283e4a0b..19f4bca86 100644 --- a/src/pages/Entry/EntryFilter.vue +++ b/src/pages/Entry/EntryFilter.vue @@ -85,7 +85,7 @@ const entryFilterPanel = ref(); </QItemSection> <QItemSection> <QCheckbox - :label="t('entry.list.tableVisibleColumns.isConfirmed')" + label="LE" v-model="params.isConfirmed" toggle-indeterminate > @@ -101,13 +101,14 @@ const entryFilterPanel = ref(); :label="t('params.landed')" v-model="params.landed" @update:model-value="searchFn()" - is-outlined + filled + data-cy="landed" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput v-model="params.id" label="Id" is-outlined /> + <VnInput v-model="params.id" label="Id" filled /> </QItemSection> </QItem> <QItem> @@ -117,15 +118,7 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" hide-selected dense - outlined - rounded - /> - </QItemSection> - <QItemSection> - <VnInput - v-model="params.invoiceNumber" - :label="t('params.invoiceNumber')" - is-outlined + filled /> </QItemSection> </QItem> @@ -134,7 +127,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.reference" :label="t('entry.list.tableVisibleColumns.reference')" - is-outlined + filled /> </QItemSection> </QItem> @@ -149,8 +142,7 @@ const entryFilterPanel = ref(); :fields="['id', 'name']" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -159,7 +151,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.evaNotes" :label="t('params.evaNotes')" - is-outlined + filled /> </QItemSection> </QItem> @@ -171,10 +163,10 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" url="Warehouses" :fields="['id', 'name']" + sort-by="name ASC" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -186,10 +178,10 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" url="Warehouses" :fields="['id', 'name']" + sort-by="name ASC" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -211,7 +203,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.invoiceNumber" :label="t('params.invoiceNumber')" - is-outlined + filled /> </QItemSection> </QItem> @@ -228,8 +220,7 @@ const entryFilterPanel = ref(); option-label="description" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -238,7 +229,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.evaNotes" :label="t('params.evaNotes')" - is-outlined + filled /> </QItemSection> </QItem> @@ -267,7 +258,7 @@ en: hasToShowDeletedEntries: Show deleted entries es: params: - isExcludedFromAvailable: Inventario + isExcludedFromAvailable: Excluida isOrdered: Pedida isConfirmed: Confirmado isReceived: Recibida diff --git a/src/pages/Entry/EntryLatestBuys.vue b/src/pages/Entry/EntryLatestBuys.vue deleted file mode 100644 index 73fdcbbbf..000000000 --- a/src/pages/Entry/EntryLatestBuys.vue +++ /dev/null @@ -1,264 +0,0 @@ -<script setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import { useStateStore } from 'stores/useStateStore'; -import { toDate } from 'src/filters'; - -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue'; -import VnTable from 'components/VnTable/VnTable.vue'; -import VnImg from 'src/components/ui/VnImg.vue'; - -const stateStore = useStateStore(); -const { t } = useI18n(); -const tableRef = ref(); -const columns = [ - { - align: 'center', - label: t('entry.latestBuys.tableVisibleColumns.image'), - name: 'itemFk', - columnField: { - component: VnImg, - attrs: ({ row }) => { - return { - id: row.id, - size: '50x50', - }; - }, - }, - columnFilter: false, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.itemFk'), - name: 'itemFk', - isTitle: true, - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.summary.packing'), - name: 'packing', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.summary.grouping'), - name: 'grouping', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('globals.quantity'), - name: 'quantity', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('globals.description'), - name: 'description', - }, - { - align: 'left', - label: t('globals.size'), - name: 'size', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('globals.tags'), - name: 'tags', - }, - { - align: 'left', - label: t('globals.type'), - name: 'type', - }, - { - align: 'left', - label: t('globals.intrastat'), - name: 'intrastat', - }, - { - align: 'left', - label: t('globals.origin'), - name: 'origin', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.weightByPiece'), - name: 'weightByPiece', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.isActive'), - name: 'isActive', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.family'), - name: 'family', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.entryFk'), - name: 'entryFk', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.summary.buyingValue'), - name: 'buyingValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.freightValue'), - name: 'freightValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.comissionValue'), - name: 'comissionValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.packageValue'), - name: 'packageValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.isIgnored'), - name: 'isIgnored', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.price2'), - name: 'price2', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.price3'), - name: 'price3', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.minPrice'), - name: 'minPrice', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.ektFk'), - name: 'ektFk', - }, - { - align: 'left', - label: t('globals.weight'), - name: 'weight', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.buys.packagingFk'), - name: 'packagingFk', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.packingOut'), - name: 'packingOut', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.landing'), - name: 'landing', - component: 'date', - columnField: { - component: null, - }, - format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)), - }, -]; - -onMounted(async () => { - stateStore.rightDrawer = true; -}); - -onUnmounted(() => (stateStore.rightDrawer = false)); -</script> - -<template> - <RightMenu> - <template #right-panel> - <EntryLatestBuysFilter data-key="LatestBuys" /> - </template> - </RightMenu> - <VnSubToolbar /> - <VnTable - ref="tableRef" - data-key="LatestBuys" - url="Buys/latestBuysFilter" - order="id DESC" - :columns="columns" - redirect="entry" - :row-click="({ entryFk }) => tableRef.redirect(entryFk)" - auto-load - :right-search="false" - /> -</template> diff --git a/src/pages/Entry/EntryLatestBuysFilter.vue b/src/pages/Entry/EntryLatestBuysFilter.vue deleted file mode 100644 index 19b457524..000000000 --- a/src/pages/Entry/EntryLatestBuysFilter.vue +++ /dev/null @@ -1,168 +0,0 @@ -<script setup> -import { ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import VnInputDate from 'src/components/common/VnInputDate.vue'; -import VnInput from 'components/common/VnInput.vue'; -import VnSelect from 'components/common/VnSelect.vue'; -import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue'; -import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; - -const { t } = useI18n(); - -defineProps({ - dataKey: { - type: String, - required: true, - }, -}); - -const tagValues = ref([]); -</script> - -<template> - <FetchData - url="TicketRequests/getItemTypeWorker" - auto-load - :filter="{ fields: ['id', 'nickname'], order: 'nickname ASC' }" - @on-fetch="(data) => (itemTypeWorkersOptions = data)" - /> - <ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']"> - <template #body="{ params, searchFn }"> - <QItem class="q-my-md"> - <QItemSection> - <VnSelect - :label="t('components.itemsFilterPanel.buyerFk')" - v-model="params.buyerFk" - :options="itemTypeWorkersOptions" - option-value="id" - option-label="nickname" - :fields="['id', 'nickname']" - sort-by="nickname ASC" - dense - outlined - rounded - use-input - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem class="q-my-md"> - <QItemSection> - <VnSelectSupplier - v-model="params.supplierFk" - url="Suppliers" - :fields="['id', 'name', 'nickname']" - sort-by="name ASC" - dense - outlined - rounded - /> - </QItemSection> - </QItem> - <QItem class="q-my-md"> - <QItemSection> - <VnInputDate - :label="t('components.itemsFilterPanel.started')" - v-model="params.from" - is-outlined - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem class="q-my-md"> - <QItemSection> - <VnInputDate - :label="t('components.itemsFilterPanel.ended')" - v-model="params.to" - is-outlined - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <QCheckbox - :label="t('components.itemsFilterPanel.active')" - v-model="params.active" - toggle-indeterminate - @update:model-value="searchFn()" - /> - </QItemSection> - <QItemSection> - <QCheckbox - :label="t('globals.visible')" - v-model="params.visible" - toggle-indeterminate - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <QCheckbox - :label="t('components.itemsFilterPanel.floramondo')" - v-model="params.floramondo" - toggle-indeterminate - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - - <QItem - v-for="(value, index) in tagValues" - :key="value" - class="q-mt-md filter-value" - > - <QItemSection class="col"> - <VnSelect - :label="t('params.tag')" - v-model="value.selectedTag" - :options="tagOptions" - option-label="name" - dense - outlined - rounded - :emit-value="false" - use-input - :is-clearable="false" - @update:model-value="getSelectedTagValues(value)" - /> - </QItemSection> - <QItemSection class="col"> - <VnSelect - v-if="!value?.selectedTag?.isFree && value.valueOptions" - :label="t('params.value')" - v-model="value.value" - :options="value.valueOptions || []" - option-value="value" - option-label="value" - dense - outlined - rounded - emit-value - use-input - :disable="!value" - :is-clearable="false" - class="filter-input" - @update:model-value="applyTags(params, searchFn)" - /> - <VnInput - v-else - v-model="value.value" - :label="t('params.value')" - :disable="!value" - is-outlined - class="filter-input" - :is-clearable="false" - @keyup.enter="applyTags(params, searchFn)" - /> - </QItemSection> - <QIcon - name="delete" - class="filter-icon" - @click="removeTag(index, params, searchFn)" - /> - </QItem> - </template> - </ItemsFilterPanel> -</template> diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index 3b5434cb8..5ebad3144 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -107,9 +107,8 @@ const columns = computed(() => [ attrs: { url: 'suppliers', fields: ['id', 'name'], - where: { order: 'name DESC' }, + sortBy: 'name ASC', }, - format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), width: '110px', }, { @@ -145,6 +144,7 @@ const columns = computed(() => [ attrs: { url: 'agencyModes', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -158,7 +158,6 @@ const columns = computed(() => [ component: 'input', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.warehouseOutFk'), name: 'warehouseOutFk', cardVisible: true, @@ -166,6 +165,7 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -174,7 +174,6 @@ const columns = computed(() => [ width: '65px', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.warehouseInFk'), name: 'warehouseInFk', cardVisible: true, @@ -182,6 +181,7 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -190,7 +190,6 @@ const columns = computed(() => [ width: '65px', }, { - align: 'left', labelAbbreviation: t('Type'), label: t('entry.list.tableVisibleColumns.entryTypeDescription'), toolTip: t('entry.list.tableVisibleColumns.entryTypeDescription'), @@ -201,6 +200,7 @@ const columns = computed(() => [ fields: ['code', 'description'], optionValue: 'code', optionLabel: 'description', + sortBy: 'description', }, width: '65px', format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription), diff --git a/src/pages/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue index 41f78617c..6168f0737 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -1,24 +1,23 @@ <script setup> import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useState } from 'src/composables/useState'; -import { useQuasar } from 'quasar'; +import { useQuasar, date } from 'quasar'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import FetchData from 'components/FetchData.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnRow from 'components/ui/VnRow.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import EntryStockBoughtFilter from './EntryStockBoughtFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import EntryStockBoughtDetail from 'src/pages/Entry/EntryStockBoughtDetail.vue'; +import TravelDescriptorProxy from '../Travel/Card/TravelDescriptorProxy.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; +import axios from 'axios'; const { t } = useI18n(); const quasar = useQuasar(); -const state = useState(); -const user = state.getUser(); +const filterDate = ref(useFilterParams('StockBoughts').params); const footer = ref({ bought: 0, reserve: 0 }); const columns = computed(() => [ { @@ -46,7 +45,7 @@ const columns = computed(() => [ optionValue: 'id', }, columnFilter: false, - width: '50px', + width: '60%', }, { align: 'center', @@ -56,20 +55,20 @@ const columns = computed(() => [ create: true, component: 'number', summation: true, - width: '50px', format: ({ reserve }, dashIfEmpty) => dashIfEmpty(round(reserve)), + width: '20%', }, { - align: 'center', + align: 'right', label: t('entryStockBought.bought'), name: 'bought', summation: true, cardVisible: true, style: ({ reserve, bought }) => boughtStyle(bought, reserve), columnFilter: false, + width: '20%', }, { - align: 'left', label: t('entryStockBought.date'), name: 'dated', component: 'date', @@ -77,7 +76,7 @@ const columns = computed(() => [ create: true, }, { - align: 'left', + align: 'center', name: 'tableActions', actions: [ { @@ -90,7 +89,7 @@ const columns = computed(() => [ component: EntryStockBoughtDetail, componentProps: { workerFk: row.workerFk, - dated: userParams.value.dated, + dated: filterDate.value.dated, }, }); }, @@ -98,39 +97,29 @@ const columns = computed(() => [ ], }, ]); - const fetchDataRef = ref(); const travelDialogRef = ref(false); const tableRef = ref(); const travel = ref(null); -const userParams = ref({ - dated: Date.vnNew().toJSON(), -}); - -const filter = ref({ - fields: ['id', 'm3', 'warehouseInFk'], +const filter = computed(() => ({ + fields: ['id', 'm3', 'ref', 'warehouseInFk'], include: [ { relation: 'warehouseIn', scope: { - fields: ['code'], + fields: ['code', 'name'], }, }, ], where: { - shipped: (userParams.value.dated - ? new Date(userParams.value.dated) - : Date.vnNew() - ).setHours(0, 0, 0, 0), + shipped: date.adjustDate(filterDate.value.dated, { + hour: 0, + minute: 0, + second: 0, + }), m3: { neq: null }, }, -}); - -const setUserParams = async ({ dated }) => { - const shipped = (dated ? new Date(dated) : Date.vnNew()).setHours(0, 0, 0, 0); - filter.value.where.shipped = shipped; - fetchDataRef.value?.fetch(); -}; +})); function openDialog() { travelDialogRef.value = true; @@ -151,6 +140,31 @@ function round(value) { function boughtStyle(bought, reserve) { return reserve < bought ? { color: 'var(--q-negative)' } : ''; } + +async function beforeSave(data, getChanges) { + const changes = data.creates; + if (!changes) return data; + const patchPromises = []; + + for (const change of changes) { + if (change?.isReal === false && change?.reserve > 0) { + const postData = { + workerFk: change.workerFk, + reserve: change.reserve, + dated: filterDate.value.dated, + }; + const promise = axios.post('StockBoughts', postData).catch((error) => { + console.error('Error processing change: ', change, error); + }); + + patchPromises.push(promise); + } + } + + await Promise.all(patchPromises); + data.creates = []; + return data; +} </script> <template> <VnSubToolbar> @@ -158,18 +172,17 @@ function boughtStyle(bought, reserve) { <FetchData ref="fetchDataRef" url="Travels" - auto-load :filter="filter" @on-fetch=" (data) => { travel = data.find( - (data) => data.warehouseIn?.code.toLowerCase() === 'vnh', + (data) => data.warehouseIn?.code?.toLowerCase() === 'vnh', ); } " /> <VnRow class="travel"> - <div v-if="travel"> + <div v-show="travel"> <span style="color: var(--vn-label-color)"> {{ t('entryStockBought.purchaseSpaces') }}: </span> @@ -180,7 +193,7 @@ function boughtStyle(bought, reserve) { v-if="travel?.m3" style="max-width: 20%" flat - icon="edit" + icon="search" @click="openDialog()" :title="t('entryStockBought.editTravel')" color="primary" @@ -190,62 +203,47 @@ function boughtStyle(bought, reserve) { </VnRow> </template> </VnSubToolbar> - <QDialog v-model="travelDialogRef" :maximized="true" :class="['vn-row', 'wrap']"> + <QDialog v-model="travelDialogRef" :class="['vn-row', 'wrap']"> <FormModelPopup :url-update="`Travels/${travel?.id}`" model="travel" :title="t('Travel m3')" - :form-initial-data="{ id: travel?.id, m3: travel?.m3 }" + :form-initial-data="travel" @on-data-saved="fetchDataRef.fetch()" > <template #form-inputs="{ data }"> - <VnInput - v-model="data.id" - :label="t('id')" - type="number" - disable - readonly - /> + <span class="link"> + {{ data.ref }} + <TravelDescriptorProxy :id="data.id" /> + </span> <VnInput v-model="data.m3" :label="t('m3')" type="number" /> </template> </FormModelPopup> </QDialog> - <RightMenu> - <template #right-panel> - <EntryStockBoughtFilter - data-key="StockBoughts" - @set-user-params="setUserParams" - /> - </template> - </RightMenu> <div class="table-container"> <div class="column items-center"> <VnTable ref="tableRef" data-key="StockBoughts" url="StockBoughts/getStockBought" + :beforeSaveFn="beforeSave" save-url="StockBoughts/crud" search-url="StockBoughts" - order="reserve DESC" - :right-search="false" + order="bought DESC" :is-editable="true" - @on-fetch="(data) => setFooter(data)" - :create="{ - urlCreate: 'StockBoughts', - title: t('entryStockBought.reserveSomeSpace'), - onDataSaved: () => tableRef.reload(), - formInitialData: { - workerFk: user.id, - dated: Date.vnNow(), - }, - }" + @on-fetch=" + async (data) => { + setFooter(data); + await fetchDataRef.fetch(); + } + " :columns="columns" - :user-params="userParams" :footer="true" table-height="80vh" - auto-load :column-search="false" :without-header="true" + :user-params="{ dated: Date.vnNew() }" + auto-load > <template #column-workerFk="{ row }"> <span class="link" @click.stop> @@ -254,12 +252,15 @@ function boughtStyle(bought, reserve) { </span> </template> <template #column-footer-reserve> - <span> + <span class="q-pr-xs"> {{ round(footer.reserve) }} </span> </template> <template #column-footer-bought> - <span :style="boughtStyle(footer?.bought, footer?.reserve)"> + <span + :style="boughtStyle(footer?.bought, footer?.reserve)" + class="q-pr-xs" + > {{ round(footer.bought) }} </span> </template> @@ -277,10 +278,7 @@ function boughtStyle(bought, reserve) { } .column { min-width: 35%; - margin-top: 5%; - display: flex; - flex-direction: column; - align-items: center; + margin-top: 1%; } .text-negative { color: $negative !important; diff --git a/src/pages/Entry/EntryStockBoughtFilter.vue b/src/pages/Entry/EntryStockBoughtFilter.vue deleted file mode 100644 index 136881f17..000000000 --- a/src/pages/Entry/EntryStockBoughtFilter.vue +++ /dev/null @@ -1,70 +0,0 @@ -<script setup> -import { useI18n } from 'vue-i18n'; -import { onMounted } from 'vue'; -import { useStateStore } from 'stores/useStateStore'; - -import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; -import VnInputDate from 'src/components/common/VnInputDate.vue'; - -const { t } = useI18n(); -const props = defineProps({ - dataKey: { - type: String, - required: true, - }, -}); -const stateStore = useStateStore(); -const emit = defineEmits(['set-user-params']); -const setUserParams = (params) => { - emit('set-user-params', params); -}; -onMounted(async () => { - stateStore.rightDrawer = true; -}); -</script> - -<template> - <VnFilterPanel - :data-key="props.dataKey" - :search-button="true" - search-url="StockBoughts" - @set-user-params="setUserParams" - > - <template #tags="{ tag, formatFn }"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> - </div> - </template> - <template #body="{ params, searchFn }"> - <QItem class="q-my-sm"> - <QItemSection> - <VnInputDate - id="date" - v-model="params.dated" - @update:model-value=" - (value) => { - params.dated = value; - setUserParams(params); - searchFn(); - } - " - :label="t('Date')" - is-outlined - /> - </QItemSection> - </QItem> - </template> - </VnFilterPanel> -</template> -<i18n> - en: - params: - dated: Date - workerFk: Worker - es: - Date: Fecha - params: - dated: Fecha - workerFk: Trabajador -</i18n> diff --git a/src/pages/Entry/MyEntries.vue b/src/pages/Entry/EntrySupplier.vue similarity index 67% rename from src/pages/Entry/MyEntries.vue rename to src/pages/Entry/EntrySupplier.vue index 3f7566ae0..d8b17007f 100644 --- a/src/pages/Entry/MyEntries.vue +++ b/src/pages/Entry/EntrySupplier.vue @@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import { toDate } from 'src/filters/index'; import { useQuasar } from 'quasar'; -import EntryBuysTableDialog from './EntryBuysTableDialog.vue'; +import EntrySupplierlDetail from './EntrySupplierlDetail.vue'; import VnTable from 'components/VnTable/VnTable.vue'; const { t } = useI18n(); @@ -18,18 +18,28 @@ const columns = computed(() => [ { align: 'left', name: 'id', - label: t('myEntries.id'), + label: t('entrySupplier.id'), columnFilter: false, + isId: true, + chip: { + condition: () => true, + }, + }, + { + align: 'left', + name: 'supplierName', + label: t('entrySupplier.supplierName'), + cardVisible: true, isTitle: true, }, { visible: false, align: 'right', - label: t('myEntries.shipped'), + label: t('entrySupplier.shipped'), name: 'shipped', columnFilter: { name: 'fromShipped', - label: t('myEntries.fromShipped'), + label: t('entrySupplier.fromShipped'), component: 'date', }, format: ({ shipped }) => toDate(shipped), @@ -37,26 +47,26 @@ const columns = computed(() => [ { visible: false, align: 'left', - label: t('myEntries.shipped'), + label: t('entrySupplier.shipped'), name: 'shipped', columnFilter: { name: 'toShipped', - label: t('myEntries.toShipped'), + label: t('entrySupplier.toShipped'), component: 'date', }, format: ({ shipped }) => toDate(shipped), cardVisible: true, }, { - align: 'right', - label: t('myEntries.shipped'), + align: 'left', + label: t('entrySupplier.shipped'), name: 'shipped', columnFilter: false, format: ({ shipped }) => toDate(shipped), }, { - align: 'right', - label: t('myEntries.landed'), + align: 'left', + label: t('entrySupplier.landed'), name: 'landed', columnFilter: false, format: ({ landed }) => toDate(landed), @@ -64,15 +74,13 @@ const columns = computed(() => [ { align: 'right', - label: t('myEntries.wareHouseIn'), + label: t('entrySupplier.wareHouseIn'), name: 'warehouseInFk', - format: (row) => { - row.warehouseInName; - }, + format: ({ warehouseInName }) => warehouseInName, cardVisible: true, columnFilter: { name: 'warehouseInFk', - label: t('myEntries.warehouseInFk'), + label: t('entrySupplier.warehouseInFk'), component: 'select', attrs: { url: 'warehouses', @@ -86,13 +94,13 @@ const columns = computed(() => [ }, { align: 'left', - label: t('myEntries.daysOnward'), + label: t('entrySupplier.daysOnward'), name: 'daysOnward', visible: false, }, { align: 'left', - label: t('myEntries.daysAgo'), + label: t('entrySupplier.daysAgo'), name: 'daysAgo', visible: false, }, @@ -101,8 +109,8 @@ const columns = computed(() => [ name: 'tableActions', actions: [ { - title: t('myEntries.printLabels'), - icon: 'move_item', + title: t('entrySupplier.printLabels'), + icon: 'search', isPrimary: true, action: (row) => printBuys(row.id), }, @@ -112,7 +120,7 @@ const columns = computed(() => [ const printBuys = (rowId) => { quasar.dialog({ - component: EntryBuysTableDialog, + component: EntrySupplierlDetail, componentProps: { id: rowId, }, @@ -121,19 +129,18 @@ const printBuys = (rowId) => { </script> <template> <VnSearchbar - data-key="myEntriesList" + data-key="entrySupplierList" url="Entries/filter" - :label="t('myEntries.search')" - :info="t('myEntries.searchInfo')" + :label="t('entrySupplier.search')" + :info="t('entrySupplier.searchInfo')" /> <VnTable - data-key="myEntriesList" + data-key="entrySupplierList" url="Entries/filter" :columns="columns" :user-params="params" default-mode="card" order="shipped DESC" auto-load - chip-locale="myEntries" /> </template> diff --git a/src/pages/Entry/EntryBuysTableDialog.vue b/src/pages/Entry/EntrySupplierlDetail.vue similarity index 87% rename from src/pages/Entry/EntryBuysTableDialog.vue rename to src/pages/Entry/EntrySupplierlDetail.vue index 7a6c4ac43..01f6012c5 100644 --- a/src/pages/Entry/EntryBuysTableDialog.vue +++ b/src/pages/Entry/EntrySupplierlDetail.vue @@ -30,7 +30,7 @@ const entriesTableColumns = computed(() => [ align: 'left', name: 'itemFk', field: 'itemFk', - label: t('entry.latestBuys.tableVisibleColumns.itemFk'), + label: t('entrySupplier.itemId'), }, { align: 'left', @@ -65,7 +65,15 @@ const entriesTableColumns = computed(() => [ ]); function downloadCSV(rows) { - const headers = ['id', 'itemFk', 'name', 'stickers', 'packing', 'grouping', 'comment']; + const headers = [ + 'id', + 'itemFk', + 'name', + 'stickers', + 'packing', + 'grouping', + 'comment', + ]; const csvRows = rows.map((row) => { const buy = row; @@ -119,17 +127,18 @@ function downloadCSV(rows) { > <template #top-left> <QBtn - :label="t('myEntries.downloadCsv')" + :label="t('entrySupplier.downloadCsv')" color="primary" icon="csv" @click="downloadCSV(rows)" unelevated + data-cy="downloadCsvBtn" /> </template> <template #top-right> <QBtn class="q-mr-lg" - :label="t('myEntries.printLabels')" + :label="t('entrySupplier.printLabels')" color="primary" icon="print" @click=" @@ -148,13 +157,18 @@ function downloadCSV(rows) { v-if="props.row.stickers > 0" @click=" openReport( - `Entries/${props.row.id}/buy-label-supplier` + `Entries/${props.row.id}/buy-label-supplier`, + {}, + true, ) " unelevated + color="primary" + flat + data-cy="seeLabelBtn" > <QTooltip>{{ - t('myEntries.viewLabel') + t('entrySupplier.viewLabel') }}</QTooltip> </QBtn> </QTr> diff --git a/src/pages/Entry/EntryWasteRecalc.vue b/src/pages/Entry/EntryWasteRecalc.vue index 6ae200ed7..2fcd0f843 100644 --- a/src/pages/Entry/EntryWasteRecalc.vue +++ b/src/pages/Entry/EntryWasteRecalc.vue @@ -38,7 +38,7 @@ const recalc = async () => { <template> <div class="q-pa-lg row justify-center"> - <QCard class="bg-light" style="width: 300px"> + <QCard class="bg-light" style="width: 300px" data-cy="wasteRecalc"> <QCardSection> <VnInputDate class="q-mb-lg" @@ -46,6 +46,7 @@ const recalc = async () => { :label="$t('globals.from')" rounded dense + data-cy="dateFrom" /> <VnInputDate class="q-mb-lg" @@ -55,6 +56,7 @@ const recalc = async () => { :disable="!dateFrom" rounded dense + data-cy="dateTo" /> <QBtn color="primary" @@ -63,6 +65,7 @@ const recalc = async () => { :loading="isLoading" :disable="isLoading || !(dateFrom && dateTo)" @click="recalc()" + data-cy="recalc" /> </QCardSection> </QCard> diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index 88b16cb03..0bc92a5ea 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -6,7 +6,7 @@ entry: list: newEntry: New entry tableVisibleColumns: - isExcludedFromAvailable: Exclude from inventory + isExcludedFromAvailable: Excluded from available isOrdered: Ordered isConfirmed: Ready to label isReceived: Received @@ -33,7 +33,7 @@ entry: invoiceAmount: Invoice amount ordered: Ordered booked: Booked - excludedFromAvailable: Inventory + excludedFromAvailable: Excluded travelReference: Reference travelAgency: Agency travelShipped: Shipped @@ -55,7 +55,7 @@ entry: commission: Commission observation: Observation booked: Booked - excludedFromAvailable: Inventory + excludedFromAvailable: Excluded initialTemperature: Ini °C finalTemperature: Fin °C buys: @@ -65,27 +65,10 @@ entry: printedStickers: Printed stickers notes: observationType: Observation type - latestBuys: - tableVisibleColumns: - image: Picture - itemFk: Item ID - weightByPiece: Weight/Piece - isActive: Active - family: Family - entryFk: Entry - freightValue: Freight value - comissionValue: Commission value - packageValue: Package value - isIgnored: Is ignored - price2: Grouping - price3: Packing - minPrice: Min - ektFk: Ekt - packingOut: Package out - landing: Landing - isExcludedFromAvailable: Es inventory params: - isExcludedFromAvailable: Exclude from inventory + entryFk: Entry + observationTypeFk: Observation type + isExcludedFromAvailable: Excluded from available isOrdered: Ordered isConfirmed: Ready to label isReceived: Received @@ -127,13 +110,17 @@ entry: company_name: Company name itemTypeFk: Item type workerFk: Worker id + daysAgo: Days ago + toShipped: T. shipped + fromShipped: F. shipped + supplierName: Supplier search: Search entries searchInfo: You can search by entry reference descriptorMenu: showEntryReport: Show entry report entryFilter: params: - isExcludedFromAvailable: Exclude from inventory + isExcludedFromAvailable: Excluded from available invoiceNumber: Invoice number travelFk: Travel companyFk: Company @@ -155,7 +142,7 @@ entryFilter: warehouseOutFk: Origin warehouseInFk: Destiny entryTypeCode: Entry type -myEntries: +entrySupplier: id: ID landed: Landed shipped: Shipped @@ -170,6 +157,8 @@ myEntries: downloadCsv: Download CSV search: Search entries searchInfo: You can search by entry reference + supplierName: Supplier + itemId: Item id entryStockBought: travel: Travel editTravel: Edit travel diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index 3025d64cb..2c80299bc 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -6,7 +6,7 @@ entry: list: newEntry: Nueva entrada tableVisibleColumns: - isExcludedFromAvailable: Excluir del inventario + isExcludedFromAvailable: Excluir del disponible isOrdered: Pedida isConfirmed: Lista para etiquetar isReceived: Recibida @@ -25,7 +25,7 @@ entry: entryTypeDescription: Tipo entrada invoiceAmount: Importe dated: Fecha - inventoryEntry: Es inventario + inventoryEntry: Es inventario summary: commission: ComisiĂłn currency: Moneda @@ -33,7 +33,8 @@ entry: invoiceAmount: Importe ordered: Pedida booked: Contabilizada - excludedFromAvailable: Inventario + excludedFromAvailable: Excluir del disponible + isConfirmed: Lista para etiquetar travelReference: Referencia travelAgency: Agencia travelShipped: F. envio @@ -56,7 +57,7 @@ entry: observation: ObservaciĂłn commission: ComisiĂłn booked: Contabilizada - excludedFromAvailable: Inventario + excludedFromAvailable: Excluir del disponible initialTemperature: Ini °C finalTemperature: Fin °C buys: @@ -66,30 +67,12 @@ entry: printedStickers: Etiquetas impresas notes: observationType: Tipo de observaciĂłn - latestBuys: - tableVisibleColumns: - image: Foto - itemFk: Id ArtĂculo - weightByPiece: Peso (gramos)/tallo - isActive: Activo - family: Familia - entryFk: Entrada - freightValue: Porte - comissionValue: ComisiĂłn - packageValue: Embalaje - isIgnored: Ignorado - price2: Grouping - price3: Packing - minPrice: Min - ektFk: Ekt - packingOut: Embalaje envĂos - landing: Llegada - isExcludedFromAvailable: Es inventario - search: Buscar entradas searchInfo: Puedes buscar por referencia de entrada params: - isExcludedFromAvailable: Excluir del inventario + entryFk: Entrada + observationTypeFk: Tipo de observaciĂłn + isExcludedFromAvailable: Excluir del disponible isOrdered: Pedida isConfirmed: Lista para etiquetar isReceived: Recibida @@ -131,11 +114,15 @@ entry: company_name: Nombre empresa itemTypeFk: Familia workerFk: Comprador + daysAgo: DĂas atras + toShipped: F. salida(hasta) + fromShipped: F. salida(desde) + supplierName: Proveedor entryFilter: params: - isExcludedFromAvailable: Inventario + isExcludedFromAvailable: Excluir del disponible isOrdered: Pedida - isConfirmed: Confirmado + isConfirmed: Lista para etiquetar isReceived: Recibida isRaid: Raid landed: Fecha @@ -149,7 +136,7 @@ entryFilter: warehouseInFk: Destino entryTypeCode: Tipo de entrada hasToShowDeletedEntries: Mostrar entradas eliminadas -myEntries: +entrySupplier: id: ID landed: F. llegada shipped: F. salida @@ -164,10 +151,12 @@ myEntries: downloadCsv: Descargar CSV search: Buscar entradas searchInfo: Puedes buscar por referencia de la entrada + supplierName: Proveedor + itemId: Id artĂculo entryStockBought: travel: EnvĂo editTravel: Editar envĂo - purchaseSpaces: Espacios de compra + purchaseSpaces: Camiones reservados buyer: Comprador reserve: Reservado bought: Comprado diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index 905ddebb2..c3b678678 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -121,42 +121,49 @@ 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 - :label="t('Undeductible VAT')" - v-model="data.deductibleExpenseFk" - :options="expenses" + :label="t('invoiceIn.summary.sage')" + v-model="data.withholdingSageFk" + :options="sageWithholdings" option-value="id" - option-label="id" - :filter-options="['id', 'name']" - data-cy="UnDeductibleVatSelect" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - {{ `${scope.opt.id}: ${scope.opt.name}` }} - </QItem> - </template> - </VnSelect> + option-label="withholding" + /> <div class="row no-wrap"> <VnInput @@ -182,6 +189,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="downloadFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDownload" /> <QBtn :class="{ @@ -197,6 +205,7 @@ function deleteFile(dmsFk) { documentDialogRef.dms = data.dms; } " + data-cy="invoiceInBasicDataDmsEdit" > <QTooltip>{{ t('Edit document') }}</QTooltip> </QBtn> @@ -210,6 +219,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="deleteFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDelete" /> </div> <QBtn @@ -224,7 +234,7 @@ function deleteFile(dmsFk) { delete documentDialogRef.dms; } " - data-cy="dms-create" + data-cy="invoiceInBasicDataDmsAdd" > <QTooltip>{{ t('Create document') }}</QTooltip> </QBtn> @@ -237,9 +247,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,17 +259,8 @@ function deleteFile(dmsFk) { :label="t('Company')" v-model="data.companyFk" :options="companies" - option-value="id" option-label="code" - /> - </VnRow> - <VnRow> - <VnSelect - :label="t('invoiceIn.summary.sage')" - v-model="data.withholdingSageFk" - :options="sageWithholdings" - option-value="id" - option-label="withholding" + data-cy="invoiceInBasicDataCompanyFk" /> </VnRow> </template> @@ -313,7 +314,6 @@ function deleteFile(dmsFk) { supplierFk: Proveedor Expedition date: Fecha expediciĂłn Operation date: Fecha operaciĂłn - Undeductible VAT: Iva no deducible Document: Documento Download file: Descargar archivo Entry date: Fecha asiento diff --git a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue index 1d0a8d078..775a2a72b 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 3843f5bf7..e8df27511 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'; import axios from 'axios'; import { toCurrency, toDate } from 'src/filters'; import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import filter from './InvoiceInFilter.js'; import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.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 }), }, }; } @@ -88,7 +84,7 @@ async function setInvoiceCorrection(id) { } </script> <template> - <CardDescriptor + <EntityDescriptor ref="cardDescriptorRef" data-key="InvoiceIn" :url="`InvoiceIns/${entityId}`" @@ -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> @@ -163,7 +159,7 @@ async function setInvoiceCorrection(id) { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style lang="scss" scoped> .q-dialog { diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue index 8b039ec27..058f17d31 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 18602f043..d5fded4bc 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -40,6 +40,13 @@ const vatColumns = ref([ sortable: true, align: 'left', }, + { + name: 'isDeductible', + label: 'invoiceIn.isDeductible', + field: (row) => row.isDeductible, + sortable: true, + align: 'center', + }, { name: 'vat', label: 'invoiceIn.summary.sageVat', @@ -198,6 +205,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 +227,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> @@ -273,10 +281,6 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :label="t('invoiceIn.summary.sage')" :value="entity.sageWithholding?.withholding" /> - <VnLv - :label="t('invoiceIn.summary.vat')" - :value="entity.expenseDeductible?.name" - /> <VnLv :label="t('invoiceIn.card.company')" :value="entity.company?.code" @@ -334,6 +338,15 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </QTh> </QTr> </template> + <template #body-cell-isDeductible="{ row }"> + <QTd align="center"> + <QCheckbox + v-model="row.isDeductible" + disable + data-cy="isDeductible_checkbox" + /> + </QTd> + </template> <template #body-cell-vat="{ value: vatCell }"> <QTd :title="vatCell" shrink> {{ vatCell }} diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index eae255120..61c3040ae 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -53,6 +53,13 @@ const columns = computed(() => [ sortable: true, align: 'left', }, + { + name: 'isDeductible', + label: t('invoiceIn.isDeductible'), + field: (row) => row.isDeductible, + model: 'isDeductible', + align: 'center', + }, { name: 'sageiva', label: t('Sage iva'), @@ -119,6 +126,7 @@ const filter = { 'foreignValue', 'taxTypeSageFk', 'transactionTypeSageFk', + 'isDeductible', ], where: { invoiceInFk: route.params.id, @@ -202,6 +210,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, @@ -227,6 +238,14 @@ function setCursor(ref) { </VnSelectDialog> </QTd> </template> + <template #body-cell-isDeductible="{ row }"> + <QTd align="center"> + <QCheckbox + v-model="row.isDeductible" + data-cy="isDeductible_checkbox" + /> + </QTd> + </template> <template #body-cell-taxablebase="{ row }"> <QTd shrink> <VnInputNumber @@ -321,6 +340,7 @@ function setCursor(ref) { </QTd> <QTd /> <QTd /> + <QTd /> <QTd> {{ toCurrency(taxRateTotal) }} </QTd> diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index e010a1edb..6551a7ca9 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'; @@ -39,17 +40,13 @@ function handleDaysAgo(params, daysAgo) { <VnInputDate :label="$t('globals.from')" v-model="params.from" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - :label="$t('globals.to')" - v-model="params.to" - is-outlined - /> + <VnInputDate :label="$t('globals.to')" v-model="params.to" filled /> </QItemSection> </QItem> <QItem> @@ -57,7 +54,7 @@ function handleDaysAgo(params, daysAgo) { <VnInputNumber :label="$t('globals.daysAgo')" v-model="params.daysAgo" - is-outlined + filled :step="0" @update:model-value="(val) => handleDaysAgo(params, val)" @remove="(val) => handleDaysAgo(params, val)" @@ -66,12 +63,7 @@ function handleDaysAgo(params, daysAgo) { </QItem> <QItem> <QItemSection> - <VnSelectSupplier - v-model="params.supplierFk" - dense - outlined - rounded - /> + <VnSelectSupplier v-model="params.supplierFk" dense filled /> </QItemSection> </QItem> <QItem> @@ -79,7 +71,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('supplierRef')" v-model="params.supplierRef" - is-outlined + filled lazy-rules /> </QItemSection> @@ -89,7 +81,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('fi')" v-model="params.fi" - is-outlined + filled lazy-rules /> </QItemSection> @@ -99,7 +91,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('serial')" v-model="params.serial" - is-outlined + filled lazy-rules /> </QItemSection> @@ -109,7 +101,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('account')" v-model="params.account" - is-outlined + filled lazy-rules /> </QItemSection> @@ -119,7 +111,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('globals.params.awbCode')" v-model="params.awbCode" - is-outlined + filled lazy-rules /> </QItemSection> @@ -129,7 +121,7 @@ function handleDaysAgo(params, daysAgo) { <VnInputNumber :label="$t('globals.amount')" v-model="params.amount" - is-outlined + filled /> </QItemSection> </QItem> @@ -141,19 +133,19 @@ function handleDaysAgo(params, daysAgo) { url="Companies" option-label="code" :fields="['id', 'code']" - is-outlined + filled /> </QItemSection> </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 5bdbe197b..23175f2e7 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/InvoiceIn/Serial/InvoiceInSerialFilter.vue b/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue index 19ed73e50..66b7fa433 100644 --- a/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue +++ b/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue @@ -25,8 +25,7 @@ const { t } = useI18n(); <VnInputNumber v-model="params.daysAgo" :label="t('params.daysAgo')" - outlined - rounded + filled dense /> </QItemSection> @@ -36,8 +35,7 @@ const { t } = useI18n(); <VnInput v-model="params.serial" :label="t('params.serial')" - outlined - rounded + filled dense /> </QItemSection> diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 548e6c201..7e3603f0f 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -4,6 +4,7 @@ invoiceIn: serial: Serial isBooked: Is booked supplierRef: Invoice nÂş + isDeductible: Deductible list: ref: Reference supplier: Supplier diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 142d95f92..e6ac9273c 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -4,6 +4,7 @@ invoiceIn: serial: Serie isBooked: Contabilizada supplierRef: NÂş factura + isDeductible: Deducible list: ref: Referencia supplier: Proveedor diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue index 2402c0bf6..b93b8c8b7 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -3,7 +3,7 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; @@ -34,7 +34,7 @@ function ticketFilter(invoice) { </script> <template> - <CardDescriptor + <EntityDescriptor ref="descriptor" :url="`InvoiceOuts/${entityId}`" :filter="filter" @@ -93,5 +93,5 @@ function ticketFilter(invoice) { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/InvoiceOut/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index 99524e0d6..93a343565 100644 --- a/src/pages/InvoiceOut/InvoiceOutFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutFilter.vue @@ -33,17 +33,13 @@ const states = ref(); <VnInput :label="t('globals.params.clientFk')" v-model="params.clientFk" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.fi" - :label="t('globals.params.fi')" - is-outlined - /> + <VnInput v-model="params.fi" :label="t('globals.params.fi')" filled /> </QItemSection> </QItem> <QItem> @@ -51,7 +47,7 @@ const states = ref(); <VnInputNumber :label="t('globals.amount')" v-model="params.amount" - is-outlined + filled data-cy="InvoiceOutFilterAmountBtn" /> </QItemSection> @@ -62,8 +58,7 @@ const states = ref(); :label="t('invoiceOut.params.min')" dense lazy-rules - outlined - rounded + filled type="number" v-model.number="params.min" /> @@ -73,8 +68,7 @@ const states = ref(); :label="t('invoiceOut.params.max')" dense lazy-rules - outlined - rounded + filled type="number" v-model.number="params.max" /> @@ -94,7 +88,7 @@ const states = ref(); <VnInputDate v-model="params.created" :label="t('invoiceOut.params.created')" - is-outlined + filled /> </QItemSection> </QItem> @@ -103,15 +97,14 @@ const states = ref(); <VnInputDate v-model="params.dued" :label="t('invoiceOut.params.dued')" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" option-value="id" diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue index 392256473..53433c56b 100644 --- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue +++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue @@ -26,7 +26,7 @@ const serialTypesOptions = ref([]); const handleInvoiceOutSerialsFetch = (data) => { serialTypesOptions.value = Array.from( - new Set(data.map((item) => item.type).filter((type) => type)) + new Set(data.map((item) => item.type).filter((type) => type)), ); }; @@ -99,8 +99,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled data-cy="InvoiceOutGlobalClientSelect" > <template #option="scope"> @@ -124,19 +123,18 @@ onMounted(async () => { option-label="type" hide-selected dense - outlined - rounded + filled data-cy="InvoiceOutGlobalSerialSelect" /> <VnInputDate v-model="formData.invoiceDate" :label="t('invoiceDate')" - is-outlined + filled /> <VnInputDate v-model="formData.maxShipped" :label="t('maxShipped')" - is-outlined + filled data-cy="InvoiceOutGlobalMaxShippedDate" /> <VnSelect @@ -145,8 +143,7 @@ onMounted(async () => { :options="companiesOptions" option-label="code" dense - outlined - rounded + filled data-cy="InvoiceOutGlobalCompanySelect" /> <VnSelect @@ -154,8 +151,7 @@ onMounted(async () => { v-model="formData.printer" :options="printersOptions" dense - outlined - rounded + filled data-cy="InvoiceOutGlobalPrinterSelect" /> </div> @@ -166,7 +162,7 @@ onMounted(async () => { color="primary" class="q-mt-md full-width" unelevated - rounded + filled dense /> <QBtn @@ -175,7 +171,7 @@ onMounted(async () => { color="primary" class="q-mt-md full-width" unelevated - rounded + filled dense @click="getStatus = 'stopping'" /> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue index b24c8b247..1e2f80ec2 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue @@ -35,17 +35,13 @@ const props = defineProps({ <VnInputDate v-model="params.from" :label="t('globals.from')" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - v-model="params.to" - :label="t('globals.to')" - is-outlined - /> + <VnInputDate v-model="params.to" :label="t('globals.to')" filled /> </QItemSection> </QItem> <QItem> @@ -57,8 +53,7 @@ const props = defineProps({ option-label="code" option-value="code" dense - outlined - rounded + filled @update:model-value="searchFn()" > <template #option="scope"> @@ -84,9 +79,8 @@ const props = defineProps({ v-model="params.country" option-label="name" option-value="name" - outlined dense - rounded + filled @update:model-value="searchFn()" > <template #option="scope"> @@ -110,9 +104,8 @@ const props = defineProps({ url="Clients" :label="t('globals.client')" v-model="params.clientId" - outlined dense - rounded + filled @update:model-value="searchFn()" /> </QItemSection> @@ -122,7 +115,7 @@ const props = defineProps({ <VnInputNumber v-model="params.amount" :label="t('globals.amount')" - is-outlined + filled :positive="false" /> </QItemSection> @@ -130,9 +123,8 @@ const props = defineProps({ <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" option-value="id" diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue index 84e07a293..09f63a3b1 100644 --- a/src/pages/Item/Card/ItemDescriptor.vue +++ b/src/pages/Item/Card/ItemDescriptor.vue @@ -3,7 +3,7 @@ import { computed, ref, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; @@ -90,7 +90,7 @@ const updateStock = async () => { </script> <template> - <CardDescriptor + <EntityDescriptor data-key="Item" :summary="$props.summary" :url="`Items/${entityId}/getCard`" @@ -162,7 +162,7 @@ const updateStock = async () => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Item/ItemFixedPriceFilter.vue b/src/pages/Item/ItemFixedPriceFilter.vue index 8d92e245d..d68b966c6 100644 --- a/src/pages/Item/ItemFixedPriceFilter.vue +++ b/src/pages/Item/ItemFixedPriceFilter.vue @@ -13,7 +13,6 @@ const props = defineProps({ required: true, }, }); - </script> <template> @@ -28,8 +27,7 @@ const props = defineProps({ :fields="['id', 'nickname']" option-label="nickname" dense - outlined - rounded + filled use-input @update:model-value="searchFn()" sort-by="nickname ASC" @@ -46,8 +44,7 @@ const props = defineProps({ :label="t('params.warehouseFk')" v-model="params.warehouseFk" dense - outlined - rounded + filled use-input @update:model-value="searchFn()" /> @@ -58,7 +55,7 @@ const props = defineProps({ <VnInputDate :label="t('params.started')" v-model="params.started" - is-outlined + filled @update:model-value="searchFn()" /> </QItemSection> @@ -68,7 +65,7 @@ const props = defineProps({ <VnInputDate :label="t('params.ended')" v-model="params.ended" - is-outlined + filled @update:model-value="searchFn()" /> </QItemSection> diff --git a/src/pages/Item/ItemListFilter.vue b/src/pages/Item/ItemListFilter.vue index 22e948e06..f4500d5fa 100644 --- a/src/pages/Item/ItemListFilter.vue +++ b/src/pages/Item/ItemListFilter.vue @@ -177,11 +177,7 @@ onMounted(async () => { <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('params.search')" - is-outlined - /> + <VnInput v-model="params.search" :label="t('params.search')" filled /> </QItemSection> </QItem> <QItem> @@ -197,8 +193,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -213,8 +208,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -240,8 +234,7 @@ onMounted(async () => { option-label="nickname" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -252,8 +245,7 @@ onMounted(async () => { @update:model-value="searchFn()" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -282,8 +274,7 @@ onMounted(async () => { :options="tagOptions" option-label="name" dense - outlined - rounded + filled :emit-value="false" use-input :is-clearable="false" @@ -299,8 +290,7 @@ onMounted(async () => { option-value="value" option-label="value" dense - outlined - rounded + filled emit-value use-input :disable="!tag" @@ -312,7 +302,7 @@ onMounted(async () => { v-model="tag.value" :label="t('params.value')" :disable="!tag" - is-outlined + filled :is-clearable="false" @keydown.enter.prevent="applyTags(params, searchFn)" /> @@ -351,8 +341,7 @@ onMounted(async () => { option-label="label" option-value="label" dense - outlined - rounded + filled :emit-value="false" use-input :is-clearable="false" @@ -377,7 +366,7 @@ onMounted(async () => { v-model="fieldFilter.value" :label="t('params.value')" :disable="!fieldFilter.selectedField" - is-outlined + filled @keydown.enter="applyFieldFilters(params, searchFn)" /> </QItemSection> diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue index a29203df3..68f36c566 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -87,11 +87,7 @@ onMounted(async () => { <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('params.search')" - is-outlined - /> + <VnInput v-model="params.search" :label="t('params.search')" filled /> </QItemSection> </QItem> <QItem> @@ -99,7 +95,7 @@ onMounted(async () => { <VnInput v-model="params.ticketFk" :label="t('params.ticketFk')" - is-outlined + filled /> </QItemSection> </QItem> @@ -114,8 +110,7 @@ onMounted(async () => { option-label="nickname" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -124,7 +119,7 @@ onMounted(async () => { <VnInput v-model="params.clientFk" :label="t('params.clientFk')" - is-outlined + filled /> </QItemSection> </QItem> @@ -139,8 +134,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -153,25 +147,16 @@ onMounted(async () => { :params="{ departmentCodes: ['VT'] }" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - v-model="params.from" - :label="t('params.from')" - is-outlined - /> + <VnInputDate v-model="params.from" :label="t('params.from')" filled /> </QItemSection> <QItemSection> - <VnInputDate - v-model="params.to" - :label="t('params.to')" - is-outlined - /> + <VnInputDate v-model="params.to" :label="t('params.to')" filled /> </QItemSection> </QItem> <QItem> @@ -180,7 +165,7 @@ onMounted(async () => { :label="t('params.daysOnward')" v-model="params.daysOnward" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -195,8 +180,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue index 725fb30aa..106b005bf 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import filter from './ItemTypeFilter.js'; @@ -25,11 +25,12 @@ const entityId = computed(() => { }); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`ItemTypes/${entityId}`" :filter="filter" title="code" data-key="ItemType" + :to-module="{ name: 'ItemTypeList' }" > <template #body="{ entity }"> <VnLv :label="$t('itemType.shared.code')" :value="entity.code" /> @@ -45,5 +46,5 @@ const entityId = computed(() => { :value="entity.category?.name" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue index 447dd35b8..535906e17 100644 --- a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue +++ b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue @@ -77,7 +77,7 @@ const getLocale = (label) => { <VnInput :label="t('globals.params.clientFk')" v-model="params.clientFk" - is-outlined + filled /> </QItemSection> </QItem> @@ -86,7 +86,7 @@ const getLocale = (label) => { <VnInput :label="t('params.orderFk')" v-model="params.orderFk" - is-outlined + filled /> </QItemSection> </QItem> @@ -95,7 +95,7 @@ const getLocale = (label) => { <VnInputNumber :label="t('params.scopeDays')" v-model="params.scopeDays" - is-outlined + filled @update:model-value="(val) => handleScopeDays(params, val)" @remove="(val) => handleScopeDays(params, val)" /> @@ -106,66 +106,54 @@ const getLocale = (label) => { <VnInput :label="t('params.nickname')" v-model="params.nickname" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" - option-value="id" - option-label="name" url="Departments" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('params.refFk')" - v-model="params.refFk" - is-outlined - /> + <VnInput :label="t('params.refFk')" v-model="params.refFk" filled /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('params.agencyModeFk')" v-model="params.agencyModeFk" url="AgencyModes/isActive" - is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.stateFk')" v-model="params.stateFk" url="States" - is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('params.groupedStates')" v-model="params.alertLevel" :options="groupedStates" @@ -176,9 +164,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.warehouseFk')" v-model="params.warehouseFk" :options="warehouses" @@ -188,9 +175,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.countryFk')" v-model="params.countryFk" url="Countries" @@ -200,9 +186,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.provinceFk')" v-model="params.provinceFk" url="Provinces" @@ -212,9 +197,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.department" option-label="name" @@ -226,9 +210,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.packing')" v-model="params.packing" url="ItemPackingTypes" diff --git a/src/pages/Order/Card/CatalogFilterValueDialog.vue b/src/pages/Order/Card/CatalogFilterValueDialog.vue index d1bd48c9e..10273a254 100644 --- a/src/pages/Order/Card/CatalogFilterValueDialog.vue +++ b/src/pages/Order/Card/CatalogFilterValueDialog.vue @@ -57,9 +57,8 @@ const getSelectedTagValues = async (tag) => { option-value="id" option-label="name" dense - outlined class="q-mb-md" - rounded + filled :emit-value="false" use-input @update:model-value="getSelectedTagValues" @@ -79,8 +78,7 @@ const getSelectedTagValues = async (tag) => { option-value="value" option-label="value" dense - outlined - rounded + filled emit-value use-input :disable="!value || !selectedTag" @@ -92,16 +90,14 @@ const getSelectedTagValues = async (tag) => { v-model="value.value" :label="t('components.itemsFilterPanel.value')" :disable="!value" - is-outlined class="col" data-cy="catalogFilterValueDialogValueInput" /> <QBtn icon="delete" size="md" - outlined dense - rounded + filled flat class="filter-icon col-2" @click="tagValues.splice(index, 1)" diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue index 7dab307a0..11dbbe532 100644 --- a/src/pages/Order/Card/OrderCard.vue +++ b/src/pages/Order/Card/OrderCard.vue @@ -6,9 +6,11 @@ import filter from './OrderFilter.js'; <template> <VnCard - data-key="Order" + :data-key="$attrs['data-key'] ?? 'Order'" url="Orders" :filter="filter" :descriptor="OrderDescriptor" + v-bind="$attrs" + v-on="$attrs" /> </template> diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue index d16a92017..cb380c48f 100644 --- a/src/pages/Order/Card/OrderCatalogFilter.vue +++ b/src/pages/Order/Card/OrderCatalogFilter.vue @@ -221,8 +221,7 @@ function addOrder(value, field, params) { option-value="id" option-label="name" dense - outlined - rounded + filled emit-value use-input sort-by="name ASC" @@ -251,8 +250,7 @@ function addOrder(value, field, params) { v-model="orderBySelected" :options="orderByList" dense - outlined - rounded + filled @update:model-value="(value) => addOrder(value, 'field', params)" /> </QItemSection> @@ -264,8 +262,7 @@ function addOrder(value, field, params) { v-model="orderWaySelected" :options="orderWayList" dense - outlined - rounded + filled @update:model-value="(value) => addOrder(value, 'way', params)" /> </QItemSection> @@ -275,8 +272,7 @@ function addOrder(value, field, params) { <VnInput :label="t('components.itemsFilterPanel.value')" dense - outlined - rounded + filled :is-clearable="false" v-model="searchByTag" @keyup.enter="(val) => onSearchByTag(val, params)" diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue index f34549c1e..ee66bb57e 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -4,10 +4,10 @@ import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { toCurrency, toDate } from 'src/filters'; import { useState } from 'src/composables/useState'; -import filter from './OrderFilter.js'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import FetchData from 'components/FetchData.vue'; +import OrderCard from './OrderCard.vue'; +import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; const DEFAULT_ITEMS = 0; @@ -24,11 +24,14 @@ const route = useRoute(); const state = useState(); const { t } = useI18n(); const getTotalRef = ref(); +const total = ref(0); const entityId = computed(() => { return $props.id || route.params.id; }); +const orderTotal = computed(() => state.get('orderTotal') ?? 0); + const setData = (entity) => { if (!entity) return; getTotalRef.value && getTotalRef.value.fetch(); @@ -38,9 +41,6 @@ const setData = (entity) => { const getConfirmationValue = (isConfirmed) => { return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed'); }; - -const orderTotal = computed(() => state.get('orderTotal') ?? 0); -const total = ref(0); </script> <template> @@ -54,12 +54,12 @@ const total = ref(0); " /> <CardDescriptor - ref="descriptor" - :url="`Orders/${entityId}`" - :filter="filter" + v-bind="$attrs" + :id="entityId" + :card="OrderCard" title="client.name" @on-fetch="setData" - data-key="Order" + module="Order" > <template #body="{ entity }"> <VnLv diff --git a/src/pages/Order/Card/OrderDescriptorProxy.vue b/src/pages/Order/Card/OrderDescriptorProxy.vue index 04ebb054a..1dff1b620 100644 --- a/src/pages/Order/Card/OrderDescriptorProxy.vue +++ b/src/pages/Order/Card/OrderDescriptorProxy.vue @@ -12,6 +12,11 @@ const $props = defineProps({ <template> <QPopupProxy> - <OrderDescriptor v-if="$props.id" :id="$props.id" :summary="OrderSummary" /> + <OrderDescriptor + v-if="$props.id" + :id="$props.id" + :summary="OrderSummary" + data-key="OrderDescriptor" + /> </QPopupProxy> </template> diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue index 42578423f..609a1215a 100644 --- a/src/pages/Order/Card/OrderFilter.vue +++ b/src/pages/Order/Card/OrderFilter.vue @@ -49,8 +49,7 @@ const sourceList = ref([]); v-model="params.clientFk" lazy-rules dense - outlined - rounded + filled /> <VnSelect :label="t('agency')" @@ -58,13 +57,11 @@ const sourceList = ref([]); :options="agencyList" :input-debounce="0" dense - outlined - rounded + filled /> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" option-value="id" @@ -75,21 +72,14 @@ const sourceList = ref([]); v-model="params.from" :label="t('fromLanded')" dense - outlined - rounded - /> - <VnInputDate - v-model="params.to" - :label="t('toLanded')" - dense - outlined - rounded + filled /> + <VnInputDate v-model="params.to" :label="t('toLanded')" dense filled /> <VnInput :label="t('orderId')" v-model="params.orderFk" lazy-rules - is-outlined + filled /> <VnSelect :label="t('application')" @@ -98,8 +88,7 @@ const sourceList = ref([]); option-label="value" option-value="value" dense - outlined - rounded + filled :input-debounce="0" /> <QCheckbox diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index d75390d96..1241c4ee2 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -65,7 +65,6 @@ const columns = computed(() => [ attrs: { url: 'Departments', }, - create: true, columnField: { component: null, }, diff --git a/src/pages/Route/Agency/Card/AgencyDescriptor.vue b/src/pages/Route/Agency/Card/AgencyDescriptor.vue index a0472c6c3..64b33cc06 100644 --- a/src/pages/Route/Agency/Card/AgencyDescriptor.vue +++ b/src/pages/Route/Agency/Card/AgencyDescriptor.vue @@ -3,7 +3,7 @@ import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; const props = defineProps({ @@ -17,18 +17,19 @@ const props = defineProps({ const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => props.id || route.params.id); -const { store } = useArrayData('Parking'); +const { store } = useArrayData(); const card = computed(() => store.data); </script> <template> - <CardDescriptor + <EntityDescriptor data-key="Agency" :url="`Agencies/${entityId}`" :title="card?.name" :subtitle="props.id" + :to-module="{ name: 'RouteAgency' }" > <template #body="{ entity: agency }"> <VnLv :label="t('globals.name')" :value="agency.name" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Route/Card/RouteAutonomousFilter.vue b/src/pages/Route/Card/RouteAutonomousFilter.vue index f70f60e1c..fe631a0be 100644 --- a/src/pages/Route/Card/RouteAutonomousFilter.vue +++ b/src/pages/Route/Card/RouteAutonomousFilter.vue @@ -71,7 +71,7 @@ const exprBuilder = (param, value) => { <QList dense> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.routeFk" :label="t('ID')" is-outlined /> + <VnInput v-model="params.routeFk" :label="t('ID')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm" v-if="agencyList"> @@ -83,8 +83,7 @@ const exprBuilder = (param, value) => { option-value="id" option-label="name" dense - outlined - rounded + filled emit-value map-options use-input @@ -102,8 +101,7 @@ const exprBuilder = (param, value) => { option-value="id" option-label="name" dense - outlined - rounded + filled emit-value map-options use-input @@ -123,8 +121,7 @@ const exprBuilder = (param, value) => { option-value="name" option-label="name" dense - outlined - rounded + filled emit-value map-options use-input @@ -135,20 +132,12 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate - v-model="params.dated" - :label="t('Date')" - is-outlined - /> + <VnInputDate v-model="params.dated" :label="t('Date')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate - v-model="params.from" - :label="t('From')" - is-outlined - /> + <VnInputDate v-model="params.from" :label="t('From')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -156,7 +145,7 @@ const exprBuilder = (param, value) => { <VnInputDate v-model="params.to" :label="t('To')" - is-outlined + filled is-clearable /> </QItemSection> @@ -166,23 +155,23 @@ const exprBuilder = (param, value) => { <VnInput v-model="params.packages" :label="t('Packages')" - is-outlined + filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.m3" :label="t('m3')" is-outlined /> + <VnInput v-model="params.m3" :label="t('m3')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.kmTotal" :label="t('Km')" is-outlined /> + <VnInput v-model="params.kmTotal" :label="t('Km')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.price" :label="t('Price')" is-outlined /> + <VnInput v-model="params.price" :label="t('Price')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -190,7 +179,7 @@ const exprBuilder = (param, value) => { <VnInput v-model="params.invoiceInFk" :label="t('Received')" - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index 01fb9c4ba..ee42d8e76 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -1,12 +1,12 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; +import useCardDescription from 'composables/useCardDescription'; 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({ @@ -41,13 +41,12 @@ const getZone = async () => { zone.value = zoneData.name; }; const data = ref(useCardDescription()); -const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); onMounted(async () => { getZone(); }); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Routes/${entityId}`" :filter="filter" :title="null" @@ -69,7 +68,7 @@ onMounted(async () => { <template #menu="{ entity }"> <RouteDescriptorMenu :route="entity" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Route/Card/RouteFilter.vue b/src/pages/Route/Card/RouteFilter.vue index cb5158517..f830b83e2 100644 --- a/src/pages/Route/Card/RouteFilter.vue +++ b/src/pages/Route/Card/RouteFilter.vue @@ -36,8 +36,7 @@ const emit = defineEmits(['search']); :label="t('globals.worker')" v-model="params.workerFk" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> @@ -52,8 +51,7 @@ const emit = defineEmits(['search']); option-value="id" option-label="name" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> @@ -63,7 +61,7 @@ const emit = defineEmits(['search']); <VnInputDate v-model="params.from" :label="t('globals.from')" - is-outlined + filled :disable="Boolean(params.scopeDays)" @update:model-value="params.scopeDays = null" /> @@ -74,7 +72,7 @@ const emit = defineEmits(['search']); <VnInputDate v-model="params.to" :label="t('globals.to')" - is-outlined + filled :disable="Boolean(params.scopeDays)" @update:model-value="params.scopeDays = null" /> @@ -86,7 +84,7 @@ const emit = defineEmits(['search']); v-model="params.scopeDays" type="number" :label="t('globals.daysOnward')" - is-outlined + filled clearable :disable="Boolean(params.from || params.to)" @update:model-value=" @@ -107,15 +105,14 @@ const emit = defineEmits(['search']); option-label="numberPlate" option-filter-value="numberPlate" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.m3" label="mÂł" is-outlined clearable /> + <VnInput v-model="params.m3" label="mÂł" filled clearable /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -127,8 +124,7 @@ const emit = defineEmits(['search']); option-value="id" option-label="name" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> @@ -138,7 +134,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.description" :label="t('globals.description')" - is-outlined + filled clearable /> </QItemSection> diff --git a/src/pages/Route/Cmr/CmrList.vue b/src/pages/Route/Cmr/CmrList.vue index d0683e481..170f73bc0 100644 --- a/src/pages/Route/Cmr/CmrList.vue +++ b/src/pages/Route/Cmr/CmrList.vue @@ -28,7 +28,6 @@ const userParams = { shipped: null, }; - const columns = computed(() => [ { align: 'left', @@ -175,6 +174,7 @@ function downloadPdfs() { :data-key url="Cmrs/filter" :columns="columns" + :order="['shipped DESC', 'cmrFk ASC']" :user-params="userParams" default-mode="table" v-model:selected="selectedRows" diff --git a/src/pages/Route/Roadmap/RoadmapDescriptor.vue b/src/pages/Route/Roadmap/RoadmapDescriptor.vue index 198bcf8c7..dfa692feb 100644 --- a/src/pages/Route/Roadmap/RoadmapDescriptor.vue +++ b/src/pages/Route/Roadmap/RoadmapDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import { dashIfEmpty, toDateHourMin } from 'src/filters'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; @@ -30,11 +30,12 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Roadmaps/${entityId}`" :filter="filter" data-key="Roadmap" :summary="summary" + :to-module="{ name: 'RouteRoadmap' }" > <template #body="{ entity }"> <VnLv :label="t('Roadmap')" :value="entity?.name" /> @@ -51,7 +52,7 @@ const entityId = computed(() => { <template #menu="{ entity }"> <RoadmapDescriptorMenu :route="entity" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Route/Roadmap/RoadmapFilter.vue b/src/pages/Route/Roadmap/RoadmapFilter.vue index 982f1efba..9acbfb740 100644 --- a/src/pages/Route/Roadmap/RoadmapFilter.vue +++ b/src/pages/Route/Roadmap/RoadmapFilter.vue @@ -31,12 +31,12 @@ const emit = defineEmits(['search']); <template #body="{ params }"> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate v-model="params.from" :label="t('From')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate v-model="params.to" :label="t('To')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -44,7 +44,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.tractorPlate" :label="t('Tractor Plate')" - is-outlined + filled clearable /> </QItemSection> @@ -54,7 +54,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.trailerPlate" :label="t('Trailer Plate')" - is-outlined + filled clearable /> </QItemSection> @@ -66,8 +66,7 @@ const emit = defineEmits(['search']); :fields="['id', 'nickname']" v-model="params.supplierFk" dense - outlined - rounded + filled emit-value map-options use-input @@ -81,7 +80,7 @@ const emit = defineEmits(['search']); v-model="params.price" :label="t('Price')" type="number" - is-outlined + filled clearable /> </QItemSection> @@ -91,7 +90,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.driverName" :label="t('Driver name')" - is-outlined + filled clearable /> </QItemSection> @@ -101,7 +100,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.phone" :label="t('Phone')" - is-outlined + filled clearable /> </QItemSection> diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue index fb19323c9..b905cfde8 100644 --- a/src/pages/Route/RouteExtendedList.vue +++ b/src/pages/Route/RouteExtendedList.vue @@ -332,6 +332,7 @@ const openTicketsDialog = (id) => { <QBtn icon="vn:clone" color="primary" + flat class="q-mr-sm" :disable="!selectedRows?.length" @click="confirmationDialog = true" @@ -341,6 +342,7 @@ const openTicketsDialog = (id) => { <QBtn icon="cloud_download" color="primary" + flat class="q-mr-sm" :disable="!selectedRows?.length" @click="showRouteReport" @@ -352,6 +354,7 @@ const openTicketsDialog = (id) => { <QBtn icon="check" color="primary" + flat class="q-mr-sm" :disable="!selectedRows?.length" @click="markAsServed()" diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index 7fc1027ea..f3b9c438c 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -3,6 +3,7 @@ import { computed, ref, markRaw } from 'vue'; import { useI18n } from 'vue-i18n'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { toHour } from 'src/filters'; +import { useRouter } from 'vue-router'; import RouteSummary from 'pages/Route/Card/RouteSummary.vue'; import RouteFilter from 'pages/Route/Card/RouteFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; @@ -11,9 +12,9 @@ import AgencyDescriptorProxy from 'src/pages/Route/Agency/Card/AgencyDescriptorP import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue'; import VnSection from 'src/components/common/VnSection.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; -import RouteTickets from './RouteTickets.vue'; const { t } = useI18n(); +const router = useRouter(); const { viewSummary } = useSummaryDialog(); const tableRef = ref([]); const dataKey = 'RouteList'; @@ -29,8 +30,10 @@ const routeFilter = { }; function redirectToTickets(id) { - const url = `#/route/${id}/tickets`; - window.open(url, '_blank'); + router.push({ + name: 'RouteTickets', + params: { id }, + }); } const columns = computed(() => [ @@ -46,26 +49,18 @@ const columns = computed(() => [ width: '25px', }, { + align: 'left', name: 'workerFk', - label: t('gloabls.worker'), + label: t('globals.worker'), component: markRaw(VnSelectWorker), create: true, + cardVisible: true, format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), columnFilter: false, width: '100px', }, { - name: 'workerFk', - label: t('globals.worker'), - visible: false, - cardVisible: true, - }, - { - name: 'agencyName', label: t('globals.agency'), - }, - { - label: t('globals.Agency'), name: 'agencyModeFk', component: 'select', attrs: { @@ -77,23 +72,12 @@ const columns = computed(() => [ }, }, create: true, - columnFilter: false, - visible: false, - }, - { - name: 'agencyName', - label: t('globals.agency'), - visible: false, + columnFilter: true, cardVisible: true, }, - { - name: 'vehiclePlateNumber', - label: t('globals.vehicle'), - }, { name: 'vehicleFk', - label: t('globals.Vehicle'), - cardVisible: true, + label: t('globals.vehicle'), component: 'select', attrs: { url: 'vehicles', @@ -106,8 +90,8 @@ const columns = computed(() => [ }, }, create: true, - columnFilter: false, - visible: false, + columnFilter: true, + cardVisible: true, }, { align: 'center', @@ -181,8 +165,8 @@ const columns = computed(() => [ <VnTable :with-filters="false" :data-key - :columns="columns" ref="tableRef" + :columns="columns" :right-search="false" redirect="route" :create="{ @@ -199,7 +183,7 @@ const columns = computed(() => [ <WorkerDescriptorProxy :id="row?.workerFk" v-if="row?.workerFk" /> </span> </template> - <template #column-agencyName="{ row }"> + <template #column-agencyModeFk="{ row }"> <span class="link" @click.stop> {{ row?.agencyName }} <AgencyDescriptorProxy @@ -208,7 +192,7 @@ const columns = computed(() => [ /> </span> </template> - <template #column-vehiclePlateNumber="{ row }"> + <template #column-vehicleFk="{ row }"> <span class="link" @click.stop> {{ row?.vehiclePlateNumber }} <VehicleDescriptorProxy diff --git a/src/pages/Route/RouteTickets.vue b/src/pages/Route/RouteTickets.vue index b17fb543f..5e28bb689 100644 --- a/src/pages/Route/RouteTickets.vue +++ b/src/pages/Route/RouteTickets.vue @@ -30,16 +30,16 @@ const columns = computed(() => [ align: 'center', }, { - name: 'street', - label: t('Street'), - field: (row) => row?.street, + name: 'client', + label: t('Client'), + field: (row) => row?.nickname, sortable: false, align: 'left', }, { - name: 'client', - label: t('Client'), - field: (row) => row?.nickname, + name: 'street', + label: t('Street'), + field: (row) => row?.street, sortable: false, align: 'left', }, @@ -199,12 +199,22 @@ const confirmRemove = (ticket) => { const openSmsDialog = async () => { const clientsId = []; const clientsPhone = []; - + const clientWithoutPhone = []; for (let ticket of selectedRows.value) { clientsId.push(ticket?.clientFk); const { data: client } = await axios.get(`Clients/${ticket?.clientFk}`); + if (!client.phone) { + clientWithoutPhone.push(ticket?.clientFk); + continue; + } clientsPhone.push(client.phone); } + if (clientWithoutPhone.length) { + quasar.notify({ + type: 'warning', + message: t('components.VnNotes.clientWithoutPhone', { clientWithoutPhone }), + }); + } quasar.dialog({ component: SendSmsDialog, @@ -319,7 +329,7 @@ const openSmsDialog = async () => { selection="multiple" > <template #body-cell-order="{ row }"> - <QTd class="order-field"> + <QTd class="order-field" auto-width> <div class="flex no-wrap items-center"> <QIcon name="low_priority" @@ -341,7 +351,7 @@ const openSmsDialog = async () => { </QTd> </template> <template #body-cell-city="{ value, row }"> - <QTd auto-width> + <QTd> <span class="link" @click="goToBuscaman(row)"> {{ value }} <QTooltip>{{ t('Open buscaman') }}</QTooltip> @@ -349,7 +359,7 @@ const openSmsDialog = async () => { </QTd> </template> <template #body-cell-client="{ value, row }"> - <QTd auto-width> + <QTd> <span class="link"> {{ value }} <CustomerDescriptorProxy :id="row?.clientFk" /> diff --git a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue index ad2ae61e4..bab7fa998 100644 --- a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue +++ b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -20,10 +20,11 @@ const route = useRoute(); const entityId = computed(() => props.id || route.params.id); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Vehicles/${entityId}`" data-key="Vehicle" title="numberPlate" + :to-module="{ name: 'RouteVehicle' }" > <template #menu="{ entity }"> <QItem @@ -53,7 +54,7 @@ const entityId = computed(() => props.id || route.params.id); <VnLv :label="$t('globals.model')" :value="entity.model" /> <VnLv :label="$t('globals.country')" :value="entity.countryCodeFk" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Shelving/Card/ShelvingDescriptor.vue b/src/pages/Shelving/Card/ShelvingDescriptor.vue index 5e618aa7f..2405467da 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptor.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import ShelvingDescriptorMenu from 'pages/Shelving/Card/ShelvingDescriptorMenu.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; @@ -24,7 +24,7 @@ const entityId = computed(() => { }); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Shelvings/${entityId}`" :filter="filter" title="code" @@ -45,5 +45,5 @@ const entityId = computed(() => { <template #menu="{ entity }"> <ShelvingDescriptorMenu :shelving="entity" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Shelving/Card/ShelvingFilter.vue b/src/pages/Shelving/Card/ShelvingFilter.vue index 88d716046..35657a972 100644 --- a/src/pages/Shelving/Card/ShelvingFilter.vue +++ b/src/pages/Shelving/Card/ShelvingFilter.vue @@ -39,15 +39,14 @@ const emit = defineEmits(['search']); option-label="code" :filter-options="['id', 'code']" dense - outlined - rounded + filled sort-by="code ASC" /> </QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnSelectWorker v-model="params.userFk" outlined rounded /> + <VnSelectWorker v-model="params.userFk" filled /> </QItemSection> </QItem> <QItem class="q-mb-md"> diff --git a/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue b/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue index 46c9f8ea0..0e01238a0 100644 --- a/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue +++ b/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue @@ -1,7 +1,7 @@ <script setup> import { computed } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import filter from './ParkingFilter.js'; const props = defineProps({ @@ -16,17 +16,17 @@ const route = useRoute(); const entityId = computed(() => props.id || route.params.id); </script> <template> - <CardDescriptor + <EntityDescriptor data-key="Parking" :url="`Parkings/${entityId}`" title="code" :filter="filter" - :to-module="{ name: 'ParkingList' }" + :to-module="{ name: 'ParkingMain' }" > <template #body="{ entity }"> <VnLv :label="$t('globals.code')" :value="entity.code" /> <VnLv :label="$t('parking.pickingOrder')" :value="entity.pickingOrder" /> <VnLv :label="$t('parking.sector')" :value="entity.sector?.description" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Shelving/Parking/ParkingFilter.vue b/src/pages/Shelving/Parking/ParkingFilter.vue index 1d7c3a4b6..59cb49459 100644 --- a/src/pages/Shelving/Parking/ParkingFilter.vue +++ b/src/pages/Shelving/Parking/ParkingFilter.vue @@ -36,11 +36,7 @@ const emit = defineEmits(['search']); <template #body="{ params }"> <QItem> <QItemSection> - <VnInput - :label="t('params.code')" - v-model="params.code" - is-outlined - /> + <VnInput :label="t('params.code')" v-model="params.code" filled /> </QItemSection> </QItem> <QItem> @@ -51,8 +47,7 @@ const emit = defineEmits(['search']); option-label="description" :label="t('params.sectorFk')" dense - outlined - rounded + filled :options="sectors" use-input input-debounce="0" diff --git a/src/pages/Supplier/Card/SupplierBalanceFilter.vue b/src/pages/Supplier/Card/SupplierBalanceFilter.vue index c4b63d9c8..c727688ad 100644 --- a/src/pages/Supplier/Card/SupplierBalanceFilter.vue +++ b/src/pages/Supplier/Card/SupplierBalanceFilter.vue @@ -33,7 +33,7 @@ defineProps({ :label="t('params.from')" v-model="params.from" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -47,8 +47,7 @@ defineProps({ :include="{ relation: 'accountingType' }" sort-by="id" dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -74,8 +73,7 @@ defineProps({ option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Supplier/Card/SupplierConsumptionFilter.vue b/src/pages/Supplier/Card/SupplierConsumptionFilter.vue index 390f7d9ff..e21e37eb3 100644 --- a/src/pages/Supplier/Card/SupplierConsumptionFilter.vue +++ b/src/pages/Supplier/Card/SupplierConsumptionFilter.vue @@ -25,20 +25,12 @@ defineProps({ <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('params.search')" - is-outlined - /> + <VnInput v-model="params.search" :label="t('params.search')" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.itemId" - :label="t('params.itemId')" - is-outlined - /> + <VnInput v-model="params.itemId" :label="t('params.itemId')" filled /> </QItemSection> </QItem> <QItem> @@ -54,8 +46,7 @@ defineProps({ option-label="nickname" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -73,8 +64,7 @@ defineProps({ option-label="name" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -102,8 +92,7 @@ defineProps({ option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -113,7 +102,7 @@ defineProps({ :label="t('params.from')" v-model="params.from" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -123,7 +112,7 @@ defineProps({ :label="t('params.to')" v-model="params.to" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 462bdf853..2863784ab 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toDateString } from 'src/filters'; @@ -61,7 +61,7 @@ const getEntryQueryParams = (supplier) => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Suppliers/${entityId}`" :filter="filter" data-key="Supplier" @@ -136,7 +136,7 @@ const getEntryQueryParams = (supplier) => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue index 3c2fe95e5..76191b099 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue @@ -44,7 +44,7 @@ const getPriceDifference = async () => { shipped: ticket.value.shipped, }; const { data } = await axios.post( - `tickets/${formData.value.id}/priceDifference`, + `tickets/${ticket.value.id}/priceDifference`, params, ); ticket.value.sale = data; @@ -71,7 +71,7 @@ const submit = async () => { }; const { data } = await axios.post( - `tickets/${formData.value.id}/componentUpdate`, + `tickets/${ticket.value.id}/componentUpdate`, params, ); diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index 743f2188c..96920231c 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -4,7 +4,7 @@ import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toDateTimeFormat } from 'src/filters/date'; @@ -57,7 +57,7 @@ function getInfo() { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Tickets/${entityId}`" :filter="filter" data-key="Ticket" @@ -155,7 +155,7 @@ function getInfo() { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 63e45c8ab..f7389b592 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -32,7 +32,7 @@ onMounted(() => { watch( () => props.ticket, - () => restoreTicket + () => restoreTicket, ); const { push, currentRoute } = useRouter(); @@ -58,7 +58,7 @@ const hasDocuwareFile = ref(); const quasar = useQuasar(); const canRestoreTicket = ref(false); -const onClientSelected = async(clientId) =>{ +const onClientSelected = async (clientId) => { client.value = clientId; await fetchClient(); await fetchAddresses(); @@ -66,10 +66,10 @@ const onClientSelected = async(clientId) =>{ const onAddressSelected = (addressId) => { address.value = addressId; -} +}; const fetchClient = async () => { - const response = await getClient(client.value) + const response = await getClient(client.value); if (!response) return; const [retrievedClient] = response.data; selectedClient.value = retrievedClient; @@ -151,7 +151,7 @@ function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') { recipientId: ticket.value.clientFk, type: type, }, - '_blank' + '_blank', ); } @@ -297,8 +297,8 @@ async function transferClient() { clientFk: client.value, addressFk: address.value, }; - - await axios.patch( `Tickets/${ticketId.value}/transferClient`, params ); + + await axios.patch(`Tickets/${ticketId.value}/transferClient`, params); window.location.reload(); } @@ -339,7 +339,7 @@ async function changeShippedHour(time) { const { data } = await axios.post( `Tickets/${ticketId.value}/updateEditableTicket`, - params + params, ); if (data) window.location.reload(); @@ -405,8 +405,7 @@ async function uploadDocuware(force) { uploadDocuware(true); }); - const { data } = await axios.post(`Docuwares/upload`, { - fileCabinet: 'deliveryNote', + const { data } = await axios.post(`Docuwares/upload-delivery-note`, { ticketIds: [parseInt(ticketId.value)], }); @@ -500,7 +499,7 @@ async function ticketToRestore() { </QItem> </template> </VnSelect> - <VnSelect + <VnSelect :disable="!client" :options="addressesOptions" :fields="['id', 'nickname']" @@ -815,7 +814,7 @@ async function ticketToRestore() { en: addTurn: Add turn invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}" - + es: Show Delivery Note...: Ver albarán... Send Delivery Note...: Enviar albarán... diff --git a/src/pages/Ticket/Negative/TicketLackFilter.vue b/src/pages/Ticket/Negative/TicketLackFilter.vue index 3762f453d..73d53b247 100644 --- a/src/pages/Ticket/Negative/TicketLackFilter.vue +++ b/src/pages/Ticket/Negative/TicketLackFilter.vue @@ -81,7 +81,7 @@ const setUserParams = (params) => { v-model="params.days" :label="t('negative.days')" dense - is-outlined + filled type="number" @update:model-value=" (value) => { @@ -97,7 +97,7 @@ const setUserParams = (params) => { v-model="params.id" :label="t('negative.id')" dense - is-outlined + filled /> </QItemSection> </QItem> @@ -107,7 +107,7 @@ const setUserParams = (params) => { v-model="params.producer" :label="t('negative.producer')" dense - is-outlined + filled /> </QItemSection> </QItem> @@ -117,7 +117,7 @@ const setUserParams = (params) => { v-model="params.origen" :label="t('negative.origen')" dense - is-outlined + filled /> </QItemSection> </QItem ><QItem> @@ -133,8 +133,7 @@ const setUserParams = (params) => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection ><QItemSection v-else> <QSkeleton class="full-width" type="QSelect" /> @@ -151,8 +150,7 @@ const setUserParams = (params) => { option-label="name" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> diff --git a/src/pages/Ticket/TicketAdvanceFilter.vue b/src/pages/Ticket/TicketAdvanceFilter.vue index 6d5c7726e..f065eaf2e 100644 --- a/src/pages/Ticket/TicketAdvanceFilter.vue +++ b/src/pages/Ticket/TicketAdvanceFilter.vue @@ -71,7 +71,7 @@ onMounted(async () => await getItemPackingTypes()); <VnInputDate v-model="params.dateFuture" :label="t('params.dateFuture')" - is-outlined + filled /> </QItemSection> </QItem> @@ -80,7 +80,7 @@ onMounted(async () => await getItemPackingTypes()); <VnInputDate v-model="params.dateToAdvance" :label="t('params.dateToAdvance')" - is-outlined + filled /> </QItemSection> </QItem> @@ -95,8 +95,7 @@ onMounted(async () => await getItemPackingTypes()); :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled :use-like="false" > </VnSelect> @@ -113,8 +112,7 @@ onMounted(async () => await getItemPackingTypes()); :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled :use-like="false" > </VnSelect> @@ -125,7 +123,7 @@ onMounted(async () => await getItemPackingTypes()); <VnInputNumber v-model="params.scopeDays" :label="t('Days onward')" - is-outlined + filled /> </QItemSection> </QItem> @@ -147,8 +145,7 @@ onMounted(async () => await getItemPackingTypes()); url="Departments" :fields="['id', 'name']" dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -162,8 +159,7 @@ onMounted(async () => await getItemPackingTypes()); option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index f959157f6..b763ef970 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -63,18 +63,10 @@ const getGroupedStates = (data) => { <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.clientFk" - :label="t('Customer ID')" - is-outlined - /> + <VnInput v-model="params.clientFk" :label="t('Customer ID')" filled /> </QItemSection> <QItemSection> - <VnInput - v-model="params.orderFk" - :label="t('Order ID')" - is-outlined - /> + <VnInput v-model="params.orderFk" :label="t('Order ID')" filled /> </QItemSection> </QItem> <QItem> @@ -82,7 +74,7 @@ const getGroupedStates = (data) => { <VnInputDate v-model="params.from" :label="t('From')" - is-outlined + filled data-cy="From_date" /> </QItemSection> @@ -90,7 +82,7 @@ const getGroupedStates = (data) => { <VnInputDate v-model="params.to" :label="t('To')" - is-outlined + filled data-cy="To_date" /> </QItemSection> @@ -98,9 +90,8 @@ const getGroupedStates = (data) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.departmentFk')" v-model="params.departmentFk" option-value="id" @@ -125,8 +116,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -146,19 +136,14 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled sort-by="name ASC" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.refFk" - :label="t('Invoice Ref.')" - is-outlined - /> + <VnInput v-model="params.refFk" :label="t('Invoice Ref.')" filled /> </QItemSection> </QItem> <QItem> @@ -166,17 +151,13 @@ const getGroupedStates = (data) => { <VnInput v-model="params.scopeDays" :label="t('Days onward')" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.nickname" - :label="t('Nickname')" - is-outlined - /> + <VnInput v-model="params.nickname" :label="t('Nickname')" filled /> </QItemSection> </QItem> <QItem> @@ -241,8 +222,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -260,8 +240,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -281,8 +260,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -291,7 +269,7 @@ const getGroupedStates = (data) => { <VnInput v-model="params.collectionFk" :label="t('Collection')" - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue index 64e060a39..033b47f72 100644 --- a/src/pages/Ticket/TicketFutureFilter.vue +++ b/src/pages/Ticket/TicketFutureFilter.vue @@ -73,7 +73,7 @@ onMounted(async () => { <VnInputDate v-model="params.originScopeDays" :label="t('params.originScopeDays')" - is-outlined + filled /> </QItemSection> </QItem> @@ -82,7 +82,7 @@ onMounted(async () => { <VnInputDate v-model="params.futureScopeDays" :label="t('params.futureScopeDays')" - is-outlined + filled /> </QItemSection> </QItem> @@ -91,7 +91,7 @@ onMounted(async () => { <VnInput :label="t('params.litersMax')" v-model="params.litersMax" - is-outlined + filled /> </QItemSection> </QItem> @@ -100,7 +100,7 @@ onMounted(async () => { <VnInput :label="t('params.linesMax')" v-model="params.linesMax" - is-outlined + filled /> </QItemSection> </QItem> @@ -115,8 +115,7 @@ onMounted(async () => { :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -132,8 +131,7 @@ onMounted(async () => { :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -148,8 +146,7 @@ onMounted(async () => { option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -164,8 +161,7 @@ onMounted(async () => { option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -191,8 +187,7 @@ onMounted(async () => { option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index c603246d1..307f34dc2 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -340,25 +340,20 @@ async function makeInvoice(ticket) { }); } -async function sendDocuware(ticket) { - try { - let ticketIds = ticket.map((item) => item.id); +async function sendDocuware(tickets) { + let ticketIds = tickets.map((item) => item.id); - const { data } = await axios.post(`Docuwares/upload`, { - fileCabinet: 'deliveryNote', - ticketIds, - }); + const { data } = await axios.post(`Docuwares/upload-delivery-note`, { + ticketIds, + }); - for (let ticket of ticketIds) { - ticket.stateFk = data.id; - ticket.state = data.name; - ticket.alertLevel = data.alertLevel; - ticket.alertLevelCode = data.code; - } - notify('globals.dataSaved', 'positive'); - } catch (err) { - console.err('err: ', err); + for (let ticket of tickets) { + ticket.stateFk = data.id; + ticket.state = data.name; + ticket.alertLevel = data.alertLevel; + ticket.alertLevelCode = data.code; } + notify('globals.dataSaved', 'positive'); } function openBalanceDialog(ticket) { diff --git a/src/pages/Travel/Card/TravelBasicData.vue b/src/pages/Travel/Card/TravelBasicData.vue index b1adc8126..a6ef8ad19 100644 --- a/src/pages/Travel/Card/TravelBasicData.vue +++ b/src/pages/Travel/Card/TravelBasicData.vue @@ -36,7 +36,7 @@ const warehousesOptionsIn = ref([]); auto-load :filter="{ where: { isDestiny: TRUE } }" /> - <FormModel :url-update="`Travels/${route.params.id}`" model="Travel" auto-load> + <FormModel :url-update="`Travels/${route.params.id}`" model="Travel"> <template #form="{ data }"> <VnRow> <VnInput v-model="data.ref" :label="t('globals.reference')" /> @@ -57,8 +57,8 @@ const warehousesOptionsIn = ref([]); <VnRow> <VnInputDate v-model="data.availabled" - :label="t('travel.summary.availabled')" - /> + :label="t('travel.summary.availabled')" + /> <VnInputTime v-model="data.availabled" :label="t('travel.summary.availabledHour')" @@ -96,6 +96,7 @@ const warehousesOptionsIn = ref([]); </QIcon> </template> </VnInput> + <VnInput v-model="data.awbFk" :label="t('travel.awbFk')" /> </VnRow> <VnRow> <QCheckbox v-model="data.isRaid" :label="t('travel.basicData.isRaid')" /> diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue index 922f89f33..d4903f794 100644 --- a/src/pages/Travel/Card/TravelDescriptor.vue +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -3,7 +3,7 @@ import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue'; @@ -31,7 +31,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Travels/${entityId}`" :title="data.title" :subtitle="data.subtitle" @@ -79,7 +79,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Travel/Card/TravelFilter.js b/src/pages/Travel/Card/TravelFilter.js index 05436834f..0799e449c 100644 --- a/src/pages/Travel/Card/TravelFilter.js +++ b/src/pages/Travel/Card/TravelFilter.js @@ -12,6 +12,7 @@ export default { 'isRaid', 'daysInForward', 'availabled', + 'awbFk', ], include: [ { diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index ae6e695be..76ffdba20 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -87,7 +87,7 @@ warehouses(); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput label="id" v-model="params.id" is-outlined /> + <VnInput label="id" v-model="params.id" filled /> </QItemSection> </QItem> <QItem> @@ -95,7 +95,7 @@ warehouses(); <VnInput :label="t('extraCommunity.filter.reference')" v-model="params.reference" - is-outlined + filled /> </QItemSection> </QItem> @@ -106,8 +106,7 @@ warehouses(); type="number" :label="t('extraCommunity.filter.totalEntries')" dense - outlined - rounded + filled min="0" class="input-number" > @@ -141,8 +140,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -152,7 +150,7 @@ warehouses(); :label="t('extraCommunity.filter.shippedFrom')" v-model="params.shippedFrom" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -162,7 +160,7 @@ warehouses(); :label="t('extraCommunity.filter.landedTo')" v-model="params.landedTo" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -176,8 +174,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -191,8 +188,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -206,8 +202,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -218,8 +213,7 @@ warehouses(); v-model="params.cargoSupplierFk" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -229,8 +223,7 @@ warehouses(); v-model="params.entrySupplierFk" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -245,8 +238,7 @@ warehouses(); :filter-options="['code', 'name']" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index 4a9c80952..a26cc0ec0 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -33,19 +33,14 @@ defineExpose({ states }); </template> <template #body="{ params, searchFn }"> <div class="q-pa-sm q-gutter-y-sm"> - <VnInput - :label="t('travel.Id')" - v-model="params.id" - lazy-rules - is-outlined - > + <VnInput :label="t('travel.Id')" v-model="params.id" lazy-rules filled> <template #prepend> <QIcon name="badge" size="xs" /></template> </VnInput> <VnInput :label="t('travel.ref')" v-model="params.ref" lazy-rules - is-outlined + filled /> <VnSelect :label="t('travel.agency')" @@ -56,8 +51,7 @@ defineExpose({ states }); :use-like="false" option-filter="name" dense - outlined - rounded + filled /> <VnSelect :label="t('travel.warehouseInFk')" @@ -69,22 +63,19 @@ defineExpose({ states }); option-label="name" option-filter="name" dense - outlined - rounded + filled /> <VnInputDate :label="t('travel.shipped')" v-model="params.shipped" @update:model-value="searchFn()" - outlined - rounded + filled /> <VnInputTime v-model="params.shipmentHour" @update:model-value="searchFn()" :label="t('travel.shipmentHour')" - outlined - rounded + filled dense /> <VnSelect @@ -97,36 +88,33 @@ defineExpose({ states }); option-label="name" option-filter="name" dense - outlined - rounded + filled /> <VnInputDate :label="t('travel.landed')" v-model="params.landed" @update:model-value="searchFn()" dense - outlined - rounded + filled /> <VnInputTime v-model="params.landingHour" @update:model-value="searchFn()" :label="t('travel.landingHour')" - outlined - rounded + filled dense /> <VnInput :label="t('travel.totalEntries')" v-model="params.totalEntries" lazy-rules - is-outlined + filled /> <VnInput :label="t('travel.daysOnward')" v-model="params.daysOnward" lazy-rules - is-outlined + filled /> </div> </template> diff --git a/src/pages/Worker/Card/WorkerBasicData.vue b/src/pages/Worker/Card/WorkerBasicData.vue index b8c1c54df..f2a16b7e1 100644 --- a/src/pages/Worker/Card/WorkerBasicData.vue +++ b/src/pages/Worker/Card/WorkerBasicData.vue @@ -1,5 +1,6 @@ <script setup> -import { ref, nextTick } from 'vue'; +import { ref, nextTick, onMounted } from 'vue'; +import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import FetchData from 'components/FetchData.vue'; @@ -17,12 +18,12 @@ const maritalStatus = [ { code: 'M', name: t('Married') }, { code: 'S', name: t('Single') }, ]; -async function setAdvancedSummary(data) { - const advanced = (await useAdvancedSummary('Workers', data.id)) ?? {}; + +onMounted(async () => { + const advanced = await useAdvancedSummary('Workers', useRoute().params.id); Object.assign(form.value.formData, advanced); - await nextTick(); - if (form.value) form.value.hasChanges = false; -} + nextTick(() => (form.value.hasChanges = false)); +}); </script> <template> <FetchData @@ -42,7 +43,6 @@ async function setAdvancedSummary(data) { :url-update="`Workers/${$route.params.id}`" auto-load model="Worker" - @on-fetch="setAdvancedSummary" > <template #form="{ data }"> <VnRow> diff --git a/src/pages/Worker/Card/WorkerCalendarFilter.vue b/src/pages/Worker/Card/WorkerCalendarFilter.vue index 48fc4094b..f0e2d758a 100644 --- a/src/pages/Worker/Card/WorkerCalendarFilter.vue +++ b/src/pages/Worker/Card/WorkerCalendarFilter.vue @@ -40,7 +40,7 @@ watch( (newValue) => { checkHolidays(newValue); }, - { deep: true, immediate: true } + { deep: true, immediate: true }, ); const emit = defineEmits(['update:businessFk', 'update:year', 'update:absenceType']); @@ -174,8 +174,7 @@ const yearList = ref(generateYears()); v-model="selectedYear" :options="yearList" dense - outlined - rounded + filled use-input :is-clearable="false" /> @@ -188,8 +187,7 @@ const yearList = ref(generateYears()); option-value="businessFk" option-label="businessFk" dense - outlined - rounded + filled use-input :is-clearable="false" > diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index 6e3a5e83f..060520e84 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -2,7 +2,7 @@ import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; @@ -52,7 +52,7 @@ const handlePhotoUpdated = (evt = false) => { }; </script> <template> - <CardDescriptor + <EntityDescriptor ref="cardDescriptorRef" :data-key="dataKey" :summary="$props.summary" @@ -167,7 +167,7 @@ const handlePhotoUpdated = (evt = false) => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> <VnChangePassword ref="changePassRef" :submit-fn=" @@ -190,9 +190,3 @@ const handlePhotoUpdated = (evt = false) => { white-space: nowrap; } </style> - -<i18n> -es: - Click to allow the user to be disabled: Marcar para deshabilitar - Click to exclude the user from getting disabled: Marcar para no deshabilitar -</i18n> diff --git a/src/pages/Worker/Card/WorkerDescriptorMenu.vue b/src/pages/Worker/Card/WorkerDescriptorMenu.vue index 0dcb4fd71..3b6b144d6 100644 --- a/src/pages/Worker/Card/WorkerDescriptorMenu.vue +++ b/src/pages/Worker/Card/WorkerDescriptorMenu.vue @@ -63,3 +63,8 @@ const showChangePasswordDialog = () => { </QItemSection> </QItem> </template> +<i18n> + es: + Click to allow the user to be disabled: Marcar para deshabilitar + Click to exclude the user from getting disabled: Marcar para no deshabilitar + </i18n> diff --git a/src/pages/Worker/Card/WorkerPda.vue b/src/pages/Worker/Card/WorkerPda.vue index d32941494..001eb368a 100644 --- a/src/pages/Worker/Card/WorkerPda.vue +++ b/src/pages/Worker/Card/WorkerPda.vue @@ -5,24 +5,25 @@ import { ref, computed } from 'vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import { useVnConfirm } from 'composables/useVnConfirm'; +import { useArrayData } from 'src/composables/useArrayData'; +import { downloadDocuware } from 'src/composables/downloadFile'; + import FetchData from 'components/FetchData.vue'; import FormModelPopup from 'src/components/FormModelPopup.vue'; -import { useVnConfirm } from 'composables/useVnConfirm'; - -import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -import VnInput from 'src/components/common/VnInput.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; const { t } = useI18n(); const { notify } = useNotify(); - -const paginate = ref(); +const loadingDocuware = ref(true); +const tableRef = ref(); const dialog = ref(); const route = useRoute(); const { openConfirmationModal } = useVnConfirm(); const routeId = computed(() => route.params.id); - +const worker = computed(() => useArrayData('Worker').store.data); const initialData = computed(() => { return { userFk: routeId.value, @@ -31,154 +32,268 @@ const initialData = computed(() => { }; }); -const deallocatePDA = async (deviceProductionFk) => { - await axios.post(`Workers/${route.params.id}/deallocatePDA`, { - pda: deviceProductionFk, - }); - notify(t('PDA deallocated'), 'positive'); - - paginate.value.fetch(); -}; +const columns = computed(() => [ + { + align: 'center', + label: t('globals.state'), + name: 'state', + format: (row) => row?.docuware?.state, + columnFilter: false, + chip: { + condition: (_, row) => !!row.docuware, + color: (row) => (isSigned(row) ? 'bg-positive' : 'bg-warning'), + }, + visible: false, + }, + { + align: 'right', + label: t('worker.pda.currentPDA'), + name: 'deviceProductionFk', + columnClass: 'shrink', + cardVisible: true, + }, + { + align: 'left', + label: t('Model'), + name: 'modelFk', + format: ({ deviceProduction }) => deviceProduction.modelFk, + cardVisible: true, + }, + { + align: 'right', + label: t('Serial number'), + name: 'serialNumber', + format: ({ deviceProduction }) => deviceProduction.serialNumber, + cardVisible: true, + }, + { + align: 'left', + label: t('Current SIM'), + name: 'simFk', + cardVisible: true, + }, + { + align: 'right', + name: 'actions', + columnFilter: false, + cardVisible: true, + }, +]); function reloadData() { initialData.value.deviceProductionFk = null; initialData.value.simFk = null; - paginate.value.fetch(); + tableRef.value.reload(); +} + +async function fetchDocuware() { + loadingDocuware.value = true; + + const id = `${worker.value?.lastName} ${worker.value?.firstName}`; + const rows = tableRef.value.CrudModelRef.formData; + + const promises = rows.map(async (row) => { + const { data } = await axios.post(`Docuwares/${id}/checkFile`, { + fileCabinet: 'hr', + signed: false, + mergeFilter: [ + { + DBName: 'TIPO_DOCUMENTO', + Value: ['PDA'], + }, + { + DBName: 'FILENAME', + Value: [`${row.deviceProductionFk}-pda`], + }, + ], + }); + row.docuware = data; + }); + + await Promise.allSettled(promises); + loadingDocuware.value = false; +} + +async function sendToTablet(rows) { + const promises = rows.map(async (row) => { + await axios.post(`Docuwares/upload-pda-pdf`, { + ids: [row.deviceProductionFk], + }); + row.docuware = true; + }); + await Promise.allSettled(promises); + notify(t('PDF sended to signed'), 'positive'); + tableRef.value.reload(); +} + +async function deallocatePDA(deviceProductionFk) { + await axios.post(`Workers/${route.params.id}/deallocatePDA`, { + pda: deviceProductionFk, + }); + const index = tableRef.value.CrudModelRef.formData.findIndex( + (data) => data?.deviceProductionFk == deviceProductionFk, + ); + delete tableRef.value.CrudModelRef.formData[index]; + notify(t('PDA deallocated'), 'positive'); +} + +function isSigned(row) { + return row.docuware?.state === 'Firmado'; } </script> <template> - <QPage class="column items-center q-pa-md centerCard"> - <FetchData - url="workers/getAvailablePda" - @on-fetch="(data) => (deviceProductions = data)" - auto-load - /> - <VnPaginate - ref="paginate" - data-key="WorkerPda" - url="DeviceProductionUsers" - :user-filter="{ where: { userFk: routeId } }" - order="id" - search-url="pda" - auto-load - > - <template #body="{ rows }"> - <QCard - flat - bordered - :key="row.id" - v-for="row of rows" - class="card q-px-md q-mb-sm container" - > - <VnRow> - <VnInput - :label="t('worker.pda.currentPDA')" - :model-value="row?.deviceProductionFk" - disable - /> - <VnInput - :label="t('Model')" - :model-value="row?.deviceProduction?.modelFk" - disable - /> - <VnInput - :label="t('Serial number')" - :model-value="row?.deviceProduction?.serialNumber" - disable - /> - <VnInput - :label="t('Current SIM')" - :model-value="row?.simFk" - disable - /> - <QBtn - flat - icon="delete" - color="primary" - class="btn-delete" - @click=" - openConfirmationModal( - t(`Remove PDA`), - t('Do you want to remove this PDA?'), - () => deallocatePDA(row.deviceProductionFk), - ) - " - > - <QTooltip> - {{ t('worker.pda.removePDA') }} - </QTooltip> - </QBtn> - </VnRow> - </QCard> - </template> - </VnPaginate> - <QPageSticky :offset="[18, 18]"> + <FetchData + url="workers/getAvailablePda" + @on-fetch="(data) => (deviceProductions = data)" + auto-load + /> + <VnTable + ref="tableRef" + data-key="WorkerPda" + url="DeviceProductionUsers" + :user-filter="{ order: 'id' }" + :filter="{ where: { userFk: routeId } }" + search-url="pda" + auto-load + :columns="columns" + @onFetch="fetchDocuware" + :hasSubToolbar="true" + :default-remove="false" + :default-reset="false" + :default-save="false" + :table="{ + 'row-key': 'deviceProductionFk', + selection: 'multiple', + }" + :table-filter="{ hiddenTags: ['userFk'] }" + > + <template #moreBeforeActions> <QBtn - @click.stop="dialog.show()" + :label="t('globals.refresh')" + icon="refresh" + @click="tableRef.reload()" + /> + <QBtn + :disable="!tableRef?.selected?.length" + :label="t('globals.send')" + icon="install_mobile" + @click="sendToTablet(tableRef?.selected)" + class="bg-primary" + /> + </template> + <template #column-actions="{ row }"> + <QBtn + flat + icon="delete" color="primary" - fab - icon="add" - v-shortcut="'+'" + @click=" + openConfirmationModal( + t(`Remove PDA`), + t('Do you want to remove this PDA?'), + () => deallocatePDA(row.deviceProductionFk), + ) + " + data-cy="workerPda-remove" > - <QDialog ref="dialog"> - <FormModelPopup - :title="t('Add new device')" - url-create="DeviceProductionUsers" - model="DeviceProductionUser" - :form-initial-data="initialData" - @on-data-saved="reloadData()" - > - <template #form-inputs="{ data }"> - <VnRow> - <VnSelect - :label="t('worker.pda.newPDA')" - v-model="data.deviceProductionFk" - :options="deviceProductions" - option-label="id" - option-value="id" - id="deviceProductionFk" - hide-selected - data-cy="pda-dialog-select" - :required="true" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel - >ID: {{ scope.opt?.id }}</QItemLabel - > - <QItemLabel caption> - {{ scope.opt?.modelFk }}, - {{ scope.opt?.serialNumber }} - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - <VnInput - v-model="data.simFk" - :label="t('SIM serial number')" - id="simSerialNumber" - use-input - /> - </VnRow> - </template> - </FormModelPopup> - </QDialog> + <QTooltip> + {{ t('worker.pda.removePDA') }} + </QTooltip> </QBtn> - <QTooltip> - {{ t('globals.new') }} - </QTooltip> - </QPageSticky> - </QPage> + <QBtn + v-if="!isSigned(row)" + :loading="loadingDocuware" + icon="install_mobile" + flat + color="primary" + @click=" + openConfirmationModal( + t('Sign PDA'), + t('Are you sure you want to send it?'), + () => sendToTablet([row]), + ) + " + data-cy="workerPda-send" + > + <QTooltip> + {{ t('worker.pda.sendToTablet') }} + </QTooltip> + </QBtn> + <QBtn + v-if="isSigned(row)" + :loading="loadingDocuware" + icon="cloud_download" + flat + color="primary" + @click=" + downloadDocuware('Docuwares/download-pda-pdf', { + file: row.deviceProductionFk + '-pda', + worker: worker?.lastName + ' ' + worker?.firstName, + }) + " + data-cy="workerPda-download" + > + <QTooltip> + {{ t('worker.pda.download') }} + </QTooltip> + </QBtn> + </template> + </VnTable> + <QPageSticky :offset="[18, 18]"> + <QBtn @click.stop="dialog.show()" color="primary" fab icon="add" v-shortcut="'+'"> + <QDialog ref="dialog"> + <FormModelPopup + :title="t('Add new device')" + url-create="DeviceProductionUsers" + model="DeviceProductionUser" + :form-initial-data="initialData" + @on-data-saved="reloadData()" + > + <template #form-inputs="{ data }"> + <VnRow> + <VnSelect + :label="t('PDA')" + v-model="data.deviceProductionFk" + :options="deviceProductions" + option-label="modelFk" + option-value="id" + id="deviceProductionFk" + hide-selected + data-cy="pda-dialog-select" + :required="true" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel + >ID: {{ scope.opt?.id }}</QItemLabel + > + <QItemLabel caption> + {{ scope.opt?.modelFk }}, + {{ scope.opt?.serialNumber }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + <VnSelect + url="Sims" + option-label="line" + option-value="code" + v-model="data.simFk" + :label="t('SIM serial number')" + id="simSerialNumber" + /> + </VnRow> + </template> + </FormModelPopup> + </QDialog> + </QBtn> + <QTooltip> + {{ t('globals.new') }} + </QTooltip> + </QPageSticky> </template> -<style lang="scss" scoped> -.btn-delete { - max-width: 4%; - margin-top: 30px; -} -</style> <i18n> es: Model: Modelo @@ -190,4 +305,6 @@ es: Do you want to remove this PDA?: ÂżDesea eliminar este PDA? You can only have one PDA: Solo puedes tener un PDA si no eres autonomo This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario + Are you sure you want to send it?: ÂżSeguro que quieres enviarlo? + Sign PDA: Firmar PDA </i18n> diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index c486a00c2..ad78a3fb9 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -39,6 +39,7 @@ onBeforeMount(async () => { url="Workers/summary" :user-filter="{ where: { id: entityId } }" data-key="Worker" + module-name="Worker" > <template #header="{ entity }"> <div>{{ entity.id }} - {{ entity.firstName }} {{ entity.lastName }}</div> diff --git a/src/pages/Worker/Department/Card/DepartmentDescriptor.vue b/src/pages/Worker/Department/Card/DepartmentDescriptor.vue index 4b7dfd9b8..820658593 100644 --- a/src/pages/Worker/Department/Card/DepartmentDescriptor.vue +++ b/src/pages/Worker/Department/Card/DepartmentDescriptor.vue @@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -40,7 +40,7 @@ const removeDepartment = async () => { const { openConfirmationModal } = useVnConfirm(); </script> <template> - <CardDescriptor + <EntityDescriptor ref="DepartmentDescriptorRef" :url="`Departments/${entityId}`" :summary="$props.summary" @@ -95,7 +95,7 @@ const { openConfirmationModal } = useVnConfirm(); </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue b/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue index 5b556f655..c793e9319 100644 --- a/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue +++ b/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue @@ -16,6 +16,7 @@ const $props = defineProps({ v-if="$props.id" :id="$props.id" :summary="DepartmentSummary" + data-key="DepartmentDescriptorProxy" /> </QPopupProxy> </template> diff --git a/src/pages/Worker/WorkerFilter.vue b/src/pages/Worker/WorkerFilter.vue index 8210ba0e3..44dfd32b4 100644 --- a/src/pages/Worker/WorkerFilter.vue +++ b/src/pages/Worker/WorkerFilter.vue @@ -35,7 +35,7 @@ const getLocale = (label) => { <template #body="{ params }"> <QItem> <QItemSection> - <VnInput :label="t('FI')" v-model="params.fi" is-outlined + <VnInput :label="t('FI')" v-model="params.fi" filled ><template #prepend> <QIcon name="badge" size="xs"></QIcon> </template ></VnInput> @@ -43,29 +43,17 @@ const getLocale = (label) => { </QItem> <QItem> <QItemSection> - <VnInput - :label="t('First Name')" - v-model="params.firstName" - is-outlined - /> + <VnInput :label="t('First Name')" v-model="params.firstName" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('Last Name')" - v-model="params.lastName" - is-outlined - /> + <VnInput :label="t('Last Name')" v-model="params.lastName" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('User Name')" - v-model="params.userName" - is-outlined - /> + <VnInput :label="t('User Name')" v-model="params.userName" filled /> </QItemSection> </QItem> <QItem> @@ -79,23 +67,18 @@ const getLocale = (label) => { emit-value map-options dense - outlined - rounded + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput :label="t('Email')" v-model="params.email" is-outlined /> + <VnInput :label="t('Email')" v-model="params.email" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('Extension')" - v-model="params.extension" - is-outlined - /> + <VnInput :label="t('Extension')" v-model="params.extension" filled /> </QItemSection> </QItem> <QItem> diff --git a/src/pages/Zone/Card/ZoneCard.vue b/src/pages/Zone/Card/ZoneCard.vue index 2ce4193a0..80b209fe3 100644 --- a/src/pages/Zone/Card/ZoneCard.vue +++ b/src/pages/Zone/Card/ZoneCard.vue @@ -1,7 +1,15 @@ <script setup> import VnCard from 'src/components/common/VnCard.vue'; import ZoneDescriptor from './ZoneDescriptor.vue'; +import filter from 'src/pages/Zone/Card/ZoneFilter.js'; +import { useRoute } from 'vue-router'; +const route = useRoute(); </script> <template> - <VnCard data-key="Zone" url="Zones" :descriptor="ZoneDescriptor" /> + <VnCard + data-key="Zone" + :url="`Zones/${route.params.id}`" + :descriptor="ZoneDescriptor" + :filter="filter" + /> </template> diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue index 27676212e..f2bcc1247 100644 --- a/src/pages/Zone/Card/ZoneDescriptor.vue +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toTimeFormat } from 'src/filters/date'; import { toCurrency } from 'filters/index'; @@ -25,7 +25,7 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> + <EntityDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> <template #menu="{ entity }"> <ZoneDescriptorMenuItems :zone="entity" /> </template> @@ -36,5 +36,5 @@ const entityId = computed(() => { <VnLv :label="$t('list.price')" :value="toCurrency(entity.price)" /> <VnLv :label="$t('zone.bonus')" :value="toCurrency(entity.bonus)" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Zone/Card/ZoneEventExclusionForm.vue b/src/pages/Zone/Card/ZoneEventExclusionForm.vue index 3828c998f..582a8bbad 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 fb552bb93..8b02c2d84 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 08b99df60..add9f6f5b 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 91d2cc7eb..1ef687b3f 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/ZoneDeliveryPanel.vue b/src/pages/Zone/ZoneDeliveryPanel.vue index a8cb05afc..fc5c04b41 100644 --- a/src/pages/Zone/ZoneDeliveryPanel.vue +++ b/src/pages/Zone/ZoneDeliveryPanel.vue @@ -95,8 +95,7 @@ watch( :filter-options="['code']" hide-selected dense - outlined - rounded + filled map-key="geoFk" data-cy="ZoneDeliveryDaysPostcodeSelect" > @@ -128,8 +127,7 @@ watch( option-label="name" hide-selected dense - outlined - rounded + filled data-cy="ZoneDeliveryDaysAgencySelect" /> <VnSelect @@ -144,7 +142,6 @@ watch( option-label="name" hide-selected dense - outlined rounded /> </div> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index 869b0c12c..8d7c4a165 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 22f4b1ae6..f46a98ee6 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 777bc1c03..7a23bdc02 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/router/modules/entry.js b/src/router/modules/entry.js index b5656dc5f..02eea8c6c 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -81,7 +81,7 @@ export default { keyBinding: 'e', menu: [ 'EntryList', - 'MyEntries', + 'EntrySupplier', 'EntryLatestBuys', 'EntryStockBought', 'EntryWasteRecalc', @@ -125,21 +125,12 @@ export default { }, { path: 'my', - name: 'MyEntries', + name: 'EntrySupplier', meta: { title: 'labeler', icon: 'sell', }, - component: () => import('src/pages/Entry/MyEntries.vue'), - }, - { - path: 'latest-buys', - name: 'EntryLatestBuys', - meta: { - title: 'latestBuys', - icon: 'contact_support', - }, - component: () => import('src/pages/Entry/EntryLatestBuys.vue'), + component: () => import('src/pages/Entry/EntrySupplier.vue'), }, { path: 'stock-Bought', diff --git a/src/router/modules/monitor.js b/src/router/modules/monitor.js index 89ba4078f..3f30ace72 100644 --- a/src/router/modules/monitor.js +++ b/src/router/modules/monitor.js @@ -8,13 +8,10 @@ export default { icon: 'grid_view', moduleName: 'Monitor', keyBinding: 'm', + menu: ['MonitorTickets', 'MonitorClientsActions'], }, component: RouterView, redirect: { name: 'MonitorMain' }, - menus: { - main: ['MonitorTickets', 'MonitorClientsActions'], - card: [], - }, children: [ { path: '', diff --git a/src/router/modules/route.js b/src/router/modules/route.js index c84795a98..62765a49c 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -220,6 +220,7 @@ export default { path: '', name: 'RouteIndexMain', redirect: { name: 'RouteList' }, + component: () => import('src/pages/Route/RouteList.vue'), children: [ { name: 'RouteList', @@ -228,7 +229,6 @@ export default { title: 'list', icon: 'view_list', }, - component: () => import('src/pages/Route/RouteList.vue'), }, routeCard, ], @@ -264,6 +264,7 @@ export default { path: 'roadmap', name: 'RouteRoadmap', redirect: { name: 'RoadmapList' }, + component: () => import('src/pages/Route/RouteRoadmap.vue'), meta: { title: 'RouteRoadmap', icon: 'vn:troncales', @@ -276,7 +277,6 @@ export default { title: 'list', icon: 'view_list', }, - component: () => import('src/pages/Route/RouteRoadmap.vue'), }, roadmapCard, ], @@ -294,6 +294,7 @@ export default { path: 'agency', name: 'RouteAgency', redirect: { name: 'AgencyList' }, + component: () => import('src/pages/Route/Agency/AgencyList.vue'), meta: { title: 'agency', icon: 'garage_home', @@ -306,8 +307,6 @@ export default { title: 'list', icon: 'view_list', }, - component: () => - import('src/pages/Route/Agency/AgencyList.vue'), }, agencyCard, ], @@ -316,6 +315,7 @@ export default { path: 'vehicle', name: 'RouteVehicle', redirect: { name: 'VehicleList' }, + component: () => import('src/pages/Route/Vehicle/VehicleList.vue'), meta: { title: 'vehicle', icon: 'directions_car', @@ -328,8 +328,6 @@ export default { title: 'vehicleList', icon: 'directions_car', }, - component: () => - import('src/pages/Route/Vehicle/VehicleList.vue'), }, vehicleCard, ], diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index 3eb95a96e..ff3d483cf 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -271,12 +271,14 @@ export default { path: 'department', name: 'Department', redirect: { name: 'WorkerDepartment' }, - component: () => import('src/pages/Worker/WorkerDepartment.vue'), + meta: { title: 'department', icon: 'vn:greuge' }, children: [ { + component: () => + import('src/pages/Worker/WorkerDepartment.vue'), + meta: { title: 'department', icon: 'vn:greuge' }, name: 'WorkerDepartment', path: 'list', - meta: { title: 'department', icon: 'vn:greuge' }, }, departmentCard, ], diff --git a/src/stores/__tests__/useDescriptorStore.spec.js b/src/stores/__tests__/useDescriptorStore.spec.js new file mode 100644 index 000000000..61aab8d14 --- /dev/null +++ b/src/stores/__tests__/useDescriptorStore.spec.js @@ -0,0 +1,28 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import 'app/test/vitest/helper'; + +import { useDescriptorStore } from 'src/stores/useDescriptorStore'; +import { useStateStore } from 'stores/useStateStore'; + +describe('useDescriptorStore', () => { + const { get, has } = useDescriptorStore(); + const stateStore = useStateStore(); + + beforeEach(() => { + stateStore.setDescriptors({}); + }); + + function getDescriptors() { + return stateStore.descriptors; + } + + it('should get descriptors in stateStore', async () => { + expect(Object.keys(getDescriptors()).length).toBe(0); + get(); + expect(Object.keys(getDescriptors()).length).toBeGreaterThan(0); + }); + + it('should find ticketDescriptor if search ticketFk', async () => { + expect(has('ticketFk')).toBeDefined(); + }); +}); diff --git a/src/stores/useDescriptorStore.js b/src/stores/useDescriptorStore.js new file mode 100644 index 000000000..a5b83a42e --- /dev/null +++ b/src/stores/useDescriptorStore.js @@ -0,0 +1,33 @@ +import { defineAsyncComponent } from 'vue'; +import { defineStore } from 'pinia'; +import { useStateStore } from 'stores/useStateStore'; + +export const useDescriptorStore = defineStore('descriptorStore', () => { + const { descriptors, setDescriptors } = useStateStore(); + function get() { + if (Object.keys(descriptors).length) return descriptors; + + const currentDescriptors = {}; + const files = import.meta.glob(`/src/**/*DescriptorProxy.vue`); + const moduleParser = { + account: 'user', + client: 'customer', + }; + for (const file in files) { + const name = file.split('/').at(-1).slice(0, -19).toLowerCase(); + const descriptor = moduleParser[name] ?? name; + currentDescriptors[descriptor + 'Fk'] = defineAsyncComponent(files[file]); + } + setDescriptors(currentDescriptors); + return currentDescriptors; + } + + function has(name) { + return get()[name]; + } + + return { + has, + get, + }; +}); diff --git a/src/stores/useStateStore.js b/src/stores/useStateStore.js index ca447bc11..44fa133d0 100644 --- a/src/stores/useStateStore.js +++ b/src/stores/useStateStore.js @@ -8,6 +8,7 @@ export const useStateStore = defineStore('stateStore', () => { const rightAdvancedDrawer = ref(false); const subToolbar = ref(false); const cardDescriptor = ref(null); + const descriptors = ref({}); function cardDescriptorChangeValue(descriptor) { cardDescriptor.value = descriptor; @@ -52,6 +53,10 @@ export const useStateStore = defineStore('stateStore', () => { return subToolbar.value; } + function setDescriptors(value) { + descriptors.value = value; + } + return { cardDescriptor, cardDescriptorChangeValue, @@ -68,5 +73,7 @@ export const useStateStore = defineStore('stateStore', () => { isSubToolbarShown, toggleSubToolbar, rightDrawerChangeValue, + descriptors, + setDescriptors, }; }); diff --git a/src/stores/useWeekdayStore.js b/src/stores/useWeekdayStore.js index 57a302dc1..bf6b2704d 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 a106d0e8a..050dd396c 100644 --- a/test/cypress/integration/Order/orderCatalog.spec.js +++ b/test/cypress/integration/Order/orderCatalog.spec.js @@ -34,7 +34,7 @@ describe('OrderCatalog', () => { searchByCustomTagInput('Silver'); }); - it('filters by custom value dialog', () => { + it.skip('filters by custom value dialog', () => { Cypress.on('uncaught:exception', (err) => { if (err.message.includes('canceled')) { return false; @@ -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/claim/claimDevelopment.spec.js b/test/cypress/integration/claim/claimDevelopment.spec.js index 05ee7f0b8..097d870df 100755 --- a/test/cypress/integration/claim/claimDevelopment.spec.js +++ b/test/cypress/integration/claim/claimDevelopment.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('ClaimDevelopment', () => { +describe.skip('ClaimDevelopment', () => { const claimId = 1; const firstLineReason = 'tbody > :nth-child(1) > :nth-child(2)'; const thirdRow = 'tbody > :nth-child(3)'; @@ -19,11 +19,10 @@ describe('ClaimDevelopment', () => { cy.getValue(firstLineReason).should('equal', lastReason); }); - it('should edit line', () => { + it.skip('should edit line', () => { cy.selectOption(firstLineReason, newReason); cy.saveCard(); - cy.login('developer'); cy.visit(`/#/claim/${claimId}/development`); cy.getValue(firstLineReason).should('equal', newReason); @@ -49,12 +48,9 @@ describe('ClaimDevelopment', () => { cy.fillRow(thirdRow, rowData); cy.saveCard(); - cy.login('developer'); - cy.visit(`/#/claim/${claimId}/development`); - cy.validateRow(thirdRow, rowData); - cy.reload(); + cy.visit(`/#/claim/${claimId}/development`); cy.validateRow(thirdRow, rowData); //remove row @@ -63,7 +59,7 @@ describe('ClaimDevelopment', () => { cy.clickConfirm(); cy.get(thirdRow).should('not.exist'); - cy.reload(); + cy.visit(`/#/claim/${claimId}/development`); cy.get(thirdRow).should('not.exist'); }); }); diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js index 467c6c37d..caf94b8bd 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/entry/commands.js b/test/cypress/integration/entry/commands.js new file mode 100644 index 000000000..7c96a5440 --- /dev/null +++ b/test/cypress/integration/entry/commands.js @@ -0,0 +1,21 @@ +Cypress.Commands.add('selectTravel', (warehouse = '1') => { + cy.get('i[data-cy="Travel_icon"]').click(); + cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('button[data-cy="save-filter-travel-form"]').click(); + cy.get('tr').eq(1).click(); +}); + +Cypress.Commands.add('deleteEntry', () => { + cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); + cy.waitForElement('div[data-cy="delete-entry"]').click(); + cy.url().should('include', 'list'); +}); + +Cypress.Commands.add('createEntry', () => { + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + cy.selectTravel('one'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + cy.url().should('include', 'summary'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); +}); diff --git a/test/cypress/integration/entry/entryCard/entryBasicData.spec.js b/test/cypress/integration/entry/entryCard/entryBasicData.spec.js new file mode 100644 index 000000000..ba689b8c7 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryBasicData.spec.js @@ -0,0 +1,19 @@ +import '../commands.js'; + +describe('EntryBasicData', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Change Travel', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); + cy.selectTravel('two'); + cy.saveCard(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryBuys.spec.js b/test/cypress/integration/entry/entryCard/entryBuys.spec.js new file mode 100644 index 000000000..b5e185a8e --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryBuys.spec.js @@ -0,0 +1,101 @@ +import '../commands.js'; +describe('EntryBuys', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Edit buys and use toolbar actions', () => { + const COLORS = { + negative: 'rgb(251, 82, 82)', + positive: 'rgb(200, 228, 132)', + enabled: 'rgb(255, 255, 255)', + disable: 'rgb(168, 168, 168)', + }; + + const selectCell = (field, row = 0) => + cy.get(`td[data-col-field="${field}"][data-row-index="${row}"]`); + const selectSpan = (field, row = 0) => selectCell(field, row).find('div > span'); + const selectButton = (cySelector) => cy.get(`button[data-cy="${cySelector}"]`); + const clickAndType = (field, value, row = 0) => { + selectCell(field, row).click().type(`${value}{esc}`); + }; + const checkText = (field, expectedText, row = 0) => + selectCell(field, row).should('have.text', expectedText); + const checkColor = (field, expectedColor, row = 0) => + selectSpan(field, row).should('have.css', 'color', expectedColor); + + cy.createEntry(); + createBuy(); + + selectCell('isIgnored').click().click().type('{esc}'); + checkText('isIgnored', 'close'); + + clickAndType('stickers', '1'); + checkText('stickers', '0/01'); + checkText('quantity', '1'); + checkText('amount', '50.00'); + clickAndType('packing', '2'); + checkText('packing', '12'); + checkText('weight', '12.0'); + checkText('quantity', '12'); + checkText('amount', '600.00'); + checkColor('packing', COLORS.enabled); + + selectCell('groupingMode').click().click().click(); + checkColor('packing', COLORS.disable); + checkColor('grouping', COLORS.enabled); + + selectCell('buyingValue').click().clear().type('{backspace}{backspace}1'); + checkText('amount', '12.00'); + checkColor('minPrice', COLORS.disable); + + selectCell('hasMinPrice').click().click(); + checkColor('minPrice', COLORS.enabled); + selectCell('hasMinPrice').click(); + + cy.saveCard(); + cy.get('span[data-cy="footer-stickers"]').should('have.text', '1'); + cy.get('.q-notification__message').contains('Data saved'); + + selectButton('change-quantity-sign').should('be.disabled'); + selectButton('check-buy-amount').should('be.disabled'); + cy.get('tr.cursor-pointer > .q-table--col-auto-width > .q-checkbox').click(); + selectButton('change-quantity-sign').should('be.enabled'); + selectButton('check-buy-amount').should('be.enabled'); + + selectButton('change-quantity-sign').click(); + selectButton('set-negative-quantity').click(); + checkText('quantity', '-12'); + selectButton('set-positive-quantity').click(); + checkText('quantity', '12'); + checkColor('amount', COLORS.disable); + + selectButton('check-buy-amount').click(); + selectButton('uncheck-amount').click(); + checkColor('amount', COLORS.disable); + + selectButton('check-amount').click(); + checkColor('amount', COLORS.positive); + cy.saveCard(); + + cy.get('tbody > tr [tabindex="0"][role="checkbox"]').click(); + cy.dataCy('transferBuys').should('be.enabled').click(); + cy.dataCy('entryDestinyInput').should('be.visible').type('100'); + cy.dataCy('transferBuysBtn').click(); + + cy.deleteEntry(); + }); + + function createBuy() { + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get('a[data-cy="EntryBuys-menu-item"]').click(); + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + + cy.get('input[data-cy="itemFk-create-popup"]').type('1'); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + } +}); diff --git a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js new file mode 100644 index 000000000..554471008 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js @@ -0,0 +1,44 @@ +import '../commands.js'; +describe('EntryDescriptor', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Clone entry and recalculate rates', () => { + cy.createEntry(); + + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.url().then((previousUrl) => { + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); + + cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); + + cy.url() + .should('not.eq', previousUrl) + .then(() => { + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="recalculate-rates"]').click(); + + cy.get('.q-notification__message') + .eq(2) + .should('have.text', 'Entry prices recalculated'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.deleteEntry(); + + cy.log(previousUrl); + + cy.visit(previousUrl); + + cy.waitForElement('[data-cy="entry-buys"]'); + cy.deleteEntry(); + }); + }); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryDms.spec.js b/test/cypress/integration/entry/entryCard/entryDms.spec.js new file mode 100644 index 000000000..f3f0ef20b --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryDms.spec.js @@ -0,0 +1,22 @@ +import '../commands.js'; +describe('EntryDms', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('should create edit and remove new dms', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.dataCy('EntryDms-menu-item').click(); + cy.dataCy('addButton').click(); + cy.dataCy('attachFile').click(); + cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { + force: true, + }); + cy.dataCy('FormModelPopup_save').click(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryLock.spec.js b/test/cypress/integration/entry/entryCard/entryLock.spec.js new file mode 100644 index 000000000..6ba4392ae --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryLock.spec.js @@ -0,0 +1,44 @@ +import '../commands.js'; +describe('EntryLock', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Should notify when entry is lock by another user', () => { + const checkLockMessage = () => { + cy.get('[role="dialog"]').should('be.visible'); + cy.get('[data-cy="VnConfirm_message"] > span').should( + 'contain.text', + 'This entry has been locked by buyerNick', + ); + }; + + cy.createEntry(); + goToEntryBuys(); + cy.get('.q-notification__message') + .eq(1) + .should('have.text', 'The entry has been locked successfully'); + + cy.login('logistic'); + cy.reload(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_cancel"]').click(); + cy.url().should('include', 'summary'); + + goToEntryBuys(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_confirm"]').click(); + cy.url().should('include', 'buys'); + + cy.deleteEntry(); + + function goToEntryBuys() { + const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; + cy.get(entryBuySelector).should('be.visible'); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get(entryBuySelector).click(); + } + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryNotes.spec.js b/test/cypress/integration/entry/entryCard/entryNotes.spec.js new file mode 100644 index 000000000..544ac23b0 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryNotes.spec.js @@ -0,0 +1,50 @@ +import '../commands.js'; + +describe('EntryNotes', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + const createObservation = (type, description) => { + cy.dataCy('vnTableCreateBtn').click(); + cy.dataCy('Observation type_select').eq(1).should('be.visible').type(type); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.dataCy('Description_input').should('be.visible').type(description); + cy.dataCy('FormModelPopup_save').should('be.enabled').click(); + }; + + const editObservation = (rowIndex, type, description) => { + cy.get(`td[data-col-field="description"][data-row-index="${rowIndex}"]`) + .click() + .clear() + .type(description); + cy.get(`td[data-col-field="observationTypeFk"][data-row-index="${rowIndex}"]`) + .click() + .clear() + .type(type); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.saveCard(); + }; + + it('Create, delete, and edit observations', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.dataCy('EntryNotes-menu-item').click(); + + createObservation('Packager', 'test'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + + editObservation(0, 'Administrative', 'test2'); + + createObservation('Administrative', 'test'); + cy.get('.q-notification__message') + .eq(2) + .should('have.text', "The observation type can't be repeated"); + cy.dataCy('FormModelPopup_cancel').click(); + + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryDms.spec.js b/test/cypress/integration/entry/entryDms.spec.js deleted file mode 100644 index 47dcdba9e..000000000 --- a/test/cypress/integration/entry/entryDms.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -describe('EntryDms', () => { - const entryId = 1; - - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('developer'); - cy.visit(`/#/entry/${entryId}/dms`); - }); - - it('should create edit and remove new dms', () => { - cy.addRow(); - cy.get('.icon-attach').click(); - cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { - force: true, - }); - - cy.get('tbody > tr').then((value) => { - const u = undefined; - - //Create and check if exist new row - let newFileTd = Cypress.$(value).length; - cy.get('.q-btn--standard > .q-btn__content > .block').click(); - expect(value).to.have.length(newFileTd++); - const newRowSelector = `tbody > :nth-child(${newFileTd})`; - cy.waitForElement(newRowSelector); - cy.validateRow(newRowSelector, [u, u, u, u, u, 'ENTRADA ID 1']); - - //Edit new dms - const newDescription = 'entry id 1 modified'; - const textAreaSelector = - '.q-textarea > .q-field__inner > .q-field__control > .q-field__control-container'; - cy.get( - `tbody :nth-child(${newFileTd}) > .text-right > .no-wrap > :nth-child(2) > .q-btn > .q-btn__content > .q-icon` - ).click(); - - cy.get(textAreaSelector).clear(); - cy.get(textAreaSelector).type(newDescription); - cy.saveCard(); - cy.reload(); - - cy.validateRow(newRowSelector, [u, u, u, u, u, newDescription]); - }); - }); -}); diff --git a/test/cypress/integration/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js index 4c4c4f004..990f74261 100644 --- a/test/cypress/integration/entry/entryList.spec.js +++ b/test/cypress/integration/entry/entryList.spec.js @@ -1,223 +1,54 @@ -describe('Entry', () => { +import './commands'; + +describe('EntryList', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('buyer'); cy.visit(`/#/entry/list`); }); - it('Filter deleted entries and other fields', () => { - createEntry(); + it('View popup summary', () => { + cy.createEntry(); cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); cy.waitForElement('[data-cy="entry-buys"]'); - deleteEntry(); + cy.deleteEntry(); cy.typeSearchbar('{enter}'); - cy.get('span[title="Date"]').click().click(); - cy.typeSearchbar('{enter}'); - cy.url().should('include', 'order'); - cy.get('td[data-row-index="0"][data-col-field="landed"]').should( - 'have.text', - '-', - ); + cy.get('button[title="Summary"]').eq(1).should('be.visible').click(); + cy.dataCy('entry-summary').should('be.visible'); }); - it.skip('Create entry, modify travel and add buys', () => { - createEntryAndBuy(); - cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); - selectTravel('two'); - cy.saveCard(); - cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); - deleteEntry(); + it('Show supplierDescriptor on click on supplierDescriptor', () => { + cy.typeSearchbar('{enter}'); + cy.get('td[data-col-field="supplierFk"] > div > span').eq(0).click(); + cy.get('div[role="menu"] > div[class="descriptor"]').should('be.visible'); }); - it('Clone entry and recalculate rates', () => { - createEntry(); + it('Landed badge should display the right color', () => { + cy.typeSearchbar('{enter}'); - cy.waitForElement('[data-cy="entry-buys"]'); - - cy.url().then((previousUrl) => { - cy.get('[data-cy="descriptor-more-opts"]').click(); - cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); - - cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); - - cy.url() - .should('not.eq', previousUrl) - .then(() => { - cy.waitForElement('[data-cy="entry-buys"]'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); - cy.get('div[data-cy="recalculate-rates"]').click(); - - cy.get('.q-notification__message') - .eq(2) - .should('have.text', 'Entry prices recalculated'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); - deleteEntry(); - - cy.log(previousUrl); - - cy.visit(previousUrl); - - cy.waitForElement('[data-cy="entry-buys"]'); - deleteEntry(); + const checkBadgeDate = (selector, comparisonFn) => { + cy.get(selector) + .should('exist') + .each(($badge) => { + const badgeText = $badge.text().trim(); + const badgeDate = new Date(badgeText); + const compareDate = new Date('01/01/2001'); + comparisonFn(badgeDate, compareDate); }); - }); - }); - - it('Should notify when entry is lock by another user', () => { - const checkLockMessage = () => { - cy.get('[role="dialog"]').should('be.visible'); - cy.get('[data-cy="VnConfirm_message"] > span').should( - 'contain.text', - 'This entry has been locked by buyerNick', - ); }; - createEntry(); - goToEntryBuys(); - cy.get('.q-notification__message') - .eq(1) - .should('have.text', 'The entry has been locked successfully'); - - cy.login('logistic'); - cy.reload(); - checkLockMessage(); - cy.get('[data-cy="VnConfirm_cancel"]').click(); - cy.url().should('include', 'summary'); - - goToEntryBuys(); - checkLockMessage(); - cy.get('[data-cy="VnConfirm_confirm"]').click(); - cy.url().should('include', 'buys'); - - deleteEntry(); - }); - - it('Edit buys and use toolbar actions', () => { - const COLORS = { - negative: 'rgb(251, 82, 82)', - positive: 'rgb(200, 228, 132)', - enabled: 'rgb(255, 255, 255)', - disable: 'rgb(168, 168, 168)', - }; - - const selectCell = (field, row = 0) => - cy.get(`td[data-col-field="${field}"][data-row-index="${row}"]`); - const selectSpan = (field, row = 0) => selectCell(field, row).find('div > span'); - const selectButton = (cySelector) => cy.get(`button[data-cy="${cySelector}"]`); - const clickAndType = (field, value, row = 0) => { - selectCell(field, row).click().type(`${value}{esc}`); - }; - const checkText = (field, expectedText, row = 0) => - selectCell(field, row).should('have.text', expectedText); - const checkColor = (field, expectedColor, row = 0) => - selectSpan(field, row).should('have.css', 'color', expectedColor); - - createEntryAndBuy(); - - selectCell('isIgnored').click().click().type('{esc}'); - checkText('isIgnored', 'close'); - - clickAndType('stickers', '1'); - checkText('stickers', '0/01'); - checkText('quantity', '1'); - checkText('amount', '50.00'); - clickAndType('packing', '2'); - checkText('packing', '12'); - checkText('weight', '12.0'); - checkText('quantity', '12'); - checkText('amount', '600.00'); - checkColor('packing', COLORS.enabled); - - selectCell('groupingMode').click().click().click(); - checkColor('packing', COLORS.disable); - checkColor('grouping', COLORS.enabled); - - selectCell('buyingValue').click().clear().type('{backspace}{backspace}1'); - checkText('amount', '12.00'); - checkColor('minPrice', COLORS.disable); - - selectCell('hasMinPrice').click().click(); - checkColor('minPrice', COLORS.enabled); - selectCell('hasMinPrice').click(); - - cy.saveCard(); - cy.get('span[data-cy="footer-stickers"]').should('have.text', '1'); - cy.get('.q-notification__message').contains('Data saved'); - - selectButton('change-quantity-sign').should('be.disabled'); - selectButton('check-buy-amount').should('be.disabled'); - cy.get('tr.cursor-pointer > .q-table--col-auto-width > .q-checkbox').click(); - selectButton('change-quantity-sign').should('be.enabled'); - selectButton('check-buy-amount').should('be.enabled'); - - selectButton('change-quantity-sign').click(); - selectButton('set-negative-quantity').click(); - checkText('quantity', '-12'); - selectButton('set-positive-quantity').click(); - checkText('quantity', '12'); - checkColor('amount', COLORS.disable); - - selectButton('check-buy-amount').click(); - selectButton('uncheck-amount').click(); - checkColor('amount', COLORS.disable); - - selectButton('check-amount').click(); - checkColor('amount', COLORS.positive); - cy.saveCard(); - - cy.get('span[data-cy="footer-amount"]').should( - 'have.css', - 'color', - COLORS.positive, + checkBadgeDate( + 'td[data-col-field="landed"] > div .bg-warning', + (badgeDate, compareDate) => { + expect(badgeDate.getTime()).to.be.greaterThan(compareDate.getTime()); + }, ); - deleteEntry(); + checkBadgeDate( + 'td[data-col-field="landed"] > div .bg-info', + (badgeDate, compareDate) => { + expect(badgeDate.getTime()).to.be.lessThan(compareDate.getTime()); + }, + ); }); - - function goToEntryBuys() { - const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; - cy.get(entryBuySelector).should('be.visible'); - cy.waitForElement('[data-cy="entry-buys"]'); - cy.get(entryBuySelector).click(); - } - - function deleteEntry() { - cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); - cy.waitForElement('div[data-cy="delete-entry"]').click(); - cy.url().should('include', 'list'); - } - - function createEntryAndBuy() { - createEntry(); - createBuy(); - } - - function createEntry() { - cy.get('button[data-cy="vnTableCreateBtn"]').click(); - selectTravel('one'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); - cy.url().should('include', 'summary'); - cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); - } - - function selectTravel(warehouse) { - cy.get('i[data-cy="Travel_icon"]').click(); - cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); - cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); - cy.get('button[data-cy="save-filter-travel-form"]').click(); - cy.get('tr').eq(1).click(); - } - - function createBuy() { - cy.get('a[data-cy="EntryBuys-menu-item"]').click(); - cy.get('a[data-cy="EntryBuys-menu-item"]').click(); - cy.get('button[data-cy="vnTableCreateBtn"]').click(); - - cy.get('input[data-cy="itemFk-create-popup"]').type('1'); - cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); - cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); - } }); diff --git a/test/cypress/integration/entry/entryStockBought.spec.js b/test/cypress/integration/entry/entryStockBought.spec.js new file mode 100644 index 000000000..3fad44d91 --- /dev/null +++ b/test/cypress/integration/entry/entryStockBought.spec.js @@ -0,0 +1,23 @@ +describe('EntryStockBought', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/stock-Bought`); + }); + + it('Should edit the reserved space adjust the purchased spaces and check detail', () => { + cy.get('[data-cy="edit-travel"]').should('be.visible').click(); + cy.get('input[aria-label="m3"]').clear().type('60'); + cy.get('[data-cy="FormModelPopup_save"]').click(); + cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); + + cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); + cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); + cy.get('input[name="reserve"]').type('10{enter}'); + cy.get('button[title="Save"]').click(); + cy.checkNotification('Data saved'); + + cy.get('[data-cy="searchBtn"]').eq(0).click(); + cy.get('tBody > tr').eq(1).its('length').should('eq', 1); + }); +}); diff --git a/test/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/entrySupplier.spec.js similarity index 71% rename from test/cypress/integration/entry/myEntry.spec.js rename to test/cypress/integration/entry/entrySupplier.spec.js index ed469d9e2..83deecea5 100644 --- a/test/cypress/integration/entry/myEntry.spec.js +++ b/test/cypress/integration/entry/entrySupplier.spec.js @@ -1,4 +1,4 @@ -describe('EntryMy when is supplier', () => { +describe('EntrySupplier when is supplier', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('supplier'); @@ -13,5 +13,7 @@ describe('EntryMy when is supplier', () => { cy.dataCy('cardBtn').eq(2).click(); cy.dataCy('printLabelsBtn').click(); cy.window().its('open').should('be.called'); + cy.dataCy('seeLabelBtn').eq(0).should('be.visible').click(); + cy.window().its('open').should('be.called'); }); }); diff --git a/test/cypress/integration/entry/entryWasteRecalc.spec.js b/test/cypress/integration/entry/entryWasteRecalc.spec.js new file mode 100644 index 000000000..1b358676c --- /dev/null +++ b/test/cypress/integration/entry/entryWasteRecalc.spec.js @@ -0,0 +1,22 @@ +import './commands'; +describe('EntryDms', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyerBoss'); + cy.visit(`/#/entry/waste-recalc`); + }); + + it('should recalc waste for today', () => { + cy.waitForElement('[data-cy="wasteRecalc"]'); + cy.dataCy('recalc').should('be.disabled'); + + cy.dataCy('dateFrom').should('be.visible').click().type('01-01-2001'); + cy.dataCy('dateTo').should('be.visible').click().type('01-01-2001'); + + cy.dataCy('recalc').should('be.enabled').click(); + cy.get('.q-notification__message').should( + 'have.text', + 'The wastes were successfully recalculated', + ); + }); +}); diff --git a/test/cypress/integration/entry/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js deleted file mode 100644 index 91e0d507e..000000000 --- a/test/cypress/integration/entry/stockBought.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -describe('EntryStockBought', () => { - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('buyer'); - cy.visit(`/#/entry/stock-Bought`); - }); - it('Should edit the reserved space', () => { - cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); - cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); - cy.get('input[name="reserve"]').type('10{enter}'); - cy.get('button[title="Save"]').click(); - cy.checkNotification('Data saved'); - }); - it('Should add a new reserved space for buyerBoss', () => { - cy.addBtnClick(); - cy.get('input[aria-label="Reserve"]').type('1'); - cy.get('input[aria-label="Date"]').eq(1).clear(); - cy.get('input[aria-label="Date"]').eq(1).type('01-01'); - cy.get('input[aria-label="Buyer"]').type('itNick'); - cy.get('div[role="listbox"] > div > div[role="option"]') - .eq(1) - .should('be.visible') - .click(); - - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get('.q-notification__message').should('have.text', 'Data created'); - - cy.get('[data-col-field="reserve"][data-row-index="1"]').click().clear(); - cy.get('[data-cy="searchBtn"]').eq(1).click(); - cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata') - .should('have.text', 'warningNo data available') - .type('{esc}'); - cy.get('[data-col-field="reserve"][data-row-index="1"]') - .click() - .type('{backspace}{enter}'); - cy.get('[data-cy="crudModelDefaultSaveBtn"]').should('be.enabled').click(); - cy.get('.q-notification__message').eq(1).should('have.text', 'Data saved'); - }); - it('Should check detail for the buyer', () => { - cy.get('[data-cy="searchBtn"]').eq(0).click(); - cy.get('tBody > tr').eq(1).its('length').should('eq', 1); - }); - - it('Should edit travel m3 and refresh', () => { - cy.get('[data-cy="edit-travel"]').should('be.visible').click(); - cy.get('input[aria-label="m3"]').clear().type('60'); - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); - }); -}); diff --git a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js index 11ca1bb59..ee4d9fb74 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 731174040..275fa1358 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 97a9fe976..7058e154c 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 5a5becd22..2fc34a7ae 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 4c2550548..6a1c18785 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 d9ab3f7e7..44a61609e 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 000000000..3750f8f06 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js @@ -0,0 +1,25 @@ +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 ', () => { + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((before) => { + cy.dataCy('Last days_input') + .type('{selectall}1{enter}') + .then(() => { + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((after) => expect(+after).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 000000000..72dbdd9a8 --- /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 3e9997a74..ff7d639e6 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -1,14 +1,14 @@ /// <reference types="cypress" /> describe('InvoiceInVat', () => { const thirdRow = 'tbody > :nth-child(3)'; - const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)'; + const firstLineVat = 'tbody > :nth-child(1) '; const vats = '[data-cy="vat-sageiva"]'; const dialogInputs = '.q-dialog label input'; const addBtn = 'tbody tr:nth-child(1) td:nth-child(2) .--add-icon'; const randomInt = Math.floor(Math.random() * 100); beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/vat`); }); @@ -18,6 +18,17 @@ describe('InvoiceInVat', () => { cy.get(vats).eq(0).should('have.value', '8: H.P. IVA 21% CEE'); }); + it('should mark the line as deductible VAT', () => { + cy.get(`${firstLineVat} [data-cy="isDeductible_checkbox"]`).click(); + + cy.saveCard(); + + cy.get(`${firstLineVat} [data-cy="isDeductible_checkbox"]`) + + .click(); + cy.saveCard(); + }); + it('should add a new row', () => { cy.addRow(); cy.fillRow(thirdRow, [true, 2000000001, 30, 'H.P. IVA 10']); diff --git a/test/cypress/integration/order/orderList.spec.js b/test/cypress/integration/order/orderList.spec.js index c48b317a8..34cd2bffc 100644 --- a/test/cypress/integration/order/orderList.spec.js +++ b/test/cypress/integration/order/orderList.spec.js @@ -30,7 +30,7 @@ describe('OrderList', () => { cy.url().should('include', `/order`); }); - it('filter list and create order', () => { + it.skip('filter list and create order', () => { cy.dataCy('Customer ID_input').type('1101{enter}'); cy.dataCy('vnTableCreateBtn').click(); cy.dataCy('landedDate').find('input').type('06/01/2001'); diff --git a/test/cypress/integration/outLogin/logout.spec.js b/test/cypress/integration/outLogin/logout.spec.js index b3583e4d3..9f022617d 100644 --- a/test/cypress/integration/outLogin/logout.spec.js +++ b/test/cypress/integration/outLogin/logout.spec.js @@ -1,8 +1,8 @@ /// <reference types="cypress" /> -describe('Logout', () => { +describe.skip('Logout', () => { beforeEach(() => { cy.login('developer'); - cy.visit(`/#/dashboard`, false); + cy.visit(`/#/dashboard`); cy.waitForElement('.q-page', 6000); }); describe('by user', () => { @@ -28,17 +28,10 @@ describe('Logout', () => { }); it('when token not exists', () => { - const exceptionHandler = (err) => { - if (err.code === 'AUTHORIZATION_REQUIRED') return; - }; - Cypress.on('uncaught:exception', exceptionHandler); - - cy.get('.q-list').first().should('be.visible').click(); + cy.get('.q-list').should('be.visible').first().should('be.visible').click(); cy.wait('@badRequest'); cy.checkNotification('Authorization Required'); - - Cypress.off('uncaught:exception', exceptionHandler); }); }); }); diff --git a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js index 22a1a0143..79dcd6f70 100644 --- a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js +++ b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js @@ -1,4 +1,4 @@ -describe.skip('AgencyWorkCenter', () => { +describe('AgencyWorkCenter', () => { const selectors = { workCenter: 'workCenter_select', popupSave: 'FormModelPopup_save', @@ -9,7 +9,7 @@ describe.skip('AgencyWorkCenter', () => { const messages = { dataCreated: 'Data created', alreadyAssigned: 'This workCenter is already assigned to this agency', - removed: 'WorkCenter removed successfully', + removed: 'Work center removed successfully', }; beforeEach(() => { diff --git a/test/cypress/integration/route/cmr/cmrList.spec.js b/test/cypress/integration/route/cmr/cmrList.spec.js new file mode 100644 index 000000000..d33508e3a --- /dev/null +++ b/test/cypress/integration/route/cmr/cmrList.spec.js @@ -0,0 +1,91 @@ +describe('Cmr list', () => { + const getLinkSelector = (colField) => + `tr:first-child > [data-col-field="${colField}"] > .no-padding > .link`; + + const selectors = { + ticket: getLinkSelector('ticketFk'), + client: getLinkSelector('clientFk'), + lastRowSelectCheckBox: + '.q-virtual-scroll__content > tr:last-child > :nth-child(1) > .q-checkbox', + downloadBtn: '#subToolbar > .q-btn', + viewCmr: 'tableAction-0', + descriptorOpenSummaryBtn: '.descriptor [data-cy="openSummaryBtn"]', + summaryTitle: '.summaryHeader', + descriptorId: '.descriptor .subtitle', + descriptorTitle: '.descriptor .title', + summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]', + descriptorGoToSummaryBtn: '.descriptor [data-cy="goToSummaryBtn"]', + removeFilter: '.q-chip__icon--remove', + }; + + const data = { + ticket: '1', + client: 'Bruce Wayne', + }; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit('/#/route/cmr'); + cy.typeSearchbar('{enter}'); + cy.get(selectors.removeFilter).click(); + }); + + it('Should download selected cmr', () => { + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.get(selectors.lastRowSelectCheckBox).should('be.visible').click(); + cy.get(selectors.downloadBtn).should('be.visible').click(); + cy.wait(3000); + + const fileName = 'cmrs.zip'; + cy.readFile(`${downloadsFolder}/${fileName}`).should('exist'); + }); + + it('Should open selected cmr pdf', () => { + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen'); + }); + cy.dataCy(selectors.viewCmr).last().click(); + cy.get('@windowOpen').should('be.calledWithMatch', '\/api\/Cmrs\/3'); + }); + + describe('Ticket pop-ups', () => { + it('Should redirect to the ticket summary from the ticket descriptor pop-up', () => { + cy.get(selectors.ticket).should('be.visible').click(); + cy.containContent(selectors.descriptorId, data.ticket); + cy.get(selectors.descriptorGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/ticket/1/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + + it('Should redirect to the ticket summary from summary pop-up from the ticket descriptor pop-up', () => { + cy.get(selectors.ticket).should('be.visible').click(); + cy.containContent(selectors.descriptorId, data.ticket); + cy.get(selectors.descriptorOpenSummaryBtn).should('be.visible').click(); + cy.containContent(selectors.summaryTitle, data.client); + cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/ticket/1/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + }); + + describe('Client pop-ups', () => { + it('Should redirect to the client summary from the client descriptor pop-up', () => { + cy.get(selectors.client).should('be.visible').click(); + cy.containContent(selectors.descriptorTitle, data.client); + cy.get(selectors.descriptorGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/customer/1101/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + + it('Should redirect to the client summary from summary pop-up from the client descriptor pop-up', () => { + cy.get(selectors.client).should('be.visible').click(); + cy.containContent(selectors.descriptorTitle, data.client); + cy.get(selectors.descriptorOpenSummaryBtn).should('be.visible').click(); + cy.containContent(selectors.summaryTitle, data.client); + cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/customer/1101/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + }); +}); diff --git a/test/cypress/integration/route/routeAutonomous.spec.js b/test/cypress/integration/route/routeAutonomous.spec.js index 08fd7d7ea..acf82bd95 100644 --- a/test/cypress/integration/route/routeAutonomous.spec.js +++ b/test/cypress/integration/route/routeAutonomous.spec.js @@ -1,4 +1,4 @@ -describe.skip('RouteAutonomous', () => { +describe('RouteAutonomous', () => { const getLinkSelector = (colField) => `tr:first-child > [data-col-field="${colField}"] > .no-padding > .link`; diff --git a/test/cypress/integration/route/routeExtendedList.spec.js b/test/cypress/integration/route/routeExtendedList.spec.js index 5fda93b25..a183c08cb 100644 --- a/test/cypress/integration/route/routeExtendedList.spec.js +++ b/test/cypress/integration/route/routeExtendedList.spec.js @@ -1,4 +1,4 @@ -describe.skip('Route extended list', () => { +describe('Route extended list', () => { const getSelector = (colField) => `tr:last-child > [data-col-field="${colField}"]`; const selectors = { @@ -8,6 +8,8 @@ describe.skip('Route extended list', () => { date: getSelector('dated'), description: getSelector('description'), served: getSelector('isOk'), + firstRowSelectCheckBox: + 'tbody > tr:first-child > :nth-child(1) .q-checkbox__inner', lastRowSelectCheckBox: 'tbody > tr:last-child > :nth-child(1) .q-checkbox__inner', removeBtn: '[title="Remove"]', resetBtn: '[title="Reset"]', @@ -19,7 +21,7 @@ describe.skip('Route extended list', () => { markServedBtn: '#st-actions > .q-btn-group > :nth-child(3)', searchbar: 'searchbar', firstTicketsRowSelectCheckBox: - '.q-card > :nth-child(2) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(1) > .q-table--col-auto-width > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg > .q-checkbox__svg', + '.q-card .q-table > tbody > :nth-child(1) .q-checkbox', }; const checkboxState = { @@ -117,12 +119,21 @@ describe.skip('Route extended list', () => { }); }); - it('Should clone selected route', () => { - cy.get(selectors.lastRowSelectCheckBox).click(); + it('Should clone selected route and add ticket', () => { + cy.get(selectors.firstRowSelectCheckBox).click(); cy.get(selectors.cloneBtn).click(); - cy.dataCy('route.Starting date_inputDate').type('10-05-2001').click(); + cy.dataCy('Starting date_inputDate').type('01-01-2001'); cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); - cy.validateContent(selectors.date, '05/10/2001'); + cy.validateContent(selectors.date, '01/01/2001'); + + cy.dataCy('tableAction-0').last().click(); + cy.get(selectors.firstTicketsRowSelectCheckBox).click(); + cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); + cy.checkNotification(dataSaved); + + cy.get(selectors.lastRowSelectCheckBox).click(); + cy.get(selectors.removeBtn).click(); + cy.dataCy(selectors.confirmBtn).click(); }); it('Should download selected route', () => { @@ -145,20 +156,12 @@ describe.skip('Route extended list', () => { it('Should delete the selected route', () => { cy.get(selectors.lastRowSelectCheckBox).click(); - cy.get(selectors.removeBtn).click(); cy.dataCy(selectors.confirmBtn).click(); cy.checkNotification(dataSaved); }); - it('Should add ticket to route', () => { - cy.dataCy('tableAction-0').last().click(); - cy.get(selectors.firstTicketsRowSelectCheckBox).click(); - cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); - cy.checkNotification(dataSaved); - }); - it('Should save changes in route', () => { updateFields.forEach(({ selector, type, value }) => { fillField(selector, type, value); diff --git a/test/cypress/integration/route/routeList.spec.js b/test/cypress/integration/route/routeList.spec.js index 04278cfc5..f08c267a4 100644 --- a/test/cypress/integration/route/routeList.spec.js +++ b/test/cypress/integration/route/routeList.spec.js @@ -1,37 +1,205 @@ describe('Route', () => { + const getSelector = (colField) => + `tr:last-child > [data-col-field="${colField}"] > .no-padding > .link`; + + const selectors = { + lastRow: 'tr:last-child > [data-col-field="workerFk"]', + workerLink: getSelector('workerFk'), + agencyLink: getSelector('agencyModeFk'), + vehicleLink: getSelector('vehicleFk'), + assignedTicketsBtn: 'tableAction-0', + rowSummaryBtn: 'tableAction-1', + summaryTitle: '.summaryHeader', + descriptorTitle: '.descriptor .title', + descriptorOpenSummaryBtn: '.descriptor [data-cy="openSummaryBtn"]', + descriptorGoToSummaryBtn: '.descriptor [data-cy="goToSummaryBtn"]', + SummaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]', + }; + + const data = { + Worker: { val: 'logistic', type: 'select' }, + Agency: { val: 'Walking', type: 'select' }, + Vehicle: { val: '3333-BAT', type: 'select' }, + Description: { val: 'routeTest' }, + }; + + const summaryUrl = '/summary'; + beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit(`/#/route/extended-list`); + cy.visit(`/#/route/list`); + cy.typeSearchbar('{enter}'); }); - it('Route list create route', () => { + it('Should list routes', () => { + cy.get('.q-table') + .children() + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + + it('Should create new route', () => { cy.addBtnClick(); - cy.get('.q-card input[name="description"]').type('routeTestOne{enter}'); - cy.get('.q-notification__message').should('have.text', 'Data created'); - cy.url().should('include', '/summary'); + + cy.fillInForm(data); + + cy.dataCy('FormModelPopup_save').should('be.visible').click(); + + cy.checkNotification('Data created'); + cy.url().should('include', summaryUrl); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); + }); }); - it('Route list search and edit', () => { - cy.get('#searchbar input').type('{enter}'); - cy.get('[data-col-field="description"][data-row-index="0"]') - .click() - .type('routeTestOne{enter}'); - cy.get('.q-table tr') - .its('length') - .then((rowCount) => { - expect(rowCount).to.be.greaterThan(0); + it('Should open route summary by clicking a route', () => { + cy.get(selectors.lastRow).should('be.visible').click(); + cy.url().should('include', summaryUrl); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); }); - cy.get('[data-col-field="workerFk"][data-row-index="0"]') - .click() - .type('{downArrow}{enter}'); - cy.get('[data-col-field="agencyModeFk"][data-row-index="0"]') - .click() - .type('{downArrow}{enter}'); - cy.get('[data-col-field="vehicleFk"][data-row-index="0"]') - .click() - .type('{downArrow}{enter}'); - cy.get('button[title="Save"]').click(); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('Should redirect to the summary from the route pop-up summary', () => { + cy.dataCy(selectors.rowSummaryBtn).last().should('be.visible').click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); + }); + }); + + it('Should redirect to the route assigned tickets from the row assignedTicketsBtn', () => { + cy.dataCy(selectors.assignedTicketsBtn).first().should('be.visible').click(); + cy.url().should('include', '1/tickets'); + cy.get('.q-table') + .children() + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + + describe('Worker pop-ups', () => { + it('Should redirect to summary from the worker pop-up descriptor', () => { + cy.get(selectors.workerLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + cy.get(selectors.descriptorGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + }); + + it('Should redirect to the summary from the worker pop-up summary', () => { + cy.get(selectors.workerLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + cy.get(selectors.descriptorOpenSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + }); + }); + + describe('Agency pop-ups', () => { + it('Should redirect to summary from the agency pop-up descriptor', () => { + cy.get(selectors.agencyLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + cy.get(selectors.descriptorGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + }); + + it('Should redirect to the summary from the agency pop-up summary', () => { + cy.get(selectors.agencyLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + cy.get(selectors.descriptorOpenSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + }); + }); + + describe('Vehicle pop-ups', () => { + it('Should redirect to summary from the vehicle pop-up descriptor', () => { + cy.get(selectors.vehicleLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + cy.get(selectors.descriptorGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + }); + + it('Should redirect to the summary from the vehicle pop-up summary', () => { + cy.get(selectors.vehicleLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + cy.get(selectors.descriptorOpenSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + }); }); }); diff --git a/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js b/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js index 64b9ca0a0..3e9c816c4 100644 --- a/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js +++ b/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js @@ -2,11 +2,11 @@ describe('Vehicle', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('deliveryAssistant'); - cy.visit(`/#/route/vehicle/7`); + cy.visit(`/#/route/vehicle/7/summary`); }); it('should delete a vehicle', () => { - cy.openActionsDescriptor(); + cy.dataCy('descriptor-more-opts').click(); cy.get('[data-cy="delete"]').click(); cy.checkNotification('Vehicle removed'); }); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 2409dd149..5613a5854 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -44,6 +44,7 @@ describe('TicketList', () => { cy.dataCy('Customer ID_input').type('1101{enter}'); cy.get('[data-cy="vnTableCreateBtn"] > .q-btn__content > .q-icon').click(); + cy.waitSpinner(); cy.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); cy.dataCy('Address_select').click(); diff --git a/test/cypress/integration/vnComponent/VnLog.spec.js b/test/cypress/integration/vnComponent/VnLog.spec.js index 80b9d07df..ae0013150 100644 --- a/test/cypress/integration/vnComponent/VnLog.spec.js +++ b/test/cypress/integration/vnComponent/VnLog.spec.js @@ -22,4 +22,10 @@ describe('VnLog', () => { cy.get('.q-page').click(); cy.validateContent(chips[0], 'Claim'); }); + + it('should show claimDescriptor', () => { + cy.dataCy('iconLaunch-claimFk').first().click(); + cy.dataCy('vnDescriptor_subtitle').contains('1'); + cy.dataCy('iconLaunch-claimFk').first().click(); + }); }); diff --git a/test/cypress/integration/worker/workerPda.spec.js b/test/cypress/integration/worker/workerPda.spec.js index 31ec19eda..2623e81cf 100644 --- a/test/cypress/integration/worker/workerPda.spec.js +++ b/test/cypress/integration/worker/workerPda.spec.js @@ -1,23 +1,80 @@ describe('WorkerPda', () => { - const select = '[data-cy="pda-dialog-select"]'; + const deviceId = 4; beforeEach(() => { - cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/worker/1110/pda`); }); - it('assign pda', () => { - cy.addBtnClick(); - cy.get(select).click(); - cy.get(select).type('{downArrow}{enter}'); - cy.get('.q-notification__message').should('have.text', 'Data created'); + it('assign and delete pda', () => { + creatNewPDA(); + cy.checkNotification('Data created'); + cy.visit(`/#/worker/1110/pda`); + removeNewPDA(); + cy.checkNotification('PDA deallocated'); }); - it('delete pda', () => { - cy.get('.btn-delete').click(); - cy.get( - '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block' - ).click(); - cy.get('.q-notification__message').should('have.text', 'PDA deallocated'); + it('send and download pdf to docuware', () => { + //Send + cy.intercept('POST', '/api/Docuwares/upload-pda-pdf', (req) => { + req.reply({ + statusCode: 200, + body: {}, + }); + }); + + creatNewPDA(); + + cy.dataCy('workerPda-send').click(); + cy.clickConfirm(); + cy.checkNotification('PDF sended to signed'); + + //Download + cy.intercept('POST', /\/api\/Docuwares\/Jones%20Jessica\/checkFile/, (req) => { + req.reply({ + statusCode: 200, + body: { + id: deviceId, + state: 'Firmado', + }, + }); + }); + cy.get('#st-actions').contains('refresh').click(); + cy.intercept('GET', '/api/Docuwares/download-pda-pdf**', (req) => { + req.reply({ + statusCode: 200, + body: {}, + }); + }); + + cy.dataCy('workerPda-download').click(); + removeNewPDA(); }); + + it('send 2 pdfs to docuware', () => { + cy.intercept('POST', '/api/Docuwares/upload-pda-pdf', (req) => { + req.reply({ + statusCode: 200, + body: {}, + }); + }); + + creatNewPDA(); + creatNewPDA(2); + cy.selectRows([1, 2]); + cy.get('#st-actions').contains('Send').click(); + cy.checkNotification('PDF sended to signed'); + + removeNewPDA(); + }); + + function creatNewPDA(id = deviceId) { + cy.addBtnClick(); + cy.selectOption('[data-cy="pda-dialog-select"]', id); + cy.saveCard(); + } + + function removeNewPDA() { + cy.dataCy('workerPda-remove').first().click(); + cy.clickConfirm(); + } }); diff --git a/test/cypress/integration/zone/zoneCalendar.spec.js b/test/cypress/integration/zone/zoneCalendar.spec.js index 07661a17d..68b85d1d2 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 291c20ce3..a89def12d 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 3a52d276c..dabd3eb2b 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/integration/zone/zoneWarehouse.spec.js b/test/cypress/integration/zone/zoneWarehouse.spec.js index bca5ced22..d7a9854bb 100644 --- a/test/cypress/integration/zone/zoneWarehouse.spec.js +++ b/test/cypress/integration/zone/zoneWarehouse.spec.js @@ -18,7 +18,7 @@ describe('ZoneWarehouse', () => { cy.checkNotification(dataError); }); - it('should create & remove a warehouse', () => { + it.skip('should create & remove a warehouse', () => { cy.addBtnClick(); cy.fillInForm(data); cy.get(saveBtn).click(); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 105d021ad..7f5203547 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(); @@ -184,13 +194,47 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { cy.get('.q-time .q-time__link').contains(val.x).click(); break; default: - cy.wrap(el).type(val); + cy.wrap(el).type(`{selectall}${val}`, { delay: 0 }); break; } }); }); }); +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,6 +370,15 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => { cy.get(selector).should('have.text', expectedValue); }); +Cypress.Commands.add('containContent', (selector, expectedValue) => { + cy.get(selector) + .should('be.visible') + .invoke('text') + .then((text) => { + expect(text).to.include(expectedValue); + }); +}); + Cypress.Commands.add('openActionDescriptor', (opt) => { cy.openActionsDescriptor(); const listItem = '[role="menu"] .q-list .q-item'; @@ -329,13 +386,7 @@ Cypress.Commands.add('openActionDescriptor', (opt) => { }); 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="vnDescriptor"] [data-cy="descriptor-more-opts"]').click(); }); Cypress.Commands.add('openUserPanel', () => { @@ -395,6 +446,7 @@ Cypress.Commands.add('clickButtonWith', (type, value) => { break; } }); + Cypress.Commands.add('clickButtonWithIcon', (iconClass) => { cy.waitForElement('[data-cy="descriptor_actions"]'); cy.get('[data-cy="loading-spinner"]', { timeout: 10000 }).should('not.be.visible'); @@ -423,3 +475,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="vnDescriptor_title"]`).contains(title); + if (description) + cy.get(`${popupSelector}[data-cy="vnDescriptor_description"]`).contains( + description, + ); + if (subtitle) + cy.get(`${popupSelector}[data-cy="vnDescriptor_subtitle"]`).contains(subtitle); + + for (const index in listbox) + cy.get(`${popupSelector}[data-cy="vnDescriptor_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]'), +);