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/README.md b/README.md index d280e29ce..8eff99137 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,9 @@ pnpm run test:e2e:summary ```bash quasar build ``` + +### Serve the app for production + +```bash +quasar build quasar serve dist/spa --host 0.0.0.0 --proxy=./proxy-serve.js +``` diff --git a/package.json b/package.json index 017412ef2..366e4bd36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "25.14.0", + "version": "25.16.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -76,4 +76,4 @@ "vite": "^6.0.11", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index dbb6f1fe6..7329ddae2 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref } from 'vue'; +import { onMounted, ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useState } from 'src/composables/useState'; import { useStateStore } from 'stores/useStateStore'; @@ -18,6 +18,14 @@ const state = useState(); const user = state.getUser(); const appName = 'Lilium'; const pinnedModulesRef = ref(); +const hostname = window.location.hostname; +const env = ref(); + +const getEnvironment = computed(() => { + env.value = hostname.split('-'); + if (env.value.length <= 1) return; + return env.value[0]; +}); onMounted(() => stateStore.setMounted()); const refresh = () => window.location.reload(); @@ -49,6 +57,9 @@ const refresh = () => window.location.reload(); {{ t('globals.backToDashboard') }} </QTooltip> </QBtn> + <QBadge v-if="getEnvironment" color="primary" align="top"> + {{ getEnvironment }} + </QBadge> </RouterLink> <VnBreadcrumbs v-if="$q.screen.gt.sm" /> <QSpinner diff --git a/src/components/TicketProblems.vue b/src/components/TicketProblems.vue index 59be95035..c15e31d80 100644 --- a/src/components/TicketProblems.vue +++ b/src/components/TicketProblems.vue @@ -17,17 +17,6 @@ defineProps({ row: { type: Object, required: true } }); </QTooltip> </QIcon> </router-link> - <QIcon - v-if="row?.reserved" - color="primary" - name="vn:reserva" - size="xs" - data-cy="ticketSaleReservedIcon" - > - <QTooltip> - {{ t('ticketSale.reserved') }} - </QTooltip> - </QIcon> <QIcon v-if="row?.isDeleted" color="primary" diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index d0e245388..3ce62c5de 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -55,6 +55,8 @@ const $props = defineProps({ }, }); +const label = $props.showLabel && $props.column.label ? $props.column.label : ''; + const defaultSelect = { attrs: { row: $props.row, @@ -62,7 +64,7 @@ const defaultSelect = { class: 'fit', }, forceAttrs: { - label: $props.showLabel && $props.column.label, + label, }, }; @@ -74,7 +76,7 @@ const defaultComponents = { class: 'fit', }, forceAttrs: { - label: $props.showLabel && $props.column.label, + label, }, }, number: { @@ -84,7 +86,7 @@ const defaultComponents = { class: 'fit', }, forceAttrs: { - label: $props.showLabel && $props.column.label, + label, }, }, date: { @@ -96,7 +98,7 @@ const defaultComponents = { class: 'fit', }, forceAttrs: { - label: $props.showLabel && $props.column.label, + label, }, }, time: { @@ -105,7 +107,7 @@ const defaultComponents = { disable: !$props.isEditable, }, forceAttrs: { - label: $props.showLabel && $props.column.label, + label, }, }, checkbox: { @@ -125,7 +127,7 @@ const defaultComponents = { return defaultAttrs; }, forceAttrs: { - label: $props.showLabel && $props.column.label, + label, autofocus: true, }, events: { diff --git a/src/components/VnTable/VnOrder.vue b/src/components/VnTable/VnOrder.vue index 47ed9acf4..d39fc8641 100644 --- a/src/components/VnTable/VnOrder.vue +++ b/src/components/VnTable/VnOrder.vue @@ -70,7 +70,7 @@ function textAlignToFlex(textAlign) { :style="textAlignToFlex(align)" > <span :title="label">{{ label }}</span> - <div v-if="name && model?.index"> + <div v-if="name"> <QChip :label="!vertical ? model?.index : ''" :icon=" @@ -83,14 +83,14 @@ function textAlignToFlex(textAlign) { :size="vertical ? '' : 'sm'" :class="[ model?.index ? 'color-vn-text' : 'bg-transparent', - vertical ? 'q-px-none' : '', + vertical ? 'q-mx-none q-py-lg' : '', ]" class="no-box-shadow" :clickable="true" style="min-width: 40px; max-height: 30px" > <div - class="column flex-center" + class="column justify-center text-center" v-if="vertical" :style="!model?.index && 'color: #5d5d5d'" > diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue index 79b903e54..109e2b77e 100644 --- a/src/components/VnTable/VnTableFilter.vue +++ b/src/components/VnTable/VnTableFilter.vue @@ -26,7 +26,12 @@ function columnName(col) { } </script> <template> - <VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true"> + <VnFilterPanel + v-bind="$attrs" + :search-button="true" + :disable-submit-event="true" + :search-url + > <template #body="{ params, orders, searchFn }"> <div class="container" @@ -34,13 +39,20 @@ function columnName(col) { :key="col.id" > <div class="filter"> - <VnFilter - ref="tableFilterRef" - :column="col" - :data-key="$attrs['data-key']" - v-model="params[columnName(col)]" - :search-url="searchUrl" - /> + <slot + :name="`filter-${col.name}`" + :params="params" + :column-name="columnName(col)" + :search-fn + > + <VnFilter + ref="tableFilterRef" + :column="col" + :data-key="$attrs['data-key']" + v-model="params[columnName(col)]" + :search-url="searchUrl" + /> + </slot> </div> <div class="order"> <VnTableOrder @@ -77,13 +89,13 @@ function columnName(col) { display: flex; justify-content: center; align-items: center; - height: 45px; + min-height: 45px; gap: 10px; } .filter { width: 70%; - height: 40px; + min-height: 40px; text-align: center; } .order { diff --git a/src/components/common/SendSmsDialog.vue b/src/components/common/SendSmsDialog.vue index 269a4ec9a..a953abd75 100644 --- a/src/components/common/SendSmsDialog.vue +++ b/src/components/common/SendSmsDialog.vue @@ -1,15 +1,15 @@ <script setup> -import {useDialogPluginComponent} from 'quasar'; -import {useI18n} from 'vue-i18n'; -import {computed, ref} from 'vue'; +import { useDialogPluginComponent } from 'quasar'; +import { useI18n } from 'vue-i18n'; +import { computed, ref } from 'vue'; import VnInput from 'components/common/VnInput.vue'; import axios from 'axios'; -import useNotify from "composables/useNotify"; +import useNotify from 'composables/useNotify'; const MESSAGE_MAX_LENGTH = 160; -const {t} = useI18n(); -const {notify} = useNotify(); +const { t } = useI18n(); +const { notify } = useNotify(); const props = defineProps({ title: { type: String, @@ -34,7 +34,7 @@ const props = defineProps({ }); const emit = defineEmits([...useDialogPluginComponent.emits, 'sent']); -const {dialogRef, onDialogHide} = useDialogPluginComponent(); +const { dialogRef, onDialogHide } = useDialogPluginComponent(); const smsRules = [ (val) => (val && val.length > 0) || t("The message can't be empty"), @@ -43,10 +43,10 @@ const smsRules = [ t("The message it's too long"), ]; -const message = ref(''); +const message = ref(t('routeDelay')); const charactersRemaining = computed( - () => MESSAGE_MAX_LENGTH - new Blob([message.value]).size + () => MESSAGE_MAX_LENGTH - new Blob([message.value]).size, ); const charactersChipColor = computed(() => { @@ -114,7 +114,7 @@ const onSubmit = async () => { <QTooltip> {{ t( - 'Special characters like accents counts as a multiple' + 'Special characters like accents counts as a multiple', ) }} </QTooltip> @@ -144,7 +144,10 @@ const onSubmit = async () => { max-width: 450px; } </style> + <i18n> +en: + routeDelay: "Your order has been delayed in transit.\nDelivery will take place throughout the day.\nWe apologize for the inconvenience and appreciate your patience." es: Message: Mensaje Send: Enviar @@ -153,4 +156,5 @@ es: The destination can't be empty: El destinatario no puede estar vacio The message can't be empty: El mensaje no puede estar vacio The message it's too long: El mensaje es demasiado largo -</i18n> + routeDelay: "Retraso en ruta.\nInformamos que la ruta que lleva su pedido ha sufrido un retraso y la entrega se hará a lo largo del día.\nDisculpe las molestias." + </i18n> diff --git a/src/components/common/VnAccountNumber.vue b/src/components/common/VnAccountNumber.vue index 56add7329..8bff3e261 100644 --- a/src/components/common/VnAccountNumber.vue +++ b/src/components/common/VnAccountNumber.vue @@ -1,35 +1,14 @@ <script setup> -import { nextTick, ref } from 'vue'; import VnInput from './VnInput.vue'; import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; -const $props = defineProps({ - insertable: { - type: Boolean, - default: false, - }, -}); - -const emit = defineEmits(['update:modelValue', 'accountShortToStandard']); const model = defineModel({ prop: 'modelValue' }); -const inputRef = ref(false); - -function setCursorPosition(pos) { - const input = inputRef.value.vnInputRef.$el.querySelector('input'); - input.focus(); - input.setSelectionRange(pos, pos); -} - -async function handleUpdateModel(val) { - model.value = val?.at(-1) === '.' ? useAccountShortToStandard(val) : val; - await nextTick(() => setCursorPosition(0)); -} </script> <template> <VnInput v-model="model" ref="inputRef" - :insertable - @update:model-value="handleUpdateModel" + @keydown.tab="model = useAccountShortToStandard($event.target.value) ?? model" + @input="model = $event.target.value.replace(/[^\d.]/g, '')" /> </template> diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 21cdc9df5..50b041060 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -1,5 +1,5 @@ <script setup> -import { onBeforeMount, computed } from 'vue'; +import { onBeforeMount, computed, markRaw } from 'vue'; import { useRoute, useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; @@ -37,7 +37,7 @@ onBeforeRouteLeave(() => { }); onBeforeMount(async () => { - stateStore.cardDescriptorChangeValue(props.descriptor); + stateStore.cardDescriptorChangeValue(markRaw(props.descriptor)); const route = router.currentRoute.value; try { diff --git a/src/components/common/VnDms.vue b/src/components/common/VnDms.vue index bee300f4e..de22e4857 100644 --- a/src/components/common/VnDms.vue +++ b/src/components/common/VnDms.vue @@ -35,6 +35,10 @@ const $props = defineProps({ type: String, default: null, }, + hasFile: { + type: Boolean, + default: false, + }, }); const warehouses = ref(); @@ -90,6 +94,7 @@ function defaultData() { if ($props.formInitialData) return (dms.value = $props.formInitialData); return addDefaultData({ reference: route.params.id, + hasFile: $props.hasFile, }); } diff --git a/src/components/common/VnDmsInput.vue b/src/components/common/VnDmsInput.vue index 25d625d5d..5a3ef351b 100644 --- a/src/components/common/VnDmsInput.vue +++ b/src/components/common/VnDmsInput.vue @@ -15,7 +15,7 @@ const editDownloadDisabled = ref(false); const $props = defineProps({ defaultDmsCode: { type: String, - default: 'InvoiceIn', + default: 'invoiceIn', }, disable: { type: Boolean, diff --git a/src/components/common/VnDropdown.vue b/src/components/common/VnDropdown.vue new file mode 100644 index 000000000..1b3f2237b --- /dev/null +++ b/src/components/common/VnDropdown.vue @@ -0,0 +1,53 @@ +<script setup> +import { ref } from 'vue'; +import VnSelect from './VnSelect.vue'; + +const stateBtnDropdownRef = ref(); + +const emit = defineEmits(['changeState']); + +const $props = defineProps({ + disable: { + type: Boolean, + default: null, + }, + options: { + type: Array, + default: null, + }, + optionLabel: { + type: String, + default: 'name', + }, + optionValue: { + type: String, + default: 'id', + }, +}); + +async function changeState(value) { + stateBtnDropdownRef.value?.hide(); + emit('changeState', value); +} +</script> + +<template> + <QBtnDropdown + ref="stateBtnDropdownRef" + color="black" + text-color="white" + :label="$t('globals.changeState')" + :disable="$props.disable" + > + <VnSelect + :options="$props.options" + :option-label="$props.optionLabel" + :option-value="$props.optionValue" + hide-selected + hide-dropdown-icon + focus-on-mount + @update:model-value="changeState" + > + </VnSelect> + </QBtnDropdown> +</template> diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 804147539..0f5a162e3 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, onUnmounted, watch } from 'vue'; +import { ref, onMounted, onUnmounted, watch, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; import axios from 'axios'; @@ -11,11 +11,11 @@ import { useCapitalize } from 'src/composables/useCapitalize'; import { useValidator } from 'src/composables/useValidator'; import VnAvatar from '../ui/VnAvatar.vue'; import VnLogValue from './VnLogValue.vue'; -import FetchData from '../FetchData.vue'; -import VnSelect from './VnSelect.vue'; import VnUserLink from '../ui/VnUserLink.vue'; import VnPaginate from '../ui/VnPaginate.vue'; +import VnLogFilter from 'src/components/common/VnLogFilter.vue'; import RightMenu from './RightMenu.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; const stateStore = useStateStore(); const validationsStore = useValidator(); @@ -72,39 +72,8 @@ const filter = { }; const paginate = ref(); -const actions = ref(); -const changeInput = ref(); -const searchInput = ref(); -const userRadio = ref(); -const userSelect = ref(); -const dateFrom = ref(); -const dateFromDialog = ref(false); -const dateTo = ref(); -const dateToDialog = ref(false); -const selectedFilters = ref({}); -const userTypes = [ - { label: 'All', value: undefined }, - { label: 'User', value: { neq: null } }, - { label: 'System', value: null }, -]; -const checkboxOptions = ref({ - insert: { - label: 'Creates', - selected: false, - }, - update: { - label: 'Edits', - selected: false, - }, - delete: { - label: 'Deletes', - selected: false, - }, - select: { - label: 'Accesses', - selected: false, - }, -}); +const dataKey = computed(() => `${props.model}Log`); +const userParams = ref(useFilterParams(dataKey.value).params); let validations = models; let pointRecord = ref(null); @@ -246,131 +215,54 @@ async function setLogTree(data) { function filterByRecord(modelLog) { byRecord.value = true; const { id, model } = modelLog; - - searchInput.value = id; - selectedFilters.value.changedModelId = id; - selectedFilters.value.changedModel = model; - applyFilter(); + applyFilter({ changedModelId: id, changedModel: model }); } -async function applyFilter() { - filter.where = { and: [] }; - if ( - !selectedFilters.value.changedModel || - (!selectedFilters.value.changedModelValue && - !selectedFilters.value.changedModelId) - ) - byRecord.value = false; - - if (!byRecord.value) filter.where.and.push({ originFk: route.params.id }); - - if (Object.keys(selectedFilters.value).length) { - filter.where.and.push(selectedFilters.value); - } - - paginate.value.fetch({ filter }); +async function applyFilter(params = {}) { + paginate.value.arrayData.applyFilter({ + filter: {}, + params: { originFk: route.params.id, ...params }, + }); } -function setDate(type) { - let from = dateFrom.value - ? date.formatDate(dateFrom.value.split('-').reverse().join('-'), 'YYYY-MM-DD') - : undefined; - from = date.adjustDate(from, { hour: 0, minute: 0, second: 0, millisecond: 0 }, true); - - let to = dateTo.value - ? date.formatDate(dateTo.value.split('-').reverse().join('-'), 'YYYY-MM-DD') - : date.formatDate(dateFrom.value.split('-').reverse().join('-'), 'YYYY-MM-DD'); - to = date.adjustDate( - to, - { hour: 21, minute: 59, second: 59, millisecond: 999 }, - true, - ); - - switch (type) { - case 'from': - return { between: [from, to] }; - case 'to': { - if (dateFrom.value) { +function exprBuilder(param, value) { + switch (param) { + case 'changedModelValue': + return { [param]: { like: `%${value}%` } }; + case 'change': + if (value) return { - between: [from, to], + or: [ + { oldJson: { like: `%${value}%` } }, + { newJson: { like: `%${value}%` } }, + { description: { like: `%${value}%` } }, + ], }; - } - return { lte: to }; - } + break; + case 'action': + if (value?.length) return { [param]: { inq: value } }; + break; + case 'from': + return { creationDate: { gte: value } }; + case 'to': + return { creationDate: { lte: value } }; + case 'userType': + if (value === 'User') return { userFk: { neq: null } }; + if (value === 'System') return { userFk: null }; + break; + default: + return { [param]: value }; } } -function selectFilter(type, dateType) { - const filter = {}; - const actions = { inq: [] }; - let reload = true; - - if (type === 'search') { - if (/^\s*[0-9]+\s*$/.test(searchInput.value) || props.byRecord) { - selectedFilters.value.changedModelId = searchInput.value.trim(); - } else if (!searchInput.value) { - selectedFilters.value.changedModelId = undefined; - selectedFilters.value.changedModelValue = undefined; - } else { - selectedFilters.value.changedModelValue = { like: `%${searchInput.value}%` }; - } - } - if (type === 'action' && selectedFilters.value.changedModel === null) { - selectedFilters.value.changedModel = undefined; - } - if (type === 'userRadio') { - selectedFilters.value.userFk = userRadio.value; - } - if (type === 'change') { - if (changeInput.value) - selectedFilters.value.or = [ - { oldJson: { like: `%${changeInput.value}%` } }, - { newJson: { like: `%${changeInput.value}%` } }, - { description: { like: `%${changeInput.value}%` } }, - ]; - else selectedFilters.value.or = undefined; - } - if (type === 'userSelect') { - selectedFilters.value.userFk = - userSelect.value !== null ? userSelect.value : undefined; - } - if (type === 'date') { - if (!dateFrom.value && !dateTo.value) { - selectedFilters.value.creationDate = undefined; - } else if (dateType === 'to') { - selectedFilters.value.creationDate = setDate('to'); - } else if (dateType === 'from') { - selectedFilters.value.creationDate = setDate('from'); - } - } - - Object.keys(checkboxOptions.value).forEach((key) => { - if (checkboxOptions.value[key].selected) actions.inq.push(key); - }); - selectedFilters.value.action = actions.inq.length ? actions : undefined; - - Object.keys(selectedFilters.value).forEach((key) => { - if (selectedFilters.value[key]) filter[key] = selectedFilters.value[key]; - }); - - if (reload) applyFilter(filter); -} - async function clearFilter() { - selectedFilters.value = {}; byRecord.value = false; - userSelect.value = undefined; - searchInput.value = undefined; - changeInput.value = undefined; - dateFrom.value = undefined; - dateTo.value = undefined; - userRadio.value = undefined; - Object.keys(checkboxOptions.value).forEach( - (opt) => (checkboxOptions.value[opt].selected = false), - ); await applyFilter(); } +onMounted(() => { + stateStore.rightDrawerChangeValue(true); +}); onUnmounted(() => { stateStore.rightDrawer = false; }); @@ -383,32 +275,18 @@ watch( ); </script> <template> - <FetchData - :url="`${props.model}Logs/${route.params.id}/models`" - :filter="{ order: ['changedModel'] }" - @on-fetch=" - (data) => - (actions = data.map((item) => { - const changedModel = item.changedModel; - return { - locale: useCapitalize( - validations[changedModel]?.locale?.name ?? changedModel, - ), - value: changedModel, - }; - })) - " - auto-load - /> <VnPaginate ref="paginate" - :data-key="`${model}Log`" - :url="`${model}Logs`" + :data-key + :url="dataKey + 's'" :user-filter="filter" :skeleton="false" auto-load @on-fetch="setLogTree" + @on-change="setLogTree" search-url="logs" + :exprBuilder + :order="['creationDate DESC', 'id DESC']" > <template #body> <div @@ -467,6 +345,7 @@ watch( backgroundColor: useColor(modelLog.model), }" :title="`${modelLog.model} #${modelLog.id}`" + data-cy="vnLog-model-chip" > {{ t(modelLog.modelI18n) }} </QChip> @@ -561,9 +440,7 @@ watch( }}: </span> <VnLogValue - :value=" - value.val.val - " + :value="value.val" :name="value.name" /> </QItem> @@ -582,6 +459,7 @@ watch( }`, ) " + data-cy="vnLog-action-icon" /> </div> </QItem> @@ -616,7 +494,7 @@ watch( {{ prop.nameI18n }}: </span> <VnLogValue - :value="prop.val.val" + :value="prop.val" :name="prop.name" /> <span @@ -647,7 +525,7 @@ watch( </span> <span v-if="log.action == 'update'"> <VnLogValue - :value="prop.old.val" + :value="prop.old" :name="prop.name" /> <span @@ -658,7 +536,7 @@ watch( </span> → <VnLogValue - :value="prop.val.val" + :value="prop.val" :name="prop.name" /> <span @@ -670,7 +548,7 @@ watch( </span> <span v-else="prop.old.val"> <VnLogValue - :value="prop.val.val" + :value="prop.val" :name="prop.name" /> <span @@ -699,181 +577,12 @@ watch( </VnPaginate> <RightMenu> <template #right-panel> - <QList dense> - <QSeparator /> - <QItem class="q-mt-sm"> - <QInput - :label="t('globals.search')" - v-model="searchInput" - class="full-width" - clearable - filled - clear-icon="close" - @keyup.enter="() => selectFilter('search')" - @focusout="() => selectFilter('search')" - @clear="() => selectFilter('search')" - > - <template #append> - <QIcon name="info" class="cursor-pointer"> - <QTooltip>{{ t('tooltips.search') }}</QTooltip> - </QIcon> - </template> - </QInput> - </QItem> - <QItem> - <VnSelect - class="full-width" - :label="t('globals.entity')" - v-model="selectedFilters.changedModel" - option-label="locale" - option-value="value" - filled - :options="actions" - @update:model-value="selectFilter('action')" - hide-selected - /> - </QItem> - <QItem class="q-mt-sm"> - <QOptionGroup - size="sm" - v-model="userRadio" - :options="userTypes" - color="primary" - @update:model-value="selectFilter('userRadio')" - right-label - > - <template #label="{ label }"> - {{ t(`Users.${label}`) }} - </template> - </QOptionGroup> - </QItem> - <QItem class="q-mt-sm"> - <QItemSection v-if="userRadio !== null"> - <VnSelect - class="full-width" - :label="t('globals.user')" - v-model="userSelect" - filled - :url="`${model}Logs/${route.params.id}/editors`" - :fields="['id', 'nickname', 'name', 'image']" - sort-by="nickname" - @update:model-value="selectFilter('userSelect')" - hide-selected - > - <template #option="{ opt, itemProps }"> - <QItem - v-bind="itemProps" - class="q-pa-xs row items-center" - > - <QItemSection class="col-3 items-center"> - <VnAvatar :worker-id="opt.id" /> - </QItemSection> - <QItemSection class="col-9 justify-center"> - <span>{{ opt.name }}</span> - <span class="text-grey">{{ opt.nickname }}</span> - </QItemSection> - </QItem> - </template> - </VnSelect> - </QItemSection> - </QItem> - <QItem class="q-mt-sm"> - <QInput - :label="t('globals.changes')" - v-model="changeInput" - class="full-width" - filled - clearable - clear-icon="close" - @keyup.enter="selectFilter('change')" - @focusout="selectFilter('change')" - @clear="selectFilter('change')" - > - <template #append> - <QIcon name="info" class="cursor-pointer"> - <QTooltip max-width="250px">{{ - t('tooltips.changes') - }}</QTooltip> - </QIcon> - </template> - </QInput> - </QItem> - <QItem - :class="index == 'create' ? 'q-mt-md' : 'q-mt-xs'" - v-for="(checkboxOption, index) in checkboxOptions" - :key="index" - > - <QCheckbox - size="sm" - v-model="checkboxOption.selected" - :label="t(`actions.${checkboxOption.label}`)" - @update:model-value="selectFilter" - /> - </QItem> - <QItem class="q-mt-sm"> - <QInput - class="full-width" - :label="t('globals.date')" - @click="dateFromDialog = true" - @focus="(evt) => evt.target.blur()" - @clear="selectFilter('date', 'to')" - v-model="dateFrom" - clearable - filled - clear-icon="close" - /> - </QItem> - <QItem class="q-mt-sm"> - <QInput - class="full-width" - :label="t('globals.to')" - @click="dateToDialog = true" - @focus="(evt) => evt.target.blur()" - @clear="selectFilter('date', 'from')" - v-model="dateTo" - clearable - filled - clear-icon="close" - /> - </QItem> - </QList> + <VnLogFilter :data-key /> </template> </RightMenu> - <QDialog v-model="dateFromDialog"> - <QDate - :years-in-month-view="false" - v-model="dateFrom" - dense - flat - minimal - filled - @update:model-value=" - (value) => { - dateFromDialog = false; - dateFrom = date.formatDate(value, 'DD-MM-YYYY'); - selectFilter('date', 'from'); - } - " - /> - </QDialog> - <QDialog v-model="dateToDialog"> - <QDate - v-model="dateTo" - dense - flat - minimal - @update:model-value=" - (value) => { - dateToDialog = false; - dateTo = date.formatDate(value, 'DD-MM-YYYY'); - selectFilter('date', 'to'); - } - " - /> - </QDialog> <QPageSticky position="bottom-right" :offset="[25, 25]"> <QBtn - v-if="Object.values(selectedFilters).some((filter) => filter !== undefined)" + v-if="Object.keys(userParams).some((filter) => filter !== 'originFk')" color="primary" icon="filter_alt_off" size="md" diff --git a/src/components/common/VnLogFilter.vue b/src/components/common/VnLogFilter.vue index b5941239c..9b06b24e6 100644 --- a/src/components/common/VnLogFilter.vue +++ b/src/components/common/VnLogFilter.vue @@ -1,77 +1,249 @@ <script setup> -import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import FetchData from 'components/FetchData.vue'; -import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnTableFilter from '../VnTable/VnTableFilter.vue'; +import VnSelect from './VnSelect.vue'; +import { useRoute } from 'vue-router'; +import VnInput from './VnInput.vue'; +import { ref, computed, watch } from 'vue'; +import VnInputDate from './VnInputDate.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; +import FetchData from '../FetchData.vue'; +import { useValidator } from 'src/composables/useValidator'; +import { useCapitalize } from 'src/composables/useCapitalize'; -const { t } = useI18n(); -const props = defineProps({ +const $props = defineProps({ dataKey: { type: String, - required: true, + default: null, }, }); -const workers = ref(); +const { t } = useI18n(); +const route = useRoute(); +const validationsStore = useValidator(); +const { models } = validationsStore; +const entities = ref([]); +const editors = ref([]); +const userParams = ref(useFilterParams($props.dataKey).params); +let validations = models; +const userTypes = [ + { value: 'All', label: t(`Users.All`) }, + { value: 'User', label: t(`Users.User`) }, + { value: 'System', label: t(`Users.System`) }, +]; +const checkboxOptions = ref([ + { name: 'insert', label: 'Creates', selected: false }, + { name: 'update', label: 'Edits', selected: false }, + { name: 'delete', label: 'Deletes', selected: false }, + { name: 'select', label: 'Accesses', selected: false }, +]); +const columns = computed(() => [ + { name: 'changedModelValue' }, + { name: 'changedModel' }, + { name: 'userType', orderBy: false }, + { name: 'userFk' }, + { name: 'change', orderBy: false }, + { name: 'action' }, + { name: 'from', orderBy: 'creationDate' }, + { name: 'to', orderBy: 'creationDate' }, +]); + +const userParamsWatcher = watch( + () => userParams.value, + (params) => { + if (params.action) { + params.action.forEach((option) => { + checkboxOptions.value.find((o) => o.name === option).selected = true; + }); + userParamsWatcher(); + } + }, +); + +function getActions() { + const actions = checkboxOptions.value + .filter((option) => option.selected) + ?.map((o) => o.name); + return actions.length ? actions : null; +} </script> <template> <FetchData - url="Workers/activeWithInheritedRole" - :filter="{ where: { role: 'salesPerson' } }" - @on-fetch="(data) => (workers = data)" + :url="`${dataKey}s/${route.params.id}/models`" + :filter="{ order: ['changedModel'] }" + @on-fetch=" + (data) => + (entities = data.map((item) => { + const changedModel = item.changedModel; + return { + locale: useCapitalize( + validations[changedModel]?.locale?.name ?? changedModel, + ), + value: changedModel, + }; + })) + " auto-load /> - <VnFilterPanel :data-key="props.dataKey" :search-button="true"> - <template #tags="{ tag, formatFn }"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> + <FetchData + :url="`${dataKey}s/${route.params.id}/editors`" + :filter="{ fields: ['id', 'nickname', 'name', 'image'] }" + sort-by="nickname" + @on-fetch="(data) => (editors = data)" + auto-load + /> + <VnTableFilter + v-if="dataKey" + :data-key + :columns="columns" + :redirect="false" + :hiddenTags="['originFk', 'creationDate']" + :exprBuilder + search-url="logs" + :showTagChips="false" + > + <template #filter-changedModelValue="{ params, columnName, searchFn }"> + <VnInput + :label="t('globals.search')" + v-model="params[columnName]" + @keyup.enter="searchFn" + @blur="searchFn" + @remove="searchFn" + :info="t('tooltips.search')" + dense + filled + data-cy="vnLog-search" + /> + </template> + <template #filter-changedModel="{ params, columnName, searchFn }"> + <VnSelect + :label="t('globals.entity')" + v-model="params[columnName]" + option-label="locale" + option-value="value" + :options="entities" + @update:model-value="() => searchFn()" + dense + filled + data-cy="vnLog-entity" + /> + </template> + <template #filter-userType="{ params, columnName, searchFn }"> + <QOptionGroup + class="text-left" + size="sm" + v-model="params[columnName]" + :options="userTypes" + color="primary" + @update:model-value=" + () => { + params.userFk = null; + searchFn(); + } + " + /> + </template> + <template #filter-userFk="{ params, columnName, searchFn }"> + <VnSelect + :label="t('globals.user')" + v-model="params[columnName]" + :options="editors" + @update:modelValue="() => searchFn()" + :disable="params.userType === 'System'" + dense + filled + > + <template #option="{ opt, itemProps }"> + <QItem v-bind="itemProps" class="q-pa-xs row items-center"> + <QItemSection class="col-3 items-center"> + <VnAvatar :worker-id="opt.id" /> + </QItemSection> + <QItemSection class="col-9 justify-center"> + <span>{{ opt.name }}</span> + <span class="text-grey">{{ opt.nickname }}</span> + </QItemSection> + </QItem> + </template> + </VnSelect> + </template> + <template #filter-change="{ params, columnName, searchFn }"> + <VnInput + :label="t('globals.changes')" + v-model="params[columnName]" + @keyup.enter="searchFn" + @blur="searchFn" + @remove="searchFn" + :info="t('tooltips.changes')" + dense + filled + /> + </template> + <template #filter-action="{ searchFn }"> + <div class="column"> + <QCheckbox + v-for="checkboxOption in checkboxOptions" + :key="checkboxOption" + size="sm" + v-model="checkboxOption.selected" + :label="t(`actions.${checkboxOption.label}`)" + @update:model-value=" + () => searchFn(undefined, 'action', getActions()) + " + data-cy="vnLog-checkbox" + /> </div> </template> - <template #body="{ params, searchFn }"> - <QDate - v-model="params.created" - @update:model-value="searchFn()" + <template #filter-from="{ params, columnName, searchFn }"> + <VnInputDate + :label="t('globals.from')" + v-model="params[columnName]" dense - flat - minimal - > - </QDate> - <QSeparator /> - <QItem> - <QItemSection v-if="!workers"> - <QSkeleton type="QInput" class="full-width" /> - </QItemSection> - <QItemSection v-if="workers"> - <QSelect - :label="t('User')" - v-model="params.userFk" - @update:model-value="searchFn()" - :options="workers" - option-value="id" - option-label="name" - emit-value - map-options - use-input - :input-debounce="0" - /> - </QItemSection> - </QItem> + filled + @update:modelValue="() => searchFn()" + /> </template> - </VnFilterPanel> + <template #filter-to="{ params, columnName, searchFn }"> + <VnInputDate + :label="t('globals.to')" + v-model="params[columnName]" + dense + filled + @update:modelValue="() => searchFn()" + /> + </template> + </VnTableFilter> </template> - <i18n> -en: - params: - search: Contains - userFk: User - created: Created es: + tooltips: + search: Buscar por identificador o concepto + changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo. + actions: + Creates: Crea + Edits: Modifica + Deletes: Elimina + Accesses: Accede + Users: + User: Usuario + All: Todo + System: Sistema params: - search: Contiene - userFk: Usuario - created: Creada - User: Usuario + changedModel: Entity + +en: + tooltips: + search: Search by identifier or concept + changes: Search by changes. Attributes must be searched by their internal name, to get it place the cursor over the attribute. + actions: + Creates: Creates + Edits: Edits + Deletes: Deletes + Accesses: Accesses + Users: + User: User + All: All + System: System + params: + changedModel: Entidad </i18n> diff --git a/src/components/common/VnLogValue.vue b/src/components/common/VnLogValue.vue index df0be4011..3f1617ce7 100644 --- a/src/components/common/VnLogValue.vue +++ b/src/components/common/VnLogValue.vue @@ -5,18 +5,24 @@ import { computed } from 'vue'; const descriptorStore = useDescriptorStore(); const $props = defineProps({ - name: { type: [String], default: undefined }, + value: { type: Object, default: () => {} }, + 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" /> + <VnJsonValue :value="value.val" /> + <span + v-if="(value.id || typeof value.val == 'number') && descriptor" + style="margin-left: 2px" + > + <QIcon + name="launch" + class="link" + :data-cy="'iconLaunch-' + $props.name" + style="padding-bottom: 2px" + /> + <component :is="descriptor" :id="value.id ?? value.val" /> + </span> </template> diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 6eda03891..32a8db16f 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -152,6 +152,10 @@ const value = computed({ }, }); +const computedSortBy = computed(() => { + return $props.sortBy || $props.optionLabel + ' ASC'; +}); + watch(options, (newValue) => { setOptions(newValue); }); @@ -186,7 +190,7 @@ function findKeyInOptions() { } function setOptions(data) { - data = dataByOrder(data, $props.sortBy); + data = dataByOrder(data, computedSortBy.value); myOptions.value = JSON.parse(JSON.stringify(data)); myOptionsOriginal.value = JSON.parse(JSON.stringify(data)); emit('update:options', data); @@ -216,7 +220,8 @@ function filter(val, options) { async function fetchFilter(val) { if (!$props.url) return; - const { fields, include, sortBy, limit } = $props; + const { fields, include, limit } = $props; + const sortBy = computedSortBy.value; const key = optionFilterValue.value ?? (new RegExp(/\d/g).test(val) diff --git a/src/components/common/__tests__/VnLog.spec.js b/src/components/common/__tests__/VnLog.spec.js index 53d2732a0..7f33578df 100644 --- a/src/components/common/__tests__/VnLog.spec.js +++ b/src/components/common/__tests__/VnLog.spec.js @@ -108,27 +108,4 @@ describe('VnLog', () => { expect(vm.logTree[0].originFk).toEqual(1); expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); }); - - it('should correctly set the selectedFilters when filtering', () => { - vm.searchInput = '1'; - vm.userSelect = '21'; - vm.checkboxOptions.insert.selected = true; - vm.checkboxOptions.update.selected = true; - - vm.selectFilter('search'); - vm.selectFilter('userSelect'); - - expect(vm.selectedFilters.changedModelId).toEqual('1'); - expect(vm.selectedFilters.userFk).toEqual('21'); - expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); - }); - - it('should correctly set the date from', () => { - vm.dateFrom = '18-09-2023'; - vm.selectFilter('date', 'from'); - expect(vm.selectedFilters.creationDate.between).toEqual([ - new Date('2023-09-18T00:00:00.000Z'), - new Date('2023-09-18T21:59:59.999Z'), - ]); - }); }); diff --git a/src/components/common/__tests__/VnLogFilter.spec.js b/src/components/common/__tests__/VnLogFilter.spec.js new file mode 100644 index 000000000..a28fa85b1 --- /dev/null +++ b/src/components/common/__tests__/VnLogFilter.spec.js @@ -0,0 +1,28 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnLogFilter from 'src/components/common/VnLogFilter.vue'; + +describe('VnLogFilter', () => { + let vm; + beforeAll(async () => { + vm = createWrapper(VnLogFilter, { + props: { + dataKey: 'ClaimLog', + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should getActions selected', async () => { + vm.checkboxOptions.find((o) => o.name == 'insert').selected = true; + vm.checkboxOptions.find((o) => o.name == 'update').selected = true; + + const actions = vm.getActions(); + + expect(actions.length).toEqual(2); + expect(actions).toEqual(['insert', 'update']); + }); +}); diff --git a/src/components/common/__tests__/VnNotes.spec.js b/src/components/common/__tests__/VnNotes.spec.js index 2603bf03c..ea595060a 100644 --- a/src/components/common/__tests__/VnNotes.spec.js +++ b/src/components/common/__tests__/VnNotes.spec.js @@ -1,16 +1,6 @@ -import { - describe, - it, - expect, - vi, - beforeAll, - afterEach, - beforeEach, - afterAll, -} from 'vitest'; +import { describe, it, expect, vi, afterEach, beforeEach, afterAll } from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import VnNotes from 'src/components/ui/VnNotes.vue'; -import vnDate from 'src/boot/vnDate'; describe('VnNotes', () => { let vm; @@ -18,6 +8,7 @@ describe('VnNotes', () => { let spyFetch; let postMock; let patchMock; + let deleteMock; let expectedInsertBody; let expectedUpdateBody; const defaultOptions = { @@ -57,6 +48,7 @@ describe('VnNotes', () => { beforeEach(() => { postMock = vi.spyOn(axios, 'post'); patchMock = vi.spyOn(axios, 'patch'); + deleteMock = vi.spyOn(axios, 'delete'); }); afterEach(() => { @@ -153,4 +145,16 @@ describe('VnNotes', () => { ); }); }); + + describe('delete', () => { + it('Should call axios.delete with url and vnPaginateRef.fetch', async () => { + generateWrapper(); + createSpyFetch(); + + await vm.deleteNote({ id: 1 }); + + expect(deleteMock).toHaveBeenCalledWith(`${vm.$props.url}/1`); + expect(spyFetch).toHaveBeenCalled(); + }); + }); }); diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 05bfed998..2ec6bea78 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -201,6 +201,29 @@ async function fetch() { } } } + +.vn-card-group { + display: flex; + flex-direction: column; +} + +.vn-card-content { + display: flex; + flex-direction: column; + text-overflow: ellipsis; + > div { + max-height: 70px; + } +} + +@media (min-width: 1010px) { + .vn-card-group { + flex-direction: row; + } + .vn-card-content { + flex: 1; + } +} </style> <style lang="scss" scoped> .summaryHeader .vn-label-value { diff --git a/src/components/ui/VnDescriptor.vue b/src/components/ui/VnDescriptor.vue index 47da98d74..878adcadc 100644 --- a/src/components/ui/VnDescriptor.vue +++ b/src/components/ui/VnDescriptor.vue @@ -30,7 +30,7 @@ const $props = defineProps({ default: null, }, toModule: { - type: String, + type: Object, default: null, }, }); diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 85cc8cde2..dc9e4e776 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -61,6 +61,10 @@ const $props = defineProps({ type: Object, default: null, }, + showTagChips: { + type: Boolean, + default: true, + }, }); const emit = defineEmits([ @@ -88,13 +92,14 @@ const userOrders = ref(useFilterParams($props.dataKey).orders); defineExpose({ search, params: userParams, remove }); const isLoading = ref(false); -async function search(evt) { +async function search(evt, name, value) { try { if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; isLoading.value = true; const filter = { ...userParams.value, ...$props.modelValue }; + if (name) filter[name] = value; store.userParamsChanged = true; await arrayData.addFilter({ params: filter, @@ -214,7 +219,7 @@ const getLocale = (label) => { </QTooltip> </QBtn> <QForm @submit="search" id="filterPanelForm" @keyup.enter="search()"> - <QList dense> + <QList dense v-if="showTagChips"> <QItem class="q-mt-xs"> <QItemSection top> <QItemLabel header lines="1" class="text-uppercase q-py-xs q-px-none"> diff --git a/src/components/ui/VnLinkPhone.vue b/src/components/ui/VnLinkPhone.vue index a9e9bc0fc..4174e4ae6 100644 --- a/src/components/ui/VnLinkPhone.vue +++ b/src/components/ui/VnLinkPhone.vue @@ -57,4 +57,5 @@ function handleClick() { {{ capitalize(type).replace('-', '') }} </QTooltip> </QBtn> + {{ phoneNumber }} </template> diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index 50da8a143..aa7342742 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -35,6 +35,7 @@ const val = computed(() => $props.value); :label="label" disable dense + size="sm" /> <template v-else> <div v-if="label || $slots.label" class="label"> @@ -42,9 +43,9 @@ const val = computed(() => $props.value); <span style="color: var(--vn-label-color)">{{ label }}</span> </slot> </div> - <div class="value"> + <div class="value" v-if="value || $slots.value"> <slot name="value"> - <span :title="value"> + <span :title="value" style="text-overflow: ellipsis"> {{ dash ? dashIfEmpty(value) : value }} </span> </slot> diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue index 984e2b64f..bc81233d5 100644 --- a/src/components/ui/VnMoreOptions.vue +++ b/src/components/ui/VnMoreOptions.vue @@ -9,7 +9,7 @@ data-cy="descriptor-more-opts" > <QTooltip> - {{ $t('components.cardDescriptor.moreOptions') }} + {{ $t('components.vnDescriptor.moreOptions') }} </QTooltip> <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> <QList data-cy="descriptor-more-opts_list"> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index b7e6ccbec..9cedbccfa 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -18,10 +18,10 @@ import VnInput from 'components/common/VnInput.vue'; const emit = defineEmits(['onFetch']); -const $attrs = useAttrs(); - -const isRequired = computed(() => { - return Object.keys($attrs).includes('required'); +const originalAttrs = useAttrs(); +const $attrs = computed(() => { + const { required, deletable, ...rest } = originalAttrs; + return rest; }); const $props = defineProps({ @@ -53,6 +53,11 @@ function handleClick(e) { else insert(); } +async function deleteNote(e) { + await axios.delete(`${$props.url}/${e.id}`); + await vnPaginateRef.value.fetch(); +} + async function insert() { if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return; @@ -157,7 +162,7 @@ const handleObservationTypes = (data) => { v-model="newNote.observationTypeFk" option-label="description" style="flex: 0.15" - :required="isRequired" + :required="'required' in originalAttrs" @keyup.enter.stop="insert" /> <VnInput @@ -165,11 +170,10 @@ const handleObservationTypes = (data) => { type="textarea" :label="$props.justInput && newNote.text ? '' : t('Add note here...')" filled - size="lg" autogrow autofocus @keyup.enter.stop="handleClick" - :required="isRequired" + :required="'required' in originalAttrs" clearable > <template #append> @@ -239,6 +243,21 @@ const handleObservationTypes = (data) => { </QBadge> </div> <span v-text="toDateHourMin(note.created)" /> + <div> + <QIcon + v-if="'deletable' in originalAttrs" + name="delete" + size="sm" + class="cursor-pointer" + color="primary" + @click="deleteNote(note)" + data-cy="notesRemoveNoteBtn" + > + <QTooltip> + {{ t('ticketNotes.removeNote') }} + </QTooltip> + </QIcon> + </div> </div> </QCardSection> <QCardSection class="q-pa-xs q-my-none q-py-none"> diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index b067381f6..7facb7916 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -215,6 +215,7 @@ defineExpose({ paginate, userParams: arrayData.store.userParams, currentFilter: arrayData.store.currentFilter, + arrayData, }); </script> diff --git a/src/composables/__tests__/useRole.spec.js b/src/composables/__tests__/useRole.spec.js index d0bca5342..54d983a13 100644 --- a/src/composables/__tests__/useRole.spec.js +++ b/src/composables/__tests__/useRole.spec.js @@ -23,18 +23,19 @@ describe('useRole', () => { name: `T'Challa`, nickname: 'Black Panther', lang: 'en', + worker: { department: { departmentFk: 155 } }, }; const expectedUser = { id: 999, name: `T'Challa`, nickname: 'Black Panther', lang: 'en', + departmentFk: 155, }; const expectedRoles = ['salesPerson', 'admin']; - vi.spyOn(axios, 'get') - .mockResolvedValueOnce({ + vi.spyOn(axios, 'get').mockResolvedValueOnce({ data: { roles: rolesData, user: fetchedUser }, - }) + }); vi.spyOn(role.state, 'setUser'); vi.spyOn(role.state, 'setRoles'); diff --git a/src/composables/__tests__/useSession.spec.js b/src/composables/__tests__/useSession.spec.js index 789b149ec..cae33f893 100644 --- a/src/composables/__tests__/useSession.spec.js +++ b/src/composables/__tests__/useSession.spec.js @@ -75,6 +75,7 @@ describe('session', () => { userConfig: { darkMode: false, }, + worker: { department: { departmentFk: 155 } }, }; const rolesData = [ { @@ -143,7 +144,7 @@ describe('session', () => { await session.destroy(); // this clears token and user for any other test }); }, - {} + {}, ); describe('RenewToken', () => { @@ -175,7 +176,7 @@ describe('session', () => { await session.checkValidity(); expect(sessionStorage.getItem('token')).toEqual(expectedToken); expect(sessionStorage.getItem('tokenMultimedia')).toEqual( - expectedTokenMultimedia + expectedTokenMultimedia, ); }); it('Should renewToken', async () => { @@ -204,7 +205,7 @@ describe('session', () => { await session.checkValidity(); expect(sessionStorage.getItem('token')).not.toEqual(expectedToken); expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual( - expectedTokenMultimedia + expectedTokenMultimedia, ); }); }); diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index d1c1b01b8..363580148 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -189,7 +189,7 @@ export function useArrayData(key, userOptions) { store.order = order; resetPagination(); - fetch({}); + await fetch({}); index++; return { index, order }; diff --git a/src/composables/useFilterParams.js b/src/composables/useFilterParams.js index 07dcdf99b..7c3f3bdeb 100644 --- a/src/composables/useFilterParams.js +++ b/src/composables/useFilterParams.js @@ -14,7 +14,7 @@ export function useFilterParams(key) { watch( () => arrayData.value.store?.currentFilter, (val, oldValue) => (val || oldValue) && setUserParams(val), - { immediate: true, deep: true } + { immediate: true, deep: true }, ); function parseOrder(urlOrders) { @@ -54,7 +54,7 @@ export function useFilterParams(key) { Object.assign(params, item); }); delete params[key]; - } else if (value && typeof value === 'object') { + } else if (value && typeof value === 'object' && !Array.isArray(value)) { const param = Object.values(value)[0]; if (typeof param == 'string') params[key] = param.replaceAll('%', ''); } diff --git a/src/composables/useRole.js b/src/composables/useRole.js index ff54b409c..e700b1f2e 100644 --- a/src/composables/useRole.js +++ b/src/composables/useRole.js @@ -13,6 +13,7 @@ export function useRole() { name: data.user.name, nickname: data.user.nickname, lang: data.user.lang || 'es', + departmentFk: data.user.worker.department.departmentFk, }; state.setUser(userData); state.setRoles(roles); diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index c62305f95..7bcf90793 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -370,6 +370,11 @@ globals: countryCodeFk: Country companyFk: Company nickname: Alias + changedModel: Entity + changedModelValue: Search + changedModelId: Entity id + userFk: User + action: Action model: Model fuel: Fuel active: Active @@ -842,6 +847,7 @@ travel: availabledHour: Availabled hour thermographs: Thermographs hb: HB + roundedCc: Rounded CC basicData: daysInForward: Automatic movement (Raid) isRaid: Raid @@ -884,7 +890,7 @@ components: openCard: View openSummary: Summary viewSummary: Summary - cardDescriptor: + vnDescriptor: mainList: Main list summary: Summary moreOptions: More options diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 86d15e985..b2512193d 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -371,6 +371,11 @@ globals: countryCodeFk: País companyFk: Empresa nickname: Alias + changedModel: Entidad + changedModelValue: Buscar + changedModelId: Id de entidad + userFk: Usuario + action: Acción errors: statusUnauthorized: Acceso denegado statusInternalServerError: Ha ocurrido un error interno del servidor @@ -925,6 +930,7 @@ travel: availabled: F. Disponible availabledHour: Hora Disponible hb: HB + roundedCc: CC redondeado basicData: daysInForward: Desplazamiento automatico (redada) isRaid: Redada @@ -968,7 +974,7 @@ components: openCard: Ficha openSummary: Detalles viewSummary: Vista previa - cardDescriptor: + vnDescriptor: mainList: Listado principal summary: Resumen moreOptions: Más opciones diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 5d06d5627..67d57004f 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -21,6 +21,7 @@ import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorP import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import ClaimDescriptorMenu from './ClaimDescriptorMenu.vue'; +import VnDropdown from 'src/components/common/VnDropdown.vue'; const route = useRoute(); const router = useRouter(); @@ -36,7 +37,7 @@ const $props = defineProps({ }); const entityId = computed(() => $props.id || route.params.id); -const ClaimStates = ref([]); +const claimStates = ref([]); const claimDmsRef = ref(); const claimDms = ref([]); const multimediaDialog = ref(); @@ -173,7 +174,9 @@ function openDialog(dmsId) { } async function changeState(value) { - await axios.patch(`Claims/updateClaim/${entityId.value}`, { claimStateFk: value }); + await axios.patch(`Claims/updateClaim/${entityId.value}`, { + claimStateFk: value, + }); router.go(route.fullPath); } @@ -183,13 +186,18 @@ function claimUrl(section) { </script> <template> + <FetchData + url="ClaimStates" + :filter="{ fields: ['id', 'description'] }" + @on-fetch="(data) => (claimStates = data)" + auto-load + /> <FetchData url="ClaimDms" :filter="claimDmsFilter" @on-fetch="(data) => setClaimDms(data)" ref="claimDmsRef" /> - <FetchData url="ClaimStates" @on-fetch="(data) => (ClaimStates = data)" auto-load /> <CardSummary ref="summary" :url="`Claims/${entityId}/getSummary`" @@ -201,34 +209,11 @@ function claimUrl(section) { {{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }}) </template> <template #header-right> - <QBtnDropdown - side - top - color="black" - text-color="white" - :label="t('globals.changeState')" - > - <QList> - <QVirtualScroll - class="max-container-height" - :items="ClaimStates" - separator - v-slot="{ item, index }" - > - <QItem - :key="index" - dense - clickable - v-close-popup - @click="changeState(item.id)" - > - <QItemSection> - <QItemLabel>{{ item.description }}</QItemLabel> - </QItemSection> - </QItem> - </QVirtualScroll> - </QList> - </QBtnDropdown> + <VnDropdown + :options="claimStates" + option-label="description" + @change-state="changeState" + /> </template> <template #menu="{ entity }"> <ClaimDescriptorMenu :claim="entity.claim" /> diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index 51460f7e4..45eb89382 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -115,6 +115,7 @@ const props = defineProps({ <i18n> en: params: + departmentFk: Department search: Contains clientFk: Customer clientName: Customer @@ -127,6 +128,7 @@ en: zoneFk: Zone es: params: + departmentFk: Departamento search: Contiene clientFk: Cliente clientName: Cliente diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index 06996c2c1..e0d9928f9 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -134,7 +134,7 @@ const columns = computed(() => [ const STATE_COLOR = { pending: 'bg-warning', - managed: 'bg-info', + loses: 'bg-negative', resolved: 'bg-positive', }; </script> diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue index 11db92eab..15f80b2f6 100644 --- a/src/pages/Customer/Card/CustomerBalance.vue +++ b/src/pages/Customer/Card/CustomerBalance.vue @@ -20,6 +20,7 @@ import VnFilter from 'components/VnTable/VnFilter.vue'; import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; +import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const { openConfirmationModal } = useVnConfirm(); const { sendEmail, openReport } = usePrintService(); @@ -89,15 +90,7 @@ const columns = computed(() => [ { align: 'left', label: t('Employee'), - columnField: { - component: 'userLink', - attrs: ({ row }) => { - return { - workerId: row.workerFk, - name: row.userName, - }; - }, - }, + name: 'workerFk', cardVisible: true, }, { @@ -131,7 +124,6 @@ const columns = computed(() => [ align: 'left', name: 'balance', label: t('Balance'), - format: ({ balance }) => toCurrency(balance), cardVisible: true, }, { @@ -146,12 +138,14 @@ const columns = computed(() => [ actions: [ { title: t('globals.downloadPdf'), + isPrimary: true, icon: 'cloud_download', show: (row) => row.isInvoice, action: (row) => showBalancePdf(row), }, { title: t('Send compensation'), + isPrimary: true, icon: 'outgoing_mail', show: (row) => !!row.isCompensation, action: ({ id }) => @@ -256,6 +250,12 @@ const showBalancePdf = ({ id }) => { <template #column-balance="{ rowIndex }"> {{ toCurrency(balances[rowIndex]?.balance) }} </template> + <template #column-workerFk="{ row }"> + <span class="link" @click.stop> + {{ row.userName }} + <WorkerDescriptorProxy :id="row.workerFk" /> + </span> + </template> <template #column-description="{ row }"> <span class="link" v-if="row.isInvoice" @click.stop> {{ t('bill', { ref: row.description }) }} diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index cd18cf2c9..c7461f890 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -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" 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 55a7f565e..107a08144 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -156,6 +156,7 @@ en: email: Email isToBeMailed: Mailed isEqualizated: Equailized + departmentFk: Department businessTypeFk: Business type sageTaxTypeFk: Sage Tax Type sageTransactionTypeFk: Sage Tax Type @@ -166,6 +167,7 @@ en: postcode: Postcode es: params: + departmentFk: Departamento search: Contiene fi: NIF isActive: Activo diff --git a/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue b/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue index 64e3baeb5..f7d4163d1 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue @@ -192,8 +192,10 @@ en: date: L. O. Date credit: Credit I. defaulterSinced: From + departmentFk: Department es: params: + departmentFk: Departamento clientFk: Cliente countryFk: País paymentMethod: F. Pago diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index ac80fdaa4..fb3804d55 100644 --- a/src/pages/Customer/components/CustomerNewPayment.vue +++ b/src/pages/Customer/components/CustomerNewPayment.vue @@ -3,18 +3,20 @@ import { onBeforeMount, reactive, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import axios from 'axios'; -import { getClientRisk } from '../composables/getClientRisk'; import { useDialogPluginComponent } from 'quasar'; -import FormModelPopup from 'components/FormModelPopup.vue'; + +import { getClientRisk } from '../composables/getClientRisk'; import { usePrintService } from 'composables/usePrintService'; import useNotify from 'src/composables/useNotify.js'; + +import FormModelPopup from 'components/FormModelPopup.vue'; import FetchData from 'components/FetchData.vue'; -import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputNumber from 'components/common/VnInputNumber.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; +import VnAccountNumber from 'src/components/common/VnAccountNumber.vue'; const { t } = useI18n(); const route = useRoute(); @@ -48,7 +50,7 @@ const maxAmount = ref(); const accountingType = ref({}); const isCash = ref(false); const formModelRef = ref(false); - +const amountToReturn = ref(); const filterBanks = { fields: ['id', 'bank', 'accountingTypeFk'], include: { relation: 'accountingType' }, @@ -90,7 +92,7 @@ function setPaymentType(data, accounting) { let descriptions = []; if (accountingType.value.receiptDescription) descriptions.push(accountingType.value.receiptDescription); - if (data.description) descriptions.push(data.description); + if (data.description > 0) descriptions.push(data.description); data.description = descriptions.join(', '); } @@ -100,7 +102,7 @@ const calculateFromAmount = (event) => { }; const calculateFromDeliveredAmount = (event) => { - initialData.amountToReturn = parseFloat(event) - initialData.amountPaid; + amountToReturn.value = event - initialData.amountPaid; }; function onBeforeSave(data) { @@ -121,17 +123,16 @@ async function onDataSaved(formData, { id }) { recipient: formData.email, }); - if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`); + if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank'); } finally { if ($props.promise) $props.promise(); if (closeButton.value) closeButton.value.click(); } } -async function accountShortToStandard({ target: { value } }) { +async function getSupplierClientReferences(value) { if (!value) return (initialData.description = ''); - initialData.compensationAccount = value.replace('.', '0'.repeat(11 - value.length)); - const params = { bankAccount: initialData.compensationAccount }; + const params = { bankAccount: value }; const { data } = await axios(`Clients/getClientOrSupplierReference`, { params }); if (!data.clientId) { initialData.description = t('Supplier Compensation Reference', { @@ -241,17 +242,16 @@ async function getAmountPaid() { @update:model-value="getAmountPaid()" /> </VnRow> - - <div v-if="data.bankFk?.accountingType?.code == 'compensation'"> + <div v-if="accountingType.code == 'compensation'"> <div class="text-h6"> {{ t('Compensation') }} </div> <VnRow> - <VnInputNumber + <VnAccountNumber :label="t('Compensation account')" clearable v-model="data.compensationAccount" - @blur="accountShortToStandard" + @blur="getSupplierClientReferences(data.compensationAccount)" /> </VnRow> </div> @@ -261,8 +261,7 @@ async function getAmountPaid() { clearable v-model="data.description" /> - - <div v-if="data.bankFk?.accountingType?.code == 'cash'"> + <div v-if="accountingType.code == 'cash'"> <div class="text-h6">{{ t('Cash') }}</div> <VnRow> <VnInputNumber @@ -274,7 +273,7 @@ async function getAmountPaid() { <VnInputNumber :label="t('Amount to return')" disable - v-model="data.amountToReturn" + v-model="amountToReturn" /> </VnRow> <VnRow> diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue index 93469338a..c5c44b55e 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -599,7 +599,7 @@ onMounted(() => { :url="`Entries/${entityId}/getBuyList`" search-url="EntryBuys" save-url="Buys/crud" - :filter="filter" + :filter="editableMode ? filter : {}" :disable-option="{ card: true }" v-model:selected="selectedRows" @on-fetch="() => footerFetchDataRef.fetch()" @@ -665,6 +665,7 @@ onMounted(() => { :fields="['id', 'nickname']" option-label="nickname" sort-by="nickname ASC" + :use-like="false" /> <VnSelect :label="t('Family')" diff --git a/src/pages/Entry/Card/EntryCard.vue b/src/pages/Entry/Card/EntryCard.vue index 50f8b8e55..e9d07889f 100644 --- a/src/pages/Entry/Card/EntryCard.vue +++ b/src/pages/Entry/Card/EntryCard.vue @@ -8,6 +8,6 @@ import filter from './EntryFilter.js'; data-key="Entry" url="Entries" :descriptor="EntryDescriptor" - :filter="filter" + :filter="{ ...filter, where: { id: $route.params.id } }" /> </template> diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 202f94997..2f9cfe0ff 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -147,7 +147,7 @@ async function deleteEntry() { <template> <EntityDescriptor :url="`Entries/${entityId}`" - :user-filter="entryFilter" + :filter="entryFilter" title="supplier.nickname" data-key="Entry" width="lg-width" diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index 5ebad3144..e42380fa3 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -248,7 +248,6 @@ function getBadgeAttrs(row) { let timeDiff = today - timeTicket; - if (timeDiff > 0) return { color: 'info', 'text-color': 'black' }; if (timeDiff < 0) return { color: 'warning', 'text-color': 'black' }; switch (row.entryTypeCode) { case 'regularization': @@ -274,6 +273,7 @@ function getBadgeAttrs(row) { default: break; } + if (timeDiff > 0) return { color: 'info', 'text-color': 'black' }; return { color: 'transparent' }; } diff --git a/src/pages/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue index 6168f0737..9e97e2ad5 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -116,6 +116,7 @@ const filter = computed(() => ({ hour: 0, minute: 0, second: 0, + milliseconds: 0, }), m3: { neq: null }, }, diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index dc963a91b..c3b678678 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -158,20 +158,12 @@ function deleteFile(dmsFk) { </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="invoiceInBasicDataDeductibleExpenseFk" - > - <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 @@ -271,16 +263,6 @@ function deleteFile(dmsFk) { data-cy="invoiceInBasicDataCompanyFk" /> </VnRow> - <VnRow> - <VnSelect - :label="t('invoiceIn.summary.sage')" - v-model="data.withholdingSageFk" - :options="sageWithholdings" - option-value="id" - option-label="withholding" - data-cy="invoiceInBasicDataWithholdingSageFk" - /> - </VnRow> </template> </FormModel> <QDialog v-model="documentDialogRef.show"> @@ -332,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/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue index 20cc1cc71..59bebcae2 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -25,7 +25,8 @@ const invoiceInFormRef = ref(); const invoiceId = +route.params.id; const filter = { where: { invoiceInFk: invoiceId } }; const areRows = ref(false); -const totals = ref(); +const totalTaxableBase = ref(); +const noMatch = computed(() => totalAmount.value != totalTaxableBase.value); const columns = computed(() => [ { name: 'duedate', @@ -74,9 +75,12 @@ async function insert() { notify(t('globals.dataSaved'), 'positive'); } -onBeforeMount(async () => { - totals.value = (await axios.get(`InvoiceIns/${invoiceId}/getTotals`)).data; -}); +async function setTaxableBase() { + const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`); + totalTaxableBase.value = data.totalTaxableBase; +} + +onBeforeMount(async () => await setTaxableBase()); </script> <template> <CrudModel @@ -89,13 +93,14 @@ onBeforeMount(async () => { :data-required="{ invoiceInFk: invoiceId }" v-model:selected="rowsSelected" @on-fetch="(data) => (areRows = !!data.length)" + @save-changes="setTaxableBase" > <template #body="{ rows }"> <QTable v-model:selected="rowsSelected" selection="multiple" - :columns="columns" - :rows="rows" + :columns + :rows row-key="$index" :grid="$q.screen.lt.sm" > @@ -151,7 +156,18 @@ onBeforeMount(async () => { <QTd /> <QTd /> <QTd> - {{ toCurrency(totalAmount) }} + <QChip + dense + :color="noMatch ? 'negative' : 'transparent'" + class="q-pa-xs" + :title=" + noMatch + ? t('invoiceIn.noMatch', { totalTaxableBase }) + : '' + " + > + {{ toCurrency(totalAmount) }} + </QChip> </QTd> <QTd> <template v-if="isNotEuro(invoiceIn.currency.code)"> @@ -237,7 +253,7 @@ onBeforeMount(async () => { if (!areRows) insert(); else invoiceInFormRef.insert({ - amount: (totals.totalTaxableBase - totalAmount).toFixed(2), + amount: (totalTaxableBase - totalAmount).toFixed(2), invoiceInFk: invoiceId, }); } @@ -249,6 +265,10 @@ onBeforeMount(async () => { .bg { background-color: var(--vn-light-gray); } + +.q-chip { + color: var(--vn-text-color); +} </style> <i18n> es: diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index f6beecd3d..74936f00a 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', @@ -207,113 +214,109 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <InvoiceInDescriptorMenu :invoice="entity" /> </template> <template #body="{ entity }"> - <!--Basic Data--> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <VnLv - :label="t('invoiceIn.list.supplier')" - :value="entity.supplier?.name" - > - <template #value> - <span class="link" data-cy="invoiceInSummary_supplier"> - {{ entity.supplier?.name }} - <SupplierDescriptorProxy :id="entity.supplierFk" /> - </span> - </template> - </VnLv> - <VnLv :label="t('invoiceIn.supplierRef')" :value="entity.supplierRef" /> - <VnLv - :label="t('invoiceIn.summary.currency')" - :value="entity.currency?.code" + <QCard class="max-width"> + <VnTitle + :url="getLink('basic-data')" + :text="t('globals.pageTitles.basicData')" /> - <VnLv :label="t('invoiceIn.serial')" :value="`${entity.serial}`" /> - <VnLv - :label="t('globals.country')" - :value="entity.supplier?.country?.code" - /> - </QCard> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <VnLv - :ellipsis-value="false" - :label="t('invoiceIn.summary.issued')" - :value="toDate(entity.issued)" - /> - <VnLv - :label="t('invoiceIn.summary.operated')" - :value="toDate(entity.operated)" - /> - <VnLv - :label="t('invoiceIn.summary.bookEntried')" - :value="toDate(entity.bookEntried)" - /> - <VnLv - :label="t('invoiceIn.summary.bookedDate')" - :value="toDate(entity.booked)" - /> - <VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" /> - </QCard> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <VnLv - :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" - /> - <VnLv :label="t('invoiceIn.isBooked')" :value="invoiceIn?.isBooked" /> - </QCard> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <QCardSection class="q-pa-none"> - <VnLv - :label="t('invoiceIn.summary.taxableBase')" - :value="toCurrency(entity.totals.totalTaxableBase)" - /> - <VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" /> - <VnLv :label="t('invoiceIn.summary.dueTotal')"> - <template #value> - <QChip - dense - class="q-pa-xs" - :color="amountsNotMatch ? 'negative' : 'transparent'" - :title=" - amountsNotMatch - ? t('invoiceIn.summary.noMatch') - : t('invoiceIn.summary.dueTotal') - " - > - {{ toCurrency(entity.totals.totalDueDay) }} - </QChip> - </template> - </VnLv> - </QCardSection> + <div class="vn-card-group"> + <div class="vn-card-content"> + <VnLv + :label="t('invoiceIn.list.supplier')" + :value="entity.supplier?.name" + > + <template #value> + <span class="link" data-cy="invoiceInSummary_supplier"> + {{ entity.supplier?.name }} + <SupplierDescriptorProxy :id="entity.supplierFk" /> + </span> + </template> + </VnLv> + <VnLv + :label="t('invoiceIn.supplierRef')" + :value="entity.supplierRef" + /> + <VnLv + :label="t('invoiceIn.summary.currency')" + :value="entity.currency?.code" + /> + <VnLv + :label="t('invoiceIn.serial')" + :value="`${entity.serial}`" + /> + <VnLv + :label="t('globals.country')" + :value="entity.supplier?.country?.code" + /> + </div> + <div class="vn-card-content"> + <VnLv + :ellipsis-value="false" + :label="t('invoiceIn.summary.issued')" + :value="toDate(entity.issued)" + /> + <VnLv + :label="t('invoiceIn.summary.operated')" + :value="toDate(entity.operated)" + /> + <VnLv + :label="t('invoiceIn.summary.bookEntried')" + :value="toDate(entity.bookEntried)" + /> + <VnLv + :label="t('invoiceIn.summary.bookedDate')" + :value="toDate(entity.booked)" + /> + <VnLv + :label="t('globals.isVies')" + :value="entity.supplier?.isVies" + /> + </div> + <div class="vn-card-content"> + <VnLv + :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" + /> + <VnLv + :label="t('invoiceIn.isBooked')" + :value="invoiceIn?.isBooked" + /> + </div> + <div class="vn-card-content"> + <VnLv + :label="t('invoiceIn.summary.taxableBase')" + :value="toCurrency(entity.totals.totalTaxableBase)" + /> + <VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" /> + <VnLv :label="t('invoiceIn.summary.dueTotal')"> + <template #value> + <QChip + dense + class="q-pa-xs" + :color="amountsNotMatch ? 'negative' : 'transparent'" + :title=" + amountsNotMatch + ? t('invoiceIn.noMatch', { + totalTaxableBase: + entity.totals.totalTaxableBase, + }) + : t('invoiceIn.summary.dueTotal') + " + > + {{ toCurrency(entity.totals.totalDueDay) }} + </QChip> + </template> + </VnLv> + </div> + </div> </QCard> <!--Vat--> <QCard v-if="entity.invoiceInTax.length" class="vat"> @@ -335,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 e37cf5b7e..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, @@ -230,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 @@ -324,6 +340,7 @@ function setCursor(ref) { </QTd> <QTd /> <QTd /> + <QTd /> <QTd> {{ toCurrency(taxRateTotal) }} </QTd> diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 0960d0d6c..10ddcbf05 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -119,7 +119,7 @@ const cols = computed(() => [ icon: 'preview', type: 'submit', isPrimary: true, - action: (row) => viewSummary(row.id, InvoiceInSummary), + action: (row) => viewSummary(row.id, InvoiceInSummary, 'lg-width'), }, { title: t('globals.download'), @@ -156,7 +156,7 @@ const cols = computed(() => [ :create="{ urlCreate: 'InvoiceIns', title: t('globals.createInvoiceIn'), - onDataSaved: ({ id }) => tableRef.redirect(id), + onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`), formInitialData: { companyFk: user.companyFk, issued: Date.vnNew() }, }" redirect="invoice-in" diff --git a/src/pages/InvoiceIn/InvoiceInToBook.vue b/src/pages/InvoiceIn/InvoiceInToBook.vue index 23175f2e7..28f54f040 100644 --- a/src/pages/InvoiceIn/InvoiceInToBook.vue +++ b/src/pages/InvoiceIn/InvoiceInToBook.vue @@ -56,8 +56,9 @@ async function checkToBook(id) { componentProps: { title: t('Are you sure you want to book this invoice?'), message: messages.reduce((acc, msg) => `${acc}<p>${msg}</p>`, ''), + promise: () => toBook(id), }, - }).onOk(() => toBook(id)); + }); } async function toBook(id) { diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 548e6c201..d341c0aa7 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 @@ -57,7 +58,6 @@ invoiceIn: bank: Bank foreignValue: Foreign value dueTotal: Due day - noMatch: Do not match code: Code net: Net stems: Stems @@ -68,3 +68,4 @@ invoiceIn: isBooked: Is booked account: Ledger account correctingFk: Rectificative + noMatch: No match with the vat({totalTaxableBase}) diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 142d95f92..c53d227fe 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 @@ -66,3 +67,4 @@ invoiceIn: isBooked: Contabilizada account: Cuenta contable correctingFk: Rectificativa + noMatch: No cuadra con el iva({totalTaxableBase}) diff --git a/src/pages/Monitor/MonitorClients.vue b/src/pages/Monitor/MonitorClients.vue index 278b0b26f..2ba5f4c0b 100644 --- a/src/pages/Monitor/MonitorClients.vue +++ b/src/pages/Monitor/MonitorClients.vue @@ -1,19 +1,22 @@ <script setup> import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from '../Worker/Department/Card/DepartmentDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import { toDateFormat } from 'src/filters/date.js'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnRow from 'src/components/ui/VnRow.vue'; import { dateRange } from 'src/filters'; +import useOpenURL from 'src/composables/useOpenURL'; +import { useState } from 'src/composables/useState'; const { t } = useI18n(); const dates = dateRange(Date.vnNew()); const from = ref(dates[0]); const to = ref(dates[1]); +const state = useState(); const filter = computed(() => { const obj = {}; const formatFrom = setHours(from.value, 'from'); @@ -23,16 +26,18 @@ const filter = computed(() => { if (!formatFrom && formatTo) stamp = { lte: formatTo }; else if (formatFrom && !formatTo) stamp = { gte: formatFrom }; else if (formatFrom && formatTo) stamp = { between: [formatFrom, formatTo] }; - - return Object.assign(obj, { where: { 'v.stamp': stamp } }); + return Object.assign(obj, { + where: { + 'v.stamp': stamp, + 'c.departmentFk': state.getUser().value.departmentFk, + }, + }); }); function exprBuilder(param, value) { switch (param) { case 'clientFk': return { [`c.id`]: value }; - case 'departmentFk': - return { [`c.${param}`]: value }; } } @@ -65,9 +70,13 @@ const columns = computed(() => [ align: 'left', name: 'departmentFk', label: t('customer.summary.team'), - component: 'select', - attrs: { - url: 'Departments', + columnFilter: { + component: 'select', + attrs: { + url: 'Departments', + }, + alias: 'c', + inWhere: true, }, columnField: { component: null, @@ -94,6 +103,7 @@ const columns = computed(() => [ columnClass: 'no-padding', }, ]); +const openTab = (id) => useOpenURL(`#/customer/${id}/summary`); </script> <template> @@ -113,6 +123,8 @@ const columns = computed(() => [ :disable-option="{ card: true }" dense class="q-px-none" + :row-click="({ id }) => openTab(id)" + :row-ctrl-click="(_, { id }) => openTab(id)" > <template #top-left> <VnRow> @@ -121,12 +133,16 @@ const columns = computed(() => [ </VnRow> </template> <template #column-departmentFk="{ row }"> - <span class="link" :title="row.department" v-text="row.department" /> - <WorkerDescriptorProxy :id="row.departmentFk" dense /> + <span @click.stop.prevent class="link" :title="row.department"> + {{ row.department }} + <DepartmentDescriptorProxy :id="row.departmentFk" dense + /></span> </template> <template #column-clientFk="{ row }"> - <span class="link" :title="row.clientName" v-text="row.clientName" /> - <CustomerDescriptorProxy :id="row.clientFk" /> + <span @click.stop.prevent class="link" :title="row.clientName"> + {{ row.clientName }} + <CustomerDescriptorProxy :id="row.clientFk" dense + /></span> </template> </VnTable> </template> diff --git a/src/pages/Monitor/MonitorOrders.vue b/src/pages/Monitor/MonitorOrders.vue index 2679f7224..bdfcf3837 100644 --- a/src/pages/Monitor/MonitorOrders.vue +++ b/src/pages/Monitor/MonitorOrders.vue @@ -9,6 +9,7 @@ import { toDateFormat, toDateTimeFormat } from 'src/filters/date.js'; import { toCurrency } from 'src/filters'; import { useVnConfirm } from 'composables/useVnConfirm'; import axios from 'axios'; +import useOpenURL from 'src/composables/useOpenURL'; const { t } = useI18n(); const { openConfirmationModal } = useVnConfirm(); @@ -108,8 +109,7 @@ const removeOrders = async () => { await table.value.reload(); }; -const openTab = (id) => - window.open(`#/order/${id}/summary`, '_blank', 'noopener, noreferrer'); +const openTab = (id) => useOpenURL(`#/order/${id}/summary`); </script> <template> <VnTable @@ -129,6 +129,7 @@ const openTab = (id) => }" default-mode="table" :row-click="({ id }) => openTab(id)" + :row-ctrl-click="(_, { id }) => openTab(id)" v-model:selected="selectedRows" :disable-option="{ card: true }" > @@ -177,16 +178,16 @@ const openTab = (id) => </template> <template #column-clientFk="{ row }"> - <QTd @click.stop> - <span class="link" v-text="row.clientName" :title="row.clientName" /> - <CustomerDescriptorProxy :id="row.clientFk" /> - </QTd> + <span class="link" @click.stop :title="row.clientName"> + {{ row.clientName }} + <CustomerDescriptorProxy :id="row.clientFk" dense + /></span> </template> <template #column-departmentFk="{ row }"> - <QTd @click.stop> - <span class="link" v-text="row.departmentName" /> - <DepartmentDescriptorProxy :id="row.departmentFk" dense /> - </QTd> + <span class="link" @click.stop :title="row.departmentName"> + {{ row.departmentName }} + <DepartmentDescriptorProxy :id="row.departmentFk" dense + /></span> </template> </VnTable> </template> diff --git a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue index 535906e17..aea47aa08 100644 --- a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue +++ b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue @@ -198,20 +198,7 @@ const getLocale = (label) => { <QItemSection> <VnSelect dense - filled - :label="t('globals.params.departmentFk')" - v-model="params.department" - option-label="name" - option-value="name" - url="Departments" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnSelect - dense - filled + rounded :label="t('globals.params.packing')" v-model="params.packing" url="ItemPackingTypes" diff --git a/src/pages/Monitor/Ticket/MonitorTickets.vue b/src/pages/Monitor/Ticket/MonitorTickets.vue index 03d751595..b46eb5bfa 100644 --- a/src/pages/Monitor/Ticket/MonitorTickets.vue +++ b/src/pages/Monitor/Ticket/MonitorTickets.vue @@ -449,21 +449,19 @@ const openTab = (id) => useOpenURL(`#/ticket/${id}/sale`); <span :title="row.province" v-text="row.province" /> </template> <template #column-state="{ row }"> - <div @click.stop.prevent> - <div v-if="row.refFk"> - <span class="link">{{ row.refFk }}</span> - <InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> - </div> - <QBadge - v-else - :color="stateColors[row.classColor] || 'transparent'" - :text-color="stateColors[row.classColor] ? 'black' : 'white'" - class="q-pa-sm" - style="font-size: 14px" - > - {{ row.state }} - </QBadge> + <div v-if="row.refFk" @click.stop.prevent> + <span class="link">{{ row.refFk }}</span> + <InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> </div> + <QBadge + v-else + :color="stateColors[row.classColor] || 'transparent'" + :text-color="stateColors[row.classColor] ? 'black' : 'white'" + class="q-pa-sm" + style="font-size: 14px" + > + {{ row.state }} + </QBadge> </template> <template #column-isFragile="{ row }"> <QIcon v-if="row.isFragile" name="local_bar" color="primary" size="sm"> diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue index 609a1215a..5f91153ac 100644 --- a/src/pages/Order/Card/OrderFilter.vue +++ b/src/pages/Order/Card/OrderFilter.vue @@ -130,8 +130,10 @@ en: myTeam: My Team isConfirmed: Order Confirmed showEmpty: Show Empty + departmentFk: Department es: params: + departmentFk: Departamento search: Búsqueda clientFk: Cliente agencyModeFk: Agencia diff --git a/src/pages/Order/Card/OrderSummary.vue b/src/pages/Order/Card/OrderSummary.vue index a4bdb2881..10a458bfb 100644 --- a/src/pages/Order/Card/OrderSummary.vue +++ b/src/pages/Order/Card/OrderSummary.vue @@ -71,180 +71,174 @@ async function handleConfirm() { </script> <template> - <div class="q-pa-md"> - <CardSummary - ref="summary" - :url="`Orders/${entityId}/summary`" - data-key="OrderSummary" - > - <template #header="{ entity }"> - {{ t('order.summary.basket') }} #{{ entity?.id }} - - {{ entity?.client?.name }} ({{ entity?.clientFk }}) - </template> - <template #header-right> - <QBtn - flat - text-color="white" - :disabled="isConfirmed" - :label="t('order.summary.confirm')" - @click="handleConfirm()" - > - <QTooltip>{{ t('order.summary.confirmLines') }}</QTooltip> - </QBtn> - </template> - <template #menu="{ entity }"> - <OrderDescriptorMenu :order="entity" /> - </template> - <template #body="{ entity }"> - <QCard class="vn-one"> - <VnTitle - :url="`#/order/${entity.id}/basic-data`" - :text="t('globals.pageTitles.basicData')" - /> - <VnLv label="ID" :value="entity.id" /> - <VnLv :label="t('globals.alias')" dash> - <template #value> - <span class="link"> - {{ dashIfEmpty(entity?.address?.nickname) }} - <CustomerDescriptorProxy :id="entity?.clientFk" /> - </span> - </template> - </VnLv> - <VnLv - :label="t('globals.company')" - :value="entity?.address?.companyFk" - /> - <VnLv - :label="t('globals.confirmed')" - :value="Boolean(entity?.isConfirmed)" - /> - </QCard> - <QCard class="vn-one"> - <VnTitle - :url="`#/order/${entity.id}/basic-data`" - :text="t('globals.pageTitles.basicData')" - /> - <VnLv - :label="t('order.summary.created')" - :value="toDateHourMinSec(entity?.created)" - /> - <VnLv - :label="t('globals.confirmed')" - :value="toDateHourMinSec(entity?.confirmed)" - /> - <VnLv - :label="t('globals.landed')" - :value="toDateHourMinSec(entity?.landed)" - /> - <VnLv :label="t('globals.phone')"> - <template #value> - {{ dashIfEmpty(entity?.address?.phone) }} - <a - v-if="entity?.address?.phone" - :href="`tel:${entity?.address?.phone}`" - class="text-primary" - > - <QIcon name="phone" /> - </a> - </template> - </VnLv> - <VnLv - :label="t('order.summary.createdFrom')" - :value="entity?.sourceApp" - /> - <VnLv - :label="t('order.summary.address')" - :value="`${entity?.address?.street} - ${entity?.address?.city} (${entity?.address?.province?.name})`" - class="order-summary-address" - /> - </QCard> - <QCard class="vn-one"> - <VnTitle :text="t('globals.pageTitles.notes')" /> - <p v-if="entity?.note" class="no-margin"> - {{ entity?.note }} - </p> - </QCard> - <QCard class="vn-one"> - <VnTitle :text="t('order.summary.total')" /> - <VnLv> - <template #label> - <span class="text-h6">{{ t('globals.subtotal') }}</span> - </template> - <template #value> - <span class="text-h6">{{ - toCurrency(entity?.subTotal) - }}</span> - </template> - </VnLv> - <VnLv> - <template #label> - <span class="text-h6">{{ t('globals.vat') }}</span> - </template> - <template #value> - <span class="text-h6">{{ toCurrency(entity?.VAT) }}</span> - </template> - </VnLv> - <VnLv> - <template #label> - <span class="text-h6">{{ t('order.summary.total') }}</span> - </template> - <template #value> - <span class="text-h6">{{ toCurrency(entity?.total) }}</span> - </template> - </VnLv> - </QCard> - <QCard> - <VnTitle :text="t('globals.details')" /> - <QTable :columns="detailsColumns" :rows="entity?.rows" flat> - <template #header="props"> - <QTr :props="props"> - <QTh auto-width>{{ t('globals.item') }}</QTh> - <QTh>{{ t('globals.description') }}</QTh> - <QTh auto-width>{{ t('globals.quantity') }}</QTh> - <QTh auto-width>{{ t('globals.price') }}</QTh> - <QTh auto-width>{{ t('order.summary.amount') }}</QTh> - </QTr> - </template> - <template #body="props"> - <QTr :props="props"> - <QTd key="item" :props="props" class="item"> - <span class="link"> - {{ props.row.item?.id }} - <ItemDescriptorProxy :id="props.row.item?.id" /> - </span> - </QTd> - <QTd key="description" :props="props"> - <div class="description"> - <div class="name"> - {{ props.row.item.name }} - <span - v-if="props.row.item.subName" - class="subName" - > - {{ props.row.item.subName }} - </span> - </div> + <CardSummary + ref="summary" + :url="`Orders/${entityId}/summary`" + data-key="OrderSummary" + > + <template #header="{ entity }"> + {{ t('order.summary.basket') }} #{{ entity?.id }} - + {{ entity?.client?.name }} ({{ entity?.clientFk }}) + </template> + <template #header-right> + <QBtn + flat + text-color="white" + :disabled="isConfirmed" + :label="t('order.summary.confirm')" + @click="handleConfirm()" + > + <QTooltip>{{ t('order.summary.confirmLines') }}</QTooltip> + </QBtn> + </template> + <template #menu="{ entity }"> + <OrderDescriptorMenu :order="entity" /> + </template> + <template #body="{ entity }"> + <QCard class="vn-two"> + <VnTitle + :url="`#/order/${entity.id}/basic-data`" + :text="t('globals.pageTitles.basicData')" + /> + <div class="vn-card-group"> + <div class="vn-card-content"> + <VnLv label="ID" :value="entity.id" /> + <VnLv :label="t('globals.alias')" dash> + <template #value> + <span class="link"> + {{ dashIfEmpty(entity?.address?.nickname) }} + <CustomerDescriptorProxy :id="entity?.clientFk" /> + </span> + </template> + </VnLv> + <VnLv + :label="t('globals.company')" + :value="entity?.address?.companyFk" + /> + <VnLv + :label="t('globals.confirmed')" + :value="Boolean(entity?.isConfirmed)" + /> + </div> + <div class="vn-card-content"> + <VnLv + :label="t('order.summary.created')" + :value="toDateHourMinSec(entity?.created)" + /> + <VnLv + :label="t('globals.confirmed')" + :value="toDateHourMinSec(entity?.confirmed)" + /> + <VnLv + :label="t('globals.landed')" + :value="toDateHourMinSec(entity?.landed)" + /> + <VnLv :label="t('globals.phone')"> + <template #value> + {{ dashIfEmpty(entity?.address?.phone) }} + <a + v-if="entity?.address?.phone" + :href="`tel:${entity?.address?.phone}`" + class="text-primary" + > + <QIcon name="phone" /> + </a> + </template> + </VnLv> + <VnLv + :label="t('order.summary.createdFrom')" + :value="entity?.sourceApp" + /> + <VnLv + :label="t('order.summary.address')" + :value="`${entity?.address?.street} - ${entity?.address?.city} (${entity?.address?.province?.name})`" + class="order-summary-address" + /> + </div> + </div> + </QCard> + <QCard class="vn-one"> + <VnTitle :text="t('globals.pageTitles.notes')" /> + <p v-if="entity?.note" class="no-margin"> + {{ entity?.note }} + </p> + </QCard> + <QCard class="vn-one"> + <VnTitle :text="t('order.summary.total')" /> + <VnLv> + <template #label> + <span class="text-h6">{{ t('globals.subtotal') }}</span> + </template> + <template #value> + <span class="text-h6">{{ toCurrency(entity?.subTotal) }}</span> + </template> + </VnLv> + <VnLv> + <template #label> + <span class="text-h6">{{ t('globals.vat') }}</span> + </template> + <template #value> + <span class="text-h6">{{ toCurrency(entity?.VAT) }}</span> + </template> + </VnLv> + <VnLv> + <template #label> + <span class="text-h6">{{ t('order.summary.total') }}</span> + </template> + <template #value> + <span class="text-h6">{{ toCurrency(entity?.total) }}</span> + </template> + </VnLv> + </QCard> + <QCard> + <VnTitle :text="t('globals.details')" /> + <QTable :columns="detailsColumns" :rows="entity?.rows" flat> + <template #header="props"> + <QTr :props="props"> + <QTh auto-width>{{ t('globals.item') }}</QTh> + <QTh>{{ t('globals.description') }}</QTh> + <QTh auto-width>{{ t('globals.quantity') }}</QTh> + <QTh auto-width>{{ t('globals.price') }}</QTh> + <QTh auto-width>{{ t('order.summary.amount') }}</QTh> + </QTr> + </template> + <template #body="props"> + <QTr :props="props"> + <QTd key="item" :props="props" class="item"> + <span class="link"> + {{ props.row.item?.id }} + <ItemDescriptorProxy :id="props.row.item?.id" /> + </span> + </QTd> + <QTd key="description" :props="props"> + <div class="description"> + <div class="name"> + {{ props.row.item.name }} + <span + v-if="props.row.item.subName" + class="subName" + > + {{ props.row.item.subName }} + </span> </div> - <FetchedTags :item="props.row.item" :columns="3" /> - </QTd> - <QTd key="quantity" :props="props"> - {{ props.row.quantity }} - </QTd> - <QTd key="price" :props="props"> - {{ toCurrency(props.row.price) }} - </QTd> - <QTd key="amount" :props="props"> - {{ - toCurrency(props.row?.quantity * props.row?.price) - }} - </QTd> - </QTr> - </template> - </QTable> - </QCard> - </template> - </CardSummary> - </div> + </div> + <FetchedTags :item="props.row.item" :columns="3" /> + </QTd> + <QTd key="quantity" :props="props"> + {{ props.row.quantity }} + </QTd> + <QTd key="price" :props="props"> + {{ toCurrency(props.row.price) }} + </QTd> + <QTd key="amount" :props="props"> + {{ toCurrency(props.row?.quantity * props.row?.price) }} + </QTd> + </QTr> + </template> + </QTable> + </QCard> + </template> + </CardSummary> </template> <style lang="scss"> .cardSummary .summaryBody .vn-label-value.order-summary-address { diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index d75390d96..f59ce6585 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, }, @@ -142,7 +141,7 @@ const columns = computed(() => [ { title: t('globals.pageTitles.summary'), icon: 'preview', - action: (row) => viewSummary(row.id, OrderSummary), + action: (row) => viewSummary(row.id, OrderSummary, 'lg-width'), isPrimary: true, }, ], diff --git a/src/pages/Route/Agency/Card/AgencyCard.vue b/src/pages/Route/Agency/Card/AgencyCard.vue index c21298470..9fd3fe5e5 100644 --- a/src/pages/Route/Agency/Card/AgencyCard.vue +++ b/src/pages/Route/Agency/Card/AgencyCard.vue @@ -3,5 +3,5 @@ import AgencyDescriptor from 'pages/Route/Agency/Card/AgencyDescriptor.vue'; import VnCard from 'src/components/common/VnCard.vue'; </script> <template> - <VnCard data-key="Agency" url="Agencies" :descriptor="AgencyDescriptor" /> + <VnCard data-key="Agency" url="Agencies" :descriptor="AgencyDescriptor" :filter="{ where: { id: $route.params.id } }" /> </template> diff --git a/src/pages/Route/Card/RouteSummary.vue b/src/pages/Route/Card/RouteSummary.vue index f68628095..86bdbb5c5 100644 --- a/src/pages/Route/Card/RouteSummary.vue +++ b/src/pages/Route/Card/RouteSummary.vue @@ -138,74 +138,79 @@ const ticketColumns = ref([ :url="`#/${route.meta.moduleName.toLowerCase()}/${entityId}/basic-data`" :text="t('globals.pageTitles.basicData')" /> - </QCard> - - <QCard class="vn-one"> - <VnLv - :label="t('route.summary.date')" - :value="toDate(entity?.route.dated)" - /> - <VnLv - :label="t('route.summary.agency')" - :value="entity?.route?.agencyMode?.name" - /> - <VnLv - :label="t('route.summary.vehicle')" - :value="entity.route?.vehicle?.numberPlate" - /> - <VnLv :label="t('route.summary.driver')"> - <template #value> - <span class="link"> - {{ dashIfEmpty(entity?.route?.worker?.user?.name) }} - <WorkerDescriptorProxy :id="entity.route?.workerFk" /> - </span> - </template> - </VnLv> - <VnLv - :label="t('route.summary.cost')" - :value="toCurrency(entity.route?.cost)" - /> - <VnLv - :label="t('route.summary.volume')" - :value="`${dashIfEmpty(entity?.route?.m3)} / ${dashIfEmpty( - entity?.route?.vehicle?.m3, - )} m³`" - /> - <VnLv - :label="t('route.summary.packages')" - :value="getTotalPackages(entity.tickets)" - /> - <QCheckbox - :label=" - entity.route.isOk - ? t('route.summary.closed') - : t('route.summary.open') - " - v-model="entity.route.isOk" - :disable="true" - /> - </QCard> - <QCard class="vn-one"> - <VnLv - :label="t('route.summary.started')" - :value="toHour(entity?.route.started)" - /> - <VnLv - :label="t('route.summary.finished')" - :value="toHour(entity?.route.finished)" - /> - <VnLv - :label="t('route.summary.kmStart')" - :value="dashIfEmpty(entity?.route?.kmStart)" - /> - <VnLv - :label="t('route.summary.kmEnd')" - :value="dashIfEmpty(entity?.route?.kmEnd)" - /> - <VnLv - :label="t('globals.description')" - :value="dashIfEmpty(entity?.route?.description)" - /> + <div class="vn-card-group"> + <div class="vn-card-content"> + <VnLv + :label="t('route.summary.date')" + :value="toDate(entity?.route.dated)" + /> + <VnLv + :label="t('route.summary.agency')" + :value="entity?.route?.agencyMode?.name" + /> + <VnLv + :label="t('route.summary.vehicle')" + :value="entity.route?.vehicle?.numberPlate" + /> + <VnLv :label="t('route.summary.driver')"> + <template #value> + <span class="link"> + {{ + dashIfEmpty(entity?.route?.worker?.user?.name) + }} + <WorkerDescriptorProxy + :id="entity.route?.workerFk" + /> + </span> + </template> + </VnLv> + <VnLv + :label="t('route.summary.cost')" + :value="toCurrency(entity.route?.cost)" + /> + <VnLv + :label="t('route.summary.volume')" + :value="`${dashIfEmpty(entity?.route?.m3)} / ${dashIfEmpty( + entity?.route?.vehicle?.m3, + )} m³`" + /> + <VnLv + :label="t('route.summary.packages')" + :value="getTotalPackages(entity.tickets)" + /> + <QCheckbox + :label=" + entity.route.isOk + ? t('route.summary.closed') + : t('route.summary.open') + " + v-model="entity.route.isOk" + :disable="true" + /> + </div> + <div class="vn-card-content"> + <VnLv + :label="t('route.summary.started')" + :value="toHour(entity?.route.started)" + /> + <VnLv + :label="t('route.summary.finished')" + :value="toHour(entity?.route.finished)" + /> + <VnLv + :label="t('route.summary.kmStart')" + :value="dashIfEmpty(entity?.route?.kmStart)" + /> + <VnLv + :label="t('route.summary.kmEnd')" + :value="dashIfEmpty(entity?.route?.kmEnd)" + /> + <VnLv + :label="t('globals.description')" + :value="dashIfEmpty(entity?.route?.description)" + /> + </div> + </div> </QCard> <QCard class="vn-max"> <VnTitle diff --git a/src/pages/Route/RouteRoadmap.vue b/src/pages/Route/RouteRoadmap.vue index c981b86a5..bdb3d12c4 100644 --- a/src/pages/Route/RouteRoadmap.vue +++ b/src/pages/Route/RouteRoadmap.vue @@ -213,7 +213,7 @@ function exprBuilder(param, value) { }" > <template #advanced-menu> - <RoadmapFilter :dataKey /> + <RoadmapFilter :data-key /> </template> <template #body> <VnTable diff --git a/src/pages/Route/Vehicle/Card/VehicleNotes.vue b/src/pages/Route/Vehicle/Card/VehicleNotes.vue new file mode 100644 index 000000000..0afc3c3ed --- /dev/null +++ b/src/pages/Route/Vehicle/Card/VehicleNotes.vue @@ -0,0 +1,35 @@ +<script setup> +import { computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useState } from 'src/composables/useState'; +import VnNotes from 'src/components/ui/VnNotes.vue'; + +const route = useRoute(); +const state = useState(); +const user = state.getUser(); +const vehicleId = computed(() => route.params.id); + +const noteFilter = computed(() => { + return { + order: 'created DESC', + where: { vehicleFk: vehicleId.value }, + }; +}); + +const body = { + vehicleFk: vehicleId.value, + workerFk: user.value.id, +}; +</script> + +<template> + <VnNotes + url="vehicleObservations" + :add-note="true" + :filter="noteFilter" + :body="body" + style="overflow-y: auto" + required + deletable + /> +</template> diff --git a/src/pages/Route/locale/en.yml b/src/pages/Route/locale/en.yml index 283b61855..e7e2d691e 100644 --- a/src/pages/Route/locale/en.yml +++ b/src/pages/Route/locale/en.yml @@ -50,7 +50,7 @@ route: agencyAgreement: Agency agreement agencyModeName: Agency route isOwn: Own - isAnyVolumeallowed: Any volume allowed + isAnyVolumeAllowed: Any volume allowed Worker: Worker Agency: Agency Vehicle: Vehicle diff --git a/src/pages/Shelving/Parking/Card/ParkingDescriptorProxy.vue b/src/pages/Shelving/Parking/Card/ParkingDescriptorProxy.vue new file mode 100644 index 000000000..e78a2b238 --- /dev/null +++ b/src/pages/Shelving/Parking/Card/ParkingDescriptorProxy.vue @@ -0,0 +1,14 @@ +<script setup> +import ParkingDescriptor from './ParkingDescriptor.vue'; +import ParkingSummary from './ParkingSummary.vue'; +</script> +<template> + <QPopupProxy style="max-width: 10px"> + <ParkingDescriptor + v-if="$attrs.id" + v-bind="$attrs.id" + :summary="ParkingSummary" + :proxy-render="true" + /> + </QPopupProxy> +</template> 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/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 2fb305cc3..c5f7d5856 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -187,7 +187,9 @@ const getRowUpdateInputEvents = (sale) => { const resetChanges = async () => { arrayData.fetch({ append: false }); - tableRef.value.reload(); + tableRef.value.CrudModelRef.hasChanges = false; + await tableRef.value.reload(); + selectedRows.value = []; }; const changeQuantity = async (sale) => { @@ -377,10 +379,12 @@ const newOrderFromTicket = async () => { const goToLog = (saleId) => { router.push({ name: 'TicketLog', - params: { - originId: route.params.id, - changedModel: 'Sale', - changedModelId: saleId, + query: { + logs: JSON.stringify({ + originFk: route.params.id, + changedModel: 'Sale', + changedModelId: saleId, + }), }, }); }; @@ -390,7 +394,7 @@ const changeTicketState = async (val) => { const params = { ticketFk: route.params.id, code: val }; await axios.post('Tickets/state', params); notify('globals.dataSaved', 'positive'); - await resetChanges(); + resetChanges(); }; const removeSelectedSales = () => { diff --git a/src/pages/Ticket/Card/TicketSaleMoreActions.vue b/src/pages/Ticket/Card/TicketSaleMoreActions.vue index 773b0807f..a1eaba53f 100644 --- a/src/pages/Ticket/Card/TicketSaleMoreActions.vue +++ b/src/pages/Ticket/Card/TicketSaleMoreActions.vue @@ -62,6 +62,7 @@ const isClaimable = computed(() => { } return false; }); + const sendSms = async (params) => { await axios.post(`Tickets/${ticket.value.id}/sendSms`, params); notify(t('SMS sent'), 'positive'); @@ -230,18 +231,6 @@ const createRefund = async (withWarehouse) => { <QItemLabel>{{ t('Add claim') }}</QItemLabel> </QItemSection> </QItem> - <QItem - v-if="isTicketEditable" - clickable - v-close-popup - v-ripple - @click="setReserved(true)" - data-cy="markAsReservedItem" - > - <QItemSection> - <QItemLabel>{{ t('Mark as reserved') }}</QItemLabel> - </QItemSection> - </QItem> <QItem clickable v-ripple data-cy="ticketSaleRefundItem"> <QItemSection> <QItemLabel>{{ t('Refund') }}</QItemLabel> @@ -287,8 +276,6 @@ es: Recalculate price: Recalcular precio Update discount: Actualizar descuento Add claim: Crear reclamación - Mark as reserved: Marcar como reservado - Unmark as reserved: Desmarcar como reservado Refund: Abono with warehouse: con almacén without warehouse: sin almacén diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index a19ab3779..119b867ed 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -21,6 +21,7 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; +import VnDropdown from 'src/components/common/VnDropdown.vue'; const route = useRoute(); const { notify } = useNotify(); @@ -40,7 +41,7 @@ const ticket = computed(() => summary.value?.entity); const editableStates = ref([]); const ticketUrl = ref(); const grafanaUrl = 'https://grafana.verdnatura.es'; -const stateBtnDropdownRef = ref(); + const descriptorData = useArrayData('Ticket'); onMounted(async () => { @@ -67,7 +68,6 @@ function isEditable() { } async function changeState(value) { - stateBtnDropdownRef.value?.hide(); const formData = { ticketFk: entityId.value, code: value, @@ -113,144 +113,139 @@ onMounted(async () => { </div> </template> <template #header-right> - <div> - <QBtnDropdown - ref="stateBtnDropdownRef" - color="black" - text-color="white" - :label="t('globals.changeState')" - :disable="!isEditable()" - > - <VnSelect - :options="editableStates" - hide-selected - option-label="name" - option-value="code" - hide-dropdown-icon - focus-on-mount - @update:model-value="changeState" - /> - </QBtnDropdown> - </div> + <VnDropdown + :disable="!isEditable()" + :options="editableStates" + option-value="code" + @change-state="changeState" + /> </template> <template #menu="{ entity }"> <TicketDescriptorMenu :ticket="entity" /> </template> <template #body="{ entity }"> - <QCard class="vn-one"> + <QCard class="vn-two"> <VnTitle :url="toTicketUrl('basic-data')" :text="t('globals.summary.basicData')" /> - <VnLv v-if="entity.ticketState" :label="t('globals.state')"> - <template #value> - <QBadge - text-color="black" - :color="entity.ticketState.state.classColor" + <div class="vn-card-group"> + <div class="vn-card-content"> + <VnLv v-if="entity.ticketState" :label="t('globals.state')"> + <template #value> + <QBadge + text-color="black" + :color="entity.ticketState.state.classColor" + > + {{ entity.ticketState.state.name }} + </QBadge> + </template> + </VnLv> + <VnLv :label="t('customer.summary.team')"> + <template #value> + <span class="link"> + {{ entity?.client?.department?.name || '-' }} + <DepartmentDescriptorProxy + :id="entity?.client?.departmentFk" + /> + </span> + </template> + </VnLv> + <VnLv + :label="t('globals.agency')" + :value="entity.agencyMode?.name" + /> + <VnLv :label="t('ticket.summary.zone')"> + <template #value> + <span class="link" @click.stop> + {{ entity?.zone?.name }} + <ZoneDescriptorProxy :id="entity.zoneFk" /> + </span> + </template> + </VnLv> + <VnLv + :label="t('globals.warehouse')" + :value="entity.warehouse?.name" + /> + <VnLv + v-if="ticket?.ticketCollections?.length > 0" + :label="t('ticket.summary.collection')" + :value="ticket?.ticketCollections[0]?.collectionFk" > - {{ entity.ticketState.state.name }} - </QBadge> - </template> - </VnLv> - <VnLv :label="t('customer.summary.team')"> - <template #value> - <span class="link"> - {{ entity?.client?.department?.name || '-' }} - <DepartmentDescriptorProxy - :id="entity?.client?.departmentFk" - /> - </span> - </template> - </VnLv> - <VnLv :label="t('globals.agency')" :value="entity.agencyMode?.name" /> - <VnLv :label="t('ticket.summary.zone')"> - <template #value> - <span class="link" @click.stop> - {{ entity?.zone?.name }} - <ZoneDescriptorProxy :id="entity.zoneFk" /> - </span> - </template> - </VnLv> - <VnLv :label="t('globals.warehouse')" :value="entity.warehouse?.name" /> - <VnLv - v-if="ticket?.ticketCollections?.length > 0" - :label="t('ticket.summary.collection')" - :value="ticket?.ticketCollections[0]?.collectionFk" - > - <template #value> - <a - :href="`${grafanaUrl}/d/d552ab74-85b4-4e7f-a279-fab7cd9c6124/control-de-expediciones?orgId=1&var-collectionFk=${entity.ticketCollections[0]?.collectionFk}`" - target="_blank" - class="grafana" - > - {{ entity.ticketCollections[0]?.collectionFk }} - </a> - </template> - </VnLv> - <VnLv :label="t('ticket.summary.route')"> - <template #value> - <span class="link"> - {{ entity.routeFk }} - <RouteDescriptorProxy :id="entity.routeFk" /> - </span> - </template> - </VnLv> - <VnLv :label="t('ticket.summary.invoice')"> - <template #value> - <span :class="{ link: entity.refFk }"> - {{ dashIfEmpty(entity.refFk) }} - <InvoiceOutDescriptorProxy - :id="entity.invoiceOut.id" - v-if="entity.refFk" - /> - </span> - </template> - </VnLv> - <VnLv :label="t('globals.weight')" :value="dashIfEmpty(entity.weight)" /> - </QCard> - <QCard class="vn-one"> - <VnTitle - :url="toTicketUrl('basic-data')" - :text="t('globals.summary.basicData')" - /> - <VnLv - :label="t('ticket.summary.shipped')" - :value="toDate(entity.shipped)" - /> - <VnLv :label="t('globals.landed')" :value="toDate(entity.landed)" /> - <VnLv :label="t('globals.packages')" :value="entity.packages" /> - <VnLv :value="entity.address.phone"> - <template #label> - {{ t('ticket.summary.consigneePhone') }} - <VnLinkPhone :phone-number="entity.address.phone" /> - </template> - </VnLv> - <VnLv :value="entity.address.mobile"> - <template #label> - {{ t('ticket.summary.consigneeMobile') }} - <VnLinkPhone :phone-number="entity.address.mobile" /> - </template> - </VnLv> - <VnLv :value="entity.client.phone"> - <template #label> - {{ t('ticket.summary.clientPhone') }} - <VnLinkPhone :phone-number="entity.client.phone" /> - </template> - </VnLv> - <VnLv :value="entity.client.mobile"> - <template #label> - {{ t('ticket.summary.clientMobile') }} - <VnLinkPhone :phone-number="entity.client.mobile" /> - </template> - </VnLv> - <VnLv - :label="t('ticket.summary.consignee')" - :value="`${entity.address?.nickname} #${entity.address?.id}`" - /> - <VnLv - :label="t('ticket.summary.consigneeStreet')" - :value="formattedAddress" - /> + <template #value> + <a + :href="`${grafanaUrl}/d/d552ab74-85b4-4e7f-a279-fab7cd9c6124/control-de-expediciones?orgId=1&var-collectionFk=${entity.ticketCollections[0]?.collectionFk}`" + target="_blank" + class="grafana" + > + {{ entity.ticketCollections[0]?.collectionFk }} + </a> + </template> + </VnLv> + <VnLv :label="t('ticket.summary.route')"> + <template #value> + <span class="link"> + {{ entity.routeFk }} + <RouteDescriptorProxy :id="entity.routeFk" /> + </span> + </template> + </VnLv> + <VnLv :label="t('ticket.summary.invoice')"> + <template #value> + <span :class="{ link: entity.refFk }"> + {{ dashIfEmpty(entity.refFk) }} + <InvoiceOutDescriptorProxy + :id="entity.invoiceOut.id" + v-if="entity.refFk" + /> + </span> + </template> + </VnLv> + <VnLv + :label="t('globals.weight')" + :value="dashIfEmpty(entity.weight)" + /> + </div> + <div class="vn-card-content"> + <VnLv + :label="t('ticket.summary.shipped')" + :value="toDate(entity.shipped)" + /> + <VnLv + :label="t('globals.landed')" + :value="toDate(entity.landed)" + /> + <VnLv :label="t('globals.packages')" :value="entity.packages" /> + <VnLv :label="t('ticket.summary.consigneePhone')"> + <template #value> + <VnLinkPhone :phone-number="entity.address.phone" /> + </template> + </VnLv> + <VnLv :label="t('ticket.summary.consigneeMobile')"> + <template #value> + <VnLinkPhone :phone-number="entity.address.mobile" /> + </template> + </VnLv> + <VnLv :label="t('ticket.summary.clientPhone')"> + <template #value> + <VnLinkPhone :phone-number="entity.client.phone" /> + </template> + </VnLv> + <VnLv :label="t('ticket.summary.clientMobile')"> + <template #value> + <VnLinkPhone :phone-number="entity.client.mobile" /> + </template> + </VnLv> + <VnLv + :label="t('ticket.summary.consignee')" + :value="`${entity.address?.nickname} #${entity.address?.id}`" + /> + <VnLv + :label="t('ticket.summary.consigneeStreet')" + :value="formattedAddress" + /> + </div> + </div> </QCard> <QCard class="vn-one" v-if="entity.notes.length"> <VnTitle diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index b763ef970..d84d1c082 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -22,16 +22,6 @@ const states = ref([]); const agencies = ref([]); const warehouses = ref([]); const groupedStates = ref([]); - -const getGroupedStates = (data) => { - for (const state of data) { - groupedStates.value.push({ - id: state.id, - name: t(`${state.code}`), - code: state.code, - }); - } -}; </script> <template> @@ -39,12 +29,11 @@ const getGroupedStates = (data) => { <FetchData url="States" @on-fetch="(data) => (states = data)" auto-load /> <FetchData url="AlertLevels" - @on-fetch=" - (data) => { - getGroupedStates(data); - } - " auto-load + @on-fetch=" + (data) => + (groupedStates = data.map((x) => Object.assign(x, { code: t(x.code) }))) + " /> <FetchData url="AgencyModes" @@ -126,12 +115,11 @@ const getGroupedStates = (data) => { </QItemSection> <QItemSection v-if="groupedStates"> <VnSelect - :label="t('Grouped state')" + :label="t('params.groupedStates')" v-model="params.groupedStates" @update:model-value="searchFn()" :options="groupedStates" - option-value="id" - option-label="name" + option-label="code" emit-value map-options use-input @@ -304,7 +292,7 @@ en: ON_PREPARATION: On preparation PACKED: Packed DELIVERED: Delivered - ON_PREVIOUS: ON_PREVIOUS + ON_PREVIOUS: On previous es: params: search: Contiene @@ -349,7 +337,7 @@ es: ON_PREPARATION: En preparación PACKED: Encajado DELIVERED: Servido - ON_PREVIOUS: ON_PREVIOUS + ON_PREVIOUS: En previa Collection: Colección Nickname: Nombre mostrado </i18n> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 307f34dc2..039d3ca9e 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -248,7 +248,7 @@ const columns = computed(() => [ name: 'TicketCard', }).href; window.open(url, '_blank'); - } else viewSummary(row.id, TicketSummary); + } else viewSummary(row.id, TicketSummary, 'lg-width'); }, }, ], diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index 479b47fb9..d452f5287 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -8,6 +8,6 @@ import filter from './TravelFilter.js'; data-key="Travel" url="Travels" :descriptor="TravelDescriptor" - :filter="filter" + :filter="{ ...filter, where: { id: $route.params.id } }" /> </template> diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index 9f9552611..22e2cff86 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; @@ -7,8 +7,6 @@ import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue'; -import FetchData from 'src/components/FetchData.vue'; -import VnRow from 'components/ui/VnRow.vue'; import { toDate, toCurrency, toCelsius } from 'src/filters'; import { toDateTimeFormat } from 'src/filters/date.js'; import { dashIfEmpty } from 'src/filters'; @@ -91,6 +89,13 @@ const entriesTableColumns = computed(() => { showValue: true, }, { label: 'CC', field: 'cc', name: 'cc', align: 'left', showValue: true }, + { + label: t('travel.summary.roundedCc'), + field: 'cc', + name: 'roundedCc', + align: 'left', + showValue: true, + }, { label: 'Pallet', field: 'pallet', @@ -193,13 +198,18 @@ const entriesTotals = computed(() => { freightValue: 0, packageValue: 0, cc: 0, + roundedCc: 0, pallet: 0, m3: 0, }; entriesTableRows.value.forEach((row) => { for (const key in totals) { - totals[key] += row[key] || 0; + if (key === 'roundedCc') { + totals['roundedCc'] += Math.ceil(row['cc'] || 0); + } else { + totals[key] += row[key] || 0; + } } }); @@ -208,6 +218,7 @@ const entriesTotals = computed(() => { freight: toCurrency(totals.freightValue), packageValue: toCurrency(totals.packageValue), cc: totals.cc.toFixed(2), + roundedCc: totals.roundedCc, pallet: totals.pallet.toFixed(2), m3: totals.m3.toFixed(2), }; @@ -252,16 +263,16 @@ async function setTravelData(travelData) { } const getLink = (param) => `#/travel/${entityId.value}/${param}`; + +onMounted(async () => { + const { data } = await axios.get('Warehouses', { + params: { fields: ['id', 'name'] }, + }); + warehouses.value = data; +}); </script> <template> - <FetchData - url="Warehouses" - :filter="{ fields: ['id', 'name'] }" - order="name" - @on-fetch="(data) => (warehouses = data)" - auto-load - /> <CardSummary ref="summaryRef" :url="`Travels/${entityId}/getTravel`" @@ -275,72 +286,68 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <TravelDescriptorMenuItems :travel="entity" /> </template> <template #body> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <VnLv :label="t('globals.shipped')" :value="toDate(travel.shipped)" /> - <VnLv - :label="t('globals.warehouseOut')" - :value="travel.warehouseOut?.name" - /> - <VnRow> - <QCheckbox - :label="t('travel.basicData.isRaid')" - v-model="travel.isRaid" - :disable="true" - /> - </VnRow> - <VnRow> - <QCheckbox - :label="t('travel.summary.delivered')" - v-model="travel.isDelivered" - :disable="true" - /> - </VnRow> - </QCard> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <VnLv :label="t('globals.landed')" :value="toDate(travel.landed)" /> - <VnLv - :label="t('globals.warehouseIn')" - :value="travel.warehouseIn?.name" - /> - <VnLv - :label="t('travel.basicData.daysInForward')" - :value="travel?.daysInForward" - /> - <QCheckbox - :label="t('travel.summary.received')" - v-model="travel.isReceived" - :disable="true" - /> - </QCard> - <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="getLink('basic-data')" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> - <VnLv :label="t('globals.agency')" :value="travel.agency?.name" /> - <VnLv :label="t('globals.reference')" :value="travel.ref" /> - <VnLv label="m³" :value="travel.m3" /> - <VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" /> - <VnLv - :label="t('travel.summary.availabled')" - :value=" - dashIfEmpty(toDateTimeFormat(travel.availabled)) - " + <QCard class="full-width"> + <VnTitle + :url="getLink('basic-data')" + :text="t('globals.pageTitles.basicData')" /> + <div class="vn-card-group"> + <div class="vn-card-content"> + <VnLv + :label="t('globals.shipped')" + :value="toDate(travel.shipped)" + /> + <VnLv + :label="t('globals.warehouseOut')" + :value="travel.warehouseOut?.name" + /> + <QCheckbox + :label="t('travel.basicData.isRaid')" + v-model="travel.isRaid" + :disable="true" + size="sm" + /> + <QCheckbox + :label="t('travel.summary.delivered')" + v-model="travel.isDelivered" + :disable="true" + size="sm" + /> + </div> + <div class="vn-card-content"> + <VnLv + :label="t('globals.landed')" + :value="toDate(travel.landed)" + /> + <VnLv + :label="t('globals.warehouseIn')" + :value="travel.warehouseIn?.name" + /> + <VnLv + :label="t('travel.basicData.daysInForward')" + :value="travel?.daysInForward" + /> + <QCheckbox + :label="t('travel.summary.received')" + v-model="travel.isReceived" + :disable="true" + size="sm" + /> + </div> + <div class="vn-card-content"> + <VnLv :label="t('globals.agency')" :value="travel.agency?.name" /> + <VnLv :label="t('globals.reference')" :value="travel.ref" /> + <VnLv label="m³" :value="travel.m3" /> + <VnLv + :label="t('globals.totalEntries')" + :value="travel.totalEntries" + /> + <VnLv + :label="t('travel.summary.availabled')" + :value="dashIfEmpty(toDateTimeFormat(travel.availabled))" + /> + </div> + </div> </QCard> <QCard class="full-width"> <VnTitle :text="t('travel.summary.entries')" /> @@ -376,6 +383,11 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; </QBtn> </QTd> </template> + <template #body-cell-roundedCc="{ col, value }"> + <QTd> + {{ Math.ceil(value) || 0 }} + </QTd> + </template> <template #body-cell-observation="{ value }"> <QTd> <QIcon name="insert_drive_file" color="primary" size="24px"> @@ -392,23 +404,24 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <QTd class="text-bold">{{ entriesTotals.freight }}</QTd> <QTd class="text-bold">{{ entriesTotals.packageValue }}</QTd> <QTd class="text-bold">{{ entriesTotals.cc }}</QTd> + <QTd class="text-bold">{{ entriesTotals.roundedCc }}</QTd> <QTd class="text-bold">{{ entriesTotals.pallet }}</QTd> <QTd class="text-bold">{{ entriesTotals.m3 }}</QTd> </template> </QTable> </QCard> - <QCard class="full-width" v-if="thermographs.length > 0"> - <RouterLink - class="header header-link" - :to="{ - name: 'TravelThermographsIndex', - params: { id: travel.id }, - }" - > - {{ t('travel.summary.thermographs') }} - <QIcon name="open_in_new" /> - </RouterLink> + <FetchData + url="Warehouses" + :filter="{ fields: ['id', 'name'] }" + order="name" + @on-fetch="(data) => (warehouses = data)" + auto-load + /> + <VnTitle + :url="getLink('thermographs')" + :text="t('travel.summary.thermographs')" + /> <QTable :rows="thermographs" :columns="thermographsTableColumns" diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue index ac46caa44..ec898719d 100644 --- a/src/pages/Travel/ExtraCommunity.vue +++ b/src/pages/Travel/ExtraCommunity.vue @@ -18,7 +18,6 @@ import { usePrintService } from 'composables/usePrintService'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import axios from 'axios'; import RightMenu from 'src/components/common/RightMenu.vue'; -import VnPopup from 'src/components/common/VnPopup.vue'; const stateStore = useStateStore(); const { t } = useI18n(); @@ -141,7 +140,6 @@ const columns = computed(() => [ label: 'id', field: 'id', name: 'id', - align: 'center', showValue: true, sortable: true, }, @@ -165,7 +163,7 @@ const columns = computed(() => [ label: t('globals.amount'), name: 'invoiceAmount', field: 'entries', - align: 'left', + align: 'right', showValue: true, sortable: true, format: (value) => @@ -184,13 +182,12 @@ const columns = computed(() => [ align: 'left', showValue: false, sortable: true, - style: 'min-width: 170px;', }, { label: t('globals.packages'), field: 'stickers', name: 'stickers', - align: 'left', + align: 'right', showValue: true, sortable: true, }, @@ -198,7 +195,7 @@ const columns = computed(() => [ label: '%', field: '', name: 'percentage', - align: 'center', + align: 'right', showValue: false, sortable: true, }, @@ -214,7 +211,7 @@ const columns = computed(() => [ label: t('extraCommunity.physicKg'), field: 'loadedKg', name: 'loadedKg', - align: 'left', + align: 'right', showValue: true, sortable: true, }, @@ -222,7 +219,7 @@ const columns = computed(() => [ label: 'KG Vol.', field: 'volumeKg', name: 'volumeKg', - align: 'left', + align: 'right', showValue: true, sortable: true, }, @@ -277,7 +274,6 @@ async function getData() { const onStoreDataChange = () => { const newData = JSON.parse(JSON.stringify(arrayData.store.data)) || []; rows.value = newData; - // el objetivo de esto es guardar una copia de los valores iniciales de todas las rows para corroborar si la data cambio antes de guardar los cambios originalRowDataCopy.value = JSON.parse(JSON.stringify(newData)); }; @@ -300,20 +296,17 @@ const openReportPdf = () => { }; const saveFieldValue = async (val, field, index) => { - // Evitar la solicitud de guardado si el valor no ha cambiado if (originalRowDataCopy.value[index][field] == val) return; const id = rows.value[index].id; const params = { [field]: val }; await axios.patch(`Travels/${id}`, params); - // Actualizar la copia de los datos originales con el nuevo valor originalRowDataCopy.value[index][field] = val; await arrayData.fetch({ append: false }); }; const stopEventPropagation = (event, col) => { - // Detener la propagación del evento de los siguientes elementos para evitar el click sobre la row que dispararía la función navigateToTravelId if (!['ref', 'id', 'cargoSupplierNickname', 'kg'].includes(col.name)) return; event.preventDefault(); event.stopPropagation(); @@ -335,14 +328,12 @@ onMounted(async () => { await getData(); }); -// Handler del evento @dragstart (inicio del drag) y guarda información inicial const handleDragStart = (event, rowIndex, entryIndex) => { draggedRowIndex.value = rowIndex; entryRowIndex.value = entryIndex; event.dataTransfer.effectAllowed = 'move'; }; -// Handler del evento @dragenter (cuando haces drag sobre une elemento y lo arrastras sobre un posible target de drop) y actualiza el targetIndex const handleDragEnter = (_, targetIndex) => { targetRowIndex.value = targetIndex; }; @@ -356,11 +347,8 @@ const saveRowDrop = async (targetRowIndex) => { const moveRow = async (draggedRowIndex, targetRowIndex, entryIndex) => { try { if (draggedRowIndex === targetRowIndex) return; - // Remover entry de la row original draggedEntry.value = rows.value[draggedRowIndex].entries.splice(entryIndex, 1)[0]; - //Si la row de destino por alguna razón no tiene la propiedad entry la creamos if (!rows.value[targetRowIndex].entries) rows.value[targetRowIndex].entries = []; - // Añadir entry a la row de destino rows.value[targetRowIndex].entries.push(draggedEntry.value); await saveRowDrop(targetRowIndex); @@ -370,13 +358,11 @@ const moveRow = async (draggedRowIndex, targetRowIndex, entryIndex) => { } }; -// Handler de cuando haces un drop tanto dentro como fuera de la tabla para limpiar acciones y data const handleDragEnd = () => { stopScroll(); cleanDragAndDropData(); }; -// Handler del evento @drop (cuando soltas el elemento draggeado sobre un target) const handleDrop = () => { if ( !draggedRowIndex.value && @@ -399,7 +385,6 @@ const cleanDragAndDropData = () => { const scrollInterval = ref(null); const startScroll = (direction) => { - // Iniciar el scroll en la dirección especificada if (!scrollInterval.value) { scrollInterval.value = requestAnimationFrame(() => scroll(direction)); } @@ -413,14 +398,12 @@ const stopScroll = () => { }; const scroll = (direction) => { - // Controlar el desplazamiento en la dirección especificada const yOffset = direction === 'up' ? -2 : 2; window.scrollBy(0, yOffset); const windowHeight = window.innerHeight; const documentHeight = document.body.offsetHeight; - // Verificar si se alcanzaron los límites de la ventana para detener el desplazamiento if ( (direction === 'up' && window.scrollY > 0) || (direction === 'down' && windowHeight + window.scrollY < documentHeight) @@ -431,13 +414,10 @@ const scroll = (direction) => { } }; -// Handler del scroll mientras se hace el drag de una row const handleDragScroll = (event) => { - // Obtener la posición y dimensiones del cursor const y = event.clientY; const windowHeight = window.innerHeight; - // Verificar si el cursor está cerca del borde superior o inferior de la ventana const nearTop = y < 150; const nearBottom = y > windowHeight - 100; @@ -525,6 +505,7 @@ watch(route, () => { :props="props" @click="stopEventPropagation($event, col)" :style="col.style" + style="padding-left: 5px" > <component :is="tableColumnComponents[col.name].component" @@ -547,7 +528,7 @@ watch(route, () => { ? `${props.row.percentageKg}%` : '-' " - class="text-left q-py-xs q-px-sm" + class="text-right q-py-xs q-px-sm" :color="getColor(props.row.percentageKg)" /> <span @@ -566,7 +547,6 @@ watch(route, () => { ]" v-text="col.value" /> - <!-- Main Row Descriptors --> <TravelDescriptorProxy v-if="col.name === 'id'" :id="props.row.id" @@ -597,7 +577,7 @@ watch(route, () => { index === props.row.entries.length - 1, }" > - <QTd> + <QTd class="text-right"> <QBtn dense flat class="link">{{ entry.id }} </QBtn> <EntryDescriptorProxy :id="entry.id" /> </QTd> @@ -605,7 +585,7 @@ watch(route, () => { <QBtn flat class="link" dense>{{ entry.supplierName }}</QBtn> <SupplierDescriptorProxy :id="entry.supplierFk" /> </QTd> - <QTd> + <QTd class="text-center"> <QIcon v-if="entry.isCustomInspectionRequired" name="warning" @@ -615,40 +595,27 @@ watch(route, () => { > </QIcon> </QTd> - <QTd> + <QTd class="text-right"> <span>{{ toCurrency(entry.invoiceAmount) }}</span> </QTd> <QTd> <span>{{ entry.reference }}</span> </QTd> - <QTd> + <QTd class="text-right"> <span>{{ entry.stickers }}</span> </QTd> <QTd /> <QTd></QTd> - <QTd> + <QTd class="text-right"> <span>{{ entry.loadedkg }}</span> </QTd> - <QTd> + <QTd class="text-right"> <span>{{ entry.volumeKg }}</span> </QTd> - <QTd /> - <QTd /> - <QTd /> - <QTd /> - <QTd> - <QBtn - v-if="entry.evaNotes" - icon="comment" - size="md" - flat - color="primary" - > - <VnPopup - :title="t('globals.observations')" - :content="entry.evaNotes" - /> - </QBtn> + <QTd :colspan="5" class="text-right"> + <span> + {{ entry.evaNotes }} + </span> </QTd> </QTr> </template> @@ -662,11 +629,12 @@ watch(route, () => { } :deep(.q-table) { + table-layout: auto; + width: 100%; border-collapse: collapse; + overflow: hidden; + text-overflow: ellipsis; - th { - padding: 0; - } tbody tr td { &:nth-child(1) { max-width: 65px; @@ -675,6 +643,10 @@ watch(route, () => { padding: 0; } } + thead > tr > th { + padding: 3px; + color: var(--vn-label-color); + } } .q-td :deep(input) { diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index a26cc0ec0..5498fd269 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -80,7 +80,7 @@ defineExpose({ states }); /> <VnSelect :label="t('travel.warehouseOut')" - v-model="params.warehouseOut" + v-model="params.warehouseOutFk" @update:model-value="searchFn()" url="warehouses" :use-like="false" @@ -128,6 +128,7 @@ en: ref: Reference agency: Agency warehouseInFk: Warehouse In + warehouseOutFk: Warehouse Out shipped: Shipped shipmentHour: Shipment Hour warehouseOut: Warehouse Out @@ -141,6 +142,7 @@ es: ref: Referencia agency: Agencia warehouseInFk: Alm.Entrada + warehouseOutFk: Alm.Salida shipped: F.Envío shipmentHour: Hora de envío warehouseOut: Alm.Salida diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index b227afcb2..32ddc639a 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -201,7 +201,7 @@ const columns = computed(() => [ { title: t('components.smartCard.viewSummary'), icon: 'preview', - action: (row) => viewSummary(row.id, TravelSummary), + action: (row) => viewSummary(row.id, TravelSummary, 'lg-width'), isPrimary: true, }, ], diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index ad78a3fb9..40787613c 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -48,77 +48,88 @@ onBeforeMount(async () => { <WorkerDescriptorMenu :worker="entity" :is-excluded="workerExcluded" /> </template> <template #body="{ entity: worker }"> - <QCard class="vn-one"> + <QCard class="vn-two"> <VnTitle :url="basicDataUrl" :text="t('globals.summary.basicData')" /> - <VnLv :label="t('globals.name')" :value="worker.user?.nickname" /> - <VnLv :label="t('globals.department')"> - <template #value> - <span class="link" v-text="worker.department?.department?.name" /> - <DepartmentDescriptorProxy - :id="worker.department?.department?.id" + <div class="vn-card-group"> + <div class="vn-card-content"> + <VnLv :label="t('globals.name')" :value="worker.user?.nickname" /> + <VnLv :label="t('globals.department')"> + <template #value> + <span + class="link" + v-text="worker.department?.department?.name" + /> + <DepartmentDescriptorProxy + :id="worker.department?.department?.id" + /> + </template> + </VnLv> + <VnLv :label="t('worker.summary.boss')" link> + <template #value> + <VnUserLink + v-if="worker.boss" + :name="dashIfEmpty(worker.boss?.name)" + :worker-id="worker.bossFk" + /> + </template> + </VnLv> + <VnLv :value="worker.mobileExtension"> + <template #label> + {{ t('worker.summary.phoneExtension') }} + <VnLinkPhone :phone-number="worker.mobileExtension" /> + </template> + </VnLv> + <VnLv :value="worker.phone"> + <template #label> + {{ t('worker.summary.entPhone') }} + <VnLinkPhone :phone-number="worker.phone" /> + </template> + </VnLv> + <VnLv :value="advancedSummary?.client?.phone"> + <template #label> + {{ t('worker.summary.personalPhone') }} + <VnLinkPhone + :phone-number="advancedSummary?.client?.phone" + /> + </template> + </VnLv> + </div> + <div class="vn-card-content"> + <VnLv + :label="t('worker.summary.fiDueDate')" + :value="toDate(advancedSummary.fiDueDate)" /> - </template> - </VnLv> - <VnLv :label="t('worker.summary.boss')" link> - <template #value> - <VnUserLink - v-if="worker.boss" - :name="dashIfEmpty(worker.boss?.name)" - :worker-id="worker.bossFk" + <VnLv :label="t('worker.summary.sex')" :value="worker.sex" /> + <VnLv + :label="t('worker.summary.seniority')" + :value="toDate(advancedSummary.seniority)" /> - </template> - </VnLv> - <VnLv :value="worker.mobileExtension"> - <template #label> - {{ t('worker.summary.phoneExtension') }} - <VnLinkPhone :phone-number="worker.mobileExtension" /> - </template> - </VnLv> - <VnLv :value="worker.phone"> - <template #label> - {{ t('worker.summary.entPhone') }} - <VnLinkPhone :phone-number="worker.phone" /> - </template> - </VnLv> - <VnLv :value="advancedSummary?.client?.phone"> - <template #label> - {{ t('worker.summary.personalPhone') }} - <VnLinkPhone :phone-number="advancedSummary?.client?.phone" /> - </template> - </VnLv> - </QCard> - <QCard class="vn-one" v-if="advancedSummary"> - <VnTitle :url="basicDataUrl" :text="t('globals.summary.basicData')" /> - <VnLv - :label="t('worker.summary.fiDueDate')" - :value="toDate(advancedSummary.fiDueDate)" - /> - <VnLv :label="t('worker.summary.sex')" :value="worker.sex" /> - <VnLv - :label="t('worker.summary.seniority')" - :value="toDate(advancedSummary.seniority)" - /> - <VnLv :label="t('worker.summary.fi')" :value="advancedSummary.fi" /> - <VnLv - :label="t('worker.summary.birth')" - :value="toDate(advancedSummary.birth)" - /> - <VnLv - :label="t('worker.summary.isFreelance')" - :value="advancedSummary.isFreelance" - /> - <VnLv - :label="t('worker.summary.isSsDiscounted')" - :value="advancedSummary.isSsDiscounted" - /> - <VnLv - :label="t('worker.summary.hasMachineryAuthorized')" - :value="advancedSummary.hasMachineryAuthorized" - /> - <VnLv - :label="t('worker.summary.isDisable')" - :value="advancedSummary.isDisable" - /> + <VnLv + :label="t('worker.summary.fi')" + :value="advancedSummary.fi" + /> + <VnLv + :label="t('worker.summary.birth')" + :value="toDate(advancedSummary.birth)" + /> + <VnLv + :label="t('worker.summary.isFreelance')" + :value="advancedSummary.isFreelance" + /> + <VnLv + :label="t('worker.summary.isSsDiscounted')" + :value="advancedSummary.isSsDiscounted" + /> + <VnLv + :label="t('worker.summary.hasMachineryAuthorized')" + :value="advancedSummary.hasMachineryAuthorized" + /> + <VnLv + :label="t('worker.summary.isDisable')" + :value="advancedSummary.isDisable" + /> + </div> + </div> </QCard> <QCard class="vn-one"> <VnTitle :text="t('worker.summary.userData')" /> diff --git a/src/pages/Worker/WorkerFilter.vue b/src/pages/Worker/WorkerFilter.vue index 44dfd32b4..c40afe57e 100644 --- a/src/pages/Worker/WorkerFilter.vue +++ b/src/pages/Worker/WorkerFilter.vue @@ -102,8 +102,11 @@ en: lastName: Last name userName: User extension: Extension + departmentFk: Department es: + params: + departmentFk: Departamento search: Contiene firstName: Nombre lastName: Apellidos diff --git a/src/pages/Worker/WorkerList.vue b/src/pages/Worker/WorkerList.vue index b76790075..cb722a139 100644 --- a/src/pages/Worker/WorkerList.vue +++ b/src/pages/Worker/WorkerList.vue @@ -105,7 +105,7 @@ const columns = computed(() => [ { title: t('components.smartCard.viewSummary'), icon: 'preview', - action: (row) => viewSummary(row.id, WorkerSummary), + action: (row) => viewSummary(row.id, WorkerSummary, 'lg-width'), isPrimary: true, }, ], diff --git a/src/pages/Zone/Card/ZoneSummary.vue b/src/pages/Zone/Card/ZoneSummary.vue index 2c56fa3e2..61475b1f6 100644 --- a/src/pages/Zone/Card/ZoneSummary.vue +++ b/src/pages/Zone/Card/ZoneSummary.vue @@ -75,21 +75,30 @@ onMounted(async () => { <template #body="{ entity: zone }"> <QCard class="vn-one"> <VnTitle :url="zoneUrl + `basic-data`" :text="t('summary.basicData')" /> - <VnLv :label="t('list.agency')" :value="zone.agencyMode?.name" /> - <VnLv :label="t('list.price')" :value="toCurrency(zone.price)" /> - <VnLv :label="t('zone.bonus')" :value="toCurrency(zone.bonus)" /> + <div class="card-group"> + <div class="card-content"> + <VnLv :label="t('list.agency')" :value="zone.agencyMode?.name" /> + <VnLv :label="t('list.price')" :value="toCurrency(zone.price)" /> + <VnLv :label="t('zone.bonus')" :value="toCurrency(zone.bonus)" /> + </div> + <div class="card-content"> + <VnLv + :label="t('summary.closeHour')" + :value="toTimeFormat(zone.hour)" + /> + <VnLv + :label="t('zone.travelingDays')" + :value="zone.travelingDays" + /> + <QCheckbox + :label="t('zone.volumetric')" + v-model="zone.isVolumetric" + :disable="true" + /> + </div> + </div> </QCard> <QCard class="vn-one"> - <VnTitle :url="zoneUrl + `basic-data`" :text="t('summary.basicData')" /> - <VnLv :label="t('summary.closeHour')" :value="toTimeFormat(zone.hour)" /> - <VnLv :label="t('zone.travelingDays')" :value="zone.travelingDays" /> - <QCheckbox - :label="t('zone.volumetric')" - v-model="zone.isVolumetric" - :disable="true" - /> - </QCard> - <QCard class="full-width"> <VnTitle :url="zoneUrl + `warehouses`" :text="t('list.warehouse')" /> <QTable :columns="columns" @@ -100,3 +109,15 @@ onMounted(async () => { </template> </CardSummary> </template> + +<style lang="scss" scoped> +.card-group { + display: flex; + flex-direction: column; +} + +.card-content { + display: flex; + flex-direction: column; +} +</style> diff --git a/src/router/__tests__/hooks.spec.js b/src/router/__tests__/hooks.spec.js new file mode 100644 index 000000000..97f5eacdc --- /dev/null +++ b/src/router/__tests__/hooks.spec.js @@ -0,0 +1,36 @@ +import { describe, it, expect, vi } from 'vitest'; +import { ref, nextTick } from 'vue'; +import { stateQueryGuard } from 'src/router/hooks'; +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; + +vi.mock('src/stores/useStateQueryStore', () => { + const isLoading = ref(true); + return { + useStateQueryStore: () => ({ + isLoading: () => isLoading, + setLoading: isLoading, + }), + }; +}); + +describe('hooks', () => { + describe('stateQueryGuard', () => { + const foo = { name: 'foo' }; + it('should wait until the state query is not loading and then call next()', async () => { + const next = vi.fn(); + + stateQueryGuard(foo, { name: 'bar' }, next); + expect(next).not.toHaveBeenCalled(); + + useStateQueryStore().setLoading.value = false; + await nextTick(); + expect(next).toHaveBeenCalled(); + }); + + it('should ignore if both routes are the same', () => { + const next = vi.fn(); + stateQueryGuard(foo, foo, next); + expect(next).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/router/hooks.js b/src/router/hooks.js new file mode 100644 index 000000000..bd9e5334f --- /dev/null +++ b/src/router/hooks.js @@ -0,0 +1,95 @@ +import { useRole } from 'src/composables/useRole'; +import { useUserConfig } from 'src/composables/useUserConfig'; +import { useTokenConfig } from 'src/composables/useTokenConfig'; +import { useAcl } from 'src/composables/useAcl'; +import { isLoggedIn } from 'src/utils/session'; +import { useSession } from 'src/composables/useSession'; +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; +import { watch } from 'vue'; +import { i18n } from 'src/boot/i18n'; + +let session = null; +const { t, te } = i18n.global; + +export async function navigationGuard(to, from, next, Router, state) { + if (!session) session = useSession(); + const outLayout = Router.options.routes[0].children.map((r) => r.name); + if (!session.isLoggedIn() && !outLayout.includes(to.name)) { + return next({ name: 'Login', query: { redirect: to.fullPath } }); + } + + if (isLoggedIn()) { + const stateRoles = state.getRoles().value; + if (stateRoles.length === 0) { + await useRole().fetch(); + await useAcl().fetch(); + await useUserConfig().fetch(); + await useTokenConfig().fetch(); + } + const matches = to.matched; + const hasRequiredAcls = matches.every((route) => { + const meta = route.meta; + if (!meta?.acls) return true; + return useAcl().hasAny(meta.acls); + }); + if (!hasRequiredAcls) return next({ path: '/' }); + } + + next(); +} + +export async function stateQueryGuard(to, from, next) { + if (to.name !== from.name) { + const stateQuery = useStateQueryStore(); + await waitUntilFalse(stateQuery.isLoading()); + } + + next(); +} + +export function setPageTitle(to) { + let title = t(`login.title`); + + const matches = to.matched; + if (matches && matches.length > 1) { + const module = matches[1]; + const moduleTitle = module.meta?.title; + if (moduleTitle) { + title = t(`globals.pageTitles.${moduleTitle}`); + } + } + + const childPage = to.meta; + const childPageTitle = childPage?.title; + if (childPageTitle && matches.length > 2) { + if (title != '') title += ': '; + + const moduleLocale = `globals.pageTitles.${childPageTitle}`; + const pageTitle = te(moduleLocale) + ? t(moduleLocale) + : t(`globals.pageTitles.${childPageTitle}`); + const idParam = to.params?.id; + const idPageTitle = `${idParam} - ${pageTitle}`; + const builtTitle = idParam ? idPageTitle : pageTitle; + + title += builtTitle; + } + + document.title = title; +} + +function waitUntilFalse(ref) { + return new Promise((resolve) => { + if (!ref.value) return resolve(); + const stop = watch( + ref, + (val) => { + if (!val) { + stop(); + resolve(); + } + }, + { immediate: true }, + ); + }); +} diff --git a/src/router/index.js b/src/router/index.js index 4403901cb..628a53c8e 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -6,101 +6,25 @@ import { createWebHashHistory, } from 'vue-router'; import routes from './routes'; -import { i18n } from 'src/boot/i18n'; import { useState } from 'src/composables/useState'; -import { useRole } from 'src/composables/useRole'; -import { useUserConfig } from 'src/composables/useUserConfig'; -import { useTokenConfig } from 'src/composables/useTokenConfig'; -import { useAcl } from 'src/composables/useAcl'; -import { isLoggedIn } from 'src/utils/session'; -import { useSession } from 'src/composables/useSession'; +import { navigationGuard, setPageTitle, stateQueryGuard } from './hooks'; -let session = null; -const { t, te } = i18n.global; - -const createHistory = process.env.SERVER - ? createMemoryHistory - : process.env.VUE_ROUTER_MODE === 'history' - ? createWebHistory - : createWebHashHistory; +const webHistory = + process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory; +const createHistory = process.env.SERVER ? createMemoryHistory : webHistory; const Router = createRouter({ scrollBehavior: () => ({ left: 0, top: 0 }), routes, - - // Leave this as is and make changes in quasar.conf.js instead! - // quasar.conf.js -> build -> vueRouterMode - // quasar.conf.js -> build -> publicPath history: createHistory(process.env.VUE_ROUTER_BASE), }); -/* - * If not building with SSR mode, you can - * directly export the Router instantiation; - * - * The function below can be async too; either use - * async/await or return a Promise which resolves - * with the Router instance. - */ export { Router }; -export default defineRouter(function (/* { store, ssrContext } */) { +export default defineRouter(() => { const state = useState(); - Router.beforeEach(async (to, from, next) => { - if (!session) session = useSession(); - const outLayout = Router.options.routes[0].children.map((r) => r.name); - if (!session.isLoggedIn() && !outLayout.includes(to.name)) { - return next({ name: 'Login', query: { redirect: to.fullPath } }); - } - - if (isLoggedIn()) { - const stateRoles = state.getRoles().value; - if (stateRoles.length === 0) { - await useRole().fetch(); - await useAcl().fetch(); - await useUserConfig().fetch(); - await useTokenConfig().fetch(); - } - const matches = to.matched; - const hasRequiredAcls = matches.every((route) => { - const meta = route.meta; - if (!meta?.acls) return true; - return useAcl().hasAny(meta.acls); - }); - if (!hasRequiredAcls) return next({ path: '/' }); - } - - next(); - }); - - Router.afterEach((to) => { - let title = t(`login.title`); - - const matches = to.matched; - if (matches && matches.length > 1) { - const module = matches[1]; - const moduleTitle = module.meta && module.meta.title; - if (moduleTitle) { - title = t(`globals.pageTitles.${moduleTitle}`); - } - } - - const childPage = to.meta; - const childPageTitle = childPage && childPage.title; - if (childPageTitle && matches.length > 2) { - if (title != '') title += ': '; - - const moduleLocale = `globals.pageTitles.${childPageTitle}`; - const pageTitle = te(moduleLocale) - ? t(moduleLocale) - : t(`globals.pageTitles.${childPageTitle}`); - const idParam = to.params && to.params.id; - const idPageTitle = `${idParam} - ${pageTitle}`; - const builtTitle = idParam ? idPageTitle : pageTitle; - - title += builtTitle; - } - document.title = title; - }); + Router.beforeEach((to, from, next) => navigationGuard(to, from, next, Router, state)); + Router.beforeEach(stateQueryGuard); + Router.afterEach(setPageTitle); Router.onError(({ message }) => { const errorMessages = [ diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 62765a49c..0dd41c86e 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -166,7 +166,7 @@ const vehicleCard = { component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'), redirect: { name: 'VehicleSummary' }, meta: { - menu: ['VehicleBasicData'], + menu: ['VehicleBasicData', 'VehicleNotes'], }, children: [ { @@ -187,6 +187,15 @@ const vehicleCard = { }, component: () => import('src/pages/Route/Vehicle/Card/VehicleBasicData.vue'), }, + { + name: 'VehicleNotes', + path: 'notes', + meta: { + title: 'notes', + icon: 'vn:notes', + }, + component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'), + } ], }; @@ -229,6 +238,7 @@ export default { title: 'list', icon: 'view_list', }, + component: () => import('src/pages/Route/RouteList.vue'), }, routeCard, ], @@ -277,6 +287,7 @@ export default { title: 'list', icon: 'view_list', }, + component: () => import('src/pages/Route/RouteRoadmap.vue'), }, roadmapCard, ], @@ -307,6 +318,8 @@ export default { title: 'list', icon: 'view_list', }, + component: () => + import('src/pages/Route/Agency/AgencyList.vue'), }, agencyCard, ], @@ -328,6 +341,8 @@ export default { title: 'vehicleList', icon: 'directions_car', }, + component: () => + import('src/pages/Route/Vehicle/VehicleList.vue'), }, vehicleCard, ], diff --git a/src/stores/useDescriptorStore.js b/src/stores/useDescriptorStore.js index 89189f32e..be342b016 100644 --- a/src/stores/useDescriptorStore.js +++ b/src/stores/useDescriptorStore.js @@ -11,14 +11,12 @@ export const useDescriptorStore = defineStore('descriptorStore', () => { const files = import.meta.glob(`/src/**/*DescriptorProxy.vue`); const moduleParser = { account: 'user', - client: 'customer', + customer: 'client', }; for (const file in files) { const name = file.split('/').at(-1).slice(0, -19).toLowerCase(); const descriptor = moduleParser[name] ?? name; - currentDescriptors[descriptor + 'Fk'] = defineAsyncComponent( - () => import(/* @vite-ignore */ file), - ); + currentDescriptors[descriptor + 'Fk'] = defineAsyncComponent(files[file]); } setDescriptors(currentDescriptors); return currentDescriptors; diff --git a/test/cypress/integration/account/accountDescriptorMenu.spec.js b/test/cypress/integration/account/accountDescriptorMenu.spec.js index 67a7d8ef6..04fc57040 100644 --- a/test/cypress/integration/account/accountDescriptorMenu.spec.js +++ b/test/cypress/integration/account/accountDescriptorMenu.spec.js @@ -1,4 +1,4 @@ -describe('ClaimNotes', () => { +describe('Account descriptor', () => { const descriptorOptions = '[data-cy="descriptor-more-opts-menu"] > .q-list'; const url = '/#/account/1/summary'; @@ -7,6 +7,9 @@ describe('ClaimNotes', () => { cy.visit(url); cy.dataCy('descriptor-more-opts').click(); cy.get(descriptorOptions) + .should('exist') + .should('be.visible') + .find('.q-item') .its('length') .then((count) => { diff --git a/test/cypress/integration/entry/commands.js b/test/cypress/integration/entry/commands.js index 7c96a5440..4d4a8f980 100644 --- a/test/cypress/integration/entry/commands.js +++ b/test/cypress/integration/entry/commands.js @@ -1,6 +1,6 @@ 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.selectOption('input[data-cy="Warehouse Out_select"]', 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(); @@ -9,7 +9,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => { 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', () => { diff --git a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js index 554471008..8185866db 100644 --- a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js +++ b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js @@ -28,12 +28,8 @@ describe('EntryDescriptor', () => { 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"]'); diff --git a/test/cypress/integration/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js index 990f74261..bad47615f 100644 --- a/test/cypress/integration/entry/entryList.spec.js +++ b/test/cypress/integration/entry/entryList.spec.js @@ -44,11 +44,12 @@ describe('EntryList', () => { }, ); - checkBadgeDate( + // fix on task https://redmine.verdnatura.es/issues/8638 + /* checkBadgeDate( 'td[data-col-field="landed"] > div .bg-info', (badgeDate, compareDate) => { expect(badgeDate.getTime()).to.be.lessThan(compareDate.getTime()); }, - ); + ); */ }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js index 44a61609e..7254e8909 100644 --- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -4,7 +4,7 @@ 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 summaryHeaders = (opt) => `.summaryBody > .${opt} > .q-pb-lg > .header-link`; const mockInvoiceRef = `createMockInvoice${Math.floor(Math.random() * 100)}`; const mock = { vnSupplierSelect: { val: 'farmer king', type: 'select' }, @@ -32,8 +32,8 @@ describe('InvoiceInList', () => { it('should open the details', () => { cy.get(firstDetailBtn).click(); - cy.get(summaryHeaders).eq(1).contains('Basic data'); - cy.get(summaryHeaders).eq(4).contains('Vat'); + cy.get(summaryHeaders('max-width')).contains('Basic data'); + cy.get(summaryHeaders('vat')).contains('Vat'); }); it('should create a new Invoice', () => { @@ -41,12 +41,12 @@ describe('InvoiceInList', () => { 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.wait('@invoice').then(() => { cy.validateDescriptor({ title: mockInvoiceRef, listBox: { 0: '11/16/2001', 3: 'The farmer' }, - }), - ); - cy.get('[data-cy="vnLvCompany"]').should('contain.text', 'ORN'); + }); + cy.dataCy('invoiceInBasicDataCompanyFk').should('have.value', 'ORN'); + }); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js index e9412244f..ff7d639e6 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -1,7 +1,7 @@ /// <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'; @@ -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 34cd2bffc..ee011ea05 100644 --- a/test/cypress/integration/order/orderList.spec.js +++ b/test/cypress/integration/order/orderList.spec.js @@ -30,9 +30,11 @@ describe('OrderList', () => { cy.url().should('include', `/order`); }); - it.skip('filter list and create order', () => { + it('filter list and create order', () => { cy.dataCy('Customer ID_input').type('1101{enter}'); + cy.intercept('GET', /\/api\/Clients/).as('clientFilter'); cy.dataCy('vnTableCreateBtn').click(); + cy.wait('@clientFilter'); cy.dataCy('landedDate').find('input').type('06/01/2001'); cy.selectOption(agencyCreateSelect, 1); diff --git a/test/cypress/integration/route/agency/agencyModes.spec.js b/test/cypress/integration/route/agency/agencyModes.spec.js new file mode 100644 index 000000000..3f5784997 --- /dev/null +++ b/test/cypress/integration/route/agency/agencyModes.spec.js @@ -0,0 +1,15 @@ +describe('Agency modes', () => { + const name = 'inhouse pickup'; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/route/agency/1/modes`); + }); + + it('should display the agency modes page', () => { + cy.get('.flex > .title').should('have.text', name); + cy.get('.flex > .q-chip > .q-chip__content').should('have.text', 'ID: 1'); + cy.get('.list-items > :nth-child(1) > .value').should('have.text', name); + }); +}); diff --git a/test/cypress/integration/route/routeAutonomous.spec.js b/test/cypress/integration/route/routeAutonomous.spec.js index acf82bd95..d77584c04 100644 --- a/test/cypress/integration/route/routeAutonomous.spec.js +++ b/test/cypress/integration/route/routeAutonomous.spec.js @@ -1,4 +1,4 @@ -describe('RouteAutonomous', () => { +describe.skip('RouteAutonomous', () => { const getLinkSelector = (colField) => `tr:first-child > [data-col-field="${colField}"] > .no-padding > .link`; @@ -49,12 +49,12 @@ describe('RouteAutonomous', () => { cy.get(selectors.firstRowCheckbox).click(); cy.get(selectors.createInvoiceBtn).click(); cy.dataCy(selectors.reference).type(data.reference); + cy.dataCy('attachFile').click(); cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { force: true, }); - cy.dataCy(selectors.saveFormBtn).click(); + cy.dataCy(selectors.saveFormBtn).should('be.visible').click(); cy.checkNotification(dataSaved); - cy.typeSearchbar('{enter}'); }); it('Should display the total price of the selected rows', () => { diff --git a/test/cypress/integration/route/routeExtendedList.spec.js b/test/cypress/integration/route/routeExtendedList.spec.js index a183c08cb..e6c873d5e 100644 --- a/test/cypress/integration/route/routeExtendedList.spec.js +++ b/test/cypress/integration/route/routeExtendedList.spec.js @@ -53,17 +53,20 @@ describe('Route extended list', () => { function fillField(selector, type, value) { switch (type) { case 'select': - cy.get(selector).should('be.visible').click(); - cy.dataCy('null_select').clear().type(value); + cy.get(selector).should('be.visible').click().clear().type(value); cy.get('.q-item').contains(value).click(); break; case 'input': - cy.get(selector).should('be.visible').click(); - cy.dataCy('null_input').clear().type(`${value}`); + cy.get(selector) + .should('be.visible') + .click() + .type(`{selectall}{backspace}${value}`); break; case 'date': - cy.get(selector).should('be.visible').click(); - cy.dataCy('null_inputDate').clear().type(`${value}`); + cy.get(selector) + .should('be.visible') + .click() + .type(`{selectall}{backspace}${value}`); break; case 'checkbox': cy.get(selector).should('be.visible').click().click(); @@ -103,8 +106,8 @@ describe('Route extended list', () => { cy.fillInForm(data); cy.dataCy(selectors.saveFormBtn).click(); - cy.checkNotification(dataCreated); cy.url().should('include', '/summary'); + cy.checkNotification(dataCreated); }); it('Should reset changed values when click reset button', () => { @@ -140,7 +143,7 @@ describe('Route extended list', () => { const downloadsFolder = Cypress.config('downloadsFolder'); cy.get(selectors.lastRowSelectCheckBox).click(); cy.get(selectors.downloadBtn).click(); - cy.wait(5000); + cy.wait(3000); const fileName = 'download.zip'; cy.readFile(`${downloadsFolder}/${fileName}`).should('exist'); @@ -177,7 +180,7 @@ describe('Route extended list', () => { const [month, day, year] = value.split('/'); value = `${day}/${month}/${year}`; } - cy.validateContent(selector, value); + cy.get(selector).should('contain', value); }); }); diff --git a/test/cypress/integration/route/vehicle/vehicleNotes.spec.js b/test/cypress/integration/route/vehicle/vehicleNotes.spec.js new file mode 100644 index 000000000..cd92cc4af --- /dev/null +++ b/test/cypress/integration/route/vehicle/vehicleNotes.spec.js @@ -0,0 +1,28 @@ +describe('Vehicle Notes', () => { + const selectors = { + addNoteInput: 'Add note here..._input', + saveNoteBtn: 'saveNote', + deleteNoteBtn: 'notesRemoveNoteBtn', + noteCard: '.column.full-width > :nth-child(1) > .q-card__section--vert', + }; + + const noteText = 'Golpe parachoques trasero'; + const newNoteText = 'probando'; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/route/vehicle/1/notes`); + }); + + it('Should add new note', () => { + cy.dataCy(selectors.addNoteInput).should('be.visible').type(newNoteText); + cy.dataCy(selectors.saveNoteBtn).click(); + cy.validateContent(selectors.noteCard, newNoteText); + }); + + it('Should delete note', () => { + cy.dataCy(selectors.deleteNoteBtn).first().should('be.visible').click(); + cy.get(selectors.noteCard).first().should('have.text', noteText); + }); +}); diff --git a/test/cypress/integration/ticket/ticketDescriptor.spec.js b/test/cypress/integration/ticket/ticketDescriptor.spec.js index 0ba2723a2..b5c95c463 100644 --- a/test/cypress/integration/ticket/ticketDescriptor.spec.js +++ b/test/cypress/integration/ticket/ticketDescriptor.spec.js @@ -3,10 +3,10 @@ describe('Ticket descriptor', () => { const listItem = '[role="menu"] .q-list .q-item'; const toCloneOpt = 'To clone ticket'; const setWeightOpt = 'Set weight'; - const warehouseValue = ':nth-child(1) > :nth-child(6) > .value > span'; + const warehouseValue = ':nth-child(1) > [data-cy="vnLvWarehouse"]'; const summaryHeader = '.summaryHeader > div'; const weight = 25; - const weightValue = '.summaryBody.row :nth-child(1) > :nth-child(9) > .value > span'; + const weightValue = '[data-cy="vnLvWeight"]'; beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); diff --git a/test/cypress/integration/vnComponent/UserPanel.spec.js b/test/cypress/integration/vnComponent/UserPanel.spec.js index 25724e873..8722fe37e 100644 --- a/test/cypress/integration/vnComponent/UserPanel.spec.js +++ b/test/cypress/integration/vnComponent/UserPanel.spec.js @@ -39,11 +39,11 @@ describe('UserPanel', () => { cy.get(userCompany).should('have.value', 'Warehouse One').click(); //Actualizo la opción - cy.getOption(2); + cy.getOption(3); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); - cy.get(userCompany).should('have.value', 'Warehouse Two'); + cy.get(userCompany).should('have.value', 'TestingWarehouse'); //Restauro el valor cy.get(userCompany).click(); diff --git a/test/cypress/integration/vnComponent/VnAccountNumber.spec.js b/test/cypress/integration/vnComponent/VnAccountNumber.spec.js deleted file mode 100644 index 053902f35..000000000 --- a/test/cypress/integration/vnComponent/VnAccountNumber.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -describe('VnAccountNumber', () => { - const accountInput = 'input[data-cy="supplierFiscalDataAccount_input"]'; - beforeEach(() => { - cy.login('developer'); - cy.viewport(1920, 1080); - cy.visit('/#/supplier/1/fiscal-data'); - }); - - describe('VnInput handleInsertMode()', () => { - it('should replace character at cursor position in insert mode', () => { - cy.get(accountInput).type('{selectall}4100000001'); - cy.get(accountInput).type('{movetostart}'); - cy.get(accountInput).type('999'); - cy.get(accountInput).should('have.value', '9990000001'); - }); - - it('should replace character at cursor position in insert mode', () => { - cy.get(accountInput).clear(); - cy.get(accountInput).type('4100000001'); - cy.get(accountInput).type('{movetostart}'); - cy.get(accountInput).type('999'); - cy.get(accountInput).should('have.value', '9990000001'); - }); - - it('should respect maxlength prop', () => { - cy.get(accountInput).clear(); - cy.get(accountInput).type('123456789012345'); - cy.get(accountInput).should('have.value', '1234567890'); - }); - }); - - it('should convert short account number to standard format', () => { - cy.get(accountInput).clear(); - cy.get(accountInput).type('123.'); - cy.get(accountInput).should('have.value', '1230000000'); - }); -}); diff --git a/test/cypress/integration/vnComponent/VnLog.spec.js b/test/cypress/integration/vnComponent/VnLog.spec.js index ae0013150..57faeac85 100644 --- a/test/cypress/integration/vnComponent/VnLog.spec.js +++ b/test/cypress/integration/vnComponent/VnLog.spec.js @@ -1,26 +1,23 @@ /// <reference types="cypress" /> describe('VnLog', () => { - const chips = [ - ':nth-child(1) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content', - ':nth-child(2) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content', - ]; beforeEach(() => { cy.login('developer'); cy.visit(`/#/claim/${1}/log`); - cy.openRightMenu(); }); it('should filter by insert actions', () => { - cy.checkOption(':nth-child(7) > .q-checkbox'); - cy.get('.q-page').click(); - cy.validateContent(chips[0], 'Document'); - cy.validateContent(chips[1], 'Beginning'); + cy.get('[data-cy="vnLog-checkbox"]').eq(0).click(); + cy.get('[data-cy="vnLog-action-icon"]').each(($el) => { + cy.wrap($el).should('have.attr', 'title', 'Creates'); + }); }); it('should filter by entity', () => { - cy.selectOption('.q-drawer--right .q-item > .q-select', 'Claim'); - cy.get('.q-page').click(); - cy.validateContent(chips[0], 'Claim'); + const entity = 'Document'; + cy.selectOption('[data-cy="vnLog-entity"]', entity); + cy.get('[data-cy="vnLog-model-chip"]').each(($el) => { + cy.wrap($el).should('have.text', entity); + }); }); it('should show claimDescriptor', () => { diff --git a/test/cypress/integration/worker/workerSummary.spec.js b/test/cypress/integration/worker/workerSummary.spec.js index c50b2c943..6071c4cdf 100644 --- a/test/cypress/integration/worker/workerSummary.spec.js +++ b/test/cypress/integration/worker/workerSummary.spec.js @@ -1,27 +1,25 @@ describe('WorkerSummary', () => { - const departmentDescriptor = ':nth-child(1) > :nth-child(3) > .value > .link'; - const roleDescriptor = ':nth-child(3) > :nth-child(4) > .value > .link'; + const department = ':nth-child(1) > [data-cy="vnLvDepartment"] > .value'; + const role = '[data-cy="vnLvRole"] > .value'; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); cy.visit('/#/worker/19/summary'); }); - it('should load worker summary', () => { + it('should load worker summary and show the department', () => { cy.waitForElement('.summaryHeader'); cy.get('.summaryHeader > div').should('have.text', '19 - salesboss salesboss'); - cy.get(':nth-child(1) > :nth-child(2) > .value > span').should( - 'have.text', - 'salesBossNick', - ); + cy.get(department).should('have.text', 'VENTAS'); }); it('should try descriptors', () => { cy.waitForElement('.summaryHeader'); - cy.get(departmentDescriptor).click(); + cy.get(department).click(); cy.get('.descriptor').should('be.visible'); cy.get('.q-item > .q-item__label').should('include.text', '43'); - cy.get(roleDescriptor).click(); + cy.get('.summaryBody').click(); + cy.get(role).click(); cy.get('.descriptor').should('be.visible'); cy.get('.q-item > .q-item__label').should('include.text', '19'); });