diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 263e5eb89..000000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -/dist -/src-capacitor -/src-cordova -/.quasar -/node_modules -.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5c33d2118..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,75 +0,0 @@ -export default { - // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy - // This option interrupts the configuration hierarchy at this file - // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) - root: true, - - parserOptions: { - ecmaVersion: '2021', // Allows for the parsing of modern ECMAScript features - }, - - env: { - node: true, - browser: true, - 'vue/setup-compiler-macros': true, - }, - - // Rules order is important, please avoid shuffling them - extends: [ - // Base ESLint recommended rules - 'eslint:recommended', - - // Uncomment any of the lines below to choose desired strictness, - // but leave only one uncommented! - // See https://eslint.vuejs.org/rules/#available-rules - // 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) - 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) - // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) - - // https://github.com/prettier/eslint-config-prettier#installation - // usage with Prettier, provided by 'eslint-config-prettier'. - 'prettier', - ], - - plugins: [ - // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files - // required to lint *.vue files - 'vue', - - // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 - // Prettier has not been included as plugin to avoid performance impact - // add it as an extension for your IDE - ], - - globals: { - ga: 'readonly', // Google Analytics - cordova: 'readonly', - __statics: 'readonly', - __QUASAR_SSR__: 'readonly', - __QUASAR_SSR_SERVER__: 'readonly', - __QUASAR_SSR_CLIENT__: 'readonly', - __QUASAR_SSR_PWA__: 'readonly', - process: 'readonly', - Capacitor: 'readonly', - chrome: 'readonly', - }, - - // add your custom rules here - rules: { - 'prefer-promise-reject-errors': 'off', - 'no-unused-vars': 'warn', - 'vue/no-multiple-template-root': 'off', - // allow debugger during development only - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - }, - overrides: [ - { - files: ['test/cypress/**/*.*'], - extends: [ - // Add Cypress-specific lint rules, globals and Cypress plugin - // See https://github.com/cypress-io/eslint-plugin-cypress#rules - 'plugin:cypress/recommended', - ], - }, - ], -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..ba1902263 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["plugin:cypress/recommended"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b7c73f7..3b654a1ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,398 @@ +# 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 🆕 + +- chore: refs #6695 empty commit by:alexm +- chore: refs #6695 get docker compose version by:alexm +- chore: refs #6695 try use docker compose by:alexm +- feat: add --browser chromium by:Javier Segarra +- feat: docker pull back image by:alexm +- feat(jenkinsE2E): refs #6695 new image by:alexm +- feat(jenkinsE2E): refs #6695 try fix db by:alexm +- feat(jenkinsE2E): refs #6695 try new sintax by:alexm +- feat(Jenkinsfile): refs #8714 add CHANGE_TARGET environment variable logging (origin/8714-devToTest, 8714-devToTest) by:alexm +- feat: refs #6695 add additional test directories for Cypress integration tests in Jenkinsfile by:alexm +- feat: refs #6695 add cypress-cache volume to docker-compose.e2e.yml by:alexm +- feat: refs #6695 add Dockerfile for Cypress setup and update Jenkinsfile for installation steps by:alexm +- feat: refs #6695 add setup and e2e testing by:alexm +- feat: refs #6695 better stages for e2e by:alexm +- feat: refs #6695 better stages for e2e rollback by:alexm +- feat: refs #6695 install Cypress during Jenkins pipeline setup by:alexm +- feat: refs #6695 jenkins run e2e by:alexm +- feat: refs #6695 jenkins run e2e front deteach by:alexm +- feat: refs #6695 jenkins run e2e rebuild by:alexm +- feat: refs #6695 jenkins run e2e remove ports by:alexm +- feat: refs #6695 jenkins run e2e try down and rm by:alexm +- feat: refs #6695 jenkins run e2e try fix db by:alexm +- feat: refs #6695 jenkins run e2e whitout rebuild by:alexm +- feat: refs #6695 pull salix-back image and use by:alexm +- feat: refs #6695 run e2e in docker by:alexm +- feat: refs #6695 run front by:alexm +- feat: refs #6695 run front quasar build by:alexm +- feat: refs #6695 run parallel e2e in local by:alexm +- feat: refs #6695 update cypress cache path command in Jenkinsfile by:alexm +- feat: refs #6695 update cypress-cache volume path in docker-compose.e2e.yml by:alexm +- feat: refs #6695 update cypress command in Jenkinsfile and docker-compose.e2e.yml by:alexm +- feat: refs #6695 update Docker configurations and Cypress settings for improved local development (origin/6695-docker_push_2, 6695-docker_push_2) by:alexm +- feat: refs #6695 when failure, clean by:alexm +- feat: refs #7937 add import claim button to ClaimAction component by:jgallego +- feat: refs #7937 add shelving selection to claim actions with data fetching by:jgallego +- feat: refs #8348 Added grouping by:guillermo +- feat: refs #8402 added lost filters from Salix by:Jon +- feat: refs #8484 add addressId to createForm in CustomerDescriptor by:jorgep +- feat: refs #8484 overwrite Cypress visit command to ensure main element exists by:jorgep +- feat: refs #8555 added new filter field and translations by:Jon +- feat: refs #8593 added summary button & modified e2e tests by:provira +- feat: refs #8593 changed parking to VnTable and modified e2e tests by:provira +- feat: refs #8599 added new test and translations by:Jon +- feat: refs #8599 modified tests to be more complete and added new ones by:Jon +- feat: refs #8697 enable data-cy attribute for VnTable, update test cases to remove skips and adjust selectors by:pablone +- feat: rename test:unit by test:front by:Javier Segarra +- feat: try run salix back by:alexm +- fix: style w-80 by:Javier Segarra +- Merge pull request 'fix: style' (!1425) from warmfix_vntable_card_style into test by:Javier Segarra + +### Changed 📦 + +- ci: refs #6695 Docker & Jenkinsfile fixes/refactor by:Juan Ferrer Toribio +- ci: refs #6695 refactor Cypress setup in Jenkinsfile and replace local docker-compose with new configuration by:alexm +- perf: refs #6695 only necessary by:alexm +- refactor: adjust translation to standardize it by:Jon +- refactor: refs #6695 comment out vnComponent tests in Jenkinsfile by:alexm +- refactor: refs #6695 improve group size calculation for parallel test execution in Jenkinsfile by:alexm +- refactor: refs #6695 improve parallel test execution logic in Jenkinsfile by:alexm +- refactor: refs #6695 simplify Docker cleanup commands in Jenkinsfile by:alexm +- refactor: refs #6695 update Docker setup for Cypress and remove obsolete files by:alexm +- refactor: refs #6695 update E2E test execution to support parallel groups and improve by:alexm +- refactor: refs #6695 update Jenkinsfile and Dockerfile to use 'developer' by:alexm +- refactor: refs #6695 update Jenkinsfile to run E2E tests in parallel and simplify docker-compose command by:alexm +- refactor: refs #6897 clean up Cypress configuration and improve entry list filtering (origin/6897-fixEntryE2e) by:pablone +- refactor: refs #7414 update VnLog component to change display order value changes on update action by:jtubau +- refactor: refs #7937 align columns to the right and add shelvingCode to ClaimSummaryAction by:jgallego +- refactor: refs #8484 add data-cy attribute for claim photo image and update test to use it by:jorgep +- refactor: refs #8484 clean up test files by removing commented issue references and updating test cases by:jorgep +- refactor: refs #8484 enhance login command with session management and clean up unused commands by:jtubau +- refactor: refs #8484 improve search input behavior and enhance visit command with DOM content load by:jtubau +- refactor: refs #8484 improve selectOption command with retry logic for visibility checks by:jtubau +- refactor: refs #8484 remove comment in wagonCreate.spec.js by:jtubau +- refactor: refs #8484 remove redundant visit command overwrite by:jorgep +- refactor: refs #8484 remove unnecessary domContentLoad calls from client tests by:jorgep +- refactor: refs #8484 remove unnecessary intercepts and waits in ticket and zone tests by:jorgep +- refactor: refs #8484 simplify image dialog test by using aliases for elements by:jorgep +- refactor: refs #8484 streamline assertions in ClaimNotes test by:jorgep +- refactor: refs #8484 streamline login command and remove commented code by:jorgep +- refactor: refs #8484 update specPattern to include all spec files and remove data-cy attribute by:jorgep +- refactor: refs #8594 update vehicle summary tests to use expected variable for consistency by:jtubau +- refactor: refs #8599 corrected it name by:Jon +- refactor: refs #8599 invoice out list e2e by:Jon +- refactor: refs #8599 requested changes by:Jon +- refactor: refs #8606 modified table height and deleted void file by:Jon +- refactor: refs #8606 modified table width and order by:Jon +- refactor: refs #8606 modified upcoming deliveries view by:Jon +- refactor: refs #8606 translations by:Jon +- refactor: refs #8618 simplify selectors and improve test readability in routeExtendedList.spec.js by:jtubau +- refactor: refs #8620 update RouteAutonomous to notify on data save and change invoice reference display by:jtubau +- refactor: remove default browser setting from Cypress configuration by:alexm +- refactor: remove unused variables by:alexm +- refactor: skip claimNotes by:alexm +- refactor: update labels and conditions in Claim components by:jgallego +- refactor: use constant for account input selector in VnAccountNumber tests by:alexm + +### Fixed 🛠️ + +- build: refs #6695 cypress-setup fix volume by:alexm +- build: refs #6695 cypress-setup fix volume (origin/6695-docker_push, 6695-docker_push) by:alexm +- ci: refs #6695 cypress reporter fix by:Juan Ferrer Toribio +- ci: refs #6695 Docker & Jenkinsfile fixes/refactor by:Juan Ferrer Toribio +- ci: refs #6695 JUnit report fixes by:Juan Ferrer Toribio +- ci: refs #6695 vitest junit file fix by:Juan Ferrer Toribio +- feat(jenkinsE2E): refs #6695 try fix db by:alexm +- feat: refs #6695 jenkins run e2e try fix db by:alexm +- fix: add data-cy attribute to card button for improved testing by:jtubau +- fix: added lost code by:Jon +- fix: add --init flag to Cypress Docker container for improved stability by:alexm +- fix: add mapper before Save by:Javier Segarra +- fix: cy.domContentLoad(); not exist by:alexm +- fix: elements position by:Javier Segarra +- fix: fixed select not filtering when typing by:Jon +- fix: fixed wagonTypeCreate test (origin/wagonTypeTestFix) by:PAU ROVIRA ROSALENY +- fix: fix sctions by:carlossa +- fix(Jenkinsfile): enhance Docker registry credentials handling with dynamic URL (origin/warmFix_use_withDockerRegistry, warmFix_use_withDockerRegistry) by:alexm +- fix(Jenkinsfile): update Docker registry credentials handling in E2E stage by:alexm +- fix: junit report by:alexm +- fix: merge revert by:alexm +- fix: merge test to dev by:alexm +- fix: prevent 'cypress run' error to show junit by:alexm +- fix: refs #6695 add --volumes flag to docker-compose down command by:alexm +- fix: refs #6695 checkErrors(folderName) by:alexm +- fix: refs #6695 clientBasicData by:alexm +- fix: refs #6695 dockerFile by:alexm +- fix: refs #6695 e2e.sh by:alexm +- fix: refs #6695 e2e stockBought by:alexm +- fix: refs #6695 fix e2e's by:alexm +- fix: refs #6695 storage by:alexm +- fix: refs #6695 try by:alexm +- fix: refs #6695 try parallel by:alexm +- fix: refs #6695 update Cypress cache handling and increase wait timeout for elements by:alexm +- fix: refs #6695 update Cypress configuration and Docker setup for improved testing by:alexm +- fix: refs #6695 update E2E stages to run tests in parallel for specific folders by:alexm +- fix: refs #6695 update remove Cypress installation by:alexm +- fix: refs #6695 zoneWarehouse est by:alexm +- fix: refs #6943 e2e clientList, formModel by:carlossa +- fix: refs #6943 formModel workerDepartment by:carlossa +- fix: refs #7323 e2e (origin/7323-fixe2e) by:carlossa +- fix: refs #7323 notification manager by:carlossa +- fix: refs #7414 updated default value rendering for non-update scenarios by:jtubau +- fix: refs #7414 update VnLog.vue to correctly display log actions and values by:jtubau +- fix: refs #7937 update claimId in ClaimAction test to reflect correct value (origin/7937-claimAgile) by:jgallego +- fix: refs #8484 ensure document is fully loaded before visiting pages in tests by:jorgep +- fix: refs #8484 fixed some tests to enable previously skipped cases and enhance functionality by:jtubau +- fix: refs #8484 remove unused addressId from createForm in CustomerDescriptor.vue by:jtubau +- fix: refs #8484 rollback by:jorgep +- fix: refs #8484 update Boss field type to 'selectWorker' and add selectWorkerOption command by:jtubau +- fix: refs #8484 update Boss type from 'selectWorker' to 'select' by:jorgep +- fix: refs #8484 update parking list URL to correct shelving path in integration test by:jtubau +- fix: refs #8484 update selector for buyLabel button in myEntry test by:jtubau +- fix: refs #8484 update selector for removing wagon type in wagonCreate.spec.js by:jtubau +- fix: refs #8484 update wagon type deletion selector and clean up unused code in commands.js by:jtubau +- fix: refs #8593 fixed parking e2e tests by:provira +- fix: refs #8606 fixed list e2e test by:Jon +- fix: refs #8620 add module name to InvoiceInSummary by:jtubau +- fix: refs #8623 fixed different errors by:Jon +- fix: remove info by:carlossa +- fix: remove old end-to-end test files before building Docker image by:alexm +- fix: revert cypress.config by:alexm +- fix: style w-80 by:Javier Segarra +- fix: unnecessary function by:alexm +- fix: update docker-compose command to remove volumes on teardown by:alexm +- fix: update Jenkinsfile to remove specific end-to-end test files by:alexm +- fix: update Jenkinsfile to use environment variable for Docker registry credentials by:alexm +- fix: warmFix vnInput dataCy by:alexm +- Merge pull request 'fix: style' (!1425) from warmfix_vntable_card_style into test by:Javier Segarra +- revert: browser chromium package.json by:Javier Segarra +- Revert "revert 1015acefb7e400be2d8b5958dba69b4d98276b34" by:alexm +- test: refs #6695 e2e fix allowedHosts by:alexm +- test: refs #6695 e2e fix back image by:alexm +- test: refs #6695 e2e fix base urls by:alexm +- test: refs #6695 e2e fix command by:alexm +- test: refs #6695 e2e fix connection db by:alexm +- test: refs #6695 e2e fix network by:alexm +- test: refs #6695 e2e fix sequential by:alexm +- test: refs #6695 fix e2e by:alexm +- test: refs #6695 fix e2e command by:alexm +- test: refs #6695 fix selectOption command by:alexm + # Version 25.08 - 2025-03-04 ### Added 🆕 diff --git a/Jenkinsfile b/Jenkinsfile index df2421a0e..a9db9d369 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -115,21 +115,27 @@ pipeline { steps { script { sh 'rm -f junit/e2e-*.xml' + sh 'rm -rf test/cypress/screenshots' env.COMPOSE_TAG = PROTECTED_BRANCH.contains(env.CHANGE_TARGET) ? env.CHANGE_TARGET : 'dev' def image = docker.build('lilium-dev', '-f docs/Dockerfile.dev docs') sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY' + sh "docker-compose ${env.COMPOSE_PARAMS} pull back" + sh "docker-compose ${env.COMPOSE_PARAMS} pull db" sh "docker-compose ${env.COMPOSE_PARAMS} up -d" + def modules = sh(script: 'node test/cypress/docker/find/find.js', returnStdout: true).trim() + echo "E2E MODULES: ${modules}" image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") { - sh 'sh test/cypress/cypressParallel.sh 3' + sh "sh test/cypress/docker/cypressParallel.sh 1 '${modules}'" } } } post { always { sh "docker-compose ${env.COMPOSE_PARAMS} down -v" + archiveArtifacts artifacts: 'test/cypress/screenshots/**/*', allowEmptyArchive: true junit( testResults: 'junit/e2e-*.xml', allowEmptyResults: true @@ -179,3 +185,4 @@ pipeline { } } } + diff --git a/README.md b/README.md index 262e12e58..8eff99137 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,26 @@ pnpm run test:front pnpm run test:e2e ``` +### Run e2e parallel + +```bash +pnpm run test:e2e:parallel +``` + +### View e2e parallel report + +```bash +pnpm run test:e2e:summary +``` + ### Build the app for production ```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/docs/Dockerfile.dev b/docs/Dockerfile.dev index 84a4d80bc..3117e2c20 100644 --- a/docs/Dockerfile.dev +++ b/docs/Dockerfile.dev @@ -25,6 +25,8 @@ RUN apt-get update \ libnss3 \ libxss1 \ libxtst6 \ + mesa-vulkan-drivers \ + vulkan-tools \ xauth \ xvfb \ && apt-get clean \ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..70f738bbe --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,87 @@ +import cypress from 'eslint-plugin-cypress'; +import eslint from 'eslint-plugin-import'; +import globals from 'globals'; +import js from '@eslint/js'; +import vue from 'eslint-plugin-vue'; +export default { + plugins: { vue, eslint, cypress }, + languageOptions: { + globals: { + ...globals.node, + ...globals.browser, + ...vue.configs['vue3-strongly-recommended'].globals, + ...cypress.environments.globals.globals, + ga: 'readonly', + cordova: 'readonly', + __statics: 'readonly', + __QUASAR_SSR__: 'readonly', + __QUASAR_SSR_SERVER__: 'readonly', + __QUASAR_SSR_CLIENT__: 'readonly', + __QUASAR_SSR_PWA__: 'readonly', + process: 'readonly', + Capacitor: 'readonly', + chrome: 'readonly', + }, + + ecmaVersion: 2020, + sourceType: 'module', + + parserOptions: { + parser: '@babel/eslint-parser', + }, + }, + rules: { + ...vue.rules['flat/strongly-recommended'], + ...js.configs.recommended.rules, + semi: 'off', + 'generator-star-spacing': 'warn', + 'arrow-parens': 'warn', + 'no-var': 'error', + 'prefer-const': 'error', + 'prefer-template': 'warn', + 'prefer-destructuring': 'off', + 'prefer-spread': 'warn', + 'prefer-rest-params': 'warn', + 'prefer-object-spread': 'warn', + 'prefer-arrow-callback': 'warn', + 'prefer-numeric-literals': 'warn', + 'prefer-exponentiation-operator': 'warn', + 'prefer-regex-literals': 'warn', + 'one-var': [ + 'error', + { + let: 'never', + const: 'never', + }, + ], + 'no-void': 'off', + 'prefer-promise-reject-errors': 'error', + 'multiline-ternary': 'warn', + 'no-restricted-imports': 'warn', + 'no-import-assign': 'warn', + 'no-duplicate-imports': 'warn', + 'no-useless-rename': 'warn', + 'eslint/no-named-as-default': 'warn', + 'eslint/no-named-as-default-member': 'warn', + 'no-unsafe-optional-chaining': 'warn', + 'no-undef': 'error', + 'no-unused-vars': 'error', + 'no-console': 'error', + 'no-debugger': 'error', + 'no-useless-escape': 'error', + 'no-prototype-builtins': 'error', + 'no-async-promise-executor': 'error', + 'no-irregular-whitespace': 'error', + 'no-constant-condition': 'error', + 'no-unsafe-finally': 'error', + 'no-extend-native': 'error', + }, + ignores: [ + '/dist', + '/src-capacitor', + '/src-cordova', + '/.quasar', + '/node_modules', + '.eslintrc.js', + ], +}; diff --git a/package.json b/package.json index 33b730b9e..19b4c7a6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "25.12.0", + "version": "25.16.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -9,14 +9,17 @@ "type": "module", "scripts": { "resetDatabase": "cd ../salix && gulp docker", - "lint": "eslint --ext .js,.vue ./", + "lint": "eslint \"**/*.{vue,js}\" ", + "lint:fix": "eslint \"**/*.{vue,js}\" --fix ", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", "test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run", - "test:e2e:parallel": "bash ./test/cypress/cypressParallel.sh", + "test:e2e:parallel": "bash ./test/cypress/run.sh", "test:e2e:summary": "bash ./test/cypress/summary.sh", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:front": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "test:front:ci": "vitest run", "commitlint": "commitlint --edit", "prepare": "npx husky install", @@ -26,42 +29,53 @@ "docs:preview": "vitepress preview docs" }, "dependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.20.0", "@quasar/cli": "^2.4.1", "@quasar/extras": "^1.16.16", "axios": "^1.4.0", "chromium": "^3.0.3", "croppie": "^2.6.5", + "es-module-lexer": "^1.6.0", + "fast-glob": "^3.3.3", "moment": "^2.30.1", "pinia": "^2.1.3", "quasar": "^2.17.7", "validator": "^13.9.0", "vue": "^3.5.13", - "vue-i18n": "^9.3.0", + "vue-i18n": "^9.4.0", "vue-router": "^4.2.5" }, "devDependencies": { "@commitlint/cli": "^19.2.1", "@commitlint/config-conventional": "^19.1.0", - "@intlify/unplugin-vue-i18n": "^0.8.2", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.20.0", + "@intlify/unplugin-vue-i18n": "^4.0.0", "@pinia/testing": "^0.1.2", "@quasar/app-vite": "^2.0.8", "@quasar/quasar-app-extension-qcalendar": "^4.0.2", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", + "@vitest/ui": "3.1.1", + "@vue/compiler-sfc": "^3.5.13", "@vue/test-utils": "^2.4.4", "autoprefixer": "^10.4.14", "cypress": "^14.1.0", "cypress-mochawesome-reporter": "^3.8.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", + "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-cypress": "^4.1.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-vue": "^9.32.0", + "globals": "^16.0.0", "husky": "^8.0.0", + "junit-merge": "^2.0.0", "mocha": "^11.1.0", "postcss": "^8.4.23", "prettier": "^3.4.2", "sass": "^1.83.4", - "vitepress": "^1.6.3", - "vitest": "^0.34.0", + "vitest": "^3.0.3", "xunit-viewer": "^10.6.1" }, "engines": { @@ -75,4 +89,4 @@ "vite": "^6.0.11", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 168fb9e0d..02d82f325 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,39 +5,51 @@ settings: excludeLinksFromLockfile: false dependencies: + '@eslint/eslintrc': + specifier: ^3.2.0 + version: 3.3.1 + '@eslint/js': + specifier: ^9.20.0 + version: 9.24.0 '@quasar/cli': specifier: ^2.4.1 - version: 2.4.1 + version: 2.5.0 '@quasar/extras': specifier: ^1.16.16 version: 1.16.17 axios: specifier: ^1.4.0 - version: 1.7.9 + version: 1.8.4 chromium: specifier: ^3.0.3 version: 3.0.3 croppie: specifier: ^2.6.5 version: 2.6.5 + es-module-lexer: + specifier: ^1.6.0 + version: 1.6.0 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 moment: specifier: ^2.30.1 version: 2.30.1 pinia: specifier: ^2.1.3 - version: 2.3.1(typescript@5.7.3)(vue@3.5.13) + version: 2.3.1(typescript@5.8.3)(vue@3.5.13) quasar: specifier: ^2.17.7 - version: 2.17.7 + version: 2.18.1 validator: specifier: ^13.9.0 - version: 13.12.0 + version: 13.15.0 vue: specifier: ^3.5.13 - version: 3.5.13(typescript@5.7.3) + version: 3.5.13(typescript@5.8.3) vue-i18n: - specifier: ^9.3.0 - version: 9.14.2(vue@3.5.13) + specifier: ^9.4.0 + version: 9.14.4(vue@3.5.13) vue-router: specifier: ^4.2.5 version: 4.5.0(vue@3.5.13) @@ -45,52 +57,70 @@ dependencies: devDependencies: '@commitlint/cli': specifier: ^19.2.1 - version: 19.7.1(@types/node@22.13.5)(typescript@5.7.3) + version: 19.8.0(@types/node@22.14.0)(typescript@5.8.3) '@commitlint/config-conventional': specifier: ^19.1.0 - version: 19.7.1 + version: 19.8.0 '@intlify/unplugin-vue-i18n': - specifier: ^0.8.2 - version: 0.8.2(vue-i18n@9.14.2) + specifier: ^4.0.0 + version: 4.0.0(vue-i18n@9.14.4) '@pinia/testing': specifier: ^0.1.2 version: 0.1.7(pinia@2.3.1)(vue@3.5.13) '@quasar/app-vite': specifier: ^2.0.8 - version: 2.1.0(@types/node@22.13.5)(eslint@9.20.1)(pinia@2.3.1)(quasar@2.17.7)(sass@1.85.0)(typescript@5.7.3)(vue-router@4.5.0)(vue@3.5.13) + version: 2.2.0(@types/node@22.14.0)(eslint@9.24.0)(pinia@2.3.1)(quasar@2.18.1)(sass@1.86.3)(typescript@5.8.3)(vue-router@4.5.0)(vue@3.5.13) '@quasar/quasar-app-extension-qcalendar': specifier: ^4.0.2 version: 4.1.2 '@quasar/quasar-app-extension-testing-unit-vitest': specifier: ^0.4.0 - version: 0.4.0(@vue/test-utils@2.4.6)(quasar@2.17.7)(typescript@5.7.3)(vite@6.2.0)(vitest@0.34.6)(vue@3.5.13) + version: 0.4.0(@vitest/ui@3.1.1)(@vue/test-utils@2.4.6)(quasar@2.18.1)(typescript@5.8.3)(vite@6.2.5)(vitest@3.1.1)(vue@3.5.13) + '@vitest/ui': + specifier: 3.1.1 + version: 3.1.1(vitest@3.1.1) + '@vue/compiler-sfc': + specifier: ^3.5.13 + version: 3.5.13 '@vue/test-utils': specifier: ^2.4.4 version: 2.4.6 autoprefixer: specifier: ^10.4.14 - version: 10.4.20(postcss@8.5.3) + version: 10.4.21(postcss@8.5.3) cypress: specifier: ^14.1.0 - version: 14.1.0 + version: 14.2.1 cypress-mochawesome-reporter: specifier: ^3.8.2 - version: 3.8.2(cypress@14.1.0)(mocha@11.1.0) + version: 3.8.2(cypress@14.2.1)(mocha@11.1.0) eslint: specifier: ^9.18.0 - version: 9.20.1 + version: 9.24.0 eslint-config-prettier: specifier: ^10.0.1 - version: 10.0.1(eslint@9.20.1) + version: 10.1.1(eslint@9.24.0) + eslint-import-resolver-alias: + specifier: ^1.1.2 + version: 1.1.2(eslint-plugin-import@2.31.0) eslint-plugin-cypress: specifier: ^4.1.0 - version: 4.1.0(eslint@9.20.1) + version: 4.2.0(eslint@9.24.0) + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(eslint@9.24.0) eslint-plugin-vue: specifier: ^9.32.0 - version: 9.32.0(eslint@9.20.1) + version: 9.33.0(eslint@9.24.0) + globals: + specifier: ^16.0.0 + version: 16.0.0 husky: specifier: ^8.0.0 version: 8.0.3 + junit-merge: + specifier: ^2.0.0 + version: 2.0.0 mocha: specifier: ^11.1.0 version: 11.1.0 @@ -99,182 +129,19 @@ devDependencies: version: 8.5.3 prettier: specifier: ^3.4.2 - version: 3.5.1 + version: 3.5.3 sass: specifier: ^1.83.4 - version: 1.85.0 - vitepress: - specifier: ^1.6.3 - version: 1.6.3(@algolia/client-search@5.20.3)(@types/node@22.13.5)(axios@1.7.9)(postcss@8.5.3)(react-dom@19.0.0)(react@19.0.0)(sass@1.85.0)(search-insights@2.17.3)(typescript@5.7.3) + version: 1.86.3 vitest: - specifier: ^0.34.0 - version: 0.34.6(sass@1.85.0) + specifier: ^3.0.3 + version: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3) xunit-viewer: specifier: ^10.6.1 - version: 10.6.1(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.3)(codemirror@6.0.1)(react-dom@19.0.0)(react@19.0.0) + version: 10.6.1(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.5)(codemirror@6.0.1)(react-dom@19.1.0)(react@19.1.0) packages: - /@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3)(search-insights@2.17.3): - resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} - dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3) - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - - search-insights - dev: true - - /@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3)(search-insights@2.17.3): - resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} - peerDependencies: - search-insights: '>= 1 < 3' - dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3) - search-insights: 2.17.3 - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - dev: true - - /@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3): - resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3) - '@algolia/client-search': 5.20.3 - algoliasearch: 5.20.3 - dev: true - - /@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3): - resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - dependencies: - '@algolia/client-search': 5.20.3 - algoliasearch: 5.20.3 - dev: true - - /@algolia/client-abtesting@5.20.3: - resolution: {integrity: sha512-wPOzHYSsW+H97JkBLmnlOdJSpbb9mIiuNPycUCV5DgzSkJFaI/OFxXfZXAh1gqxK+hf0miKue1C9bltjWljrNA==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/client-analytics@5.20.3: - resolution: {integrity: sha512-XE3iduH9lA7iTQacDGofBQyIyIgaX8qbTRRdj1bOCmfzc9b98CoiMwhNwdTifmmMewmN0EhVF3hP8KjKWwX7Yw==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/client-common@5.20.3: - resolution: {integrity: sha512-IYRd/A/R3BXeaQVT2805lZEdWo54v39Lqa7ABOxIYnUvX2vvOMW1AyzCuT0U7Q+uPdD4UW48zksUKRixShcWxA==} - engines: {node: '>= 14.0.0'} - dev: true - - /@algolia/client-insights@5.20.3: - resolution: {integrity: sha512-QGc/bmDUBgzB71rDL6kihI2e1Mx6G6PxYO5Ks84iL3tDcIel1aFuxtRF14P8saGgdIe1B6I6QkpkeIddZ6vWQw==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/client-personalization@5.20.3: - resolution: {integrity: sha512-zuM31VNPDJ1LBIwKbYGz/7+CSm+M8EhlljDamTg8AnDilnCpKjBebWZR5Tftv/FdWSro4tnYGOIz1AURQgZ+tQ==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/client-query-suggestions@5.20.3: - resolution: {integrity: sha512-Nn872PuOI8qzi1bxMMhJ0t2AzVBqN01jbymBQOkypvZHrrjZPso3iTpuuLLo9gi3yc/08vaaWTAwJfPhxPwJUw==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/client-search@5.20.3: - resolution: {integrity: sha512-9+Fm1ahV8/2goSIPIqZnVitV5yHW5E5xTdKy33xnqGd45A9yVv5tTkudWzEXsbfBB47j9Xb3uYPZjAvV5RHbKA==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/ingestion@1.20.3: - resolution: {integrity: sha512-5GHNTiZ3saLjTNyr6WkP5hzDg2eFFAYWomvPcm9eHWskjzXt8R0IOiW9kkTS6I6hXBwN5H9Zna5mZDSqqJdg+g==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/monitoring@1.20.3: - resolution: {integrity: sha512-KUWQbTPoRjP37ivXSQ1+lWMfaifCCMzTnEcEnXwAmherS5Tp7us6BAqQDMGOD4E7xyaS2I8pto6WlOzxH+CxmA==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/recommend@5.20.3: - resolution: {integrity: sha512-oo/gG77xTTTclkrdFem0Kmx5+iSRFiwuRRdxZETDjwzCI7svutdbwBgV/Vy4D4QpYaX4nhY/P43k84uEowCE4Q==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - - /@algolia/requester-browser-xhr@5.20.3: - resolution: {integrity: sha512-BkkW7otbiI/Er1AiEPZs1h7lxbtSO9p09jFhv3/iT8/0Yz0CY79VJ9iq+Wv1+dq/l0OxnMpBy8mozrieGA3mXQ==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - dev: true - - /@algolia/requester-fetch@5.20.3: - resolution: {integrity: sha512-eAVlXz7UNzTsA1EDr+p0nlIH7WFxo7k3NMxYe8p38DH8YVWLgm2MgOVFUMNg9HCi6ZNOi/A2w/id2ZZ4sKgUOw==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - dev: true - - /@algolia/requester-node-http@5.20.3: - resolution: {integrity: sha512-FqR3pQPfHfQyX1wgcdK6iyqu86yP76MZd4Pzj1y/YLMj9rRmRCY0E0AffKr//nrOFEwv6uY8BQY4fd9/6b0ZCg==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-common': 5.20.3 - dev: true - /@babel/code-frame@7.26.2: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -292,65 +159,65 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - /@babel/parser@7.26.9: - resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + /@babel/parser@7.27.0: + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.27.0 - /@babel/runtime@7.26.9: - resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} + /@babel/runtime@7.27.0: + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 dev: true - /@babel/types@7.26.9: - resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} + /@babel/types@7.27.0: + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - /@bufbuild/protobuf@2.2.3: - resolution: {integrity: sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==} + /@bufbuild/protobuf@2.2.5: + resolution: {integrity: sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==} dev: true /@codemirror/autocomplete@6.18.6: resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} dependencies: - '@codemirror/language': 6.10.8 + '@codemirror/language': 6.11.0 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 '@lezer/common': 1.2.3 dev: true - /@codemirror/commands@6.8.0: - resolution: {integrity: sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==} + /@codemirror/commands@6.8.1: + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} dependencies: - '@codemirror/language': 6.10.8 + '@codemirror/language': 6.11.0 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 '@lezer/common': 1.2.3 dev: true - /@codemirror/language@6.10.8: - resolution: {integrity: sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==} + /@codemirror/language@6.11.0: + resolution: {integrity: sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==} dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 style-mod: 4.1.2 dev: true - /@codemirror/lint@6.8.4: - resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==} + /@codemirror/lint@6.8.5: + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 crelt: 1.0.6 dev: true @@ -358,7 +225,7 @@ packages: resolution: {integrity: sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==} dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 crelt: 1.0.6 dev: true @@ -371,14 +238,14 @@ packages: /@codemirror/theme-one-dark@6.1.2: resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} dependencies: - '@codemirror/language': 6.10.8 + '@codemirror/language': 6.11.0 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 '@lezer/highlight': 1.2.1 dev: true - /@codemirror/view@6.36.3: - resolution: {integrity: sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==} + /@codemirror/view@6.36.5: + resolution: {integrity: sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==} dependencies: '@codemirror/state': 6.5.2 style-mod: 4.1.2 @@ -392,16 +259,16 @@ packages: dev: true optional: true - /@commitlint/cli@19.7.1(@types/node@22.13.5)(typescript@5.7.3): - resolution: {integrity: sha512-iObGjR1tE/PfDtDTEfd+tnRkB3/HJzpQqRTyofS2MPPkDn1mp3DBC8SoPDayokfAy+xKhF8+bwRCJO25Nea0YQ==} + /@commitlint/cli@19.8.0(@types/node@22.14.0)(typescript@5.8.3): + resolution: {integrity: sha512-t/fCrLVu+Ru01h0DtlgHZXbHV2Y8gKocTR5elDOqIRUzQd0/6hpt2VIWOj9b3NDo7y4/gfxeR2zRtXq/qO6iUg==} engines: {node: '>=v18'} hasBin: true dependencies: - '@commitlint/format': 19.5.0 - '@commitlint/lint': 19.7.1 - '@commitlint/load': 19.6.1(@types/node@22.13.5)(typescript@5.7.3) - '@commitlint/read': 19.5.0 - '@commitlint/types': 19.5.0 + '@commitlint/format': 19.8.0 + '@commitlint/lint': 19.8.0 + '@commitlint/load': 19.8.0(@types/node@22.14.0)(typescript@5.8.3) + '@commitlint/read': 19.8.0 + '@commitlint/types': 19.8.0 tinyexec: 0.3.2 yargs: 17.7.2 transitivePeerDependencies: @@ -409,27 +276,27 @@ packages: - typescript dev: true - /@commitlint/config-conventional@19.7.1: - resolution: {integrity: sha512-fsEIF8zgiI/FIWSnykdQNj/0JE4av08MudLTyYHm4FlLWemKoQvPNUYU2M/3tktWcCEyq7aOkDDgtjrmgWFbvg==} + /@commitlint/config-conventional@19.8.0: + resolution: {integrity: sha512-9I2kKJwcAPwMoAj38hwqFXG0CzS2Kj+SAByPUQ0SlHTfb7VUhYVmo7G2w2tBrqmOf7PFd6MpZ/a1GQJo8na8kw==} engines: {node: '>=v18'} dependencies: - '@commitlint/types': 19.5.0 + '@commitlint/types': 19.8.0 conventional-changelog-conventionalcommits: 7.0.2 dev: true - /@commitlint/config-validator@19.5.0: - resolution: {integrity: sha512-CHtj92H5rdhKt17RmgALhfQt95VayrUo2tSqY9g2w+laAXyk7K/Ef6uPm9tn5qSIwSmrLjKaXK9eiNuxmQrDBw==} + /@commitlint/config-validator@19.8.0: + resolution: {integrity: sha512-+r5ZvD/0hQC3w5VOHJhGcCooiAVdynFlCe2d6I9dU+PvXdV3O+fU4vipVg+6hyLbQUuCH82mz3HnT/cBQTYYuA==} engines: {node: '>=v18'} dependencies: - '@commitlint/types': 19.5.0 + '@commitlint/types': 19.8.0 ajv: 8.17.1 dev: true - /@commitlint/ensure@19.5.0: - resolution: {integrity: sha512-Kv0pYZeMrdg48bHFEU5KKcccRfKmISSm9MvgIgkpI6m+ohFTB55qZlBW6eYqh/XDfRuIO0x4zSmvBjmOwWTwkg==} + /@commitlint/ensure@19.8.0: + resolution: {integrity: sha512-kNiNU4/bhEQ/wutI1tp1pVW1mQ0QbAjfPRo5v8SaxoVV+ARhkB8Wjg3BSseNYECPzWWfg/WDqQGIfV1RaBFQZg==} engines: {node: '>=v18'} dependencies: - '@commitlint/types': 19.5.0 + '@commitlint/types': 19.8.0 lodash.camelcase: 4.3.0 lodash.kebabcase: 4.1.1 lodash.snakecase: 4.1.1 @@ -437,48 +304,48 @@ packages: lodash.upperfirst: 4.3.1 dev: true - /@commitlint/execute-rule@19.5.0: - resolution: {integrity: sha512-aqyGgytXhl2ejlk+/rfgtwpPexYyri4t8/n4ku6rRJoRhGZpLFMqrZ+YaubeGysCP6oz4mMA34YSTaSOKEeNrg==} + /@commitlint/execute-rule@19.8.0: + resolution: {integrity: sha512-fuLeI+EZ9x2v/+TXKAjplBJWI9CNrHnyi5nvUQGQt4WRkww/d95oVRsc9ajpt4xFrFmqMZkd/xBQHZDvALIY7A==} engines: {node: '>=v18'} dev: true - /@commitlint/format@19.5.0: - resolution: {integrity: sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A==} + /@commitlint/format@19.8.0: + resolution: {integrity: sha512-EOpA8IERpQstxwp/WGnDArA7S+wlZDeTeKi98WMOvaDLKbjptuHWdOYYr790iO7kTCif/z971PKPI2PkWMfOxg==} engines: {node: '>=v18'} dependencies: - '@commitlint/types': 19.5.0 + '@commitlint/types': 19.8.0 chalk: 5.4.1 dev: true - /@commitlint/is-ignored@19.7.1: - resolution: {integrity: sha512-3IaOc6HVg2hAoGleRK3r9vL9zZ3XY0rf1RsUf6jdQLuaD46ZHnXBiOPTyQ004C4IvYjSWqJwlh0/u2P73aIE3g==} + /@commitlint/is-ignored@19.8.0: + resolution: {integrity: sha512-L2Jv9yUg/I+jF3zikOV0rdiHUul9X3a/oU5HIXhAJLE2+TXTnEBfqYP9G5yMw/Yb40SnR764g4fyDK6WR2xtpw==} engines: {node: '>=v18'} dependencies: - '@commitlint/types': 19.5.0 + '@commitlint/types': 19.8.0 semver: 7.7.1 dev: true - /@commitlint/lint@19.7.1: - resolution: {integrity: sha512-LhcPfVjcOcOZA7LEuBBeO00o3MeZa+tWrX9Xyl1r9PMd5FWsEoZI9IgnGqTKZ0lZt5pO3ZlstgnRyY1CJJc9Xg==} + /@commitlint/lint@19.8.0: + resolution: {integrity: sha512-+/NZKyWKSf39FeNpqhfMebmaLa1P90i1Nrb1SrA7oSU5GNN/lksA4z6+ZTnsft01YfhRZSYMbgGsARXvkr/VLQ==} engines: {node: '>=v18'} dependencies: - '@commitlint/is-ignored': 19.7.1 - '@commitlint/parse': 19.5.0 - '@commitlint/rules': 19.6.0 - '@commitlint/types': 19.5.0 + '@commitlint/is-ignored': 19.8.0 + '@commitlint/parse': 19.8.0 + '@commitlint/rules': 19.8.0 + '@commitlint/types': 19.8.0 dev: true - /@commitlint/load@19.6.1(@types/node@22.13.5)(typescript@5.7.3): - resolution: {integrity: sha512-kE4mRKWWNju2QpsCWt428XBvUH55OET2N4QKQ0bF85qS/XbsRGG1MiTByDNlEVpEPceMkDr46LNH95DtRwcsfA==} + /@commitlint/load@19.8.0(@types/node@22.14.0)(typescript@5.8.3): + resolution: {integrity: sha512-4rvmm3ff81Sfb+mcWT5WKlyOa+Hd33WSbirTVUer0wjS1Hv/Hzr07Uv1ULIV9DkimZKNyOwXn593c+h8lsDQPQ==} engines: {node: '>=v18'} dependencies: - '@commitlint/config-validator': 19.5.0 - '@commitlint/execute-rule': 19.5.0 - '@commitlint/resolve-extends': 19.5.0 - '@commitlint/types': 19.5.0 + '@commitlint/config-validator': 19.8.0 + '@commitlint/execute-rule': 19.8.0 + '@commitlint/resolve-extends': 19.8.0 + '@commitlint/types': 19.8.0 chalk: 5.4.1 - cosmiconfig: 9.0.0(typescript@5.7.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.13.5)(cosmiconfig@9.0.0)(typescript@5.7.3) + cosmiconfig: 9.0.0(typescript@5.8.3) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.14.0)(cosmiconfig@9.0.0)(typescript@5.8.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -487,67 +354,67 @@ packages: - typescript dev: true - /@commitlint/message@19.5.0: - resolution: {integrity: sha512-R7AM4YnbxN1Joj1tMfCyBryOC5aNJBdxadTZkuqtWi3Xj0kMdutq16XQwuoGbIzL2Pk62TALV1fZDCv36+JhTQ==} + /@commitlint/message@19.8.0: + resolution: {integrity: sha512-qs/5Vi9bYjf+ZV40bvdCyBn5DvbuelhR6qewLE8Bh476F7KnNyLfdM/ETJ4cp96WgeeHo6tesA2TMXS0sh5X4A==} engines: {node: '>=v18'} dev: true - /@commitlint/parse@19.5.0: - resolution: {integrity: sha512-cZ/IxfAlfWYhAQV0TwcbdR1Oc0/r0Ik1GEessDJ3Lbuma/MRO8FRQX76eurcXtmhJC//rj52ZSZuXUg0oIX0Fw==} + /@commitlint/parse@19.8.0: + resolution: {integrity: sha512-YNIKAc4EXvNeAvyeEnzgvm1VyAe0/b3Wax7pjJSwXuhqIQ1/t2hD3OYRXb6D5/GffIvaX82RbjD+nWtMZCLL7Q==} engines: {node: '>=v18'} dependencies: - '@commitlint/types': 19.5.0 + '@commitlint/types': 19.8.0 conventional-changelog-angular: 7.0.0 conventional-commits-parser: 5.0.0 dev: true - /@commitlint/read@19.5.0: - resolution: {integrity: sha512-TjS3HLPsLsxFPQj6jou8/CZFAmOP2y+6V4PGYt3ihbQKTY1Jnv0QG28WRKl/d1ha6zLODPZqsxLEov52dhR9BQ==} + /@commitlint/read@19.8.0: + resolution: {integrity: sha512-6ywxOGYajcxK1y1MfzrOnwsXO6nnErna88gRWEl3qqOOP8MDu/DTeRkGLXBFIZuRZ7mm5yyxU5BmeUvMpNte5w==} engines: {node: '>=v18'} dependencies: - '@commitlint/top-level': 19.5.0 - '@commitlint/types': 19.5.0 + '@commitlint/top-level': 19.8.0 + '@commitlint/types': 19.8.0 git-raw-commits: 4.0.0 minimist: 1.2.8 tinyexec: 0.3.2 dev: true - /@commitlint/resolve-extends@19.5.0: - resolution: {integrity: sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA==} + /@commitlint/resolve-extends@19.8.0: + resolution: {integrity: sha512-CLanRQwuG2LPfFVvrkTrBR/L/DMy3+ETsgBqW1OvRxmzp/bbVJW0Xw23LnnExgYcsaFtos967lul1CsbsnJlzQ==} engines: {node: '>=v18'} dependencies: - '@commitlint/config-validator': 19.5.0 - '@commitlint/types': 19.5.0 + '@commitlint/config-validator': 19.8.0 + '@commitlint/types': 19.8.0 global-directory: 4.0.1 import-meta-resolve: 4.1.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 dev: true - /@commitlint/rules@19.6.0: - resolution: {integrity: sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw==} + /@commitlint/rules@19.8.0: + resolution: {integrity: sha512-IZ5IE90h6DSWNuNK/cwjABLAKdy8tP8OgGVGbXe1noBEX5hSsu00uRlLu6JuruiXjWJz2dZc+YSw3H0UZyl/mA==} engines: {node: '>=v18'} dependencies: - '@commitlint/ensure': 19.5.0 - '@commitlint/message': 19.5.0 - '@commitlint/to-lines': 19.5.0 - '@commitlint/types': 19.5.0 + '@commitlint/ensure': 19.8.0 + '@commitlint/message': 19.8.0 + '@commitlint/to-lines': 19.8.0 + '@commitlint/types': 19.8.0 dev: true - /@commitlint/to-lines@19.5.0: - resolution: {integrity: sha512-R772oj3NHPkodOSRZ9bBVNq224DOxQtNef5Pl8l2M8ZnkkzQfeSTr4uxawV2Sd3ui05dUVzvLNnzenDBO1KBeQ==} + /@commitlint/to-lines@19.8.0: + resolution: {integrity: sha512-3CKLUw41Cur8VMjh16y8LcsOaKbmQjAKCWlXx6B0vOUREplp6em9uIVhI8Cv934qiwkbi2+uv+mVZPnXJi1o9A==} engines: {node: '>=v18'} dev: true - /@commitlint/top-level@19.5.0: - resolution: {integrity: sha512-IP1YLmGAk0yWrImPRRc578I3dDUI5A2UBJx9FbSOjxe9sTlzFiwVJ+zeMLgAtHMtGZsC8LUnzmW1qRemkFU4ng==} + /@commitlint/top-level@19.8.0: + resolution: {integrity: sha512-Rphgoc/omYZisoNkcfaBRPQr4myZEHhLPx2/vTXNLjiCw4RgfPR1wEgUpJ9OOmDCiv5ZyIExhprNLhteqH4FuQ==} engines: {node: '>=v18'} dependencies: find-up: 7.0.0 dev: true - /@commitlint/types@19.5.0: - resolution: {integrity: sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg==} + /@commitlint/types@19.8.0: + resolution: {integrity: sha512-LRjP623jPyf3Poyfb0ohMj8I3ORyBDOwXAgxxVPbSD0unJuW2mJWeiRfaQinjtccMqC5Wy1HOMfa4btKjbNxbg==} engines: {node: '>=v18'} dependencies: '@types/conventional-commits-parser': 5.0.1 @@ -558,8 +425,8 @@ packages: resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} dev: true - /@cypress/request@3.0.7: - resolution: {integrity: sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==} + /@cypress/request@3.0.8: + resolution: {integrity: sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==} engines: {node: '>= 6'} dependencies: aws-sign2: 0.7.0 @@ -575,9 +442,9 @@ packages: json-stringify-safe: 5.0.1 mime-types: 2.1.35 performance-now: 2.1.0 - qs: 6.13.1 + qs: 6.14.0 safe-buffer: 5.2.1 - tough-cookie: 5.1.1 + tough-cookie: 5.1.2 tunnel-agent: 0.6.0 uuid: 8.3.2 dev: true @@ -591,62 +458,8 @@ packages: - supports-color dev: true - /@docsearch/css@3.8.2: - resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} - dev: true - - /@docsearch/js@3.8.2(@algolia/client-search@5.20.3)(react-dom@19.0.0)(react@19.0.0)(search-insights@2.17.3): - resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} - dependencies: - '@docsearch/react': 3.8.2(@algolia/client-search@5.20.3)(react-dom@19.0.0)(react@19.0.0)(search-insights@2.17.3) - preact: 10.26.2 - transitivePeerDependencies: - - '@algolia/client-search' - - '@types/react' - - react - - react-dom - - search-insights - dev: true - - /@docsearch/react@3.8.2(@algolia/client-search@5.20.3)(react-dom@19.0.0)(react@19.0.0)(search-insights@2.17.3): - resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} - peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' - react: '>= 16.8.0 < 19.0.0' - react-dom: '>= 16.8.0 < 19.0.0' - search-insights: '>= 1 < 3' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true - dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.20.3)(algoliasearch@5.20.3) - '@docsearch/css': 3.8.2 - algoliasearch: 5.20.3 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - search-insights: 2.17.3 - transitivePeerDependencies: - - '@algolia/client-search' - dev: true - - /@esbuild/aix-ppc64@0.21.5: - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: true - optional: true - - /@esbuild/aix-ppc64@0.24.2: - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + /@esbuild/aix-ppc64@0.25.2: + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -654,26 +467,8 @@ packages: dev: true optional: true - /@esbuild/aix-ppc64@0.25.0: - resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm64@0.21.5: - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm64@0.24.2: - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + /@esbuild/android-arm64@0.25.2: + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -681,26 +476,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.25.0: - resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm@0.21.5: - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm@0.24.2: - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + /@esbuild/android-arm@0.25.2: + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -708,26 +485,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.25.0: - resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-x64@0.21.5: - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-x64@0.24.2: - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + /@esbuild/android-x64@0.25.2: + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -735,26 +494,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.25.0: - resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-arm64@0.21.5: - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-arm64@0.24.2: - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + /@esbuild/darwin-arm64@0.25.2: + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -762,26 +503,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.25.0: - resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64@0.21.5: - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64@0.24.2: - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + /@esbuild/darwin-x64@0.25.2: + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -789,26 +512,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.25.0: - resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64@0.21.5: - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64@0.24.2: - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + /@esbuild/freebsd-arm64@0.25.2: + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -816,26 +521,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.25.0: - resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64@0.21.5: - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64@0.24.2: - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + /@esbuild/freebsd-x64@0.25.2: + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -843,26 +530,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.25.0: - resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64@0.21.5: - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64@0.24.2: - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + /@esbuild/linux-arm64@0.25.2: + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -870,26 +539,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.25.0: - resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm@0.21.5: - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm@0.24.2: - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + /@esbuild/linux-arm@0.25.2: + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -897,26 +548,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.25.0: - resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32@0.21.5: - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32@0.24.2: - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + /@esbuild/linux-ia32@0.25.2: + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -924,26 +557,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.25.0: - resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.21.5: - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.24.2: - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + /@esbuild/linux-loong64@0.25.2: + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -951,26 +566,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.25.0: - resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el@0.21.5: - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el@0.24.2: - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + /@esbuild/linux-mips64el@0.25.2: + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -978,26 +575,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.25.0: - resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64@0.21.5: - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64@0.24.2: - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + /@esbuild/linux-ppc64@0.25.2: + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1005,26 +584,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.25.0: - resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64@0.21.5: - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64@0.24.2: - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + /@esbuild/linux-riscv64@0.25.2: + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1032,26 +593,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.25.0: - resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x@0.21.5: - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x@0.24.2: - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + /@esbuild/linux-s390x@0.25.2: + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1059,26 +602,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.25.0: - resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64@0.21.5: - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64@0.24.2: - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + /@esbuild/linux-x64@0.25.2: + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -1086,17 +611,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.25.0: - resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-arm64@0.24.2: - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + /@esbuild/netbsd-arm64@0.25.2: + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -1104,26 +620,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-arm64@0.25.0: - resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64@0.21.5: - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64@0.24.2: - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + /@esbuild/netbsd-x64@0.25.2: + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -1131,17 +629,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.25.0: - resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-arm64@0.24.2: - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + /@esbuild/openbsd-arm64@0.25.2: + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1149,26 +638,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-arm64@0.25.0: - resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64@0.21.5: - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64@0.24.2: - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + /@esbuild/openbsd-x64@0.25.2: + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1176,26 +647,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.25.0: - resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64@0.21.5: - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64@0.24.2: - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + /@esbuild/sunos-x64@0.25.2: + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1203,26 +656,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.25.0: - resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64@0.21.5: - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64@0.24.2: - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + /@esbuild/win32-arm64@0.25.2: + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1230,26 +665,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.25.0: - resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32@0.21.5: - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32@0.24.2: - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + /@esbuild/win32-ia32@0.25.2: + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1257,26 +674,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.25.0: - resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64@0.21.5: - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64@0.24.2: - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + /@esbuild/win32-x64@0.25.2: + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1284,22 +683,13 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.25.0: - resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@eslint-community/eslint-utils@4.4.1(eslint@9.20.1): - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + /@eslint-community/eslint-utils@4.5.1(eslint@9.24.0): + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 9.20.1 + eslint: 9.24.0 eslint-visitor-keys: 3.4.3 dev: true @@ -1308,8 +698,8 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/config-array@0.19.2: - resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + /@eslint/config-array@0.20.0: + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/object-schema': 2.1.6 @@ -1319,15 +709,27 @@ packages: - supports-color dev: true - /@eslint/core@0.11.0: - resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} + /@eslint/config-helpers@0.2.1: + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + + /@eslint/core@0.12.0: + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@types/json-schema': 7.0.15 dev: true - /@eslint/eslintrc@3.2.0: - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + /@eslint/core@0.13.0: + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@types/json-schema': 7.0.15 + dev: true + + /@eslint/eslintrc@3.3.1: + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 @@ -1341,23 +743,21 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - dev: true - /@eslint/js@9.20.0: - resolution: {integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==} + /@eslint/js@9.24.0: + resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true /@eslint/object-schema@2.1.6: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@eslint/plugin-kit@0.2.6: - resolution: {integrity: sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==} + /@eslint/plugin-kit@0.2.8: + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@eslint/core': 0.11.0 + '@eslint/core': 0.13.0 levn: 0.4.1 dev: true @@ -1389,24 +789,14 @@ packages: engines: {node: '>=18.18'} dev: true - /@iconify-json/simple-icons@1.2.25: - resolution: {integrity: sha512-2E1/gOCO97rF6usfhhiXxwzCb+UhdEsxW3lW1Sew+xZY0COY6dp82Z/r1rUt2fWKneWjuoGcNeJHHXQyG8mIuw==} - dependencies: - '@iconify/types': 2.0.0 - dev: true - - /@iconify/types@2.0.0: - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - dev: true - - /@inquirer/figures@1.0.10: - resolution: {integrity: sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==} + /@inquirer/figures@1.0.11: + resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} engines: {node: '>=18'} dev: true - /@intlify/bundle-utils@4.0.0(vue-i18n@9.14.2): - resolution: {integrity: sha512-klXrYT9VXyKEXsD6UY3pShg0O5MPC07n0TZ5RrSs5ry6T1eZVolIFGJi9c3qcDrh1qjJxgikRnPBmD7qGDqbjw==} - engines: {node: '>= 12'} + /@intlify/bundle-utils@8.0.0(vue-i18n@9.14.4): + resolution: {integrity: sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==} + engines: {node: '>= 14.16'} peerDependencies: petite-vue-i18n: '*' vue-i18n: '*' @@ -1416,47 +806,38 @@ packages: vue-i18n: optional: true dependencies: - '@intlify/message-compiler': 11.0.0-rc.1 - '@intlify/shared': 11.0.0-rc.1 - jsonc-eslint-parser: 1.4.1 - source-map: 0.6.1 - vue-i18n: 9.14.2(vue@3.5.13) - yaml-eslint-parser: 0.3.2 - dev: true - - /@intlify/core-base@9.14.2: - resolution: {integrity: sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==} - engines: {node: '>= 16'} - dependencies: - '@intlify/message-compiler': 9.14.2 - '@intlify/shared': 9.14.2 - - /@intlify/message-compiler@11.0.0-rc.1: - resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==} - engines: {node: '>= 16'} - dependencies: - '@intlify/shared': 11.0.0-rc.1 + '@intlify/message-compiler': 9.14.4 + '@intlify/shared': 9.14.4 + acorn: 8.14.1 + escodegen: 2.1.0 + estree-walker: 2.0.2 + jsonc-eslint-parser: 2.4.0 + mlly: 1.7.4 source-map-js: 1.2.1 + vue-i18n: 9.14.4(vue@3.5.13) + yaml-eslint-parser: 1.3.0 dev: true - /@intlify/message-compiler@9.14.2: - resolution: {integrity: sha512-YsKKuV4Qv4wrLNsvgWbTf0E40uRv+Qiw1BeLQ0LAxifQuhiMe+hfTIzOMdWj/ZpnTDj4RSZtkXjJM7JDiiB5LQ==} + /@intlify/core-base@9.14.4: + resolution: {integrity: sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==} engines: {node: '>= 16'} dependencies: - '@intlify/shared': 9.14.2 + '@intlify/message-compiler': 9.14.4 + '@intlify/shared': 9.14.4 + + /@intlify/message-compiler@9.14.4: + resolution: {integrity: sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==} + engines: {node: '>= 16'} + dependencies: + '@intlify/shared': 9.14.4 source-map-js: 1.2.1 - /@intlify/shared@11.0.0-rc.1: - resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} - engines: {node: '>= 16'} - dev: true - - /@intlify/shared@9.14.2: - resolution: {integrity: sha512-uRAHAxYPeF+G5DBIboKpPgC/Waecd4Jz8ihtkpJQD5ycb5PwXp0k/+hBGl5dAjwF7w+l74kz/PKA8r8OK//RUw==} + /@intlify/shared@9.14.4: + resolution: {integrity: sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==} engines: {node: '>= 16'} - /@intlify/unplugin-vue-i18n@0.8.2(vue-i18n@9.14.2): - resolution: {integrity: sha512-cRnzPqSEZQOmTD+p4pwc3RTS9HxreLqfID0keoqZDZweCy/CGRMLLTNd15S4TUf1vSBhPF03DItEFDr1F+8MDA==} + /@intlify/unplugin-vue-i18n@4.0.0(vue-i18n@9.14.4): + resolution: {integrity: sha512-q2Mhqa/mLi0tulfLFO4fMXXvEbkSZpI5yGhNNsLTNJJ41icEGUuyDe+j5zRZIKSkOJRgX6YbCyibTDJdRsukmw==} engines: {node: '>= 14.16'} peerDependencies: petite-vue-i18n: '*' @@ -1470,9 +851,9 @@ packages: vue-i18n-bridge: optional: true dependencies: - '@intlify/bundle-utils': 4.0.0(vue-i18n@9.14.2) - '@intlify/shared': 11.0.0-rc.1 - '@rollup/pluginutils': 4.2.1 + '@intlify/bundle-utils': 8.0.0(vue-i18n@9.14.4) + '@intlify/shared': 9.14.4 + '@rollup/pluginutils': 5.1.4 '@vue/compiler-sfc': 3.5.13 debug: 4.4.0(supports-color@8.1.1) fast-glob: 3.3.3 @@ -1480,10 +861,11 @@ packages: json5: 2.2.3 pathe: 1.1.2 picocolors: 1.1.1 - source-map: 0.6.1 + source-map-js: 1.2.1 unplugin: 1.16.1 - vue-i18n: 9.14.2(vue@3.5.13) + vue-i18n: 9.14.4(vue@3.5.13) transitivePeerDependencies: + - rollup - supports-color dev: true @@ -1499,13 +881,6 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.27.8 - dev: true - /@jridgewell/gen-mapping@0.3.8: resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -1568,20 +943,17 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.0 - dev: true + fastq: 1.19.1 /@one-ini/wasm@0.1.1: resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -1735,7 +1107,7 @@ packages: peerDependencies: pinia: '>=2.2.6' dependencies: - pinia: 2.3.1(typescript@5.7.3)(vue@3.5.13) + pinia: 2.3.1(typescript@5.8.3)(vue@3.5.13) vue-demi: 0.14.10(vue@3.5.13) transitivePeerDependencies: - '@vue/composition-api' @@ -1770,8 +1142,12 @@ packages: config-chain: 1.1.13 dev: false - /@quasar/app-vite@2.1.0(@types/node@22.13.5)(eslint@9.20.1)(pinia@2.3.1)(quasar@2.17.7)(sass@1.85.0)(typescript@5.7.3)(vue-router@4.5.0)(vue@3.5.13): - resolution: {integrity: sha512-BzT1UW6fe3X+akyNgkWNqeIXZSV2+RX4+IYXmYORh09VNKl+Vd8/oOcYWBqh3XWpy4CYkKC+H484dQmaQU6uHA==} + /@polka/url@1.0.0-next.28: + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + dev: true + + /@quasar/app-vite@2.2.0(@types/node@22.14.0)(eslint@9.24.0)(pinia@2.3.1)(quasar@2.18.1)(sass@1.86.3)(typescript@5.8.3)(vue-router@4.5.0)(vue@3.5.13): + resolution: {integrity: sha512-MvCfJrCbxUYvoGaK5jPq0h0hjO8mbxYOWngf+dIKrxhwb+1h5ERh6aVYEUuCtMIwTMEVfPkCez4DIfZIoReuDw==} engines: {node: ^30 || ^28 || ^26 || ^24 || ^22 || ^20 || ^18, npm: '>= 6.14.12', yarn: '>= 1.17.3'} hasBin: true peerDependencies: @@ -1800,15 +1176,15 @@ packages: dependencies: '@quasar/render-ssr-error': 1.0.3 '@quasar/ssl-certificate': 1.0.0 - '@quasar/vite-plugin': 1.9.0(@vitejs/plugin-vue@5.2.1)(quasar@2.17.7)(vite@6.1.1)(vue@3.5.13) + '@quasar/vite-plugin': 1.9.0(@vitejs/plugin-vue@5.2.3)(quasar@2.18.1)(vite@6.2.5)(vue@3.5.13) '@types/chrome': 0.0.262 '@types/compression': 1.7.5 '@types/cordova': 11.0.3 '@types/express': 4.17.21 - '@vitejs/plugin-vue': 5.2.1(vite@6.2.0)(vue@3.5.13) + '@vitejs/plugin-vue': 5.2.3(vite@6.2.5)(vue@3.5.13) archiver: 7.0.1 chokidar: 3.6.0 - ci-info: 4.1.0 + ci-info: 4.2.0 compression: 1.8.0 confbox: 0.1.8 cross-spawn: 7.0.6 @@ -1816,8 +1192,8 @@ packages: dotenv: 16.4.7 dotenv-expand: 11.0.7 elementtree: 0.1.7 - esbuild: 0.24.2 - eslint: 9.20.1 + esbuild: 0.25.2 + eslint: 9.24.0 express: 4.21.2 fs-extra: 11.3.0 html-minifier-terser: 7.2.0 @@ -1826,18 +1202,19 @@ packages: kolorist: 1.8.0 lodash: 4.17.21 minimist: 1.2.8 + mlly: 1.7.4 open: 10.1.0 - pinia: 2.3.1(typescript@5.7.3)(vue@3.5.13) - quasar: 2.17.7 + pinia: 2.3.1(typescript@5.8.3)(vue@3.5.13) + quasar: 2.18.1 rollup-plugin-visualizer: 5.14.0 - sass-embedded: 1.85.0 + sass-embedded: 1.86.3 semver: 7.7.1 serialize-javascript: 6.0.2 tinyglobby: 0.2.12 - ts-essentials: 9.4.2(typescript@5.7.3) - typescript: 5.7.3 - vite: 6.1.1(@types/node@22.13.5)(sass-embedded@1.85.0)(sass@1.85.0) - vue: 3.5.13(typescript@5.7.3) + ts-essentials: 9.4.2(typescript@5.8.3) + typescript: 5.8.3 + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) + vue: 3.5.13(typescript@5.8.3) vue-router: 4.5.0(vue@3.5.13) webpack-merge: 6.0.1 transitivePeerDependencies: @@ -1856,13 +1233,13 @@ packages: - yaml dev: true - /@quasar/cli@2.4.1: - resolution: {integrity: sha512-MrOmlqdkQhBxfPMbSrch3O7ClCAc0sLTLp9AWLzdB7uNaLbxcLP6zXN8+EPhDzFfMyxdG7jBP0FKEi7Wh+ezrQ==} + /@quasar/cli@2.5.0: + resolution: {integrity: sha512-2Vdltr47k7iwjSAYdtpu2ekPdGCmtrKU84wrGMs4taPRsfFyVyRBnxM1jruSvcmk54eA5chwof+ljmrui37AOA==} engines: {node: '>= 16', npm: '>= 5.6.0', yarn: '>= 1.6.0'} hasBin: true dependencies: '@quasar/ssl-certificate': 1.0.0 - ci-info: 4.1.0 + ci-info: 4.2.0 compression: 1.8.0 connect-history-api-fallback: 2.0.0 cors: 2.8.5 @@ -1892,7 +1269,7 @@ packages: '@quasar/quasar-ui-qcalendar': 4.1.2 dev: true - /@quasar/quasar-app-extension-testing-unit-vitest@0.4.0(@vue/test-utils@2.4.6)(quasar@2.17.7)(typescript@5.7.3)(vite@6.2.0)(vitest@0.34.6)(vue@3.5.13): + /@quasar/quasar-app-extension-testing-unit-vitest@0.4.0(@vitest/ui@3.1.1)(@vue/test-utils@2.4.6)(quasar@2.18.1)(typescript@5.8.3)(vite@6.2.5)(vitest@3.1.1)(vue@3.5.13): resolution: {integrity: sha512-eyzdUdmZiCueNS+5nedjMmzdbpCetSrtdGIwW6KplW1dTzRbLiNvYUjpBOxQGmJCgEhWy9zuswJ7MZ/bTql24Q==} engines: {node: '>= 12.22.1', npm: '>= 6.14.12', yarn: '>= 1.17.3'} peerDependencies: @@ -1905,14 +1282,15 @@ packages: '@vitest/ui': optional: true dependencies: + '@vitest/ui': 3.1.1(vitest@3.1.1) '@vue/test-utils': 2.4.6 happy-dom: 11.2.0 lodash-es: 4.17.21 - quasar: 2.17.7 - vite-jsconfig-paths: 2.0.1(vite@6.2.0) - vite-tsconfig-paths: 4.3.2(typescript@5.7.3)(vite@6.2.0) - vitest: 0.34.6(sass@1.85.0) - vue: 3.5.13(typescript@5.7.3) + quasar: 2.18.1 + vite-jsconfig-paths: 2.0.1(vite@6.2.5) + vite-tsconfig-paths: 4.3.2(typescript@5.8.3)(vite@6.2.5) + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3) + vue: 3.5.13(typescript@5.8.3) transitivePeerDependencies: - supports-color - typescript @@ -1937,7 +1315,7 @@ packages: fs-extra: 11.3.0 selfsigned: 2.4.1 - /@quasar/vite-plugin@1.9.0(@vitejs/plugin-vue@5.2.1)(quasar@2.17.7)(vite@6.1.1)(vue@3.5.13): + /@quasar/vite-plugin@1.9.0(@vitejs/plugin-vue@5.2.3)(quasar@2.18.1)(vite@6.2.5)(vue@3.5.13): resolution: {integrity: sha512-r1MFtI2QZJ2g20pe75Zuv4aoi0uoK8oP0yEdzLWRoOLCbhtf2+StJpUza9TydYi3KcvCl9+4HUf3OAWVKoxDmQ==} engines: {node: '>=18'} peerDependencies: @@ -1946,230 +1324,188 @@ packages: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 vue: ^3.0.0 dependencies: - '@vitejs/plugin-vue': 5.2.1(vite@6.2.0)(vue@3.5.13) - quasar: 2.17.7 - vite: 6.1.1(@types/node@22.13.5)(sass-embedded@1.85.0)(sass@1.85.0) - vue: 3.5.13(typescript@5.7.3) + '@vitejs/plugin-vue': 5.2.3(vite@6.2.5)(vue@3.5.13) + quasar: 2.18.1 + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) + vue: 3.5.13(typescript@5.8.3) dev: true - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} + /@rollup/pluginutils@5.1.4: + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: + '@types/estree': 1.0.7 estree-walker: 2.0.2 - picomatch: 2.3.1 + picomatch: 4.0.2 dev: true - /@rollup/rollup-android-arm-eabi@4.34.8: - resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} + /@rollup/rollup-android-arm-eabi@4.39.0: + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.34.8: - resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} + /@rollup/rollup-android-arm64@4.39.0: + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.34.8: - resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} + /@rollup/rollup-darwin-arm64@4.39.0: + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.34.8: - resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} + /@rollup/rollup-darwin-x64@4.39.0: + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-freebsd-arm64@4.34.8: - resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} + /@rollup/rollup-freebsd-arm64@4.39.0: + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} cpu: [arm64] os: [freebsd] requiresBuild: true dev: true optional: true - /@rollup/rollup-freebsd-x64@4.34.8: - resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} + /@rollup/rollup-freebsd-x64@4.39.0: + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} cpu: [x64] os: [freebsd] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.34.8: - resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} + /@rollup/rollup-linux-arm-gnueabihf@4.39.0: + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf@4.34.8: - resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} + /@rollup/rollup-linux-arm-musleabihf@4.39.0: + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.34.8: - resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} + /@rollup/rollup-linux-arm64-gnu@4.39.0: + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.34.8: - resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} + /@rollup/rollup-linux-arm64-musl@4.39.0: + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-loongarch64-gnu@4.34.8: - resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} + /@rollup/rollup-linux-loongarch64-gnu@4.39.0: + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} cpu: [loong64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.34.8: - resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} + /@rollup/rollup-linux-powerpc64le-gnu@4.39.0: + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.34.8: - resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} + /@rollup/rollup-linux-riscv64-gnu@4.39.0: + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.34.8: - resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} + /@rollup/rollup-linux-riscv64-musl@4.39.0: + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.39.0: + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.34.8: - resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} + /@rollup/rollup-linux-x64-gnu@4.39.0: + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.34.8: - resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} + /@rollup/rollup-linux-x64-musl@4.39.0: + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.34.8: - resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} + /@rollup/rollup-win32-arm64-msvc@4.39.0: + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.34.8: - resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} + /@rollup/rollup-win32-ia32-msvc@4.39.0: + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.34.8: - resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} + /@rollup/rollup-win32-x64-msvc@4.39.0: + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@shikijs/core@2.5.0: - resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} - dependencies: - '@shikijs/engine-javascript': 2.5.0 - '@shikijs/engine-oniguruma': 2.5.0 - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - hast-util-to-html: 9.0.5 - dev: true - - /@shikijs/engine-javascript@2.5.0: - resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} - dependencies: - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 3.1.1 - dev: true - - /@shikijs/engine-oniguruma@2.5.0: - resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} - dependencies: - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - dev: true - - /@shikijs/langs@2.5.0: - resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} - dependencies: - '@shikijs/types': 2.5.0 - dev: true - - /@shikijs/themes@2.5.0: - resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} - dependencies: - '@shikijs/types': 2.5.0 - dev: true - - /@shikijs/transformers@2.5.0: - resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} - dependencies: - '@shikijs/core': 2.5.0 - '@shikijs/types': 2.5.0 - dev: true - - /@shikijs/types@2.5.0: - resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - dev: true - - /@shikijs/vscode-textmate@10.0.2: - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - dev: true - - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + /@rtsao/scc@1.1.0: + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} dev: true /@sindresorhus/is@4.6.0: @@ -2204,7 +1540,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: true /@types/cacheable-request@6.0.3: @@ -2212,20 +1548,10 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 22.13.4 + '@types/node': 22.14.0 '@types/responselike': 1.0.3 dev: false - /@types/chai-subset@1.3.5: - resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==} - dependencies: - '@types/chai': 4.3.20 - dev: true - - /@types/chai@4.3.20: - resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} - dev: true - /@types/chrome@0.0.262: resolution: {integrity: sha512-TOoj3dqSYE13PD2fRuMQ6X6pggEvL9rRk/yOYOyWE6sfqRWxsJm4VoVm+wr9pkr4Sht/M5t7FFL4vXato8d1gA==} dependencies: @@ -2242,13 +1568,13 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: true /@types/conventional-commits-parser@5.0.1: resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: true /@types/cordova@11.0.3: @@ -2258,17 +1584,17 @@ packages: /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: - '@types/node': 22.13.5 + '@types/node': 22.14.0 dev: true - /@types/estree@1.0.6: - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + /@types/estree@1.0.7: + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} dev: true /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -2297,12 +1623,6 @@ packages: resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} dev: true - /@types/hast@3.0.4: - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - dependencies: - '@types/unist': 3.0.3 - dev: true - /@types/http-cache-semantics@4.0.4: resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} dev: false @@ -2314,7 +1634,7 @@ packages: /@types/http-proxy@1.17.16: resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: false /@types/json-schema@7.0.15: @@ -2328,30 +1648,9 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: false - /@types/linkify-it@5.0.0: - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - dev: true - - /@types/markdown-it@14.1.2: - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} - dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 - dev: true - - /@types/mdast@4.0.4: - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - dependencies: - '@types/unist': 3.0.3 - dev: true - - /@types/mdurl@2.0.0: - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - dev: true - /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: true @@ -2359,18 +1658,12 @@ packages: /@types/node-forge@1.3.11: resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 - /@types/node@22.13.4: - resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==} + /@types/node@22.14.0: + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} dependencies: - undici-types: 6.20.0 - - /@types/node@22.13.5: - resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==} - dependencies: - undici-types: 6.20.0 - dev: true + undici-types: 6.21.0 /@types/qs@6.9.18: resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} @@ -2383,21 +1676,21 @@ packages: /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: false /@types/send@0.17.4: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 22.13.4 + '@types/node': 22.14.0 dev: true /@types/serve-static@1.15.7: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.13.4 + '@types/node': 22.14.0 '@types/send': 0.17.4 dev: true @@ -2409,24 +1702,16 @@ packages: resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==} dev: true - /@types/unist@3.0.3: - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - dev: true - - /@types/web-bluetooth@0.0.20: - resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - dev: true - /@types/yauzl@2.10.3: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 22.13.5 + '@types/node': 22.14.0 dev: true optional: true - /@uiw/codemirror-extensions-basic-setup@4.23.8(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/view@6.36.3): - resolution: {integrity: sha512-XJR/8AEVcE7ufy1BhW2nCN9qSVDYEdCtYLfvhaMwl6Q3qcaYYCGE2K5QbFCy7LsdP/3uZKvc1OskuqatoOPdhQ==} + /@uiw/codemirror-extensions-basic-setup@4.23.10(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5): + resolution: {integrity: sha512-zpbmSeNs3OU/f/Eyd6brFnjsBUYwv2mFjWxlAsIRSwTlW+skIT60rQHFBSfsj/5UVSxSLWVeUYczN7AyXvgTGQ==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' '@codemirror/commands': '>=6.0.0' @@ -2437,16 +1722,16 @@ packages: '@codemirror/view': '>=6.0.0' dependencies: '@codemirror/autocomplete': 6.18.6 - '@codemirror/commands': 6.8.0 - '@codemirror/language': 6.10.8 - '@codemirror/lint': 6.8.4 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.0 + '@codemirror/lint': 6.8.5 '@codemirror/search': 6.5.10 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 dev: true - /@uiw/react-codemirror@4.23.8(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.3)(codemirror@6.0.1)(react-dom@19.0.0)(react@19.0.0): - resolution: {integrity: sha512-/NA5Pj4MmXkLSlmlUm4yfEmRLntrNq5TkQKBSINn7TukXQ4fc+C6Bk0U60Qa4rkvCSgwzZdQ2exyP0t0+2GtqA==} + /@uiw/react-codemirror@4.23.10(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.5)(codemirror@6.0.1)(react-dom@19.1.0)(react@19.1.0): + resolution: {integrity: sha512-AbN4eVHOL4ckRuIXpZxkzEqL/1ChVA+BSdEnAKjIB68pLQvKsVoYbiFP8zkXkYc4+Fcgq5KbAjvYqdo4ewemKw==} peerDependencies: '@babel/runtime': '>=7.11.0' '@codemirror/state': '>=6.0.0' @@ -2456,15 +1741,15 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@babel/runtime': 7.26.9 - '@codemirror/commands': 6.8.0 + '@babel/runtime': 7.27.0 + '@codemirror/commands': 6.8.1 '@codemirror/state': 6.5.2 '@codemirror/theme-one-dark': 6.1.2 - '@codemirror/view': 6.36.3 - '@uiw/codemirror-extensions-basic-setup': 4.23.8(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/view@6.36.3) + '@codemirror/view': 6.36.5 + '@uiw/codemirror-extensions-basic-setup': 4.23.10(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5) codemirror: 6.0.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: - '@codemirror/autocomplete' - '@codemirror/language' @@ -2472,74 +1757,97 @@ packages: - '@codemirror/search' dev: true - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true - - /@vitejs/plugin-vue@5.2.1(vite@5.4.14)(vue@3.5.13): - resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} + /@vitejs/plugin-vue@5.2.3(vite@6.2.5)(vue@3.5.13): + resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 dependencies: - vite: 5.4.14(@types/node@22.13.5)(sass@1.85.0) - vue: 3.5.13(typescript@5.7.3) + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) + vue: 3.5.13(typescript@5.8.3) dev: true - /@vitejs/plugin-vue@5.2.1(vite@6.2.0)(vue@3.5.13): - resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} - engines: {node: ^18.0.0 || >=20.0.0} + /@vitest/expect@3.1.1: + resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==} + dependencies: + '@vitest/spy': 3.1.1 + '@vitest/utils': 3.1.1 + chai: 5.2.0 + tinyrainbow: 2.0.0 + dev: true + + /@vitest/mocker@3.1.1(vite@6.2.5): + resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==} peerDependencies: + msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 - vue: ^3.2.25 - dependencies: - vite: 6.2.0(@types/node@22.13.5)(sass@1.85.0) - vue: 3.5.13(typescript@5.7.3) - dev: true - - /@vitest/expect@0.34.6: - resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} - dependencies: - '@vitest/spy': 0.34.6 - '@vitest/utils': 0.34.6 - chai: 4.5.0 - dev: true - - /@vitest/runner@0.34.6: - resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} - dependencies: - '@vitest/utils': 0.34.6 - p-limit: 4.0.0 - pathe: 1.1.2 - dev: true - - /@vitest/snapshot@0.34.6: - resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true dependencies: + '@vitest/spy': 3.1.1 + estree-walker: 3.0.3 magic-string: 0.30.17 - pathe: 1.1.2 - pretty-format: 29.7.0 + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) dev: true - /@vitest/spy@0.34.6: - resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + /@vitest/pretty-format@3.1.1: + resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==} dependencies: - tinyspy: 2.2.1 + tinyrainbow: 2.0.0 dev: true - /@vitest/utils@0.34.6: - resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + /@vitest/runner@3.1.1: + resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==} dependencies: - diff-sequences: 29.6.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/utils': 3.1.1 + pathe: 2.0.3 + dev: true + + /@vitest/snapshot@3.1.1: + resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==} + dependencies: + '@vitest/pretty-format': 3.1.1 + magic-string: 0.30.17 + pathe: 2.0.3 + dev: true + + /@vitest/spy@3.1.1: + resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==} + dependencies: + tinyspy: 3.0.2 + dev: true + + /@vitest/ui@3.1.1(vitest@3.1.1): + resolution: {integrity: sha512-2HpiRIYg3dlvAJBV9RtsVswFgUSJK4Sv7QhpxoP0eBGkYwzGIKP34PjaV00AULQi9Ovl6LGyZfsetxDWY5BQdQ==} + peerDependencies: + vitest: 3.1.1 + dependencies: + '@vitest/utils': 3.1.1 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.1 + tinyglobby: 0.2.12 + tinyrainbow: 2.0.0 + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3) + dev: true + + /@vitest/utils@3.1.1: + resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} + dependencies: + '@vitest/pretty-format': 3.1.1 + loupe: 3.1.3 + tinyrainbow: 2.0.0 dev: true /@vue/compiler-core@3.5.13: resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} dependencies: - '@babel/parser': 7.26.9 + '@babel/parser': 7.27.0 '@vue/shared': 3.5.13 entities: 4.5.0 estree-walker: 2.0.2 @@ -2554,7 +1862,7 @@ packages: /@vue/compiler-sfc@3.5.13: resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} dependencies: - '@babel/parser': 7.26.9 + '@babel/parser': 7.27.0 '@vue/compiler-core': 3.5.13 '@vue/compiler-dom': 3.5.13 '@vue/compiler-ssr': 3.5.13 @@ -2573,30 +1881,6 @@ packages: /@vue/devtools-api@6.6.4: resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - /@vue/devtools-api@7.7.2: - resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==} - dependencies: - '@vue/devtools-kit': 7.7.2 - dev: true - - /@vue/devtools-kit@7.7.2: - resolution: {integrity: sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==} - dependencies: - '@vue/devtools-shared': 7.7.2 - birpc: 0.2.19 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.2 - dev: true - - /@vue/devtools-shared@7.7.2: - resolution: {integrity: sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==} - dependencies: - rfdc: 1.4.1 - dev: true - /@vue/reactivity@3.5.13: resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} dependencies: @@ -2623,7 +1907,7 @@ packages: dependencies: '@vue/compiler-ssr': 3.5.13 '@vue/shared': 3.5.13 - vue: 3.5.13(typescript@5.7.3) + vue: 3.5.13(typescript@5.8.3) /@vue/shared@3.5.13: resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} @@ -2631,81 +1915,8 @@ packages: /@vue/test-utils@2.4.6: resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} dependencies: - js-beautify: 1.15.3 - vue-component-type-helpers: 2.2.2 - dev: true - - /@vueuse/core@12.7.0(typescript@5.7.3): - resolution: {integrity: sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==} - dependencies: - '@types/web-bluetooth': 0.0.20 - '@vueuse/metadata': 12.7.0 - '@vueuse/shared': 12.7.0(typescript@5.7.3) - vue: 3.5.13(typescript@5.7.3) - transitivePeerDependencies: - - typescript - dev: true - - /@vueuse/integrations@12.7.0(axios@1.7.9)(focus-trap@7.6.4)(typescript@5.7.3): - resolution: {integrity: sha512-IEq7K4bCl7mn3uKJaWtNXnd1CAPaHLUMuyj5K1/k/pVcItt0VONZW8xiGxdIovJcQjkzOHjImhX5t6gija+0/g==} - peerDependencies: - async-validator: ^4 - axios: ^1 - change-case: ^5 - drauu: ^0.4 - focus-trap: ^7 - fuse.js: ^7 - idb-keyval: ^6 - jwt-decode: ^4 - nprogress: ^0.2 - qrcode: ^1.5 - sortablejs: ^1 - universal-cookie: ^7 - peerDependenciesMeta: - async-validator: - optional: true - axios: - optional: true - change-case: - optional: true - drauu: - optional: true - focus-trap: - optional: true - fuse.js: - optional: true - idb-keyval: - optional: true - jwt-decode: - optional: true - nprogress: - optional: true - qrcode: - optional: true - sortablejs: - optional: true - universal-cookie: - optional: true - dependencies: - '@vueuse/core': 12.7.0(typescript@5.7.3) - '@vueuse/shared': 12.7.0(typescript@5.7.3) - axios: 1.7.9 - focus-trap: 7.6.4 - vue: 3.5.13(typescript@5.7.3) - transitivePeerDependencies: - - typescript - dev: true - - /@vueuse/metadata@12.7.0: - resolution: {integrity: sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==} - dev: true - - /@vueuse/shared@12.7.0(typescript@5.7.3): - resolution: {integrity: sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==} - dependencies: - vue: 3.5.13(typescript@5.7.3) - transitivePeerDependencies: - - typescript + js-beautify: 1.15.4 + vue-component-type-helpers: 2.2.8 dev: true /JSONStream@1.3.5: @@ -2716,9 +1927,9 @@ packages: through: 2.3.8 dev: true - /abbrev@3.0.0: - resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==} - engines: {node: ^18.17.0 || >=20.5.0} + /abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true /abort-controller@3.0.0: @@ -2735,40 +1946,17 @@ packages: mime-types: 2.1.35 negotiator: 0.6.3 - /acorn-jsx@5.3.2(acorn@7.4.1): + /acorn-jsx@5.3.2(acorn@8.14.1): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 7.4.1 - dev: true + acorn: 8.14.1 - /acorn-jsx@5.3.2(acorn@8.14.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.14.0 - dev: true - - /acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - dependencies: - acorn: 8.14.0 - dev: true - - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + /acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true - - /acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -2785,7 +1973,6 @@ packages: fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - dev: true /ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -2796,25 +1983,6 @@ packages: require-from-string: 2.0.2 dev: true - /algoliasearch@5.20.3: - resolution: {integrity: sha512-iNC6BGvipaalFfDfDnXUje8GUlW5asj0cTMsZJwO/0rhsyLx1L7GZFAY8wW+eQ6AM4Yge2p5GSE5hrBlfSD90Q==} - engines: {node: '>= 14.0.0'} - dependencies: - '@algolia/client-abtesting': 5.20.3 - '@algolia/client-analytics': 5.20.3 - '@algolia/client-common': 5.20.3 - '@algolia/client-insights': 5.20.3 - '@algolia/client-personalization': 5.20.3 - '@algolia/client-query-suggestions': 5.20.3 - '@algolia/client-search': 5.20.3 - '@algolia/ingestion': 1.20.3 - '@algolia/monitoring': 1.20.3 - '@algolia/recommend': 5.20.3 - '@algolia/requester-browser-xhr': 5.20.3 - '@algolia/requester-fetch': 5.20.3 - '@algolia/requester-node-http': 5.20.3 - dev: true - /ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} dependencies: @@ -2848,11 +2016,6 @@ packages: color-convert: 2.0.1 dev: true - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true - /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -2901,6 +2064,13 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + /array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 dev: true /array-flatten@1.1.1: @@ -2910,6 +2080,64 @@ packages: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true + /array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + dev: true + + /array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + dev: true + + /array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + dev: true + + /array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + dev: true + + /arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + dev: true + /asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: @@ -2921,8 +2149,9 @@ packages: engines: {node: '>=0.8'} dev: true - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} dev: true /astral-regex@2.0.0: @@ -2930,6 +2159,11 @@ packages: engines: {node: '>=8'} dev: true + /async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + dev: true + /async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} dev: true @@ -2942,15 +2176,15 @@ packages: engines: {node: '>= 4.0.0'} dev: true - /autoprefixer@10.4.20(postcss@8.5.3): - resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + /autoprefixer@10.4.21(postcss@8.5.3): + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: browserslist: 4.24.4 - caniuse-lite: 1.0.30001700 + caniuse-lite: 1.0.30001712 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -2958,6 +2192,13 @@ packages: postcss-value-parser: 4.2.0 dev: true + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + dependencies: + possible-typed-array-names: 1.1.0 + dev: true + /aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} dev: true @@ -2966,14 +2207,15 @@ packages: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} dev: true - /axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + /axios@1.8.4: + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} dependencies: follow-redirects: 1.15.9 form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug + dev: false /b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -3013,10 +2255,6 @@ packages: engines: {node: '>=8'} dev: true - /birpc@0.2.19: - resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} - dev: true - /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -3104,10 +2342,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001700 - electron-to-chromium: 1.5.102 + caniuse-lite: 1.0.30001712 + electron-to-chromium: 1.5.132 node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.24.4) dev: true /buffer-builder@0.2.0: @@ -3209,17 +2447,26 @@ packages: es-errors: 1.3.0 function-bind: 1.1.2 - /call-bound@1.0.3: - resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + /call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.2.7 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + dev: true + + /call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: true /camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} @@ -3243,29 +2490,23 @@ packages: engines: {node: '>=14.16'} dev: false - /caniuse-lite@1.0.30001700: - resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} + /caniuse-lite@1.0.30001712: + resolution: {integrity: sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==} dev: true /caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: true - /ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - dev: true - - /chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} + /chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.1.0 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 dev: true /chalk@4.1.2: @@ -3280,22 +2521,13 @@ packages: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - /character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - dev: true - - /character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - dev: true - /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - dependencies: - get-func-name: 2.0.2 + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} dev: true /check-more-types@2.24.0: @@ -3347,8 +2579,8 @@ packages: engines: {node: '>=8'} dev: false - /ci-info@4.1.0: - resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + /ci-info@4.2.0: + resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} engines: {node: '>=8'} /clean-css@5.3.3: @@ -3443,12 +2675,12 @@ packages: resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} dependencies: '@codemirror/autocomplete': 6.18.6 - '@codemirror/commands': 6.8.0 - '@codemirror/language': 6.10.8 - '@codemirror/lint': 6.8.4 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.0 + '@codemirror/lint': 6.8.5 '@codemirror/search': 6.5.10 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.36.3 + '@codemirror/view': 6.36.5 dev: true /color-convert@2.0.1: @@ -3476,10 +2708,6 @@ packages: dependencies: delayed-stream: 1.0.0 - /comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - dev: true - /commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -3526,7 +2754,7 @@ packages: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} dependencies: - mime-db: 1.53.0 + mime-db: 1.54.0 /compression@1.8.0: resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==} @@ -3633,13 +2861,6 @@ packages: engines: {node: '>= 0.6'} dev: true - /copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - dependencies: - is-what: 4.1.16 - dev: true - /core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} dev: true @@ -3654,7 +2875,7 @@ packages: object-assign: 4.1.1 vary: 1.1.2 - /cosmiconfig-typescript-loader@6.1.0(@types/node@22.13.5)(cosmiconfig@9.0.0)(typescript@5.7.3): + /cosmiconfig-typescript-loader@6.1.0(@types/node@22.14.0)(cosmiconfig@9.0.0)(typescript@5.8.3): resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} engines: {node: '>=v18'} peerDependencies: @@ -3662,13 +2883,13 @@ packages: cosmiconfig: '>=9' typescript: '>=5' dependencies: - '@types/node': 22.13.5 - cosmiconfig: 9.0.0(typescript@5.7.3) + '@types/node': 22.14.0 + cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 2.4.2 - typescript: 5.7.3 + typescript: 5.8.3 dev: true - /cosmiconfig@9.0.0(typescript@5.7.3): + /cosmiconfig@9.0.0(typescript@5.8.3): resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: @@ -3681,7 +2902,7 @@ packages: import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 - typescript: 5.7.3 + typescript: 5.8.3 dev: true /crc-32@1.2.2: @@ -3734,7 +2955,7 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - /cypress-mochawesome-reporter@3.8.2(cypress@14.1.0)(mocha@11.1.0): + /cypress-mochawesome-reporter@3.8.2(cypress@14.2.1)(mocha@11.1.0): resolution: {integrity: sha512-oJZkNzhNmN9ZD+LmZyFuPb8aWaIijyHyqYh52YOBvR6B6ckfJNCHP3A98a+/nG0H4t46CKTNwo+wNpMa4d2kjA==} engines: {node: '>=14'} hasBin: true @@ -3742,7 +2963,7 @@ packages: cypress: '>=6.2.0' dependencies: commander: 10.0.1 - cypress: 14.1.0 + cypress: 14.2.1 fs-extra: 10.1.0 mochawesome: 7.1.3(mocha@11.1.0) mochawesome-merge: 4.4.1 @@ -3751,13 +2972,13 @@ packages: - mocha dev: true - /cypress@14.1.0: - resolution: {integrity: sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==} + /cypress@14.2.1: + resolution: {integrity: sha512-5xd0E7fUp0pjjib1D7ljkmCwFDgMkWuW06jWiz8dKrI7MNRrDo0C65i4Sh+oZ9YHjMHZRJBR0XZk1DfekOhOUw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true requiresBuild: true dependencies: - '@cypress/request': 3.0.7 + '@cypress/request': 3.0.8 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) '@types/sinonjs__fake-timers': 8.1.1 '@types/sizzle': 2.3.9 @@ -3768,7 +2989,7 @@ packages: cachedir: 2.4.0 chalk: 4.1.2 check-more-types: 2.24.0 - ci-info: 4.1.0 + ci-info: 4.2.0 cli-cursor: 3.1.0 cli-table3: 0.6.5 commander: 6.2.1 @@ -3814,6 +3035,33 @@ packages: assert-plus: 1.0.0 dev: true + /data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + dev: true + + /data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + dev: true + + /data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + dev: true + /dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} dev: true @@ -3900,11 +3148,9 @@ packages: mimic-response: 3.1.0 dev: false - /deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - dependencies: - type-detect: 4.1.0 dev: true /deep-extend@0.6.0: @@ -3958,6 +3204,15 @@ packages: engines: {node: '>=10'} dev: false + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + dev: true + /define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -3967,6 +3222,15 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + dev: true + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -3975,11 +3239,6 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - dev: true - /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -3997,22 +3256,18 @@ packages: dev: true optional: true - /devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dependencies: - dequal: 2.0.3 - dev: true - - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - /diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} dev: true + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + /dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: @@ -4038,7 +3293,7 @@ packages: resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} engines: {node: '>=18'} dependencies: - type-fest: 4.35.0 + type-fest: 4.39.1 dev: true /dotenv-expand@11.0.7: @@ -4085,8 +3340,8 @@ packages: /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - /electron-to-chromium@1.5.102: - resolution: {integrity: sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==} + /electron-to-chromium@1.5.132: + resolution: {integrity: sha512-QgX9EBvWGmvSRa74zqfnG7+Eno0Ak0vftBll0Pt2/z5b3bEGYL6OUXLgKPtvx73dn3dvwrlyVkjPKRRlhLYTEg==} dev: true /elementtree@0.1.7: @@ -4096,10 +3351,6 @@ packages: sax: 1.1.4 dev: true - /emoji-regex-xs@1.0.0: - resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} - dev: true - /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -4129,7 +3380,7 @@ packages: engines: {node: '>=10.2.0'} dependencies: '@types/cors': 2.8.17 - '@types/node': 22.13.5 + '@types/node': 22.14.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -4166,6 +3417,63 @@ packages: is-arrayish: 0.2.1 dev: true + /es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + dev: true + /es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -4174,6 +3482,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + /es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + /es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -4185,105 +3496,57 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - /esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 + /es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + dependencies: + hasown: 2.0.2 dev: true - /esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + /es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + dev: true + + /esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} engines: {node: '>=18'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 - dev: true - - /esbuild@0.25.0: - resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.0 - '@esbuild/android-arm': 0.25.0 - '@esbuild/android-arm64': 0.25.0 - '@esbuild/android-x64': 0.25.0 - '@esbuild/darwin-arm64': 0.25.0 - '@esbuild/darwin-x64': 0.25.0 - '@esbuild/freebsd-arm64': 0.25.0 - '@esbuild/freebsd-x64': 0.25.0 - '@esbuild/linux-arm': 0.25.0 - '@esbuild/linux-arm64': 0.25.0 - '@esbuild/linux-ia32': 0.25.0 - '@esbuild/linux-loong64': 0.25.0 - '@esbuild/linux-mips64el': 0.25.0 - '@esbuild/linux-ppc64': 0.25.0 - '@esbuild/linux-riscv64': 0.25.0 - '@esbuild/linux-s390x': 0.25.0 - '@esbuild/linux-x64': 0.25.0 - '@esbuild/netbsd-arm64': 0.25.0 - '@esbuild/netbsd-x64': 0.25.0 - '@esbuild/openbsd-arm64': 0.25.0 - '@esbuild/openbsd-x64': 0.25.0 - '@esbuild/sunos-x64': 0.25.0 - '@esbuild/win32-arm64': 0.25.0 - '@esbuild/win32-ia32': 0.25.0 - '@esbuild/win32-x64': 0.25.0 + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 dev: true /escalade@3.2.0: @@ -4309,38 +3572,133 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier@10.0.1(eslint@9.20.1): - resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==} + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + + /eslint-config-prettier@10.1.1(eslint@9.24.0): + resolution: {integrity: sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.20.1 + eslint: 9.24.0 dev: true - /eslint-plugin-cypress@4.1.0(eslint@9.20.1): - resolution: {integrity: sha512-JhqkMY02mw74USwK9OFhectx3YSj6Co1NgWBxlGdKvlqiAp9vdEuQqt33DKGQFvvGS/NWtduuhWXWNnU29xDSg==} + /eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + dependencies: + eslint-plugin-import: 2.31.0(eslint@9.24.0) + dev: true + + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + dependencies: + debug: 3.2.7(supports-color@8.1.1) + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint@9.24.0): + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + debug: 3.2.7(supports-color@8.1.1) + eslint: 9.24.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-cypress@4.2.0(eslint@9.24.0): + resolution: {integrity: sha512-v5cyt0VYb1tEEODBJSE44PocYOwQsckyexJhCs7LtdD3FGO6D2GjnZB2s2Sts4RcxdxECTWX01nObOZRs26bQw==} peerDependencies: eslint: '>=9' dependencies: - eslint: 9.20.1 + eslint: 9.24.0 globals: 15.15.0 dev: true - /eslint-plugin-vue@9.32.0(eslint@9.20.1): - resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==} + /eslint-plugin-import@2.31.0(eslint@9.24.0): + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 9.24.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint@9.24.0) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-vue@9.33.0(eslint@9.24.0): + resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) - eslint: 9.20.1 + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0) + eslint: 9.24.0 globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 semver: 7.7.1 - vue-eslint-parser: 9.4.3(eslint@9.20.1) + vue-eslint-parser: 9.4.3(eslint@9.24.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -4354,26 +3712,14 @@ packages: estraverse: 5.3.0 dev: true - /eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + /eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-utils@2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} - dependencies: - eslint-visitor-keys: 1.3.0 - dev: true - - /eslint-visitor-keys@1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} - dev: true - /eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4382,10 +3728,9 @@ packages: /eslint-visitor-keys@4.2.0: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - /eslint@9.20.1: - resolution: {integrity: sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==} + /eslint@9.24.0: + resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -4394,24 +3739,25 @@ packages: jiti: optional: true dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.2 - '@eslint/core': 0.11.0 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.20.0 - '@eslint/plugin-kit': 0.2.6 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.24.0 + '@eslint/plugin-kit': 0.2.8 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.2 - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 + eslint-scope: 8.3.0 eslint-visitor-keys: 4.2.0 espree: 10.3.0 esquery: 1.6.0 @@ -4436,29 +3782,25 @@ packages: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.0 - dev: true - - /espree@6.2.1: - resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} - engines: {node: '>=6.0.0'} - dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - eslint-visitor-keys: 1.3.0 - dev: true /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 3.4.3 dev: true + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + /esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -4481,6 +3823,12 @@ packages: /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.7 + dev: true + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -4560,6 +3908,11 @@ packages: pify: 2.3.0 dev: true + /expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + dev: true + /express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -4644,7 +3997,6 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true /fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -4659,11 +4011,9 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 - dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} @@ -4673,11 +4023,10 @@ packages: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} dev: true - /fastq@1.19.0: - resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} dependencies: - reusify: 1.0.4 - dev: true + reusify: 1.1.0 /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -4695,6 +4044,10 @@ packages: picomatch: 4.0.2 dev: true + /fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + dev: true + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -4771,12 +4124,6 @@ packages: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} dev: true - /focus-trap@7.6.4: - resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} - dependencies: - tabbable: 6.2.0 - dev: true - /follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -4785,9 +4132,17 @@ packages: peerDependenciesMeta: debug: optional: true + dev: false - /foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + /for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + dev: true + + /foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} dependencies: cross-spawn: 7.0.6 @@ -4860,6 +4215,10 @@ packages: universalify: 2.0.1 dev: true + /fs-readdir-recursive@1.1.0: + resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4878,17 +4237,29 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + /function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true - - /get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} + /get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 @@ -4925,6 +4296,15 @@ packages: engines: {node: '>=10'} dev: false + /get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + dev: true + /getos@3.2.1: resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} dependencies: @@ -4952,7 +4332,6 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -4969,7 +4348,7 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 @@ -5011,13 +4390,25 @@ packages: /globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - dev: true /globals@15.15.0: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} dev: true + /globals@16.0.0: + resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} + engines: {node: '>=18'} + dev: true + + /globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + dev: true + /globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} dev: true @@ -5091,10 +4482,28 @@ packages: whatwg-mimetype: 3.0.0 dev: true + /has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + dev: true + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + dependencies: + es-define-property: 1.0.1 + dev: true + + /has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + dependencies: + dunder-proto: 1.0.1 + dev: true + /has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -5116,37 +4525,11 @@ packages: dependencies: function-bind: 1.1.2 - /hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.0 - property-information: 7.0.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - dev: true - - /hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - dependencies: - '@types/hast': 3.0.4 - dev: true - /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true dev: true - /hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - dev: true - /html-minifier-terser@7.2.0: resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} engines: {node: ^14.13.1 || >=16.0.0} @@ -5161,10 +4544,6 @@ packages: terser: 5.39.0 dev: true - /html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - dev: true - /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: false @@ -5274,10 +4653,9 @@ packages: /ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - dev: true - /immutable@5.0.3: - resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} + /immutable@5.1.1: + resolution: {integrity: sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==} dev: true /import-fresh@3.3.1: @@ -5286,7 +4664,6 @@ packages: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true /import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} @@ -5332,20 +4709,29 @@ packages: resolution: {integrity: sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==} engines: {node: '>=18'} dependencies: - '@inquirer/figures': 1.0.10 + '@inquirer/figures': 1.0.11 ansi-escapes: 4.3.2 cli-width: 4.1.0 external-editor: 3.1.0 mute-stream: 1.0.0 ora: 5.4.1 run-async: 3.0.0 - rxjs: 7.8.1 + rxjs: 7.8.2 string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 dev: true + /internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + dev: true + /ip@1.1.9: resolution: {integrity: sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==} dev: true @@ -5354,10 +4740,37 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + /is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + dev: true + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true + /is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + dev: true + + /is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + dependencies: + has-bigints: 1.1.0 + dev: true + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -5365,6 +4778,19 @@ packages: binary-extensions: 2.3.0 dev: true + /is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true @@ -5372,6 +4798,30 @@ packages: ci-info: 3.9.0 dev: false + /is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + dependencies: + hasown: 2.0.2 + dev: true + + /is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + dev: true + + /is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + dev: true + /is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -5386,10 +4836,27 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + /is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + /is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + dev: true + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -5415,11 +4882,24 @@ packages: engines: {node: '>=8'} dev: true + /is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + dev: true + /is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + dev: true + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -5449,6 +4929,28 @@ packages: isobject: 3.0.1 dev: true + /is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + dev: true + + /is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + dev: true + + /is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + dev: true + /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -5458,6 +4960,23 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + dev: true + + /is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + dev: true + /is-text-path@2.0.0: resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} engines: {node: '>=8'} @@ -5465,6 +4984,13 @@ packages: text-extensions: 2.4.0 dev: true + /is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.19 + dev: true + /is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -5473,9 +4999,24 @@ packages: engines: {node: '>=10'} dev: true - /is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} + /is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + dev: true + + /is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + dev: true + + /is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 dev: true /is-wsl@2.2.0: @@ -5499,6 +5040,10 @@ packages: /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isbinaryfile@5.0.4: resolution: {integrity: sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==} engines: {node: '>= 18.0.0'} @@ -5529,8 +5074,8 @@ packages: hasBin: true dev: true - /js-beautify@1.15.3: - resolution: {integrity: sha512-rKKGuyTxGNlyN4EQKWzNndzXpi0bOl8Gl8YQAW1as/oMz0XhD6sHJO1hTvoBDOSzKuJb9WkwoAb34FfdkKMv2A==} + /js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} engines: {node: '>=14'} hasBin: true dependencies: @@ -5538,7 +5083,7 @@ packages: editorconfig: 1.0.4 glob: 10.4.5 js-cookie: 3.0.5 - nopt: 8.1.0 + nopt: 7.2.1 dev: true /js-cookie@3.0.5: @@ -5555,7 +5100,6 @@ packages: hasBin: true dependencies: argparse: 2.0.1 - dev: true /jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -5570,7 +5114,6 @@ packages: /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -5601,15 +5144,14 @@ packages: hasBin: true dev: true - /jsonc-eslint-parser@1.4.1: - resolution: {integrity: sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==} - engines: {node: '>=8.10.0'} + /jsonc-eslint-parser@2.4.0: + resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 7.4.1 - eslint-utils: 2.1.0 - eslint-visitor-keys: 1.3.0 - espree: 6.2.1 - semver: 6.3.1 + acorn: 8.14.1 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.7.1 dev: true /jsonfile@4.0.0: @@ -5640,6 +5182,16 @@ packages: verror: 1.10.0 dev: true + /junit-merge@2.0.0: + resolution: {integrity: sha512-qwENzBWcdHPazNqPO0fKyFIqEyaSKyO0iyBeIU4Y/scjkXYpwTi88P2S/PWecqgMhzG2MOCwXk8QB9ucvXeIPw==} + hasBin: true + dependencies: + commander: 2.20.3 + fs-readdir-recursive: 1.1.0 + mkdirp: 0.5.6 + xmldoc: 1.3.0 + dev: true + /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: @@ -5699,16 +5251,11 @@ packages: log-update: 4.0.0 p-map: 4.0.0 rfdc: 1.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 through: 2.3.8 wrap-ansi: 7.0.0 dev: true - /local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} - engines: {node: '>=14'} - dev: true - /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -5819,10 +5366,8 @@ packages: js-tokens: 4.0.0 dev: true - /loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - dependencies: - get-func-name: 2.0.2 + /loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} dev: true /lower-case@2.0.2: @@ -5863,28 +5408,10 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - /mark.js@8.11.1: - resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} - dev: true - /math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - /mdast-util-to-hast@13.2.0: - resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 - vfile: 6.0.3 - dev: true - /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -5903,7 +5430,6 @@ packages: /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true /merge@2.1.1: resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} @@ -5913,33 +5439,6 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - /micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.1 - dev: true - - /micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - dev: true - - /micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - dev: true - - /micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - dev: true - - /micromark-util-types@2.0.1: - resolution: {integrity: sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==} - dev: true - /micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -5951,8 +5450,8 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - /mime-db@1.53.0: - resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + /mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} /mime-types@2.1.35: @@ -6024,28 +5523,19 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true - /minisearch@7.1.2: - resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} - dev: true - - /mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - dev: true - /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true dependencies: minimist: 1.2.8 - dev: false /mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} dependencies: - acorn: 8.14.0 + acorn: 8.14.1 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.5.4 + ufo: 1.6.0 dev: true /mocha@11.1.0: @@ -6099,7 +5589,7 @@ packages: prop-types: 15.8.1 tcomb: 3.2.29 tcomb-validation: 3.4.1 - validator: 13.12.0 + validator: 13.15.0 yargs: 17.7.2 dev: true @@ -6125,6 +5615,11 @@ packages: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} dev: false + /mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + dev: true + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -6144,8 +5639,8 @@ packages: thenify-all: 1.6.0 dev: true - /nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -6186,12 +5681,12 @@ packages: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} dev: true - /nopt@8.1.0: - resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} - engines: {node: ^18.17.0 || >=20.5.0} + /nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true dependencies: - abbrev: 3.0.0 + abbrev: 2.0.0 dev: true /normalize-path@3.0.0: @@ -6241,6 +5736,52 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + dev: true + + /object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + dev: true + + /object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + dev: true + + /object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + dev: true + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -6269,14 +5810,6 @@ packages: mimic-fn: 4.0.0 dev: false - /oniguruma-to-es@3.1.1: - resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} - dependencies: - emoji-regex-xs: 1.0.0 - regex: 6.0.1 - regex-recursion: 6.0.2 - dev: true - /open@10.1.0: resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} engines: {node: '>=18'} @@ -6346,6 +5879,15 @@ packages: resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} dev: true + /own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + dev: true + /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -6374,7 +5916,7 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - yocto-queue: 1.1.1 + yocto-queue: 1.2.1 dev: true /p-locate@4.1.0: @@ -6436,7 +5978,6 @@ packages: engines: {node: '>=6'} dependencies: callsites: 3.1.0 - dev: true /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} @@ -6482,6 +6023,10 @@ packages: engines: {node: '>=12'} dev: false + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + /path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -6501,17 +6046,14 @@ packages: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} dev: true - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} dev: true /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - /perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - dev: true - /performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} dev: true @@ -6533,7 +6075,7 @@ packages: engines: {node: '>=0.10.0'} dev: true - /pinia@2.3.1(typescript@5.7.3)(vue@3.5.13): + /pinia@2.3.1(typescript@5.8.3)(vue@3.5.13): resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} peerDependencies: typescript: '>=4.4.4' @@ -6543,14 +6085,14 @@ packages: optional: true dependencies: '@vue/devtools-api': 6.6.4 - typescript: 5.7.3 - vue: 3.5.13(typescript@5.7.3) + typescript: 5.8.3 + vue: 3.5.13(typescript@5.8.3) vue-demi: 0.14.10(vue@3.5.13) transitivePeerDependencies: - '@vue/composition-api' - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + /pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} dev: true @@ -6562,6 +6104,11 @@ packages: pathe: 2.0.3 dev: true + /possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + dev: true + /postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} @@ -6578,21 +6125,17 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - /preact@10.26.2: - resolution: {integrity: sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==} - dev: true - /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true - /prettier@3.5.1: - resolution: {integrity: sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==} + /prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} hasBin: true dev: true @@ -6602,15 +6145,6 @@ packages: engines: {node: '>=6'} dev: true - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - dev: true - /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -6632,10 +6166,6 @@ packages: react-is: 16.13.1 dev: true - /property-information@7.0.0: - resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} - dev: true - /proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -6652,6 +6182,7 @@ packages: /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false /pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -6666,7 +6197,6 @@ packages: /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true /pupa@3.1.0: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} @@ -6681,20 +6211,19 @@ packages: dependencies: side-channel: 1.1.0 - /qs@6.13.1: - resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} + /qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} dependencies: side-channel: 1.1.0 dev: true - /quasar@2.17.7: - resolution: {integrity: sha512-nPJdHoONlcW7WEU2Ody907Wx945Zfyuea/KP4LBaEn5AcL95PUWp8Gz/0zDYNnFw0aCWRtye3SUAdQl5tmrn5w==} + /quasar@2.18.1: + resolution: {integrity: sha512-db/P64Mzpt1uXJ0MapaG+IYJQ9hHDb5KtTCoszwC78DR7sA+Uoj7nBW2EytwYykIExEmqavOvKrdasTvqhkgEg==} engines: {node: '>= 10.18.1', npm: '>= 6.13.4', yarn: '>= 1.21.1'} /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} @@ -6730,25 +6259,21 @@ packages: strip-json-comments: 2.0.1 dev: false - /react-dom@19.0.0(react@19.0.0): - resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + /react-dom@19.1.0(react@19.1.0): + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: - react: ^19.0.0 + react: ^19.1.0 dependencies: - react: 19.0.0 - scheduler: 0.25.0 + react: 19.1.0 + scheduler: 0.26.0 dev: true /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true - /react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - dev: true - - /react@19.0.0: - resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + /react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} dev: true @@ -6811,24 +6336,34 @@ packages: tslib: 1.14.1 dev: true + /reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + dev: true + /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} dev: true - /regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + /regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} dependencies: - regex-utilities: 2.3.0 - dev: true - - /regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - dev: true - - /regex@6.0.1: - resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} - dependencies: - regex-utilities: 2.3.0 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 dev: true /registry-auth-token@5.1.0: @@ -6881,13 +6416,22 @@ packages: /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - dev: true /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} dev: true + /resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} dependencies: @@ -6909,10 +6453,9 @@ packages: signal-exit: 3.0.7 dev: true - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + /reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true /rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -6945,32 +6488,33 @@ packages: yargs: 17.7.2 dev: true - /rollup@4.34.8: - resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} + /rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.8 - '@rollup/rollup-android-arm64': 4.34.8 - '@rollup/rollup-darwin-arm64': 4.34.8 - '@rollup/rollup-darwin-x64': 4.34.8 - '@rollup/rollup-freebsd-arm64': 4.34.8 - '@rollup/rollup-freebsd-x64': 4.34.8 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 - '@rollup/rollup-linux-arm-musleabihf': 4.34.8 - '@rollup/rollup-linux-arm64-gnu': 4.34.8 - '@rollup/rollup-linux-arm64-musl': 4.34.8 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 - '@rollup/rollup-linux-riscv64-gnu': 4.34.8 - '@rollup/rollup-linux-s390x-gnu': 4.34.8 - '@rollup/rollup-linux-x64-gnu': 4.34.8 - '@rollup/rollup-linux-x64-musl': 4.34.8 - '@rollup/rollup-win32-arm64-msvc': 4.34.8 - '@rollup/rollup-win32-ia32-msvc': 4.34.8 - '@rollup/rollup-win32-x64-msvc': 4.34.8 + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 fsevents: 2.3.3 dev: true @@ -7004,25 +6548,52 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - dev: true - /rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + /rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} dependencies: tslib: 2.8.1 dev: true + /safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + dev: true + /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + dev: true + + /safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + dev: true + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sass-embedded-android-arm64@1.85.0: - resolution: {integrity: sha512-4itDzRwezwrW8+YzMLIwHtMeH+qrBNdBsRn9lTVI15K+cNLC8z5JWJi6UCZ8TNNZr9LDBfsh5jUdjSub0yF7jg==} + /sass-embedded-android-arm64@1.86.3: + resolution: {integrity: sha512-q+XwFp6WgAv+UgnQhsB8KQ95kppvWAB7DSoJp+8Vino8b9ND+1ai3cUUZPE5u4SnLZrgo5NtrbPvN5KLc4Pfyg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [android] @@ -7030,8 +6601,8 @@ packages: dev: true optional: true - /sass-embedded-android-arm@1.85.0: - resolution: {integrity: sha512-pPBT7Ad6G8Mlao8ypVNXW2ya7I/Bhcny+RYZ/EmrunEXfhzCNp4PWV2VAweitPO9RnPIJwvUTkLc8Fu6K3nVmw==} + /sass-embedded-android-arm@1.86.3: + resolution: {integrity: sha512-UyeXrFzZSvrGbvrWUBcspbsbivGgAgebLGJdSqJulgSyGbA6no3DWQ5Qpdd6+OAUC39BlpPu74Wx9s4RrVuaFw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [android] @@ -7039,8 +6610,8 @@ packages: dev: true optional: true - /sass-embedded-android-ia32@1.85.0: - resolution: {integrity: sha512-bwqKq95hzbGbMTeXCMQhH7yEdc2xJVwIXj7rGdD3McvyFWbED6362XRFFPI5YyjfD2wRJd9yWLh/hn+6VyjcYA==} + /sass-embedded-android-ia32@1.86.3: + resolution: {integrity: sha512-gTJjVh2cRzvGujXj5ApPk/owUTL5SiO7rDtNLrzYAzi1N5HRuLYXqk3h1IQY3+eCOBjGl7mQ9XyySbJs/3hDvg==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [android] @@ -7048,8 +6619,8 @@ packages: dev: true optional: true - /sass-embedded-android-riscv64@1.85.0: - resolution: {integrity: sha512-Fgkgay+5EePJXZFHR5Vlkutnsmox2V6nX4U3mfGbSN1xjLRm8F5ST72V2s5Z0mnIFpGvEu/v7hfptgViqMvaxg==} + /sass-embedded-android-riscv64@1.86.3: + resolution: {integrity: sha512-Po3JnyiCS16kd6REo1IMUbFGYtvL9O0rmKaXx5vOuBaJD1LPy2LiSSp7TU7wkJ9IxsTDGzFaSeP1I9qb6D8VVg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [android] @@ -7057,8 +6628,8 @@ packages: dev: true optional: true - /sass-embedded-android-x64@1.85.0: - resolution: {integrity: sha512-/bG3JgTn3eoIDHCiJNVkLeJgUesat4ghxqYmKMZUJx++4e6iKCDj8XwQTJAgm+QDrsPKXHBacHEANJ9LEAuTqg==} + /sass-embedded-android-x64@1.86.3: + resolution: {integrity: sha512-+7h3jdDv/0kUFx0BvxYlq2fa7CcHiDPlta6k5OxO5K6jyqJwo9hc0Z052BoYEauWTqZ+vK6bB5rv2BIzq4U9nA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [android] @@ -7066,8 +6637,8 @@ packages: dev: true optional: true - /sass-embedded-darwin-arm64@1.85.0: - resolution: {integrity: sha512-plp8TyMz97YFBCB3ndftEvoW29vyfsSBJILM5U84cGzr06SvLh/Npjj8psfUeRw+upEk1zkFtw5u61sRCdgwIw==} + /sass-embedded-darwin-arm64@1.86.3: + resolution: {integrity: sha512-EgLwV4ORm5Hr0DmIXo0Xw/vlzwLnfAiqD2jDXIglkBsc5czJmo4/IBdGXOP65TRnsgJEqvbU3aQhuawX5++x9A==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [darwin] @@ -7075,8 +6646,8 @@ packages: dev: true optional: true - /sass-embedded-darwin-x64@1.85.0: - resolution: {integrity: sha512-LP8Zv8DG57Gn6PmSwWzC0gEZUsGdg36Ps3m0i1fVTOelql7N3HZIrlPYRjJvidL8ZlB3ISxNANebTREUHn/wkQ==} + /sass-embedded-darwin-x64@1.86.3: + resolution: {integrity: sha512-dfKhfrGPRNLWLC82vy/vQGmNKmAiKWpdFuWiePRtg/E95pqw+sCu6080Y6oQLfFu37Iq3MpnXiSpDuSo7UnPWA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [darwin] @@ -7084,8 +6655,8 @@ packages: dev: true optional: true - /sass-embedded-linux-arm64@1.85.0: - resolution: {integrity: sha512-JRIRKVOY5Y8M1zlUOv9AQGju4P6lj8i5vLJZsVYVN/uY8Cd2dDJZPC8EOhjntp+IpF8AOGIHqCeCkHBceIyIjA==} + /sass-embedded-linux-arm64@1.86.3: + resolution: {integrity: sha512-tYq5rywR53Qtc+0KI6pPipOvW7a47ETY69VxfqI9BR2RKw2hBbaz0bIw6OaOgEBv2/XNwcWb7a4sr7TqgkqKAA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] @@ -7093,8 +6664,8 @@ packages: dev: true optional: true - /sass-embedded-linux-arm@1.85.0: - resolution: {integrity: sha512-18xOAEfazJt1MMVS2TRHV94n81VyMnywOoJ7/S7I79qno/zx26OoqqP4XvH107xu8+mZ9Gg54LrUH6ZcgHk08g==} + /sass-embedded-linux-arm@1.86.3: + resolution: {integrity: sha512-+fVCIH+OR0SMHn2NEhb/VfbpHuUxcPtqMS34OCV3Ka99LYZUJZqth4M3lT/ppGl52mwIVLNYzR4iLe6mdZ6mYA==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] @@ -7102,8 +6673,8 @@ packages: dev: true optional: true - /sass-embedded-linux-ia32@1.85.0: - resolution: {integrity: sha512-4JH+h+gLt9So22nNPQtsKojEsLzjld9ol3zWcOtMGclv+HojZGbCuhJUrLUcK72F8adXYsULmWhJPKROLIwYMA==} + /sass-embedded-linux-ia32@1.86.3: + resolution: {integrity: sha512-CmQ5OkqnaeLdaF+bMqlYGooBuenqm3LvEN9H8BLhjkpWiFW8hnYMetiqMcJjhrXLvDw601KGqA5sr/Rsg5s45g==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [linux] @@ -7111,8 +6682,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-arm64@1.85.0: - resolution: {integrity: sha512-aoQjUjK28bvdw9XKTjQeayn8oWQ2QqvoTD11myklGd3IHH7Jj0nwXUstI4NxDueCKt3wghuZoIQkjOheReQxlg==} + /sass-embedded-linux-musl-arm64@1.86.3: + resolution: {integrity: sha512-4zOr2C/eW89rxb4ozTfn7lBzyyM5ZigA1ZSRTcAR26Qbg/t2UksLdGnVX9/yxga0d6aOi0IvO/7iM2DPPRRotg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] @@ -7120,8 +6691,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-arm@1.85.0: - resolution: {integrity: sha512-Z1j4ageDVFihqNUBnm89fxY46pY0zD/Clp1D3ZdI7S+D280+AEpbm5vMoH8LLhBQfQLf2w7H++SZGpQwrisudQ==} + /sass-embedded-linux-musl-arm@1.86.3: + resolution: {integrity: sha512-SEm65SQknI4pl+mH5Xf231hOkHJyrlgh5nj4qDbiBG6gFeutaNkNIeRgKEg3cflXchCr8iV/q/SyPgjhhzQb7w==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] @@ -7129,8 +6700,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-ia32@1.85.0: - resolution: {integrity: sha512-/cJCSXOfXmQFH8deE+3U9x+BSz8i0d1Tt9gKV/Gat1Xm43Oumw8pmZgno+cDuGjYQInr9ryW5121pTMlj/PBXQ==} + /sass-embedded-linux-musl-ia32@1.86.3: + resolution: {integrity: sha512-84Tcld32LB1loiqUvczWyVBQRCChm0wNLlkT59qF29nxh8njFIVf9yaPgXcSyyjpPoD9Tu0wnq3dvVzoMCh9AQ==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [linux] @@ -7138,8 +6709,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-riscv64@1.85.0: - resolution: {integrity: sha512-l+FJxMXkmg42RZq5RFKXg4InX0IA7yEiPHe4kVSdrczP7z3NLxk+W9wVkPnoRKYIMe1qZPPQ25y0TgI4HNWouA==} + /sass-embedded-linux-musl-riscv64@1.86.3: + resolution: {integrity: sha512-IxEqoiD7vdNpiOwccybbV93NljBy64wSTkUOknGy21SyV43C8uqESOwTwW9ywa3KufImKm8L3uQAW/B0KhJMWg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] @@ -7147,8 +6718,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-x64@1.85.0: - resolution: {integrity: sha512-M9ffjcYfFcRvkFA6V3DpOS955AyvmpvPAhL/xNK45d/ma1n1ehTWpd24tVeKiNK5CZkNjjMEfyw2fHa6MpqmEA==} + /sass-embedded-linux-musl-x64@1.86.3: + resolution: {integrity: sha512-ePeTPXUxPK6JgHcUfnrkIyDtyt+zlAvF22mVZv6y1g/PZFm1lSfX+Za7TYHg9KaYqaaXDiw6zICX4i44HhR8rA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] @@ -7156,8 +6727,8 @@ packages: dev: true optional: true - /sass-embedded-linux-riscv64@1.85.0: - resolution: {integrity: sha512-yqPXQWfM+qiIPkfn++48GOlbmSvUZIyL9nwFstBk0k4x40UhbhilfknqeTUpxoHfQzylTGVhrm5JE7MjM+LNZA==} + /sass-embedded-linux-riscv64@1.86.3: + resolution: {integrity: sha512-NuXQ72dwfNLe35E+RaXJ4Noq4EkFwM65eWwCwxEWyJO9qxOx1EXiCAJii6x8kkOh5daWuMU0VAI1B9RsJaqqQQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] @@ -7165,8 +6736,8 @@ packages: dev: true optional: true - /sass-embedded-linux-x64@1.85.0: - resolution: {integrity: sha512-NTDeQFZcuVR7COoaRy8pZD6/+QznwBR8kVFsj7NpmvX9aJ7TX/q+OQZHX7Bfb3tsfKXhf1YZozegPuYxRnMKAQ==} + /sass-embedded-linux-x64@1.86.3: + resolution: {integrity: sha512-t8be9zJ5B82+og9bQmIQ83yMGYZMTMrlGA+uGWtYacmwg6w3093dk91Fx0YzNSZBp3Tk60qVYjCZnEIwy60x0g==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] @@ -7174,8 +6745,8 @@ packages: dev: true optional: true - /sass-embedded-win32-arm64@1.85.0: - resolution: {integrity: sha512-gO0VAuxC4AdV+uZYJESRWVVHQWCGzNs0C3OKCAdH4r1vGRugooMi7J/5wbwUdXDA1MV9ICfhlKsph2n3GiPdqA==} + /sass-embedded-win32-arm64@1.86.3: + resolution: {integrity: sha512-4ghuAzjX4q8Nksm0aifRz8hgXMMxS0SuymrFfkfJlrSx68pIgvAge6AOw0edoZoe0Tf5ZbsWUWamhkNyNxkTvw==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [win32] @@ -7183,8 +6754,8 @@ packages: dev: true optional: true - /sass-embedded-win32-ia32@1.85.0: - resolution: {integrity: sha512-PCyn6xeFIBUgBceNypuf73/5DWF2VWPlPqPuBprPsTvpZOMUJeBtP+Lf4mnu3dNy1z76mYVnpaCnQmzZ0zHZaA==} + /sass-embedded-win32-ia32@1.86.3: + resolution: {integrity: sha512-tCaK4zIRq9mLRPxLzBAdYlfCuS/xLNpmjunYxeWkIwlJo+k53h1udyXH/FInnQ2GgEz0xMXyvH3buuPgzwWYsw==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [win32] @@ -7192,8 +6763,8 @@ packages: dev: true optional: true - /sass-embedded-win32-x64@1.85.0: - resolution: {integrity: sha512-AknE2jLp6OBwrR5hQ8pDsG94KhJCeSheFJ2xgbnk8RUjZX909JiNbgh2sNt9LG+RXf4xZa55dDL537gZoCx/iw==} + /sass-embedded-win32-x64@1.86.3: + resolution: {integrity: sha512-zS+YNKfTF4SnOfpC77VTb0qNZyTXrxnAezSoRV0xnw6HlY+1WawMSSB6PbWtmbvyfXNgpmJUttoTtsvJjRCucg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [win32] @@ -7201,49 +6772,49 @@ packages: dev: true optional: true - /sass-embedded@1.85.0: - resolution: {integrity: sha512-x3Vv54g0jv1aPSW8OTA/0GzQCs/HMQOjIkLtZJ3Xsn/I4vnyjKbVTQmFTax9bQjldqLEEkdbvy6ES/cOOnYNwA==} + /sass-embedded@1.86.3: + resolution: {integrity: sha512-3pZSp24ibO1hdopj+W9DuiWsZOb2YY6AFRo/jjutKLBkqJGM1nJjXzhAYfzRV+Xn5BX1eTI4bBTE09P0XNHOZg==} engines: {node: '>=16.0.0'} hasBin: true dependencies: - '@bufbuild/protobuf': 2.2.3 + '@bufbuild/protobuf': 2.2.5 buffer-builder: 0.2.0 colorjs.io: 0.5.2 - immutable: 5.0.3 - rxjs: 7.8.1 + immutable: 5.1.1 + rxjs: 7.8.2 supports-color: 8.1.1 sync-child-process: 1.0.2 varint: 6.0.0 optionalDependencies: - sass-embedded-android-arm: 1.85.0 - sass-embedded-android-arm64: 1.85.0 - sass-embedded-android-ia32: 1.85.0 - sass-embedded-android-riscv64: 1.85.0 - sass-embedded-android-x64: 1.85.0 - sass-embedded-darwin-arm64: 1.85.0 - sass-embedded-darwin-x64: 1.85.0 - sass-embedded-linux-arm: 1.85.0 - sass-embedded-linux-arm64: 1.85.0 - sass-embedded-linux-ia32: 1.85.0 - sass-embedded-linux-musl-arm: 1.85.0 - sass-embedded-linux-musl-arm64: 1.85.0 - sass-embedded-linux-musl-ia32: 1.85.0 - sass-embedded-linux-musl-riscv64: 1.85.0 - sass-embedded-linux-musl-x64: 1.85.0 - sass-embedded-linux-riscv64: 1.85.0 - sass-embedded-linux-x64: 1.85.0 - sass-embedded-win32-arm64: 1.85.0 - sass-embedded-win32-ia32: 1.85.0 - sass-embedded-win32-x64: 1.85.0 + sass-embedded-android-arm: 1.86.3 + sass-embedded-android-arm64: 1.86.3 + sass-embedded-android-ia32: 1.86.3 + sass-embedded-android-riscv64: 1.86.3 + sass-embedded-android-x64: 1.86.3 + sass-embedded-darwin-arm64: 1.86.3 + sass-embedded-darwin-x64: 1.86.3 + sass-embedded-linux-arm: 1.86.3 + sass-embedded-linux-arm64: 1.86.3 + sass-embedded-linux-ia32: 1.86.3 + sass-embedded-linux-musl-arm: 1.86.3 + sass-embedded-linux-musl-arm64: 1.86.3 + sass-embedded-linux-musl-ia32: 1.86.3 + sass-embedded-linux-musl-riscv64: 1.86.3 + sass-embedded-linux-musl-x64: 1.86.3 + sass-embedded-linux-riscv64: 1.86.3 + sass-embedded-linux-x64: 1.86.3 + sass-embedded-win32-arm64: 1.86.3 + sass-embedded-win32-ia32: 1.86.3 + sass-embedded-win32-x64: 1.86.3 dev: true - /sass@1.85.0: - resolution: {integrity: sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==} + /sass@1.86.3: + resolution: {integrity: sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==} engines: {node: '>=14.0.0'} hasBin: true dependencies: chokidar: 4.0.3 - immutable: 5.0.3 + immutable: 5.1.1 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -7253,12 +6824,12 @@ packages: resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} dev: true - /scheduler@0.25.0: - resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + /sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} dev: true - /search-insights@2.17.3: - resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + /scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} dev: true /selfsigned@2.4.1: @@ -7326,6 +6897,37 @@ packages: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + dev: true + + /set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + dev: true + + /set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + dev: true + /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -7346,19 +6948,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - /shiki@2.5.0: - resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} - dependencies: - '@shikijs/core': 2.5.0 - '@shikijs/engine-javascript': 2.5.0 - '@shikijs/engine-oniguruma': 2.5.0 - '@shikijs/langs': 2.5.0 - '@shikijs/themes': 2.5.0 - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - dev: true - /side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -7370,18 +6959,18 @@ packages: resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} engines: {node: '>= 0.4'} dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 object-inspect: 1.13.4 /side-channel-weakmap@1.0.2: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 @@ -7407,6 +6996,15 @@ packages: engines: {node: '>=14'} dev: true + /sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.1 + totalist: 3.0.1 + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -7489,15 +7087,6 @@ packages: engines: {node: '>= 8'} dev: true - /space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - dev: true - - /speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} - engines: {node: '>=0.10.0'} - dev: true - /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -7532,8 +7121,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - /std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + /std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} dev: true /streamx@2.22.0: @@ -7561,6 +7150,38 @@ packages: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + /string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + dev: true + + /string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + dev: true + + /string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + dev: true + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: @@ -7572,13 +7193,6 @@ packages: safe-buffer: 5.2.1 dev: true - /stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - dev: true - /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -7613,13 +7227,6 @@ packages: /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - dev: true - - /strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} - dependencies: - acorn: 8.14.0 - dev: true /style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} @@ -7635,17 +7242,10 @@ packages: glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.6 + pirates: 4.0.7 ts-interface-checker: 0.1.13 dev: true - /superjson@2.2.2: - resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} - engines: {node: '>=16'} - dependencies: - copy-anything: 3.0.5 - dev: true - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -7659,6 +7259,11 @@ packages: dependencies: has-flag: 4.0.0 + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + /sync-child-process@1.0.2: resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} engines: {node: '>=16.0.0'} @@ -7671,10 +7276,6 @@ packages: engines: {node: '>=16.0.0'} dev: true - /tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - dev: true - /tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} dependencies: @@ -7699,7 +7300,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.14.0 + acorn: 8.14.1 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -7752,13 +7353,18 @@ packages: picomatch: 4.0.2 dev: true - /tinypool@0.7.0: - resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + /tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + dev: true + + /tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} dev: true - /tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + /tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} dev: true @@ -7767,15 +7373,15 @@ packages: engines: {node: '>=12'} dev: false - /tldts-core@6.1.78: - resolution: {integrity: sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==} + /tldts-core@6.1.85: + resolution: {integrity: sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA==} dev: true - /tldts@6.1.78: - resolution: {integrity: sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ==} + /tldts@6.1.85: + resolution: {integrity: sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w==} hasBin: true dependencies: - tldts-core: 6.1.78 + tldts-core: 6.1.85 dev: true /tmp@0.0.33: @@ -7799,11 +7405,16 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - /tough-cookie@5.1.1: - resolution: {integrity: sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==} + /totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + dev: true + + /tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} dependencies: - tldts: 6.1.78 + tldts: 6.1.85 dev: true /tree-kill@1.2.2: @@ -7811,11 +7422,7 @@ packages: hasBin: true dev: true - /trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - dev: true - - /ts-essentials@9.4.2(typescript@5.7.3): + /ts-essentials@9.4.2(typescript@5.8.3): resolution: {integrity: sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==} peerDependencies: typescript: '>=4.1.0' @@ -7823,14 +7430,14 @@ packages: typescript: optional: true dependencies: - typescript: 5.7.3 + typescript: 5.8.3 dev: true /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /tsconfck@3.1.5(typescript@5.7.3): + /tsconfck@3.1.5(typescript@5.8.3): resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} engines: {node: ^18 || >=20} hasBin: true @@ -7840,7 +7447,7 @@ packages: typescript: optional: true dependencies: - typescript: 5.7.3 + typescript: 5.8.3 dev: true /tsconfig-paths@3.15.0: @@ -7882,11 +7489,6 @@ packages: prelude-ls: 1.2.1 dev: true - /type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} - dev: true - /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -7907,8 +7509,8 @@ packages: engines: {node: '>=12.20'} dev: false - /type-fest@4.35.0: - resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==} + /type-fest@4.39.1: + resolution: {integrity: sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==} engines: {node: '>=16'} dev: true @@ -7919,6 +7521,51 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 + /typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + dev: true + + /typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + dev: true + + /typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + dev: true + + /typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + dev: true + /typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} dependencies: @@ -7929,13 +7576,13 @@ packages: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: false - /typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + /typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - /ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + /ufo@1.6.0: + resolution: {integrity: sha512-AkgU2cV/+Xb4Uz6cic0kMZbtM42nbltnGvTVOt/8gMCbO2/z64nE47TOygh7HjgFPkUkVRBEyNFqpqi3zo+BJA==} dev: true /uglify-js@3.19.3: @@ -7946,8 +7593,18 @@ packages: dev: true optional: true - /undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + /unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + dev: true + + /undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} /unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} @@ -7961,39 +7618,6 @@ packages: crypto-random-string: 4.0.0 dev: false - /unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} - dependencies: - '@types/unist': 3.0.3 - dev: true - - /unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - dependencies: - '@types/unist': 3.0.3 - dev: true - - /unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - dependencies: - '@types/unist': 3.0.3 - dev: true - - /unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - dev: true - - /unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - dev: true - /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -8011,7 +7635,7 @@ packages: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} dependencies: - acorn: 8.14.0 + acorn: 8.14.1 webpack-virtual-modules: 0.6.2 dev: true @@ -8019,8 +7643,8 @@ packages: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} - /update-browserslist-db@1.1.2(browserslist@4.24.4): - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + /update-browserslist-db@1.1.3(browserslist@4.24.4): + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -8054,7 +7678,6 @@ packages: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 - dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -8068,8 +7691,8 @@ packages: hasBin: true dev: true - /validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + /validator@13.15.0: + resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==} engines: {node: '>= 0.10'} /varint@6.0.0: @@ -8089,21 +7712,7 @@ packages: extsprintf: 1.3.0 dev: true - /vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - dev: true - - /vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.2 - dev: true - - /vite-jsconfig-paths@2.0.1(vite@6.2.0): + /vite-jsconfig-paths@2.0.1(vite@6.2.5): resolution: {integrity: sha512-rabcTTfKs0MdAsQWcZjbIMo5fcp6jthZce7uFEPgVPgpSY+RNOwjzIJOPES6cB/GJZLSoLGfHM9kt5HNmJvp7A==} peerDependencies: vite: '>2.0.0-0' @@ -8112,24 +7721,24 @@ packages: globrex: 0.1.2 recrawl-sync: 2.2.3 tsconfig-paths: 3.15.0 - vite: 6.2.0(@types/node@22.13.5)(sass@1.85.0) + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) transitivePeerDependencies: - supports-color dev: true - /vite-node@0.34.6(@types/node@22.13.4)(sass@1.85.0): - resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} - engines: {node: '>=v14.18.0'} + /vite-node@3.1.1(@types/node@22.14.0)(sass@1.86.3): + resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@8.1.1) - mlly: 1.7.4 - pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.14(@types/node@22.13.4)(sass@1.85.0) + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) transitivePeerDependencies: - '@types/node' + - jiti - less - lightningcss - sass @@ -8138,9 +7747,11 @@ packages: - sugarss - supports-color - terser + - tsx + - yaml dev: true - /vite-tsconfig-paths@4.3.2(typescript@5.7.3)(vite@6.2.0): + /vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.2.5): resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: vite: '*' @@ -8150,95 +7761,15 @@ packages: dependencies: debug: 4.4.0(supports-color@8.1.1) globrex: 0.1.2 - tsconfck: 3.1.5(typescript@5.7.3) - vite: 6.2.0(@types/node@22.13.5)(sass@1.85.0) + tsconfck: 3.1.5(typescript@5.8.3) + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) transitivePeerDependencies: - supports-color - typescript dev: true - /vite@5.4.14(@types/node@22.13.4)(sass@1.85.0): - resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 22.13.4 - esbuild: 0.21.5 - postcss: 8.5.3 - rollup: 4.34.8 - sass: 1.85.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /vite@5.4.14(@types/node@22.13.5)(sass@1.85.0): - resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 22.13.5 - esbuild: 0.21.5 - postcss: 8.5.3 - rollup: 4.34.8 - sass: 1.85.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /vite@6.1.1(@types/node@22.13.5)(sass-embedded@1.85.0)(sass@1.85.0): - resolution: {integrity: sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==} + /vite@6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3): + resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -8277,140 +7808,35 @@ packages: yaml: optional: true dependencies: - '@types/node': 22.13.5 - esbuild: 0.24.2 + '@types/node': 22.14.0 + esbuild: 0.25.2 postcss: 8.5.3 - rollup: 4.34.8 - sass: 1.85.0 - sass-embedded: 1.85.0 + rollup: 4.39.0 + sass: 1.86.3 + sass-embedded: 1.86.3 optionalDependencies: fsevents: 2.3.3 dev: true - /vite@6.2.0(@types/node@22.13.5)(sass@1.85.0): - resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} + /vitest@3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3): + resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - dependencies: - '@types/node': 22.13.5 - esbuild: 0.25.0 - postcss: 8.5.3 - rollup: 4.34.8 - sass: 1.85.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /vitepress@1.6.3(@algolia/client-search@5.20.3)(@types/node@22.13.5)(axios@1.7.9)(postcss@8.5.3)(react-dom@19.0.0)(react@19.0.0)(sass@1.85.0)(search-insights@2.17.3)(typescript@5.7.3): - resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} - hasBin: true - peerDependencies: - markdown-it-mathjax3: ^4 - postcss: ^8 - peerDependenciesMeta: - markdown-it-mathjax3: - optional: true - postcss: - optional: true - dependencies: - '@docsearch/css': 3.8.2 - '@docsearch/js': 3.8.2(@algolia/client-search@5.20.3)(react-dom@19.0.0)(react@19.0.0)(search-insights@2.17.3) - '@iconify-json/simple-icons': 1.2.25 - '@shikijs/core': 2.5.0 - '@shikijs/transformers': 2.5.0 - '@shikijs/types': 2.5.0 - '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.1(vite@5.4.14)(vue@3.5.13) - '@vue/devtools-api': 7.7.2 - '@vue/shared': 3.5.13 - '@vueuse/core': 12.7.0(typescript@5.7.3) - '@vueuse/integrations': 12.7.0(axios@1.7.9)(focus-trap@7.6.4)(typescript@5.7.3) - focus-trap: 7.6.4 - mark.js: 8.11.1 - minisearch: 7.1.2 - postcss: 8.5.3 - shiki: 2.5.0 - vite: 5.4.14(@types/node@22.13.5)(sass@1.85.0) - vue: 3.5.13(typescript@5.7.3) - transitivePeerDependencies: - - '@algolia/client-search' - - '@types/node' - - '@types/react' - - async-validator - - axios - - change-case - - drauu - - fuse.js - - idb-keyval - - jwt-decode - - less - - lightningcss - - nprogress - - qrcode - - react - - react-dom - - sass - - sass-embedded - - search-insights - - sortablejs - - stylus - - sugarss - - terser - - typescript - - universal-cookie - dev: true - - /vitest@0.34.6(sass@1.85.0): - resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} - engines: {node: '>=v14.18.0'} - hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.1.1 + '@vitest/ui': 3.1.1 happy-dom: '*' jsdom: '*' - playwright: '*' - safaridriver: '*' - webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/debug': + optional: true + '@types/node': + optional: true '@vitest/browser': optional: true '@vitest/ui': @@ -8419,50 +7845,46 @@ packages: optional: true jsdom: optional: true - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true dependencies: - '@types/chai': 4.3.20 - '@types/chai-subset': 1.3.5 - '@types/node': 22.13.4 - '@vitest/expect': 0.34.6 - '@vitest/runner': 0.34.6 - '@vitest/snapshot': 0.34.6 - '@vitest/spy': 0.34.6 - '@vitest/utils': 0.34.6 - acorn: 8.14.0 - acorn-walk: 8.3.4 - cac: 6.7.14 - chai: 4.5.0 + '@types/node': 22.14.0 + '@vitest/expect': 3.1.1 + '@vitest/mocker': 3.1.1(vite@6.2.5) + '@vitest/pretty-format': 3.1.1 + '@vitest/runner': 3.1.1 + '@vitest/snapshot': 3.1.1 + '@vitest/spy': 3.1.1 + '@vitest/ui': 3.1.1(vitest@3.1.1) + '@vitest/utils': 3.1.1 + chai: 5.2.0 debug: 4.4.0(supports-color@8.1.1) - local-pkg: 0.4.3 + expect-type: 1.2.1 magic-string: 0.30.17 - pathe: 1.1.2 - picocolors: 1.1.1 - std-env: 3.8.0 - strip-literal: 1.3.0 + pathe: 2.0.3 + std-env: 3.9.0 tinybench: 2.9.0 - tinypool: 0.7.0 - vite: 5.4.14(@types/node@22.13.4)(sass@1.85.0) - vite-node: 0.34.6(@types/node@22.13.4)(sass@1.85.0) + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.2.5(@types/node@22.14.0)(sass-embedded@1.86.3)(sass@1.86.3) + vite-node: 3.1.1(@types/node@22.14.0)(sass@1.86.3) why-is-node-running: 2.3.0 transitivePeerDependencies: + - jiti - less - lightningcss + - msw - sass - sass-embedded - stylus - sugarss - supports-color - terser + - tsx + - yaml dev: true - /vue-component-type-helpers@2.2.2: - resolution: {integrity: sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==} + /vue-component-type-helpers@2.2.8: + resolution: {integrity: sha512-4bjIsC284coDO9om4HPA62M7wfsTvcmZyzdfR0aUlFXqq4tXxM1APyXpNVxPC8QazKw9OhmZNHBVDA6ODaZsrA==} dev: true /vue-demi@0.14.10(vue@3.5.13): @@ -8477,16 +7899,16 @@ packages: '@vue/composition-api': optional: true dependencies: - vue: 3.5.13(typescript@5.7.3) + vue: 3.5.13(typescript@5.8.3) - /vue-eslint-parser@9.4.3(eslint@9.20.1): + /vue-eslint-parser@9.4.3(eslint@9.24.0): resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' dependencies: debug: 4.4.0(supports-color@8.1.1) - eslint: 9.20.1 + eslint: 9.24.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 @@ -8497,16 +7919,16 @@ packages: - supports-color dev: true - /vue-i18n@9.14.2(vue@3.5.13): - resolution: {integrity: sha512-JK9Pm80OqssGJU2Y6F7DcM8RFHqVG4WkuCqOZTVsXkEzZME7ABejAUqUdA931zEBedc4thBgSUWxeQh4uocJAQ==} + /vue-i18n@9.14.4(vue@3.5.13): + resolution: {integrity: sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==} engines: {node: '>= 16'} peerDependencies: vue: ^3.0.0 dependencies: - '@intlify/core-base': 9.14.2 - '@intlify/shared': 9.14.2 + '@intlify/core-base': 9.14.4 + '@intlify/shared': 9.14.4 '@vue/devtools-api': 6.6.4 - vue: 3.5.13(typescript@5.7.3) + vue: 3.5.13(typescript@5.8.3) /vue-router@4.5.0(vue@3.5.13): resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} @@ -8514,9 +7936,9 @@ packages: vue: ^3.2.0 dependencies: '@vue/devtools-api': 6.6.4 - vue: 3.5.13(typescript@5.7.3) + vue: 3.5.13(typescript@5.8.3) - /vue@3.5.13(typescript@5.7.3): + /vue@3.5.13(typescript@5.8.3): resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} peerDependencies: typescript: '*' @@ -8529,7 +7951,7 @@ packages: '@vue/runtime-dom': 3.5.13 '@vue/server-renderer': 3.5.13(vue@3.5.13) '@vue/shared': 3.5.13 - typescript: 5.7.3 + typescript: 5.8.3 /w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -8571,10 +7993,63 @@ packages: engines: {node: '>=12'} dev: true + /which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + dev: true + + /which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + dev: true + + /which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + dev: true + /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: true + /which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + dev: true + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -8680,7 +8155,7 @@ packages: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} dependencies: - sax: 1.1.4 + sax: 1.4.1 xmlbuilder: 11.0.1 dev: true @@ -8689,11 +8164,17 @@ packages: engines: {node: '>=4.0'} dev: true - /xunit-viewer@10.6.1(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.3)(codemirror@6.0.1)(react-dom@19.0.0)(react@19.0.0): + /xmldoc@1.3.0: + resolution: {integrity: sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==} + dependencies: + sax: 1.4.1 + dev: true + + /xunit-viewer@10.6.1(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.5)(codemirror@6.0.1)(react-dom@19.1.0)(react@19.1.0): resolution: {integrity: sha512-ZMprLPVhCQJf2KD56tv2hlOjc4T+KnUe1E9DkEBHnuliOq7IOXWJf61pxyBMo/7H83B7Ln0DIeWNMMbx/3I7Jg==} hasBin: true dependencies: - '@uiw/react-codemirror': 4.23.8(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.3)(codemirror@6.0.1)(react-dom@19.0.0)(react@19.0.0) + '@uiw/react-codemirror': 4.23.10(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.5)(codemirror@6.0.1)(react-dom@19.1.0)(react@19.1.0) chalk: 5.4.1 chokidar: 3.6.0 console-clear: 1.1.1 @@ -8738,17 +8219,18 @@ packages: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} dev: false - /yaml-eslint-parser@0.3.2: - resolution: {integrity: sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==} + /yaml-eslint-parser@1.3.0: + resolution: {integrity: sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA==} + engines: {node: ^14.17.0 || >=16.0.0} dependencies: - eslint-visitor-keys: 1.3.0 - lodash: 4.17.21 - yaml: 1.10.2 + eslint-visitor-keys: 3.4.3 + yaml: 2.7.1 dev: true - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + /yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true dev: true /yargs-parser@18.1.3: @@ -8815,8 +8297,8 @@ packages: engines: {node: '>=10'} dev: true - /yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + /yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} dev: true @@ -8833,7 +8315,3 @@ packages: compress-commons: 6.0.2 readable-stream: 4.7.0 dev: true - - /zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - dev: true diff --git a/quasar.config.js b/quasar.config.js index 8b6125a90..2bc0be37f 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -53,7 +53,7 @@ export default configure(function (/* ctx */) { build: { target: { browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'], - node: 'node18', + node: 'node20', }, vueRouterMode: 'hash', // available values: 'hash', 'history' @@ -92,6 +92,7 @@ export default configure(function (/* ctx */) { vitePlugins: [ [ VueI18nPlugin({ + strictMessage: false, runtimeOnly: false, include: [ path.resolve(__dirname, './src/i18n/locale/**'), diff --git a/quasar.config.js.temporary.compiled.1744020058024.mjs b/quasar.config.js.temporary.compiled.1744020058024.mjs new file mode 100644 index 000000000..54ecb84d9 --- /dev/null +++ b/quasar.config.js.temporary.compiled.1744020058024.mjs @@ -0,0 +1,227 @@ +/* eslint-disable */ +/** + * THIS FILE IS GENERATED AUTOMATICALLY. + * 1. DO NOT edit this file directly as it won't do anything. + * 2. EDIT the original quasar.config file INSTEAD. + * 3. DO NOT git commit this file. It should be ignored. + * + * This file is still here because there was an error in + * the original quasar.config file and this allows you to + * investigate the Node.js stack error. + * + * After you fix the original file, this file will be + * deleted automatically. + **/ + + +// quasar.config.js +import { configure } from "quasar/wrappers"; +import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"; +import path from "path"; +var __quasar_inject_dirname__ = "/home/jsegarra/Projects/salix-front"; +var target = `http://${process.env.CI ? "back" : "localhost"}:3000`; +var quasar_config_default = configure(function() { + return { + eslint: { + // fix: true, + // include = [], + // exclude = [], + // rawOptions = {}, + warnings: true, + errors: true + }, + // https://v2.quasar.dev/quasar-cli/prefetch-feature + // preFetch: true, + // app boot file (/src/boot) + // --> boot files are part of "main.js" + // https://v2.quasar.dev/quasar-cli/boot-files + boot: ["i18n", "axios", "vnDate", "validations", "quasar", "quasar.defaults"], + // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css + css: ["app.scss"], + // https://github.com/quasarframework/quasar/tree/dev/extras + extras: [ + // 'ionicons-v4', + // 'mdi-v5', + // 'fontawesome-v6', + // 'eva-icons', + // 'themify', + // 'line-awesome', + // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! + "roboto-font", + "material-icons-outlined", + "material-symbols-outlined" + ], + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build + build: { + target: { + browser: ["es2022", "edge88", "firefox78", "chrome87", "safari13.1"], + node: "node20" + }, + vueRouterMode: "hash", + // available values: 'hash', 'history' + // vueRouterBase, + // vueDevtools, + // vueOptionsAPI: false, + // rebuildCache: true, // rebuilds Vite/linter/etc cache on startup + // publicPath: '/', + // analyze: true, + // env: {}, + rawDefine: { + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) + }, + // ignorePublicFolder: true, + // minify: false, + // polyfillModulePreload: true, + // distDir + extendViteConf(viteConf) { + delete viteConf.build.polyfillModulePreload; + viteConf.build.modulePreload = { + polyfill: false + }; + }, + // viteVuePluginOptions: {}, + alias: { + composables: path.join(__quasar_inject_dirname__, "./src/composables"), + filters: path.join(__quasar_inject_dirname__, "./src/filters") + }, + vitePlugins: [ + [ + VueI18nPlugin({ + strictMessage: false, + runtimeOnly: false, + include: [ + path.resolve(__quasar_inject_dirname__, "./src/i18n/locale/**"), + path.resolve(__quasar_inject_dirname__, "./src/pages/**/locale/**") + ] + }) + ] + ] + }, + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer + devServer: { + server: { + type: "http" + }, + proxy: { + "/api": { + target, + logLevel: "debug", + changeOrigin: true, + secure: false + } + }, + open: false, + allowedHosts: [ + "front", + // Agrega este nombre de host + "localhost" + // Opcional, para pruebas locales + ] + }, + // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework + framework: { + config: { + config: { + dark: "auto" + } + }, + lang: "en-GB", + // iconSet: 'material-icons', // Quasar icon set + // lang: 'en-US', // Quasar language pack + // For special cases outside of where the auto-import strategy can have an impact + // (like functional components as one of the examples), + // you can manually specify Quasar components/directives to be available everywhere: + // + // components: [], + // directives: [], + // Quasar plugins + plugins: ["Notify", "Dialog"], + all: "auto", + autoImportComponentCase: "pascal" + }, + // animations: 'all', // --- includes all animations + // https://v2.quasar.dev/options/animations + animations: [], + // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles + // sourceFiles: { + // rootComponent: 'src/App.vue', + // router: 'src/router/index', + // store: 'src/store/index', + // registerServiceWorker: 'src-pwa/register-service-worker', + // serviceWorker: 'src-pwa/custom-service-worker', + // pwaManifestFile: 'src-pwa/manifest.json', + // electronMain: 'src-electron/electron-main', + // electronPreload: 'src-electron/electron-preload' + // }, + // https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr + ssr: { + // ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name! + // will mess up SSR + // extendSSRWebserverConf (esbuildConf) {}, + // extendPackageJson (json) {}, + pwa: false, + // manualStoreHydration: true, + // manualPostHydrationTrigger: true, + prodPort: 3e3, + // The default port that the production server should use + // (gets superseded if process.env.PORT is specified at runtime) + middlewares: [ + "render" + // keep this as last one + ] + }, + // https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa + pwa: { + workboxMode: "generateSW", + // or 'injectManifest' + injectPwaMetaTags: true, + swFilename: "sw.js", + manifestFilename: "manifest.json", + useCredentialsForManifestTag: false + // useFilenameHashes: true, + // extendGenerateSWOptions (cfg) {} + // extendInjectManifestOptions (cfg) {}, + // extendManifestJson (json) {} + // extendPWACustomSWConf (esbuildConf) {} + }, + // Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova + cordova: { + // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing + }, + // Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor + capacitor: { + hideSplashscreen: true + }, + // Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron + electron: { + // extendElectronMainConf (esbuildConf) + // extendElectronPreloadConf (esbuildConf) + inspectPort: 5858, + bundler: "packager", + // 'packager' or 'builder' + packager: { + // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options + // OS X / Mac App Store + // appBundleId: '', + // appCategoryType: '', + // osxSign: '', + // protocol: 'myapp://path', + // Windows only + // win32metadata: { ... } + }, + builder: { + // https://www.electron.build/configuration/configuration + appId: "salix-frontend" + } + }, + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex + bex: { + contentScripts: ["my-content-script"] + // extendBexScriptsConf (esbuildConf) {} + // extendBexManifestJson (json) {} + } + }; +}); +export { + quasar_config_default as default +}; diff --git a/src/boot/__tests__/axios.spec.js b/src/boot/__tests__/axios.spec.js index 7dffaefc1..85d578517 100644 --- a/src/boot/__tests__/axios.spec.js +++ b/src/boot/__tests__/axios.spec.js @@ -9,6 +9,30 @@ vi.mock('src/composables/useSession', () => ({ }), })); +// Mock axios +vi.mock('axios', () => ({ + default: { + create: vi.fn(() => ({ + interceptors: { + request: { use: vi.fn() }, + response: { use: vi.fn() }, + }, + })), + interceptors: { + request: { use: vi.fn() }, + response: { use: vi.fn() }, + }, + defaults: { + baseURL: '', + }, + }, +})); + +vi.mock('src/router', () => ({ + Router: { + push: vi.fn(), + }, +})); vi.mock('src/stores/useStateQueryStore', () => ({ useStateQueryStore: () => ({ add: () => vi.fn(), @@ -29,7 +53,7 @@ describe('Axios boot', () => { 'Accept-Language': 'en-US', Authorization: 'DEFAULT_TOKEN', }, - }) + }), ); }); }); diff --git a/src/boot/quasar.defaults.js b/src/boot/quasar.defaults.js index 9638e2057..e2b195b16 100644 --- a/src/boot/quasar.defaults.js +++ b/src/boot/quasar.defaults.js @@ -1,3 +1,4 @@ +/* eslint-disable eslint/export */ export * from './defaults/qTable'; export * from './defaults/qInput'; export * from './defaults/qSelect'; diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 8d7188f77..1d49d1d75 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -181,9 +181,8 @@ async function saveChanges(data) { return; } let changes = data || getChanges(); - if ($props.beforeSaveFn) { - changes = await $props.beforeSaveFn(changes, getChanges); - } + if ($props.beforeSaveFn) changes = await $props.beforeSaveFn(changes, getChanges); + try { if (changes?.creates?.length === 0 && changes?.updates?.length === 0) { return; @@ -194,7 +193,7 @@ async function saveChanges(data) { isLoading.value = false; } originalData.value = JSON.parse(JSON.stringify(formData.value)); - if (changes.creates?.length) await vnPaginateRef.value.fetch(); + if (changes?.creates?.length) await vnPaginateRef.value.fetch(); hasChanges.value = false; emit('saveChanges', data); @@ -333,6 +332,7 @@ watch(formUrl, async () => { :disable="!selected?.length" :title="t('globals.remove')" v-if="$props.defaultRemove" + data-cy="crudModelDefaultRemoveBtn" /> <QBtn :label="tMobile('globals.reset')" diff --git a/src/components/FilterItemForm.vue b/src/components/FilterItemForm.vue index cacfde1b3..cca8d80c3 100644 --- a/src/components/FilterItemForm.vue +++ b/src/components/FilterItemForm.vue @@ -188,7 +188,7 @@ const selectItem = ({ id }) => { > <template #body-cell-id="{ row }"> <QTd auto-width @click.stop> - <QBtn flat color="blue">{{ row.id }}</QBtn> + <QBtn flat class="link">{{ row.id }}</QBtn> <ItemDescriptorProxy :id="row.id" /> </QTd> </template> diff --git a/src/components/FilterTravelForm.vue b/src/components/FilterTravelForm.vue index c522d0269..4aad327b2 100644 --- a/src/components/FilterTravelForm.vue +++ b/src/components/FilterTravelForm.vue @@ -196,7 +196,7 @@ const selectTravel = ({ id }) => { > <template #body-cell-id="{ row }"> <QTd auto-width @click.stop data-cy="travelFk-travel-form"> - <QBtn flat color="blue">{{ row.id }}</QBtn> + <QBtn flat class="link">{{ row.id }}</QBtn> <TravelDescriptorProxy :id="row.id" /> </QTd> </template> diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index c4d9a4149..1fec1e6c9 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -13,13 +13,12 @@ import VnConfirm from './ui/VnConfirm.vue'; import { tMobile } from 'src/composables/tMobile'; import { useArrayData } from 'src/composables/useArrayData'; import { getDifferences, getUpdatedValues } from 'src/filters'; - const { push } = useRouter(); const quasar = useQuasar(); const state = useState(); const stateStore = useStateStore(); const { t } = useI18n(); -const { validate } = useValidator(); +const { validate, validations } = useValidator(); const { notify } = useNotify(); const route = useRoute(); const myForm = ref(null); @@ -119,7 +118,7 @@ const defaultButtons = computed(() => ({ color: 'primary', icon: 'save', label: 'globals.save', - click: async () => await save(), + click: async (evt) => submitForm(evt), type: 'submit', }, reset: { @@ -132,6 +131,13 @@ const defaultButtons = computed(() => ({ ...$props.defaultButtons, })); +const submitForm = async (evt) => { + const isFormValid = await myForm.value.validate(); + if (isFormValid) { + await save(evt); + } +}; + onMounted(async () => { nextTick(() => (componentIsRendered.value = true)); @@ -227,10 +233,9 @@ async function save() { const method = $props.urlCreate ? 'post' : 'patch'; const url = $props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url; - let response; - - if ($props.saveFn) response = await $props.saveFn(body); - else response = await axios[method](url, body); + const response = await Promise.resolve( + $props.saveFn ? $props.saveFn(body) : axios[method](url, body), + ); if ($props.urlCreate) notify('globals.dataCreated', 'positive'); @@ -307,11 +312,13 @@ async function onKeyup(evt) { selectionStart = selectionEnd = selectionStart + 1; return; } - await save(); + await myForm.value.submit(evt); } } defineExpose({ + submitForm, + myForm, save, isLoading, hasChanges, @@ -325,7 +332,7 @@ defineExpose({ <QForm ref="myForm" v-if="formData" - @submit.prevent + @submit.prevent="save" @keyup.prevent="onKeyup" @reset="reset" class="q-pa-md" @@ -339,6 +346,7 @@ defineExpose({ name="form" :data="formData" :validate="validate" + :validations="validations()" :filter="filter" /> <SkeletonForm v-else /> diff --git a/src/components/FormModelPopup.vue b/src/components/FormModelPopup.vue index 85943e91e..34aec96d8 100644 --- a/src/components/FormModelPopup.vue +++ b/src/components/FormModelPopup.vue @@ -41,9 +41,12 @@ const onDataSaved = async (formData, requestResponse) => { emit('onDataSaved', formData, requestResponse); }; -const onClick = async (saveAndContinue) => { +const onClick = async (saveAndContinue = showSaveAndContinueBtn) => { + await formModelRef.value.myForm.validate(true); isSaveAndContinue.value = saveAndContinue; - await formModelRef.value.save(); + if (formModelRef.value) { + await formModelRef.value.submitForm(); + } }; defineExpose({ @@ -59,16 +62,23 @@ defineExpose({ ref="formModelRef" :observe-form-changes="false" :default-actions="false" + @submit="onClick" v-bind="$attrs" @on-data-saved="onDataSaved" + :prevent-submit="false" > - <template #form="{ data, validate }"> + <template #form="{ data, validate, validations }"> <span ref="closeButton" class="close-icon" v-close-popup> <QIcon name="close" size="sm" /> </span> <h1 class="title">{{ title }}</h1> <p>{{ subtitle }}</p> - <slot name="form-inputs" :data="data" :validate="validate" /> + <slot + name="form-inputs" + :data="data" + :validate="validate" + :validations="validations" + /> <div class="q-mt-lg row justify-end"> <QBtn :label="t('globals.cancel')" @@ -87,12 +97,13 @@ defineExpose({ :flat="showSaveAndContinueBtn" :label="t('globals.save')" :title="t('globals.save')" - @click="onClick(false)" + :type="!showSaveAndContinueBtn ? 'submit' : 'button'" color="primary" class="q-ml-sm" :disabled="isLoading" :loading="isLoading" data-cy="FormModelPopup_save" + @click="showSaveAndContinueBtn ? onClick(false) : null" z-max /> <QBtn @@ -100,12 +111,13 @@ defineExpose({ :label="t('globals.isSaveAndContinue')" :title="t('globals.isSaveAndContinue')" color="primary" + :type="showSaveAndContinueBtn ? 'submit' : 'button'" class="q-ml-sm" :disabled="isLoading" :loading="isLoading" data-cy="FormModelPopup_isSaveAndContinue" + @click="showSaveAndContinueBtn ? onClick(true) : null" z-max - @click="onClick(true)" /> </div> </template> diff --git a/src/components/ItemsFilterPanel.vue b/src/components/ItemsFilterPanel.vue index f73753a6b..0f1e3f1eb 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -198,8 +198,7 @@ const setCategoryList = (data) => { v-model="params.typeFk" :options="itemTypesOptions" dense - outlined - rounded + filled use-input :disable="!selectedCategoryFk" @update:model-value=" @@ -235,8 +234,7 @@ const setCategoryList = (data) => { v-model="value.selectedTag" :options="tagOptions" dense - outlined - rounded + filled :emit-value="false" use-input :is-clearable="false" @@ -252,8 +250,7 @@ const setCategoryList = (data) => { option-value="value" option-label="value" dense - outlined - rounded + filled emit-value use-input :disable="!value" @@ -265,7 +262,6 @@ const setCategoryList = (data) => { v-model="value.value" :label="t('components.itemsFilterPanel.value')" :disable="!value" - is-outlined :is-clearable="false" @keyup.enter="applyTags(params, searchFn)" /> diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index 6a7865e68..4d44501c0 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -77,6 +77,7 @@ watch( function findMatches(search, item) { const matches = []; function findRoute(search, item) { + if (!item?.children) return; for (const child of item.children) { if (search?.indexOf(child.name) > -1) { matches.push(child); @@ -92,7 +93,7 @@ function findMatches(search, item) { } function addChildren(module, route, parent) { - const menus = route?.meta?.menu ?? route?.menus?.[props.source]; //backwards compatible + const menus = route?.meta?.menu; if (!menus) return; const matches = findMatches(menus, route); @@ -107,11 +108,7 @@ function getRoutes() { main: getMainRoutes, card: getCardRoutes, }; - try { - handleRoutes[props.source](); - } catch (error) { - throw new Error(`Method is not defined`); - } + handleRoutes[props.source](); } function getMainRoutes() { const modules = Object.assign([], navigation.getModules().value); @@ -122,7 +119,6 @@ function getMainRoutes() { ); if (!moduleDef) continue; item.children = []; - addChildren(item.module, moduleDef, item.children); } @@ -132,21 +128,16 @@ function getMainRoutes() { function getCardRoutes() { const currentRoute = route.matched[1]; const currentModule = toLowerCamel(currentRoute.name); - let moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule); + let moduleDef; - if (!moduleDef) return; - if (!moduleDef?.menus) moduleDef = betaGetRoutes(); - addChildren(currentModule, moduleDef, items.value); -} - -function betaGetRoutes() { - let menuRoute; let index = route.matched.length - 1; - while (!menuRoute && index > 0) { - if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; + while (!moduleDef && index > 0) { + if (route.matched[index]?.meta?.menu) moduleDef = route.matched[index]; index--; } - return menuRoute; + + if (!moduleDef) return; + addChildren(currentModule, moduleDef, items.value); } async function togglePinned(item, event) { 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 255bea9cd..c15e31d80 100644 --- a/src/components/TicketProblems.vue +++ b/src/components/TicketProblems.vue @@ -18,14 +18,14 @@ defineProps({ row: { type: Object, required: true } }); </QIcon> </router-link> <QIcon - v-if="row?.reserved" + v-if="row?.isDeleted" color="primary" - name="vn:reserva" + name="vn:deletedTicket" size="xs" - data-cy="ticketSaleReservedIcon" + data-cy="ticketDeletedIcon" > <QTooltip> - {{ t('ticketSale.reserved') }} + {{ t('Ticket deleted') }} </QTooltip> </QIcon> <QIcon diff --git a/src/components/TransferInvoiceForm.vue b/src/components/TransferInvoiceForm.vue index c4ef1454a..1434b79bc 100644 --- a/src/components/TransferInvoiceForm.vue +++ b/src/components/TransferInvoiceForm.vue @@ -87,7 +87,7 @@ const makeInvoice = async () => { (data) => ( (rectificativeTypeOptions = data), (transferInvoiceParams.cplusRectificationTypeFk = data.filter( - (type) => type.description == 'I – Por diferencias' + (type) => type.description == 'I – Por diferencias', )[0].id) ) " @@ -100,7 +100,7 @@ const makeInvoice = async () => { (data) => ( (siiTypeInvoiceOutsOptions = data), (transferInvoiceParams.siiTypeInvoiceOutFk = data.filter( - (type) => type.code == 'R4' + (type) => type.code == 'R4', )[0].id) ) " @@ -122,7 +122,6 @@ const makeInvoice = async () => { <VnRow> <VnSelect :label="t('Client')" - :options="clientsOptions" hide-selected option-label="name" option-value="id" 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/VnFilter.vue b/src/components/VnTable/VnFilter.vue index e9660e4c2..82d7c772c 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -6,6 +6,7 @@ import VnSelect from 'components/common/VnSelect.vue'; import VnInput from 'components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputTime from 'components/common/VnInputTime.vue'; +import VnCheckbox from 'components/common/VnCheckbox.vue'; import VnColumn from 'components/VnTable/VnColumn.vue'; const $props = defineProps({ @@ -106,7 +107,7 @@ const components = { }, }, checkbox: { - component: markRaw(QCheckbox), + component: markRaw(VnCheckbox), event: updateEvent, attrs: { class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit', diff --git a/src/components/VnTable/VnOrder.vue b/src/components/VnTable/VnOrder.vue index 47ed9acf4..fe071a57f 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 && (model?.index || vertical)"> <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/VnTable.vue b/src/components/VnTable/VnTable.vue index 4622c9fa9..36554320b 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -19,6 +19,7 @@ import { useQuasar, date } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; import { useFilterParams } from 'src/composables/useFilterParams'; import { dashIfEmpty, toDate } from 'src/filters'; +import { useTableHeight } from './filters/useTableHeight'; import CrudModel from 'src/components/CrudModel.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; @@ -117,7 +118,7 @@ const $props = defineProps({ }, tableHeight: { type: String, - default: '90vh', + default: undefined, }, footer: { type: Boolean, @@ -140,7 +141,7 @@ const $props = defineProps({ }, dataCy: { type: String, - default: 'vn-table', + default: 'vnTable', }, }); @@ -166,6 +167,7 @@ const tableRef = ref(); const params = ref(useFilterParams($attrs['data-key']).params); const orders = ref(useFilterParams($attrs['data-key']).orders); const app = inject('app'); +const tableHeight = useTableHeight(); const editingRow = ref(null); const editingField = ref(null); @@ -227,6 +229,7 @@ watch( defineExpose({ create: createForm, + showForm, reload, redirect: redirectFn, selected, @@ -595,18 +598,17 @@ function cardClick(_, row) { function removeTextValue(data, getChanges) { let changes = data.updates; - if (!changes) return data; - - for (const change of changes) { - for (const key in change.data) { - if (key.endsWith('VnTableTextValue')) { - delete change.data[key]; + if (changes) { + for (const change of changes) { + for (const key in change.data) { + if (key.endsWith('VnTableTextValue')) { + delete change.data[key]; + } } } + + data.updates = changes.filter((change) => Object.keys(change.data).length > 0); } - - data.updates = changes.filter((change) => Object.keys(change.data).length > 0); - if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges); return data; @@ -634,6 +636,7 @@ const rowCtrlClickFunction = computed(() => { :data-key="$attrs['data-key']" :columns="columns" :redirect="redirect" + v-bind="$attrs?.['table-filter']" > <template v-for="(_, slotName) in $slots" @@ -678,14 +681,14 @@ const rowCtrlClickFunction = computed(() => { table-header-class="bg-header" card-container-class="grid-three" flat - :style="isTableMode && `max-height: ${tableHeight}`" + :style="isTableMode && `max-height: ${$props.tableHeight || tableHeight}`" :virtual-scroll="isTableMode" @virtual-scroll="handleScroll" @row-click="(event, row) => handleRowClick(event, row)" @update:selected="emit('update:selected', $event)" @selection="(details) => handleSelection(details, rows)" :hide-selected-banner="true" - :data-cy="$props.dataCy ?? 'vnTable'" + :data-cy > <template #top-left v-if="!$props.withoutHeader"> <slot name="top-left"> </slot> @@ -776,12 +779,13 @@ const rowCtrlClickFunction = computed(() => { :data-col-field="col?.name" > <div - class="no-padding no-margin peter" + class="no-padding no-margin" style=" overflow: hidden; text-overflow: ellipsis; white-space: nowrap; " + :data-cy="`vnTableCell_${col.name}`" > <slot :name="`column-${col.name}`" @@ -896,7 +900,7 @@ const rowCtrlClickFunction = computed(() => { {{ row[splittedColumns.title.name] }} </span> </QCardSection> - <!-- Fields --> + <!-- Fields --> <QCardSection class="q-pl-sm q-py-xs" :class="$props.cardClass" @@ -920,12 +924,24 @@ const rowCtrlClickFunction = computed(() => { :row-index="index" > <VnColumn - :column="col" + :column="{ + ...col, + disable: + col?.component === + 'checkbox' + ? true + : false, + }" :row="row" :is-editable="false" v-model="row[col.name]" component-prop="columnField" - :show-label="true" + :show-label=" + col?.component === + 'checkbox' + ? false + : true + " /> </slot> </span> @@ -966,6 +982,8 @@ const rowCtrlClickFunction = computed(() => { v-for="col of cols.filter((cols) => cols.visible ?? true)" :key="col?.id" :class="getColAlign(col)" + :style="col?.width ? `max-width: ${col?.width}` : ''" + style="font-size: small" > <slot :name="`column-footer-${col.name}`" @@ -1027,39 +1045,45 @@ const rowCtrlClickFunction = computed(() => { :model="$attrs['data-key'] + 'Create'" @on-data-saved="(_, res) => createForm.onDataSaved(res)" > - <template #form-inputs="{ data }"> - <div :style="createComplement?.containerStyle"> - <div - :style="createComplement?.previousStyle" - v-if="!quasar.screen.xs" - > - <slot name="previous-create-dialog" :data="data" /> - </div> - <div class="grid-create" :style="createComplement?.columnGridStyle"> - <slot - v-for="column of splittedColumns.create" - :key="column.name" - :name="`column-create-${column.name}`" - :data="data" - :column-name="column.name" - :label="column.label" + <template #form-inputs="{ data, validations }"> + <slot name="alter-create" :data="data"> + <div :style="createComplement?.containerStyle"> + <div + :style="createComplement?.previousStyle" + v-if="!quasar.screen.xs" > - <VnColumn - :column="{ - ...column, - ...{ disable: column?.createDisable ?? false }, - }" - :row="{}" - default="input" - v-model="data[column.name]" - :show-label="true" - component-prop="columnCreate" - :data-cy="`${column.name}-create-popup`" - /> - </slot> - <slot name="more-create-dialog" :data="data" /> + <slot name="previous-create-dialog" :data="data" /> + </div> + <div + class="grid-create" + :style="createComplement?.columnGridStyle" + > + <slot + v-for="column of splittedColumns.create" + :key="column.name" + :name="`column-create-${column.name}`" + :data="data" + :validations="validations" + :column-name="column.name" + :label="column.label" + > + <VnColumn + :column="{ + ...column, + ...column?.createAttrs, + }" + :row="{}" + default="input" + v-model="data[column.name]" + :show-label="true" + component-prop="columnCreate" + :data-cy="`${column.name}-create-popup`" + /> + </slot> + <slot name="more-create-dialog" :data="data" /> + </div> </div> - </div> + </slot> </template> </FormModelPopup> </QDialog> @@ -1137,8 +1161,12 @@ es: .grid-create { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); + max-width: 100%; grid-gap: 20px; margin: 0 auto; + .col-span-2 { + grid-column: span 2; + } } .flex-one { diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue index c62b8b0fc..a7b2108ed 100644 --- a/src/components/VnTable/VnTableFilter.vue +++ b/src/components/VnTable/VnTableFilter.vue @@ -31,6 +31,7 @@ function columnName(col) { :search-button="true" :disable-submit-event="true" :data-key="$attrs['data-key']" + :search-url > <template #body="{ params, orders, searchFn }"> <div @@ -39,13 +40,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 @@ -82,13 +90,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/VnTable/__tests__/VnVisibleColumns.spec.js b/src/components/VnTable/__tests__/VnVisibleColumns.spec.js index bf767688b..3e4e9ecc8 100644 --- a/src/components/VnTable/__tests__/VnVisibleColumns.spec.js +++ b/src/components/VnTable/__tests__/VnVisibleColumns.spec.js @@ -1,8 +1,7 @@ import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import VnVisibleColumn from '../VnVisibleColumn.vue'; -import { axios } from 'app/test/vitest/helper'; - +import { default as axios } from 'axios'; describe('VnVisibleColumns', () => { let wrapper; let vm; diff --git a/src/components/VnTable/filters/useTableHeight.js b/src/components/VnTable/filters/useTableHeight.js new file mode 100644 index 000000000..2397ce16f --- /dev/null +++ b/src/components/VnTable/filters/useTableHeight.js @@ -0,0 +1,18 @@ +import { onMounted, nextTick, ref } from 'vue'; + +export function useTableHeight() { + const tableHeight = ref('90vh'); + + onMounted(async () => { + await nextTick(); + let height = 100; + Array.from(document.querySelectorAll('[role="toolbar"]')) + .filter((element) => window.getComputedStyle(element).display !== 'none') + .forEach(() => { + height -= 10; + }); + tableHeight.value = `${height}vh`; + }); + + return tableHeight; +} diff --git a/src/components/__tests__/CrudModel.spec.js b/src/components/__tests__/CrudModel.spec.js index b0eafbc02..6278f8ee4 100644 --- a/src/components/__tests__/CrudModel.spec.js +++ b/src/components/__tests__/CrudModel.spec.js @@ -1,4 +1,6 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; + import CrudModel from 'components/CrudModel.vue'; import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; diff --git a/src/components/__tests__/EditTableCellValueForm.spec.js b/src/components/__tests__/EditTableCellValueForm.spec.js deleted file mode 100644 index fa47d8f73..000000000 --- a/src/components/__tests__/EditTableCellValueForm.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; -import EditForm from 'components/EditTableCellValueForm.vue'; -import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; - -const fieldA = 'fieldA'; -const fieldB = 'fieldB'; - -describe('EditForm', () => { - let vm; - const mockRows = [ - { id: 1, itemFk: 101 }, - { id: 2, itemFk: 102 }, - ]; - const mockFieldsOptions = [ - { label: 'Field A', field: fieldA, component: 'input', attrs: {} }, - { label: 'Field B', field: fieldB, component: 'date', attrs: {} }, - ]; - const editUrl = '/api/edit'; - - beforeAll(() => { - vi.spyOn(axios, 'post').mockResolvedValue({ status: 200 }); - vm = createWrapper(EditForm, { - props: { - rows: mockRows, - fieldsOptions: mockFieldsOptions, - editUrl, - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('onSubmit()', () => { - it('should call axios.post with the correct parameters in the payload', async () => { - const selectedField = { field: fieldA, component: 'input', attrs: {} }; - const newValue = 'Test Value'; - - vm.selectedField = selectedField; - vm.newValue = newValue; - - await vm.onSubmit(); - - const payload = axios.post.mock.calls[0][1]; - - expect(axios.post).toHaveBeenCalledWith(editUrl, expect.any(Object)); - expect(payload.field).toEqual(fieldA); - expect(payload.newValue).toEqual(newValue); - - expect(payload.lines).toEqual(expect.arrayContaining(mockRows)); - - expect(vm.isLoading).toEqual(false); - }); - }); -}); diff --git a/src/components/__tests__/FilterItemForm.spec.js b/src/components/__tests__/FilterItemForm.spec.js index 210d6bf02..fb8332c31 100644 --- a/src/components/__tests__/FilterItemForm.spec.js +++ b/src/components/__tests__/FilterItemForm.spec.js @@ -1,4 +1,6 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; + import FilterItemForm from 'src/components/FilterItemForm.vue'; import { vi, beforeAll, describe, expect, it } from 'vitest'; @@ -38,9 +40,9 @@ describe('FilterItemForm', () => { { relation: 'producer', scope: { fields: ['name'] } }, { relation: 'ink', scope: { fields: ['name'] } }, ], - where: {"name":{"like":"%bolas de madera%"}}, + where: { name: { like: '%bolas de madera%' } }, }; - + expect(axios.get).toHaveBeenCalledWith('Items/withName', { params: { filter: JSON.stringify(expectedFilter) }, }); @@ -79,4 +81,4 @@ describe('FilterItemForm', () => { vm.selectItem({ id: 12345 }); expect(wrapper.emitted('itemSelected')[0]).toEqual([12345]); }); -}); \ No newline at end of file +}); diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 3dce04374..20e99b55b 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -1,5 +1,7 @@ import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; + import FormModel from 'src/components/FormModel.vue'; describe('FormModel', () => { diff --git a/src/components/__tests__/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js index 7c8470589..5bddc104c 100644 --- a/src/components/__tests__/Leftmenu.spec.js +++ b/src/components/__tests__/Leftmenu.spec.js @@ -1,6 +1,7 @@ -import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import Leftmenu from 'components/LeftMenu.vue'; +import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; +import { default as axios } from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; +import LeftMenu from 'components/LeftMenu.vue'; import * as vueRouter from 'vue-router'; import { useNavigationStore } from 'src/stores/useNavigationStore'; @@ -15,10 +16,7 @@ vi.mock('src/router/modules', () => ({ meta: { title: 'customers', icon: 'vn:client', - }, - menus: { - main: ['CustomerList', 'CustomerCreate'], - card: ['CustomerBasicData'], + menu: ['CustomerList', 'CustomerCreate'], }, children: [ { @@ -50,14 +48,6 @@ vi.mock('src/router/modules', () => ({ ], }, }, - { - path: 'create', - name: 'CustomerCreate', - meta: { - title: 'createCustomer', - icon: 'vn:addperson', - }, - }, ], }, ], @@ -98,7 +88,7 @@ vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ icon: 'vn:client', moduleName: 'Customer', keyBinding: 'c', - menu: 'customer', + menu: ['customer'], }, }, ], @@ -112,7 +102,7 @@ function mount(source = 'main') { vi.spyOn(axios, 'get').mockResolvedValue({ data: [], }); - const wrapper = createWrapper(Leftmenu, { + const wrapper = createWrapper(LeftMenu, { propsData: { source, }, @@ -175,7 +165,7 @@ describe('getRoutes', () => { }); }); -describe('Leftmenu as card', () => { +describe('LeftMenu as card', () => { beforeAll(() => { vm = mount('card').vm; }); @@ -184,7 +174,7 @@ describe('Leftmenu as card', () => { vm.getRoutes(); }); }); -describe('Leftmenu as main', () => { +describe('LeftMenu as main', () => { beforeEach(() => { vm = mount().vm; }); @@ -260,15 +250,6 @@ describe('Leftmenu as main', () => { }); }); - it('should handle a single matched route with a menu', () => { - const route = { - matched: [{ meta: { menu: 'customer' } }], - }; - - const result = vm.betaGetRoutes(); - - expect(result.meta.menu).toEqual(route.matched[0].meta.menu); - }); it('should get routes for main source', () => { vm.getRoutes(); expect(navigation.getModules).toHaveBeenCalled(); @@ -350,8 +331,9 @@ describe('addChildren', () => { it('should handle routes with no meta menu', () => { const route = { - meta: {}, - menus: {}, + meta: { + menu: [], + }, }; const parent = []; diff --git a/src/components/common/SendEmailDialog.vue b/src/components/common/SendEmailDialog.vue index 254eb9cf9..a8209bdf7 100644 --- a/src/components/common/SendEmailDialog.vue +++ b/src/components/common/SendEmailDialog.vue @@ -60,7 +60,7 @@ async function confirm() { v-model="address" is-outlined autofocus - data-cy="SendEmailNotifiactionDialogInput" + data-cy="SendEmailNotificationDialogInput" /> </QCardSection> <QCardActions align="right"> 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 44002c22a..0b9cc2cce 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -1,89 +1,93 @@ <script setup> -import { onBeforeMount, computed } from 'vue'; -import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; +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'; import useCardSize from 'src/composables/useCardSize'; import VnSubToolbar from '../ui/VnSubToolbar.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; -import LeftMenu from 'components/LeftMenu.vue'; -import RightMenu from 'components/common/RightMenu.vue'; + +const emit = defineEmits(['onFetch']); + const props = defineProps({ + id: { type: Number, required: false, default: null }, dataKey: { type: String, required: true }, url: { type: String, default: undefined }, + idInWhere: { type: Boolean, default: false }, filter: { type: Object, default: () => {} }, descriptor: { type: Object, required: true }, filterPanel: { type: Object, default: undefined }, - idInWhere: { type: Boolean, default: false }, searchDataKey: { type: String, default: undefined }, searchbarProps: { type: Object, default: undefined }, redirectOnError: { type: Boolean, default: false }, + visual: { type: Boolean, default: true }, }); -const stateStore = useStateStore(); const route = useRoute(); +const stateStore = useStateStore(); const router = useRouter(); -const searchRightDataKey = computed(() => { - if (!props.searchDataKey) return route.name; - return props.searchDataKey; -}); +const entityId = computed(() => props.id || route?.params?.id); +let arrayData = getArrayData(entityId.value, props.url); -const arrayData = useArrayData(props.dataKey, { - url: props.url, - userFilter: props.filter, - oneRecord: true, +onBeforeRouteLeave(() => { + stateStore.cardDescriptorChangeValue(null); }); onBeforeMount(async () => { + stateStore.cardDescriptorChangeValue(markRaw(props.descriptor)); + + const route = router.currentRoute.value; try { - await fetch(route.params.id); + await fetch(entityId.value); } catch { - const { matched: matches } = router.currentRoute.value; + const { matched: matches } = route; const { path } = matches.at(-1); router.push({ path: path.replace(/:id.*/, '') }); } }); onBeforeRouteUpdate(async (to, from) => { - const id = to.params.id; - if (id !== from.params.id) await fetch(id, true); + if (hasRouteParam(to.params)) { + const { matched } = router.currentRoute.value; + const { name } = matched.at(-3); + if (name) { + router.push({ name, params: to.params }); + } + } + if (entityId.value !== to.params.id) await fetch(to.params.id, true); }); async function fetch(id, append = false) { - const regex = /\/(\d+)/; if (props.idInWhere) arrayData.store.filter.where = { id }; - else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; - else arrayData.store.url = props.url.replace(regex, `/${id}`); + else { + arrayData = getArrayData(id); + } await arrayData.fetch({ append, updateRouter: false }); + emit('onFetch', arrayData.store.data); +} +function hasRouteParam(params, valueToCheck = ':addressId') { + return Object.values(params).includes(valueToCheck); +} + +function formatUrl(id) { + const newId = id || entityId.value; + const regex = /\/(\d+)/; + if (!regex.test(props.url)) return `${props.url}/${newId}`; + return props.url.replace(regex, `/${newId}`); +} + +function getArrayData(id, url) { + return useArrayData(props.dataKey, { + url: url ?? formatUrl(id), + userFilter: props.filter, + oneRecord: true, + }); } </script> <template> - <QDrawer - v-model="stateStore.leftDrawer" - show-if-above - :width="256" - v-if="stateStore.isHeaderMounted()" - > - <QScrollArea class="fit"> - <component :is="descriptor" /> - <QSeparator /> - <LeftMenu source="card" /> - </QScrollArea> - </QDrawer> - <slot name="searchbar" v-if="props.searchDataKey"> - <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> - </slot> - <RightMenu> - <template #right-panel v-if="props.filterPanel"> - <component :is="props.filterPanel" :data-key="searchRightDataKey" /> - </template> - </RightMenu> - <QPageContainer> - <QPage> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="$route.path" /> - </div> - </QPage> - </QPageContainer> + <template v-if="visual"> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="$route.path" /> + </div> + </template> </template> diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue deleted file mode 100644 index 620dc2ad2..000000000 --- a/src/components/common/VnCardBeta.vue +++ /dev/null @@ -1,74 +0,0 @@ -<script setup> -import { onBeforeMount } from 'vue'; -import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'; -import { useArrayData } from 'src/composables/useArrayData'; -import { useStateStore } from 'stores/useStateStore'; -import useCardSize from 'src/composables/useCardSize'; -import VnSubToolbar from '../ui/VnSubToolbar.vue'; - -const props = defineProps({ - dataKey: { type: String, required: true }, - url: { type: String, default: undefined }, - idInWhere: { type: Boolean, default: false }, - filter: { type: Object, default: () => {} }, - descriptor: { type: Object, required: true }, - filterPanel: { type: Object, default: undefined }, - searchDataKey: { type: String, default: undefined }, - searchbarProps: { type: Object, default: undefined }, - redirectOnError: { type: Boolean, default: false }, -}); - -const stateStore = useStateStore(); -const router = useRouter(); -const arrayData = useArrayData(props.dataKey, { - url: props.url, - userFilter: props.filter, - oneRecord: true, -}); - -onBeforeRouteLeave(() => { - stateStore.cardDescriptorChangeValue(null); -}); - -onBeforeMount(async () => { - stateStore.cardDescriptorChangeValue(props.descriptor); - - const route = router.currentRoute.value; - try { - await fetch(route.params.id); - } catch { - const { matched: matches } = route; - const { path } = matches.at(-1); - router.push({ path: path.replace(/:id.*/, '') }); - } -}); - -onBeforeRouteUpdate(async (to, from) => { - if (hasRouteParam(to.params)) { - const { matched } = router.currentRoute.value; - const { name } = matched.at(-3); - if (name) { - router.push({ name, params: to.params }); - } - } - const id = to.params.id; - if (id !== from.params.id) await fetch(id, true); -}); - -async function fetch(id, append = false) { - const regex = /\/(\d+)/; - if (props.idInWhere) arrayData.store.filter.where = { id }; - else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; - else arrayData.store.url = props.url.replace(regex, `/${id}`); - await arrayData.fetch({ append, updateRouter: false }); -} -function hasRouteParam(params, valueToCheck = ':addressId') { - return Object.values(params).includes(valueToCheck); -} -</script> -<template> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="$route.path" /> - </div> -</template> diff --git a/src/components/common/VnCheckbox.vue b/src/components/common/VnCheckbox.vue index 94e91328b..daaf891dc 100644 --- a/src/components/common/VnCheckbox.vue +++ b/src/components/common/VnCheckbox.vue @@ -27,7 +27,11 @@ const checkboxModel = computed({ </script> <template> <div> - <QCheckbox v-bind="$attrs" v-model="checkboxModel" /> + <QCheckbox + v-bind="$attrs" + v-model="checkboxModel" + :data-cy="$attrs['data-cy'] ?? `vnCheckbox${$attrs['label'] ?? ''}`" + /> <QIcon v-if="info" v-bind="$attrs" diff --git a/src/components/common/VnColor.vue b/src/components/common/VnColor.vue index 8a5a787b0..dccbc7102 100644 --- a/src/components/common/VnColor.vue +++ b/src/components/common/VnColor.vue @@ -1,4 +1,6 @@ <script setup> +import { computed } from 'vue'; + const $props = defineProps({ colors: { type: String, @@ -6,9 +8,9 @@ const $props = defineProps({ }, }); -const colorArray = JSON.parse($props.colors)?.value; +const colorArray = computed(() => JSON.parse($props.colors)?.value); const maxHeight = 30; -const colorHeight = maxHeight / colorArray?.length; +const colorHeight = maxHeight / colorArray.value?.length; </script> <template> <div v-if="colors" class="color-div" :style="{ height: `${maxHeight}px` }"> diff --git a/src/components/common/VnDms.vue b/src/components/common/VnDms.vue index 9884a447c..c713ac5ec 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, }); } @@ -177,6 +182,7 @@ function addDefaultData(data) { name="vn:attach" class="cursor-pointer" @click="inputFileRef.pickFiles()" + data-cy="attachFile" > <QTooltip>{{ t('globals.selectFile') }}</QTooltip> </QIcon> diff --git a/src/components/common/VnDmsInput.vue b/src/components/common/VnDmsInput.vue new file mode 100644 index 000000000..5a3ef351b --- /dev/null +++ b/src/components/common/VnDmsInput.vue @@ -0,0 +1,166 @@ +<script setup> +import VnConfirm from '../ui/VnConfirm.vue'; +import VnInput from './VnInput.vue'; +import VnDms from './VnDms.vue'; +import axios from 'axios'; +import { useQuasar } from 'quasar'; +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { downloadFile } from 'src/composables/downloadFile'; + +const { t } = useI18n(); +const quasar = useQuasar(); +const documentDialogRef = ref({}); +const editDownloadDisabled = ref(false); +const $props = defineProps({ + defaultDmsCode: { + type: String, + default: 'invoiceIn', + }, + disable: { + type: Boolean, + default: true, + }, + data: { + type: Object, + default: null, + }, + formRef: { + type: Object, + default: null, + }, +}); + +function deleteFile(dmsFk) { + quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('globals.confirmDeletion'), + message: t('globals.confirmDeletionMessage'), + }, + }) + .onOk(async () => { + await axios.post(`dms/${dmsFk}/removeFile`); + $props.formRef.formData.dmsFk = null; + $props.formRef.formData.dms = undefined; + $props.formRef.hasChanges = true; + $props.formRef.save(); + }); +} +</script> +<template> + <div class="row no-wrap"> + <VnInput + :label="t('Document')" + v-model="data.dmsFk" + clearable + clear-icon="close" + class="full-width" + :disable="disable" + /> + <div + v-if="data.dmsFk" + class="row no-wrap q-pa-xs q-gutter-x-xs" + data-cy="dms-buttons" + > + <QBtn + :disable="editDownloadDisabled" + @click="downloadFile(data.dmsFk)" + icon="cloud_download" + color="primary" + flat + :class="{ + 'no-pointer-events': editDownloadDisabled, + }" + padding="xs" + round + > + <QTooltip>{{ t('Download file') }}</QTooltip> + </QBtn> + <QBtn + :disable="editDownloadDisabled" + @click=" + () => { + documentDialogRef.show = true; + documentDialogRef.dms = data.dms; + } + " + icon="edit" + color="primary" + flat + :class="{ + 'no-pointer-events': editDownloadDisabled, + }" + padding="xs" + round + > + <QTooltip>{{ t('Edit document') }}</QTooltip> + </QBtn> + <QBtn + :disable="editDownloadDisabled" + @click="deleteFile(data.dmsFk)" + icon="delete" + color="primary" + flat + round + :class="{ + 'no-pointer-events': editDownloadDisabled, + }" + padding="xs" + > + <QTooltip>{{ t('Delete file') }}</QTooltip> + </QBtn> + </div> + <QBtn + v-else + icon="add_circle" + color="primary" + flat + round + v-shortcut="'+'" + padding="xs" + @click=" + () => { + documentDialogRef.show = true; + delete documentDialogRef.dms; + } + " + data-cy="dms-create" + > + <QTooltip>{{ t('Create document') }}</QTooltip> + </QBtn> + </div> + <QDialog v-model="documentDialogRef.show"> + <VnDms + model="dms" + :default-dms-code="defaultDmsCode" + :form-initial-data="documentDialogRef.dms" + :url=" + documentDialogRef.dms + ? `Dms/${documentDialogRef.dms.id}/updateFile` + : 'Dms/uploadFile' + " + :description="documentDialogRef.supplierName" + @on-data-saved=" + (_, { data }) => { + let dmsData = data; + if (Array.isArray(data)) dmsData = data[0]; + formRef.formData.dmsFk = dmsData.id; + formRef.formData.dms = dmsData; + formRef.hasChanges = true; + formRef.save(); + } + " + /> + </QDialog> +</template> +<i18n> +es: + Document: Documento + Download file: Descargar archivo + Edit document: Editar documento + Delete file: Eliminar archivo + Create document: Crear documento + +</i18n> diff --git a/src/components/common/VnDmsList.vue b/src/components/common/VnDmsList.vue index 424781a26..aafa9f4ba 100644 --- a/src/components/common/VnDmsList.vue +++ b/src/components/common/VnDmsList.vue @@ -389,10 +389,7 @@ defineExpose({ </div> </template> </QTable> - <div - v-else - class="info-row q-pa-md text-center" - > + <div v-else class="info-row q-pa-md text-center"> <h5> {{ t('No data to display') }} </h5> @@ -416,6 +413,7 @@ defineExpose({ v-shortcut @click="showFormDialog()" class="fill-icon" + data-cy="addButton" > <QTooltip> {{ t('Upload file') }} diff --git a/src/components/common/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/VnInput.vue b/src/components/common/VnInput.vue index 9821992cb..474d68116 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -84,7 +84,7 @@ const mixinRules = [ ...($attrs.rules ?? []), (val) => { const maxlength = $props.maxlength; - if (maxlength && +val.length > maxlength) + if (maxlength && +val?.length > maxlength) return t(`maxLength`, { value: maxlength }); const { min, max } = vnInputRef.value.$attrs; if (!min) return null; diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 1f4705faa..343130f1d 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -107,7 +107,7 @@ const manageDate = (date) => { @click="isPopupOpen = !isPopupOpen" @keydown="isPopupOpen = false" hide-bottom-space - :data-cy="$attrs.dataCy ?? $attrs.label + '_inputDate'" + :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'" > <template #append> <QIcon diff --git a/src/components/common/VnJsonValue.vue b/src/components/common/VnJsonValue.vue index a2e858d0d..ba37ab1d6 100644 --- a/src/components/common/VnJsonValue.vue +++ b/src/components/common/VnJsonValue.vue @@ -1,6 +1,6 @@ <script setup> import { watch } from 'vue'; -import { toDateString } from 'src/filters'; +import { toDateHourMinSec } from 'src/filters'; const props = defineProps({ value: { type: [String, Number, Boolean, Object], default: undefined }, @@ -40,7 +40,7 @@ const updateValue = () => { break; case 'object': if (props.value instanceof Date) { - t = toDateString(props.value); + t = toDateHourMinSec(props.value); } else { t = props.value.toString(); } diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 8f106a9f1..e2f18866a 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'; @@ -10,12 +10,12 @@ import { useColor } from 'src/composables/useColor'; import { useCapitalize } from 'src/composables/useCapitalize'; import { useValidator } from 'src/composables/useValidator'; import VnAvatar from '../ui/VnAvatar.vue'; -import VnJsonValue from '../common/VnJsonValue.vue'; -import FetchData from '../FetchData.vue'; -import VnSelect from './VnSelect.vue'; +import VnLogValue from './VnLogValue.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,55 @@ 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.resetPagination(); + 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 +276,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 +346,7 @@ watch( backgroundColor: useColor(modelLog.model), }" :title="`${modelLog.model} #${modelLog.id}`" + data-cy="vnLog-model-chip" > {{ t(modelLog.modelI18n) }} </QChip> @@ -560,10 +440,9 @@ watch( value.nameI18n }}: </span> - <VnJsonValue - :value=" - value.val.val - " + <VnLogValue + :value="value.val" + :name="value.name" /> </QItem> </QCardSection> @@ -581,6 +460,7 @@ watch( }`, ) " + data-cy="vnLog-action-icon" /> </div> </QItem> @@ -614,7 +494,10 @@ watch( > {{ prop.nameI18n }}: </span> - <VnJsonValue :value="prop.val.val" /> + <VnLogValue + :value="prop.val" + :name="prop.name" + /> <span v-if=" propIndex < @@ -642,8 +525,9 @@ watch( {{ prop.nameI18n }}: </span> <span v-if="log.action == 'update'"> - <VnJsonValue - :value="prop.old.val" + <VnLogValue + :value="prop.old" + :name="prop.name" /> <span v-if="prop.old.id" @@ -652,8 +536,9 @@ watch( #{{ prop.old.id }} </span> → - <VnJsonValue - :value="prop.val.val" + <VnLogValue + :value="prop.val" + :name="prop.name" /> <span v-if="prop.val.id" @@ -663,8 +548,9 @@ watch( </span> </span> <span v-else="prop.old.val"> - <VnJsonValue - :value="prop.val.val" + <VnLogValue + :value="prop.val" + :name="prop.name" /> <span v-if="prop.old.id" @@ -692,176 +578,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 - 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" - :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" - option-label="name" - option-value="id" - :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" - 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 - 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 - 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 - @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..c7be68e9e 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'; +import VnAvatar from '../ui/VnAvatar.vue'; -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']" + 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 new file mode 100644 index 000000000..3f1617ce7 --- /dev/null +++ b/src/components/common/VnLogValue.vue @@ -0,0 +1,28 @@ +<script setup> +import { useDescriptorStore } from 'src/stores/useDescriptorStore'; +import VnJsonValue from './VnJsonValue.vue'; +import { computed } from 'vue'; +const descriptorStore = useDescriptorStore(); + +const $props = defineProps({ + value: { type: Object, default: () => {} }, + name: { type: String, default: undefined }, +}); + +const descriptor = computed(() => descriptorStore.has($props.name)); +</script> +<template> + <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/VnSection.vue b/src/components/common/VnSection.vue index 4bd17124f..34eb14601 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -40,10 +40,6 @@ const $props = defineProps({ type: Boolean, default: true, }, - keepData: { - type: Boolean, - default: true, - }, }); const route = useRoute(); @@ -61,7 +57,6 @@ onBeforeMount(() => { if ($props.dataKey) arrayData = useArrayData($props.dataKey, { searchUrl: 'table', - keepData: $props.keepData, ...$props.arrayDataProps, navigate: $props.redirect, }); diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 9433ec819..944a2c6d9 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue'; +import { ref, toRefs, computed, watch, onMounted, useAttrs, nextTick } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; import { useRequired } from 'src/composables/useRequired'; @@ -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) @@ -295,6 +300,7 @@ async function onScroll({ to, direction, from, index }) { await arrayData.loadMore(); setOptions(arrayData.store.data); vnSelectRef.value.scrollTo(lastIndex); + await nextTick(); isLoading.value = false; } } diff --git a/src/components/common/__tests__/VnChangePassword.spec.js b/src/components/common/__tests__/VnChangePassword.spec.js index f5a967bb5..b610ce44d 100644 --- a/src/components/common/__tests__/VnChangePassword.spec.js +++ b/src/components/common/__tests__/VnChangePassword.spec.js @@ -1,4 +1,5 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import { vi, beforeEach, afterEach, beforeAll, describe, expect, it } from 'vitest'; import { Notify } from 'quasar'; diff --git a/src/components/common/__tests__/VnDms.spec.js b/src/components/common/__tests__/VnDms.spec.js index d12bd781d..a2fdb4810 100644 --- a/src/components/common/__tests__/VnDms.spec.js +++ b/src/components/common/__tests__/VnDms.spec.js @@ -1,4 +1,5 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; import VnDms from 'src/components/common/VnDms.vue'; @@ -40,12 +41,10 @@ describe('VnDms', () => { companyFk: 2, dmsTypeFk: 3, description: 'This is a test description', - files: [ - { - name: 'example.txt', - content: new Blob(['file content'], { type: 'text/plain' }), - }, - ], + files: { + name: 'example.txt', + content: new Blob(['file content'], { type: 'text/plain' }), + }, }; const expectedBody = { diff --git a/src/components/common/__tests__/VnDmsList.spec.js b/src/components/common/__tests__/VnDmsList.spec.js index deb78d62c..ee62f6971 100644 --- a/src/components/common/__tests__/VnDmsList.spec.js +++ b/src/components/common/__tests__/VnDmsList.spec.js @@ -1,4 +1,6 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; + import VnDmsList from 'src/components/common/VnDmsList.vue'; import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; @@ -10,6 +12,9 @@ describe('VnDmsList', () => { }; beforeAll(() => { + vi.mock('src/composables/getUrl', () => ({ + getUrl: vi.fn().mockResolvedValue(''), + })); vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); vm = createWrapper(VnDmsList, { props: { diff --git a/src/components/common/__tests__/VnJsonValue.spec.js b/src/components/common/__tests__/VnJsonValue.spec.js index 393b39f3a..c3aa1a769 100644 --- a/src/components/common/__tests__/VnJsonValue.spec.js +++ b/src/components/common/__tests__/VnJsonValue.spec.js @@ -65,7 +65,7 @@ describe('VnJsonValue', () => { const date = new Date('2023-01-01'); const wrapper = buildComponent({ value: date }); const span = wrapper.find('span'); - expect(span.text()).toBe('2023-01-01'); + expect(span.text()).toBe('01/01/2023, 01:00:00'); expect(span.classes()).toContain('json-object'); }); diff --git a/src/components/common/__tests__/VnLog.spec.js b/src/components/common/__tests__/VnLog.spec.js index 53d2732a0..fcb516cc5 100644 --- a/src/components/common/__tests__/VnLog.spec.js +++ b/src/components/common/__tests__/VnLog.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import VnLog from 'src/components/common/VnLog.vue'; describe('VnLog', () => { @@ -108,27 +109,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__/VnLogValue.spec.js b/src/components/common/__tests__/VnLogValue.spec.js new file mode 100644 index 000000000..c23743a02 --- /dev/null +++ b/src/components/common/__tests__/VnLogValue.spec.js @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import VnLogValue from 'src/components/common/VnLogValue.vue'; +import { createWrapper } from 'app/test/vitest/helper'; + +const buildComponent = (props) => { + return createWrapper(VnLogValue, { + props, + global: {}, + }).wrapper; +}; + +describe('VnLogValue', () => { + const id = 1; + it('renders without descriptor', async () => { + expect(getIcon('inventFk').exists()).toBe(false); + }); + + it('renders with descriptor', async () => { + expect(getIcon('claimFk').text()).toBe('launch'); + }); + + function getIcon(name) { + const wrapper = buildComponent({ value: { val: id }, name }); + return wrapper.find('.q-icon'); + } +}); diff --git a/src/components/common/__tests__/VnNotes.spec.js b/src/components/common/__tests__/VnNotes.spec.js index 6a10dc097..0d256a736 100644 --- a/src/components/common/__tests__/VnNotes.spec.js +++ b/src/components/common/__tests__/VnNotes.spec.js @@ -1,5 +1,6 @@ import { describe, it, expect, vi, afterEach, beforeEach, afterAll } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; import VnNotes from 'src/components/ui/VnNotes.vue'; describe('VnNotes', () => { @@ -8,6 +9,7 @@ describe('VnNotes', () => { let spyFetch; let postMock; let patchMock; + let deleteMock; let expectedInsertBody; let expectedUpdateBody; const defaultOptions = { @@ -47,6 +49,7 @@ describe('VnNotes', () => { beforeEach(() => { postMock = vi.spyOn(axios, 'post'); patchMock = vi.spyOn(axios, 'patch'); + deleteMock = vi.spyOn(axios, 'delete'); }); afterEach(() => { @@ -143,4 +146,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/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index a29d1d429..5f9a89d64 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -1,332 +1,38 @@ <script setup> -import { onBeforeMount, watch, computed, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; -import { useArrayData } from 'composables/useArrayData'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import { useState } from 'src/composables/useState'; -import { useRoute } from 'vue-router'; -import { useClipboard } from 'src/composables/useClipboard'; -import VnMoreOptions from './VnMoreOptions.vue'; +import { ref } from 'vue'; +import VnDescriptor from './VnDescriptor.vue'; const $props = defineProps({ - url: { - type: String, - default: '', - }, - filter: { - type: Object, - default: null, - }, - title: { - type: String, - default: '', - }, - subtitle: { + id: { type: Number, - default: null, + default: false, }, - dataKey: { - type: String, - default: null, - }, - summary: { + card: { type: Object, default: null, }, - width: { - type: String, - default: 'md-width', - }, }); -const state = useState(); -const route = useRoute(); -const { t } = useI18n(); -const { copyText } = useClipboard(); -const { viewSummary } = useSummaryDialog(); -let arrayData; -let store; -let entity; -const isLoading = ref(false); -const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); -defineExpose({ getData }); - -onBeforeMount(async () => { - arrayData = useArrayData($props.dataKey, { - url: $props.url, - userFilter: $props.filter, - skip: 0, - oneRecord: true, - }); - store = arrayData.store; - entity = computed(() => { - const data = store.data ?? {}; - if (data) emit('onFetch', data); - return data; - }); - - // It enables to load data only once if the module is the same as the dataKey - if (!isSameDataKey.value || !route.params.id) await getData(); - watch( - () => [$props.url, $props.filter], - async () => { - if (!isSameDataKey.value) await getData(); - }, - ); -}); - -const routeName = computed(() => { - const DESCRIPTOR_PROXY = 'DescriptorProxy'; - - let name = $props.dataKey; - if ($props.dataKey.includes(DESCRIPTOR_PROXY)) { - name = name.split(DESCRIPTOR_PROXY)[0]; - } - return `${name}Summary`; -}); -async function getData() { - store.url = $props.url; - store.filter = $props.filter ?? {}; - isLoading.value = true; - try { - const { data } = await arrayData.fetch({ append: false, updateRouter: false }); - state.set($props.dataKey, data); - emit('onFetch', data); - } finally { - isLoading.value = false; - } -} - -function getValueFromPath(path) { - if (!path) return; - const keys = path.toString().split('.'); - let current = entity.value; - - for (const key of keys) { - if (current[key] === undefined) return undefined; - else current = current[key]; - } - return current; -} - -function copyIdText(id) { - copyText(id, { - component: { - copyValue: id, - }, - }); -} - const emit = defineEmits(['onFetch']); - -const iconModule = computed(() => route.matched[1].meta.icon); -const toModule = computed(() => - route.matched[1].path.split('/').length > 2 - ? route.matched[1].redirect - : route.matched[1].children[0].redirect, -); +const entity = ref(); </script> <template> - <div class="descriptor"> - <template v-if="entity && !isLoading"> - <div class="header bg-primary q-pa-sm justify-between"> - <slot name="header-extra-action" - ><QBtn - round - flat - dense - size="md" - :icon="iconModule" - color="white" - class="link" - :to="$attrs['to-module'] ?? toModule" - > - <QTooltip> - {{ t('globals.goToModuleIndex') }} - </QTooltip> - </QBtn></slot - > - <QBtn - @click.stop="viewSummary(entity.id, $props.summary, $props.width)" - round - flat - dense - size="md" - icon="preview" - color="white" - class="link" - v-if="summary" - > - <QTooltip> - {{ t('components.smartCard.openSummary') }} - </QTooltip> - </QBtn> - <RouterLink :to="{ name: routeName, params: { id: entity.id } }"> - <QBtn - class="link" - color="white" - dense - flat - icon="launch" - round - size="md" - > - <QTooltip> - {{ t('components.cardDescriptor.summary') }} - </QTooltip> - </QBtn> - </RouterLink> - <VnMoreOptions v-if="$slots.menu"> - <template #menu="{ menuRef }"> - <slot name="menu" :entity="entity" :menu-ref="menuRef" /> - </template> - </VnMoreOptions> - </div> - <slot name="before" /> - <div class="body q-py-sm"> - <QList dense> - <QItemLabel header class="ellipsis text-h5" :lines="1"> - <div class="title"> - <span v-if="$props.title" :title="getValueFromPath(title)"> - {{ getValueFromPath(title) ?? $props.title }} - </span> - <slot v-else name="description" :entity="entity"> - <span :title="entity.name"> - {{ entity.name }} - </span> - </slot> - </div> - </QItemLabel> - <QItem> - <QItemLabel class="subtitle"> - #{{ getValueFromPath(subtitle) ?? entity.id }} - </QItemLabel> - <QBtn - round - flat - dense - size="sm" - icon="content_copy" - color="primary" - @click.stop="copyIdText(entity.id)" - > - <QTooltip> - {{ t('globals.copyId') }} - </QTooltip> - </QBtn> - </QItem> - </QList> - <div class="list-box q-mt-xs"> - <slot name="body" :entity="entity" /> - </div> - </div> - <div class="icons"> - <slot name="icons" :entity="entity" /> - </div> - <div class="actions justify-center" data-cy="descriptor_actions"> - <slot name="actions" :entity="entity" /> - </div> - <slot name="after" /> - </template> - <!-- Skeleton --> - <SkeletonDescriptor v-if="!entity || isLoading" /> - </div> - <QInnerLoading - :label="t('globals.pleaseWait')" - :showing="isLoading" - color="primary" - /> -</template> - -<style lang="scss"> -.body { - background-color: var(--vn-section-color); - .text-h5 { - font-size: 20px; - padding-top: 5px; - padding-bottom: 0px; - } - .q-item { - min-height: 20px; - - .link { - margin-left: 10px; - } - } - .vn-label-value { - display: flex; - padding: 0px 16px; - .label { - color: var(--vn-label-color); - font-size: 14px; - - &:not(:has(a))::after { - content: ':'; + <component + :is="card" + :id + :visual="false" + v-bind="$attrs" + @on-fetch=" + (data) => { + entity = data; + emit('onFetch', data); } - } - .value { - color: var(--vn-text-color); - font-size: 14px; - margin-left: 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: left; - } - .info { - margin-left: 5px; - } - } -} -</style> - -<style lang="scss" scoped> -.title { - overflow: hidden; - text-overflow: ellipsis; - span { - color: var(--vn-text-color); - font-weight: bold; - } -} -.subtitle { - color: var(--vn-text-color); - font-size: 16px; - margin-bottom: 2px; -} -.list-box { - .q-item__label { - color: var(--vn-label-color); - padding-bottom: 0%; - } -} -.descriptor { - width: 256px; - .header { - display: flex; - align-items: center; - } - .icons { - margin: 0 10px; - display: flex; - justify-content: center; - .q-icon { - margin-right: 5px; - } - } - .actions { - margin: 0 5px; - justify-content: center !important; - } -} -</style> -<i18n> - en: - globals: - copyId: Copy ID - es: - globals: - copyId: Copiar ID -</i18n> + " + /> + <VnDescriptor v-model="entity" v-bind="$attrs"> + <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> + <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> + </template> + </VnDescriptor> +</template> diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 6d43f9426..27794b25f 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -81,6 +81,7 @@ async function fetch() { name: `${moduleName ?? route.meta.moduleName}Summary`, params: { id: entityId || entity.id }, }" + data-cy="goToSummaryBtn" > <QIcon name="open_in_new" color="white" size="sm" /> </router-link> @@ -158,6 +159,7 @@ async function fetch() { display: flex; flex-direction: row; margin-top: 2px; + align-items: start; .label { color: var(--vn-label-color); width: 9em; @@ -168,9 +170,15 @@ async function fetch() { flex-grow: 0; flex-shrink: 0; } + &.ellipsis > .value { + text-overflow: ellipsis; + white-space: pre; + } .value { color: var(--vn-text-color); overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } } .header { @@ -200,6 +208,23 @@ async function fetch() { } } } + +.vn-card-group { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; +} + +.vn-card-content { + display: flex; + flex-direction: column; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + > div { + max-height: 70px; + } +} </style> <style lang="scss" scoped> .summaryHeader .vn-label-value { diff --git a/src/components/ui/EntityDescriptor.vue b/src/components/ui/EntityDescriptor.vue new file mode 100644 index 000000000..a5dced551 --- /dev/null +++ b/src/components/ui/EntityDescriptor.vue @@ -0,0 +1,78 @@ +<script setup> +import { onBeforeMount, watch, computed, ref } from 'vue'; +import { useArrayData } from 'composables/useArrayData'; +import { useState } from 'src/composables/useState'; +import { useRoute } from 'vue-router'; +import VnDescriptor from './VnDescriptor.vue'; + +const $props = defineProps({ + url: { + type: String, + default: '', + }, + filter: { + type: Object, + default: null, + }, + dataKey: { + type: String, + default: null, + }, +}); + +const state = useState(); +const route = useRoute(); +let arrayData; +let store; +let entity; +const isLoading = ref(false); +const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); +defineExpose({ getData }); + +onBeforeMount(async () => { + arrayData = useArrayData($props.dataKey, { + url: $props.url, + userFilter: $props.filter, + skip: 0, + oneRecord: true, + }); + store = arrayData.store; + entity = computed(() => { + const data = store.data ?? {}; + if (data) emit('onFetch', data); + return data; + }); + + // It enables to load data only once if the module is the same as the dataKey + if (!isSameDataKey.value || !route.params.id) await getData(); + watch( + () => [$props.url, $props.filter], + async () => { + if (!isSameDataKey.value) await getData(); + }, + ); +}); + +async function getData() { + store.url = $props.url; + store.filter = $props.filter ?? {}; + isLoading.value = true; + try { + const { data } = await arrayData.fetch({ append: false, updateRouter: false }); + state.set($props.dataKey, data); + emit('onFetch', data); + } finally { + isLoading.value = false; + } +} + +const emit = defineEmits(['onFetch']); +</script> + +<template> + <VnDescriptor v-model="entity" v-bind="$attrs" :module="dataKey"> + <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> + <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> + </template> + </VnDescriptor> +</template> diff --git a/src/components/ui/VnDescriptor.vue b/src/components/ui/VnDescriptor.vue new file mode 100644 index 000000000..994233eb0 --- /dev/null +++ b/src/components/ui/VnDescriptor.vue @@ -0,0 +1,322 @@ +<script setup> +import { computed, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import { useRoute, useRouter } from 'vue-router'; +import { useClipboard } from 'src/composables/useClipboard'; +import VnMoreOptions from './VnMoreOptions.vue'; + +const entity = defineModel({ type: Object, default: null }); +const $props = defineProps({ + title: { + type: String, + default: '', + }, + subtitle: { + type: Number, + default: null, + }, + summary: { + type: Object, + default: null, + }, + width: { + type: String, + default: 'md-width', + }, + module: { + type: String, + default: null, + }, + toModule: { + type: Object, + default: null, + }, +}); + +const route = useRoute(); +const router = useRouter(); +const { t } = useI18n(); +const { copyText } = useClipboard(); +const { viewSummary } = useSummaryDialog(); +const DESCRIPTOR_PROXY = 'DescriptorProxy'; +const moduleName = ref(); +const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value; + +function getName() { + let name = $props.module; + if ($props.module.includes(DESCRIPTOR_PROXY)) { + name = name.split(DESCRIPTOR_PROXY)[0]; + } + return name; +} +const routeName = computed(() => { + let routeName = getName(); + return `${routeName}Summary`; +}); + +function getValueFromPath(path) { + if (!path) return; + const keys = path.toString().split('.'); + let current = entity.value; + + for (const key of keys) { + if (current[key] === undefined) return undefined; + else current = current[key]; + } + return current; +} + +function copyIdText(id) { + copyText(id, { + component: { + copyValue: id, + }, + }); +} + +const emit = defineEmits(['onFetch']); + +const iconModule = computed(() => { + moduleName.value = getName(); + if ($props.toModule) { + return router.getRoutes().find((r) => r.name === $props.toModule.name).meta.icon; + } + if (isSameModuleName) { + return router.options.routes[1].children.find((r) => r.name === moduleName.value) + ?.meta?.icon; + } else { + return route.matched[1].meta.icon; + } +}); + +const toModule = computed(() => { + moduleName.value = getName(); + if ($props.toModule) return $props.toModule; + if (isSameModuleName) { + return router.options.routes[1].children.find((r) => r.name === moduleName.value) + ?.redirect; + } else { + return route.matched[1].path.split('/').length > 2 + ? route.matched[1].redirect + : route.matched[1].children[0].redirect; + } +}); +</script> + +<template> + <div class="descriptor" data-cy="vnDescriptor"> + <template v-if="entity && entity?.id"> + <div class="header bg-primary q-pa-sm justify-between"> + <slot name="header-extra-action"> + <QBtn + round + flat + dense + size="md" + :icon="iconModule" + color="white" + class="link" + :to="toModule" + > + <QTooltip> + {{ t('globals.goToModuleIndex') }} + </QTooltip> + </QBtn> + </slot> + <QBtn + @click.stop="viewSummary(entity.id, summary, width)" + round + flat + dense + size="md" + icon="preview" + color="white" + class="link" + v-if="summary" + data-cy="openSummaryBtn" + > + <QTooltip> + {{ t('components.smartCard.openSummary') }} + </QTooltip> + </QBtn> + <RouterLink :to="{ name: routeName, params: { id: entity.id } }"> + <QBtn + class="link" + color="white" + dense + flat + icon="launch" + round + size="md" + data-cy="goToSummaryBtn" + > + <QTooltip> + {{ t('components.vnDescriptor.summary') }} + </QTooltip> + </QBtn> + </RouterLink> + <VnMoreOptions v-if="$slots.menu"> + <template #menu="{ menuRef }"> + <slot name="menu" :entity="entity" :menu-ref="menuRef" /> + </template> + </VnMoreOptions> + </div> + <slot name="before" /> + <div class="body q-py-sm"> + <QList dense> + <QItemLabel header class="ellipsis text-h5" :lines="1"> + <div class="title"> + <span + v-if="title" + :title="getValueFromPath(title)" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_title`" + > + {{ getValueFromPath(title) ?? title }} + </span> + <slot v-else name="description" :entity="entity"> + <span + :title="entity.name" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_description`" + v-text="entity.name" + /> + </slot> + </div> + </QItemLabel> + <QItem> + <QItemLabel + class="subtitle" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_subtitle`" + > + #{{ getValueFromPath(subtitle) ?? entity.id }} + </QItemLabel> + <QBtn + round + flat + dense + size="sm" + icon="content_copy" + color="primary" + @click.stop="copyIdText(entity.id)" + > + <QTooltip> + {{ t('globals.copyId') }} + </QTooltip> + </QBtn> + </QItem> + </QList> + <div + class="list-box q-mt-xs" + :data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_listbox`" + > + <slot name="body" :entity="entity" /> + </div> + </div> + <div class="icons"> + <slot name="icons" :entity="entity" /> + </div> + <div class="actions justify-center" data-cy="descriptor_actions"> + <slot name="actions" :entity="entity" /> + </div> + <slot name="after" /> + </template> + <SkeletonDescriptor v-if="!entity" /> + </div> + <QInnerLoading :label="t('globals.pleaseWait')" :showing="!entity" color="primary" /> +</template> + +<style lang="scss"> +.body { + background-color: var(--vn-section-color); + .text-h5 { + font-size: 20px; + padding-top: 5px; + padding-bottom: 0px; + } + .q-item { + min-height: 20px; + + .link { + margin-left: 10px; + } + } + .vn-label-value { + display: flex; + padding: 0px 16px; + .label { + color: var(--vn-label-color); + font-size: 14px; + + &:not(:has(a))::after { + content: ':'; + } + } + &.ellipsis > .value { + text-overflow: ellipsis; + white-space: pre; + } + .value { + color: var(--vn-text-color); + font-size: 14px; + margin-left: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + } + .info { + margin-left: 5px; + } + } +} +</style> + +<style lang="scss" scoped> +.title { + overflow: hidden; + text-overflow: ellipsis; + span { + color: var(--vn-text-color); + font-weight: bold; + } +} +.subtitle { + color: var(--vn-text-color); + font-size: 16px; + margin-bottom: 2px; +} +.list-box { + .q-item__label { + color: var(--vn-label-color); + padding-bottom: 0%; + } +} +.descriptor { + width: 256px; + .header { + display: flex; + align-items: center; + } + .icons { + margin: 0 10px; + display: flex; + justify-content: center; + .q-icon { + margin-right: 5px; + } + } + .actions { + margin: 0 5px; + justify-content: center !important; + } +} +</style> +<i18n> + en: + globals: + copyId: Copy ID + es: + globals: + copyId: Copiar ID +</i18n> diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index d6b525dc8..dc9e4e776 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -54,13 +54,17 @@ const $props = defineProps({ default: 'table', }, redirect: { - type: Boolean, + type: [String, Boolean], default: true, }, arrayData: { 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"> @@ -249,7 +254,7 @@ const getLocale = (label) => { :key="chip.label" :removable="!unremovableParams?.includes(chip.label)" @remove="remove(chip.label)" - data-cy="vnFilterPanelChip" + :data-cy="`vnFilterPanelChip_${chip.label}`" > <slot name="tags" diff --git a/src/components/ui/VnLinkMail.vue b/src/components/ui/VnLinkMail.vue index a54f463f5..6c5129a9b 100644 --- a/src/components/ui/VnLinkMail.vue +++ b/src/components/ui/VnLinkMail.vue @@ -1,8 +1,11 @@ <script setup> +import { dashIfEmpty } from 'src/filters'; + defineProps({ email: { type: [String], default: null } }); </script> <template> <QBtn + class="q-pr-xs" v-if="email" flat round @@ -13,4 +16,5 @@ defineProps({ email: { type: [String], default: null } }); :href="`mailto:${email}`" @click.stop /> + <span>{{ dashIfEmpty(email) }}</span> </template> diff --git a/src/components/ui/VnLinkPhone.vue b/src/components/ui/VnLinkPhone.vue index a9e9bc0fc..e34a70011 100644 --- a/src/components/ui/VnLinkPhone.vue +++ b/src/components/ui/VnLinkPhone.vue @@ -1,7 +1,7 @@ <script setup> import { ref, reactive, useAttrs, onBeforeMount, capitalize } from 'vue'; import axios from 'axios'; -import { parsePhone } from 'src/filters'; +import { dashIfEmpty, parsePhone } from 'src/filters'; import useOpenURL from 'src/composables/useOpenURL'; const props = defineProps({ @@ -12,49 +12,65 @@ const props = defineProps({ const phone = ref(props.phoneNumber); const config = reactive({ - sip: { icon: 'phone', href: `sip:${props.phoneNumber}` }, 'say-simple': { icon: 'vn:saysimple', url: null, channel: props.channel, }, + sip: { icon: 'phone', href: `sip:${props.phoneNumber}` }, }); -const type = Object.keys(config).find((key) => key in useAttrs()) || 'sip'; + +const attrs = useAttrs(); +const types = Object.keys(config) + .filter((key) => key in attrs) + .sort(); +const activeTypes = types.length ? types : ['sip']; onBeforeMount(async () => { if (!phone.value) return; - let { channel } = config[type]; - if (type === 'say-simple') { - const { url, defaultChannel } = (await axios.get('SaySimpleConfigs/findOne')) - .data; - if (!channel) channel = defaultChannel; + for (const type of activeTypes) { + if (type === 'say-simple') { + let { channel } = config[type]; + const { url, defaultChannel } = (await axios.get('SaySimpleConfigs/findOne')) + .data; + if (!channel) channel = defaultChannel; - phone.value = await parsePhone(props.phoneNumber, props.country?.toLowerCase()); - config[ - type - ].url = `${url}?customerIdentity=%2B${phone.value}&channelId=${channel}`; + phone.value = await parsePhone( + props.phoneNumber, + props.country?.toLowerCase(), + ); + config[type].url = + `${url}?customerIdentity=%2B${phone.value}&channelId=${channel}`; + } } }); -function handleClick() { +function handleClick(type) { if (config[type].url) useOpenURL(config[type].url); else if (config[type].href) window.location.href = config[type].href; } </script> + <template> - <QBtn - v-if="phone" - flat - round - :icon="config[type].icon" - size="sm" - color="primary" - padding="none" - @click.stop="handleClick" - > - <QTooltip> - {{ capitalize(type).replace('-', '') }} - </QTooltip> - </QBtn> + <div class="flex items-center gap-2"> + <template v-for="type in activeTypes"> + <QBtn + :key="type" + v-if="phone" + flat + round + :icon="config[type].icon" + size="sm" + color="primary" + padding="none" + @click.stop="() => handleClick(type)" + > + <QTooltip> + {{ capitalize(type).replace('-', '') }} + </QTooltip> + </QBtn></template + > + <span>{{ dashIfEmpty(phone) }}</span> + </div> </template> diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index a198c9c05..aa7342742 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -28,13 +28,14 @@ function copyValueText() { const val = computed(() => $props.value); </script> <template> - <div class="vn-label-value"> + <div class="vn-label-value" :data-cy="`${$attrs['data-cy'] ?? 'vnLv'}${label ?? ''}`"> <QCheckbox v-if="typeof value === 'boolean'" v-model="val" :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 8a1c7a0f2..bc81233d5 100644 --- a/src/components/ui/VnMoreOptions.vue +++ b/src/components/ui/VnMoreOptions.vue @@ -9,10 +9,10 @@ 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> + <QList data-cy="descriptor-more-opts_list"> <slot name="menu" :menu-ref="$refs.menuRef" /> </QList> </QMenu> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index eb0804af0..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({ @@ -40,6 +40,11 @@ const quasar = useQuasar(); const newNote = reactive({ text: null, observationTypeFk: null }); const observationTypes = ref([]); const vnPaginateRef = ref(); + +const defaultObservationType = computed(() => + observationTypes.value.find(ot => ot.code === 'salesPerson')?.id +); + let originalText; function handleClick(e) { @@ -48,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; @@ -111,14 +121,22 @@ function fetchData([data]) { originalText = data?.notes; emit('onFetch', data); } + +const handleObservationTypes = (data) => { + observationTypes.value = data; + if(defaultObservationType.value) { + newNote.observationTypeFk = defaultObservationType.value; + } +}; + </script> <template> <FetchData v-if="selectType" url="ObservationTypes" - :filter="{ fields: ['id', 'description'] }" + :filter="{ fields: ['id', 'description', 'code'] }" auto-load - @on-fetch="(data) => (observationTypes = data)" + @on-fetch="handleObservationTypes" /> <FetchData v-if="justInput" @@ -144,7 +162,7 @@ function fetchData([data]) { v-model="newNote.observationTypeFk" option-label="description" style="flex: 0.15" - :required="isRequired" + :required="'required' in originalAttrs" @keyup.enter.stop="insert" /> <VnInput @@ -152,10 +170,10 @@ function fetchData([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> @@ -186,10 +204,9 @@ function fetchData([data]) { ref="vnPaginateRef" class="show" v-bind="$attrs" - search-url="notes" + :search-url="false" @on-fetch=" newNote.text = ''; - newNote.observationTypeFk = null; " > <template #body="{ rows }"> @@ -226,6 +243,21 @@ function fetchData([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 68968e6c5..4a5d2b459 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -117,7 +117,7 @@ onMounted(async () => { }); onBeforeUnmount(() => { - if (!store.keepData) arrayData.reset(['data']); + arrayData.reset(['data']); arrayData.resetPagination(); }); @@ -217,6 +217,7 @@ defineExpose({ paginate, userParams: arrayData.store.userParams, currentFilter: arrayData.store.currentFilter, + arrayData, }); </script> diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 1874d0ed9..59bcdf524 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -33,6 +33,10 @@ const props = defineProps({ type: String, default: '', }, + userFilter: { + type: Object, + default: null, + }, filter: { type: Object, default: null, diff --git a/src/components/ui/VnToSummary.vue b/src/components/ui/VnToSummary.vue index 305d65e02..853d26230 100644 --- a/src/components/ui/VnToSummary.vue +++ b/src/components/ui/VnToSummary.vue @@ -26,6 +26,7 @@ const id = props.entityId; :to="{ name: routeName, params: { id: id } }" class="header link" :href="url" + data-cy="goToSummaryBtn" > <QIcon name="open_in_new" color="white" size="sm" /> </router-link> diff --git a/src/components/ui/__tests__/CardSummary.spec.js b/src/components/ui/__tests__/CardSummary.spec.js index 6bd742310..bcef1d304 100644 --- a/src/components/ui/__tests__/CardSummary.spec.js +++ b/src/components/ui/__tests__/CardSummary.spec.js @@ -1,5 +1,7 @@ import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; + import CardSummary from 'src/components/ui/CardSummary.vue'; import * as vueRouter from 'vue-router'; diff --git a/src/components/ui/__tests__/Paginate.spec.js b/src/components/ui/__tests__/Paginate.spec.js index a67dfcdc6..968643b67 100644 --- a/src/components/ui/__tests__/Paginate.spec.js +++ b/src/components/ui/__tests__/Paginate.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import VnPaginate from 'src/components/ui/VnPaginate.vue'; describe('VnPaginate', () => { diff --git a/src/components/ui/__tests__/VnLinkPhone.spec.js b/src/components/ui/__tests__/VnLinkPhone.spec.js index a34ef90a5..3c92adf95 100644 --- a/src/components/ui/__tests__/VnLinkPhone.spec.js +++ b/src/components/ui/__tests__/VnLinkPhone.spec.js @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { axios } from 'app/test/vitest/helper'; +import axios from 'axios'; import parsePhone from 'src/filters/parsePhone'; describe('parsePhone filter', () => { diff --git a/src/composables/__tests__/downloadFile.spec.js b/src/composables/__tests__/downloadFile.spec.js index f53b56b3e..0cd38043f 100644 --- a/src/composables/__tests__/downloadFile.spec.js +++ b/src/composables/__tests__/downloadFile.spec.js @@ -1,15 +1,17 @@ import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { axios } from 'app/test/vitest/helper'; +import axios from 'axios'; import { downloadFile } from 'src/composables/downloadFile'; import { useSession } from 'src/composables/useSession'; const session = useSession(); const token = session.getToken(); describe('downloadFile', () => { - const baseUrl = 'http://localhost:9000'; let defaulCreateObjectURL; beforeAll(() => { + vi.mock('src/composables/getUrl', () => ({ + getUrl: vi.fn().mockResolvedValue(''), + })); defaulCreateObjectURL = window.URL.createObjectURL; window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); }); @@ -22,15 +24,14 @@ describe('downloadFile', () => { headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, }; vi.spyOn(axios, 'get').mockImplementation((url) => { - if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); - else if (url.includes('downloadFile')) return Promise.resolve(res); + if (url.includes('downloadFile')) return Promise.resolve(res); }); await downloadFile(1); expect(axios.get).toHaveBeenCalledWith( - `${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, - { responseType: 'blob' } + `/api/dms/1/downloadFile?access_token=${token}`, + { responseType: 'blob' }, ); }); }); diff --git a/src/composables/__tests__/useAcl.spec.js b/src/composables/__tests__/useAcl.spec.js index 6cb29984c..86cd58fa0 100644 --- a/src/composables/__tests__/useAcl.spec.js +++ b/src/composables/__tests__/useAcl.spec.js @@ -1,5 +1,7 @@ import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; +import axios from 'axios'; + +import { flushPromises } from '@vue/test-utils'; import { useAcl } from 'src/composables/useAcl'; describe('useAcl', () => { diff --git a/src/composables/__tests__/useArrayData.spec.js b/src/composables/__tests__/useArrayData.spec.js index a610ba9eb..a3fbbdd5d 100644 --- a/src/composables/__tests__/useArrayData.spec.js +++ b/src/composables/__tests__/useArrayData.spec.js @@ -1,15 +1,39 @@ import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; +import { default as axios } from 'axios'; import { useArrayData } from 'composables/useArrayData'; import { useRouter } from 'vue-router'; import * as vueRouter from 'vue-router'; +import { setActivePinia, createPinia } from 'pinia'; describe('useArrayData', () => { const filter = '{"limit":20,"skip":0}'; const params = { supplierFk: 2 }; + beforeEach(() => { - vi.spyOn(useRouter(), 'replace'); - vi.spyOn(useRouter(), 'push'); + setActivePinia(createPinia()); + + // Mock route + vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ + path: 'mockSection/list', + matched: [], + query: {}, + params: {}, + meta: { moduleName: 'mockName' }, + }); + + // Mock router + vi.spyOn(vueRouter, 'useRouter').mockReturnValue({ + push: vi.fn(), + replace: vi.fn(), + currentRoute: { + value: { + path: 'mockSection/list', + params: { id: 1 }, + meta: { moduleName: 'mockName' }, + matched: [{ path: 'mockName/:id' }], + }, + }, + }); }); afterEach(() => { @@ -17,103 +41,69 @@ describe('useArrayData', () => { }); it('should fetch and replace url with new params', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); + vi.spyOn(axios, 'get').mockResolvedValueOnce({ data: [] }); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); + const arrayData = useArrayData('ArrayData', { + url: 'mockUrl', + searchUrl: 'params', + }); arrayData.store.userParams = params; - arrayData.fetch({}); + await arrayData.fetch({}); - await flushPromises(); const routerReplace = useRouter().replace.mock.calls[0][0]; - expect(axios.get.mock.calls[0][1].params).toEqual({ - filter, - supplierFk: 2, + expect(axios.get).toHaveBeenCalledWith('mockUrl', { + signal: expect.any(Object), + params: { + filter, + supplierFk: 2, + }, }); - expect(routerReplace.path).toEqual('mockSection/list'); + + expect(routerReplace.path).toBe('mockSection/list'); expect(JSON.parse(routerReplace.query.params)).toEqual( expect.objectContaining(params), ); }); - it('should get data and send new URL without keeping parameters, if there is only one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] }); + it('should redirect to detail when single record is returned with navigation', async () => { + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data: [{ id: 1 }], + }); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); + const arrayData = useArrayData('ArrayData', { + url: 'mockUrl', + navigate: {}, + }); arrayData.store.userParams = params; - arrayData.fetch({}); + await arrayData.fetch({}); - await flushPromises(); const routerPush = useRouter().push.mock.calls[0][0]; - expect(axios.get.mock.calls[0][1].params).toEqual({ - filter, - supplierFk: 2, - }); - expect(routerPush.path).toEqual('mockName/1'); + expect(routerPush.path).toBe('mockName/1'); expect(routerPush.query).toBeUndefined(); }); - it('should get data and send new URL keeping parameters, if you have more than one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] }); - - vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ - matched: [], - query: {}, - params: {}, - meta: { moduleName: 'mockName' }, - path: 'mockName/1', - }); - vi.spyOn(vueRouter, 'useRouter').mockReturnValue({ - push: vi.fn(), - replace: vi.fn(), - currentRoute: { - value: { - params: { - id: 1, - }, - meta: { moduleName: 'mockName' }, - matched: [{ path: 'mockName/:id' }], - }, - }, - }); - - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); - - arrayData.store.userParams = params; - arrayData.fetch({}); - - await flushPromises(); - const routerPush = useRouter().push.mock.calls[0][0]; - - expect(axios.get.mock.calls[0][1].params).toEqual({ - filter, - supplierFk: 2, - }); - expect(routerPush.path).toEqual('mockName/'); - expect(routerPush.query.params).toBeDefined(); - }); - - it('should return one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ + it('should return one record when oneRecord is true', async () => { + vi.spyOn(axios, 'get').mockResolvedValueOnce({ data: [ { id: 1, name: 'Entity 1' }, { id: 2, name: 'Entity 2' }, ], }); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', oneRecord: true }); + + const arrayData = useArrayData('ArrayData', { + url: 'mockUrl', + oneRecord: true, + }); + await arrayData.fetch({}); - expect(arrayData.store.data).toEqual({ id: 1, name: 'Entity 1' }); - }); - - it('should handle empty data gracefully if has to return one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', oneRecord: true }); - await arrayData.fetch({}); - - expect(arrayData.store.data).toBeUndefined(); + expect(arrayData.store.data).toEqual({ + id: 1, + name: 'Entity 1', + }); }); }); diff --git a/src/composables/__tests__/useRole.spec.js b/src/composables/__tests__/useRole.spec.js index d0bca5342..017301a1b 100644 --- a/src/composables/__tests__/useRole.spec.js +++ b/src/composables/__tests__/useRole.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { flushPromises } from '@vue/test-utils'; import { useRole } from 'composables/useRole'; const role = useRole(); @@ -23,18 +24,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..e86847b70 100644 --- a/src/composables/__tests__/useSession.spec.js +++ b/src/composables/__tests__/useSession.spec.js @@ -1,5 +1,5 @@ import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest'; -import { axios } from 'app/test/vitest/helper'; +import axios from 'axios'; import { useSession } from 'composables/useSession'; import { useState } from 'composables/useState'; @@ -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/__tests__/useTokenConfig.spec.js b/src/composables/__tests__/useTokenConfig.spec.js index a25a4abb1..92664e65a 100644 --- a/src/composables/__tests__/useTokenConfig.spec.js +++ b/src/composables/__tests__/useTokenConfig.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { flushPromises } from '@vue/test-utils'; import { useTokenConfig } from 'composables/useTokenConfig'; const tokenConfig = useTokenConfig(); diff --git a/src/composables/downloadFile.js b/src/composables/downloadFile.js index 4588265a2..0c4e8edb6 100644 --- a/src/composables/downloadFile.js +++ b/src/composables/downloadFile.js @@ -7,18 +7,33 @@ const { getTokenMultimedia } = useSession(); const token = getTokenMultimedia(); export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) { - const appUrl = (await getUrl('', 'lilium')).replace('/#/', ''); + const appUrl = await getAppUrl(); const response = await axios.get( url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`, - { responseType: 'blob' } + { responseType: 'blob' }, ); + download(response); +} + +export async function downloadDocuware(url, params) { + const appUrl = await getAppUrl(); + const response = await axios.get(`${appUrl}/api/` + url, { + responseType: 'blob', + params, + }); + + download(response); +} + +function download(response) { const contentDisposition = response.headers['content-disposition']; const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); - const filename = - matches != null && matches[1] - ? matches[1].replace(/['"]/g, '') - : 'downloaded-file'; + const filename = matches?.[1] ? matches[1].replace(/['"]/g, '') : 'downloaded-file'; exportFile(filename, response.data); } + +async function getAppUrl() { + return (await getUrl('', 'lilium')).replace('/#/', ''); +} diff --git a/src/composables/getColAlign.js b/src/composables/getColAlign.js index a930fd7d8..c1841e134 100644 --- a/src/composables/getColAlign.js +++ b/src/composables/getColAlign.js @@ -9,6 +9,8 @@ export function getColAlign(col) { case 'number': align = 'right'; break; + case 'time': + case 'date': case 'checkbox': align = 'center'; break; diff --git a/src/composables/updateMinPriceBeforeSave.js b/src/composables/updateMinPriceBeforeSave.js new file mode 100644 index 000000000..d2895eeff --- /dev/null +++ b/src/composables/updateMinPriceBeforeSave.js @@ -0,0 +1,51 @@ +import axios from 'axios'; + +export async function beforeSave(data, getChanges, modelOrigin) { + try { + const changes = data.updates; + if (!changes) return data; + const patchPromises = []; + + for (const change of changes) { + let patchData = {}; + + if ('hasMinPrice' in change.data) { + patchData.hasMinPrice = change.data?.hasMinPrice; + delete change.data.hasMinPrice; + } + if ('minPrice' in change.data) { + patchData.minPrice = change.data?.minPrice; + delete change.data.minPrice; + } + + if (Object.keys(patchData).length > 0) { + const promise = axios + .get(`${modelOrigin}/findOne`, { + params: { + filter: { + fields: ['itemFk'], + where: { id: change.where.id }, + }, + }, + }) + .then((row) => { + return axios.patch(`Items/${row.data.itemFk}`, patchData); + }) + .catch((error) => { + console.error('Error processing change: ', change, error); + }); + + patchPromises.push(promise); + } + } + + await Promise.all(patchPromises); + + data.updates = changes.filter((change) => Object.keys(change.data).length > 0); + + return data; + } catch (error) { + console.error('Error in beforeSave:', error); + throw error; + } +} diff --git a/src/composables/useAcl.js b/src/composables/useAcl.js index ede359186..52704fee9 100644 --- a/src/composables/useAcl.js +++ b/src/composables/useAcl.js @@ -30,9 +30,16 @@ export function useAcl() { return false; } + function hasAcl(model, prop, accessType) { + const modelAcl = state.getAcls().value[model]; + const propAcl = modelAcl?.[prop] || modelAcl?.['*']; + return !!(propAcl?.[accessType] || propAcl?.['*']); + } + return { fetch, hasAny, state, + hasAcl, }; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 3a171191e..2e880a16d 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -5,12 +5,11 @@ import { useArrayDataStore } from 'stores/useArrayDataStore'; import { buildFilter } from 'filters/filterPanel'; import { isDialogOpened } from 'src/filters'; -const arrayDataStore = useArrayDataStore(); - export function useArrayData(key, userOptions) { key ??= useRoute().meta.moduleName; if (!key) throw new Error('ArrayData: A key is required to use this composable'); + const arrayDataStore = useArrayDataStore(); // Move inside function if (!arrayDataStore.get(key)) arrayDataStore.set(key); @@ -56,7 +55,6 @@ export function useArrayData(key, userOptions) { 'searchUrl', 'navigate', 'mapKey', - 'keepData', 'oneRecord', ]; if (typeof userOptions === 'object') { @@ -108,7 +106,7 @@ export function useArrayData(key, userOptions) { store.hasMoreData = limit && response.data.length >= limit; if (!append && !isDialogOpened() && updateRouter) { - if (updateStateParams(response.data)?.redirect && !store.keepData) return; + if (updateStateParams(response.data)?.redirect) return; } store.isLoading = false; canceller = null; @@ -148,8 +146,7 @@ export function useArrayData(key, userOptions) { } async function applyFilter({ filter, params }, fetchOptions = {}) { - if (filter) store.userFilter = filter; - store.filter = {}; + if (filter) store.filter = filter; if (params) store.userParams = { ...params }; const response = await fetch(fetchOptions); @@ -190,7 +187,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..e4e4f52c7 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/composables/useSession.js b/src/composables/useSession.js index e69819a68..36b31ab0a 100644 --- a/src/composables/useSession.js +++ b/src/composables/useSession.js @@ -60,7 +60,7 @@ export function useSession() { const { data: isValidToken } = await axios.get('VnUsers/validateToken'); if (isValidToken) destroyTokenPromises = Object.entries(tokens).map(([key, url]) => - destroyToken(url, storage, key) + destroyToken(url, storage, key), ); } } finally { diff --git a/src/composables/useValidator.js b/src/composables/useValidator.js index 7a7032608..ae6c47d91 100644 --- a/src/composables/useValidator.js +++ b/src/composables/useValidator.js @@ -78,7 +78,8 @@ export function useValidator() { if (min >= 0) if (Math.floor(value) < min) return t('inputMin', { value: min }); }, - custom: (value) => validation.bindedFunction(value) || 'Invalid value', + custom: (value) => + eval(`(${validation.bindedFunction})`)(value) || 'Invalid value', }; }; diff --git a/src/css/app.scss b/src/css/app.scss index 994ae7ff1..dd5dbe247 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -15,6 +15,7 @@ body.body--light { --vn-empty-tag: #acacac; --vn-black-text-color: black; --vn-text-color-contrast: white; + --vn-link-color: #1e90ff; background-color: var(--vn-page-color); @@ -38,6 +39,7 @@ body.body--dark { --vn-empty-tag: #2d2d2d; --vn-black-text-color: black; --vn-text-color-contrast: black; + --vn-link-color: #66bfff; background-color: var(--vn-page-color); @@ -49,7 +51,7 @@ a { } .link { - color: $color-link; + color: var(--vn-link-color); cursor: pointer; &--white { @@ -58,14 +60,14 @@ a { } .tx-color-link { - color: $color-link !important; + color: var(--vn-link-color) !important; } .tx-color-font { - color: $color-link !important; + color: var(--vn-link-color) !important; } .header-link { - color: $color-link !important; + color: var(--vn-link-color) !important; cursor: pointer; border-bottom: solid $primary; border-width: 2px; @@ -323,7 +325,6 @@ input::-webkit-inner-spin-button { min-height: auto !important; display: flex; align-items: flex-end; - padding-bottom: 2px; .q-field__native.row { min-height: auto !important; } @@ -337,5 +338,8 @@ input::-webkit-inner-spin-button { } .containerShrinked { - width: 80%; + width: 70%; +} +.q-item__section--main ~ .q-item__section--side { + padding-inline: 0; } diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index 22c6d2b56..45d18af7e 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -24,7 +24,6 @@ $alert: $negative; $white: #fff; $dark: #3d3d3d; // custom -$color-link: #66bfff; $color-spacer-light: #a3a3a31f; $color-spacer: #7979794d; $border-thin-light: 1px solid $color-spacer-light; diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index d7187371e..4f4d1d5f7 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -99,7 +99,6 @@ globals: file: File selectFile: Select a file copyClipboard: Copy on clipboard - salesPerson: SalesPerson send: Send code: Code since: Since @@ -158,7 +157,9 @@ globals: changeState: Change state raid: 'Raid {daysInForward} days' isVies: Vies + department: Department noData: No data available + vehicle: Vehicle pageTitles: logIn: Login addressEdit: Update address @@ -346,7 +347,6 @@ globals: params: description: Description clientFk: Client id - salesPersonFk: Sales person warehouseFk: Warehouse provinceFk: Province stateFk: State @@ -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 @@ -531,6 +536,7 @@ ticket: customerCard: Customer card ticketList: Ticket List newOrder: New Order + ticketClaimed: Claimed ticket boxing: expedition: Expedition created: Created @@ -603,7 +609,6 @@ worker: balance: Balance medical: Medical list: - department: Department schedule: Schedule newWorker: New worker summary: @@ -646,6 +651,7 @@ worker: model: Model serialNumber: Serial number removePDA: Deallocate PDA + sendToTablet: Send to tablet create: lastName: Last name birth: Birth @@ -816,6 +822,7 @@ travel: search: Search travel searchInfo: You can search by travel id or name id: Id + awbFk: AWB travelList: tableVisibleColumns: ref: Reference @@ -840,6 +847,7 @@ travel: availabledHour: Availabled hour thermographs: Thermographs hb: HB + roundedCc: Rounded CC basicData: daysInForward: Automatic movement (Raid) isRaid: Raid @@ -862,7 +870,6 @@ components: mine: For me hasMinPrice: Minimum price # LatestBuysFilter - salesPersonFk: Buyer supplierFk: Supplier from: From to: To @@ -870,6 +877,11 @@ components: active: Is active floramondo: Is floramondo showBadDates: Show future items + name: Nombre + rate2: Grouping price + rate3: Packing price + minPrice: Min. Price + itemFk: Item id userPanel: copyToken: Token copied to clipboard settings: Settings @@ -883,7 +895,7 @@ components: openCard: View openSummary: Summary viewSummary: Summary - cardDescriptor: + vnDescriptor: mainList: Main list summary: Summary moreOptions: More options @@ -893,6 +905,8 @@ components: VnLv: copyText: '{copyValue} has been copied to the clipboard' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' + VnNotes: + clientWithoutPhone: 'The following clients do not have a phone number and the message will not be sent to them: {clientWithoutPhone}' weekdays: sun: Sunday mon: Monday diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index fc3018f39..9c808e046 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -103,7 +103,6 @@ globals: file: Fichero selectFile: Seleccione un fichero copyClipboard: Copiar en portapapeles - salesPerson: Comercial send: Enviar code: Código since: Desde @@ -163,6 +162,8 @@ globals: raid: 'Redada {daysInForward} días' isVies: Vies noData: Datos no disponibles + department: Departamento + vehicle: Vehículo pageTitles: logIn: Inicio de sesión addressEdit: Modificar consignatario @@ -349,7 +350,6 @@ globals: params: description: Descripción clientFk: Id cliente - salesPersonFk: Comercial warehouseFk: Almacén provinceFk: Provincia stateFk: Estado @@ -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 @@ -531,13 +536,13 @@ ticket: state: Estado shipped: Enviado landed: Entregado - salesPerson: Comercial total: Total card: customerId: ID cliente customerCard: Ficha del cliente ticketList: Listado de tickets newOrder: Nuevo pedido + ticketClaimed: Ticket reclamado boxing: expedition: Expedición created: Creado @@ -622,8 +627,6 @@ invoiceOut: errors: downloadCsvFailed: Error al descargar CSV order: - field: - salesPersonFk: Comercial form: clientFk: Cliente addressFk: Dirección @@ -691,7 +694,6 @@ worker: formation: Formación medical: Mutua list: - department: Departamento schedule: Horario newWorker: Nuevo trabajador summary: @@ -734,6 +736,7 @@ worker: model: Modelo serialNumber: Número de serie removePDA: Desasignar PDA + sendToTablet: Enviar a la tablet create: lastName: Apellido birth: Fecha de nacimiento @@ -902,6 +905,7 @@ travel: search: Buscar envío searchInfo: Buscar envío por id o nombre id: Id + awbFk: Guía aérea travelList: tableVisibleColumns: ref: Referencia @@ -926,6 +930,7 @@ travel: availabled: F. Disponible availabledHour: Hora Disponible hb: HB + roundedCc: CC redondeado basicData: daysInForward: Desplazamiento automatico (redada) isRaid: Redada @@ -949,7 +954,6 @@ components: hasMinPrice: Precio mínimo wareHouseFk: Almacén # LatestBuysFilter - salesPersonFk: Comprador supplierFk: Proveedor visible: Visible active: Activo @@ -957,6 +961,11 @@ components: to: Hasta floramondo: Floramondo showBadDates: Ver items a futuro + name: Nombre + rate2: Precio grouping + rate3: Precio packing + minPrice: Precio mínimo + itemFk: Id item userPanel: copyToken: Token copiado al portapapeles settings: Configuración @@ -970,7 +979,7 @@ components: openCard: Ficha openSummary: Detalles viewSummary: Vista previa - cardDescriptor: + vnDescriptor: mainList: Listado principal summary: Resumen moreOptions: Más opciones @@ -980,6 +989,8 @@ components: VnLv: copyText: '{copyValue} se ha copiado al portapepeles' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' + VnNotes: + clientWithoutPhone: 'Estos clientes no tienen asociado número de télefono y el sms no les será enviado: {clientWithoutPhone}' weekdays: sun: Domingo mon: Lunes diff --git a/src/pages/Account/AccountFilter.vue b/src/pages/Account/AccountFilter.vue index 50c3ee1ac..732e92f77 100644 --- a/src/pages/Account/AccountFilter.vue +++ b/src/pages/Account/AccountFilter.vue @@ -47,7 +47,7 @@ const rolesOptions = ref([]); :label="t('globals.name')" v-model="params.name" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -57,7 +57,7 @@ const rolesOptions = ref([]); :label="t('account.card.alias')" v-model="params.nickname" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -75,8 +75,7 @@ const rolesOptions = ref([]); use-input hide-selected dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 976af1d19..4f3f544c1 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -149,14 +149,12 @@ const columns = computed(() => [ :right-search="false" > <template #more-create-dialog="{ data }"> - <QCardSection> <VnInputPassword :label="t('Password')" v-model="data.password" :required="true" autocomplete="new-password" /> - </QCardSection> </template> </VnTable> </template> diff --git a/src/pages/Account/Acls/AclFilter.vue b/src/pages/Account/Acls/AclFilter.vue index 8035f92b8..222fe5b77 100644 --- a/src/pages/Account/Acls/AclFilter.vue +++ b/src/pages/Account/Acls/AclFilter.vue @@ -56,8 +56,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -72,8 +71,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -83,7 +81,7 @@ onBeforeMount(() => { :label="t('acls.aclFilter.property')" v-model="params.property" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -98,8 +96,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -114,8 +111,7 @@ onBeforeMount(() => { option-label="name" use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Account/Alias/Card/AliasCard.vue b/src/pages/Account/Alias/Card/AliasCard.vue index f37bd7d0f..f3faa5bee 100644 --- a/src/pages/Account/Alias/Card/AliasCard.vue +++ b/src/pages/Account/Alias/Card/AliasCard.vue @@ -1,10 +1,10 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import AliasDescriptor from './AliasDescriptor.vue'; </script> <template> - <VnCardBeta + <VnCard data-key="Alias" url="MailAliases" :descriptor="AliasDescriptor" diff --git a/src/pages/Account/Alias/Card/AliasDescriptor.vue b/src/pages/Account/Alias/Card/AliasDescriptor.vue index 671ef7fbc..957047cc3 100644 --- a/src/pages/Account/Alias/Card/AliasDescriptor.vue +++ b/src/pages/Account/Alias/Card/AliasDescriptor.vue @@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import axios from 'axios'; @@ -48,11 +48,12 @@ const removeAlias = () => { </script> <template> - <CardDescriptor + <EntityDescriptor ref="descriptor" :url="`MailAliases/${entityId}`" data-key="Alias" title="alias" + :to-module="{ name: 'AccountAlias' }" > <template #menu> <QItem v-ripple clickable @click="removeAlias()"> @@ -62,7 +63,7 @@ const removeAlias = () => { <template #body="{ entity }"> <VnLv :label="t('role.description')" :value="entity.description" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Account/Alias/Card/AliasSummary.vue b/src/pages/Account/Alias/Card/AliasSummary.vue index b4b9abd25..cfd33ec82 100644 --- a/src/pages/Account/Alias/Card/AliasSummary.vue +++ b/src/pages/Account/Alias/Card/AliasSummary.vue @@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; const route = useRoute(); const { t } = useI18n(); @@ -27,13 +28,10 @@ const entityId = computed(() => $props.id || route.params.id); <template #body="{ entity: alias }"> <QCard class="vn-one"> <QCardSection class="q-pa-none"> - <router-link - :to="{ name: 'AliasBasicData', params: { id: entityId } }" - class="header header-link" - > - {{ t('globals.summary.basicData') }} - <QIcon name="open_in_new" /> - </router-link> + <VnTitle + :url="`#/account/alias/${entityId}/basic-data`" + :text="t('globals.summary.basicData')" + /> </QCardSection> <VnLv :label="t('role.id')" :value="alias.id" /> <VnLv :label="t('role.description')" :value="alias.description" /> diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index a5037e301..e102415c7 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -1,10 +1,10 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import AccountDescriptor from './AccountDescriptor.vue'; import filter from './AccountFilter.js'; </script> <template> - <VnCardBeta + <VnCard url="VnUsers/preview" :id-in-where="true" data-key="Account" diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 49328fe87..eb0a9013c 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -1,7 +1,7 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; import VnImg from 'src/components/ui/VnImg.vue'; @@ -20,7 +20,7 @@ onMounted(async () => { </script> <template> - <CardDescriptor + <EntityDescriptor ref="descriptor" :url="`VnUsers/preview`" :filter="{ ...filter, where: { id: entityId } }" @@ -78,7 +78,7 @@ onMounted(async () => { </QIcon> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style scoped> .q-item__label { diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index eafd62df6..f3eabb531 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -100,12 +100,8 @@ const onChangePass = (oldPass) => { }; onMounted(() => { - hasitManagementAccess.value = useAcl().hasAny([ - { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, - ]); - hasSysadminAccess.value = useAcl().hasAny([ - { model: 'VnUser', props: 'adminUser', accessType: 'WRITE' }, - ]); + hasitManagementAccess.value = useAcl().hasAcl('VnUser', 'higherPrivileges', 'WRITE'); + hasSysadminAccess.value = useAcl().hasAcl('VnUser', 'adminUser', 'WRITE'); }); </script> <template> @@ -227,7 +223,7 @@ onMounted(() => { <QItemSection>{{ t('account.card.actions.deactivateUser.name') }}</QItemSection> </QItem> <QItem - v-if="useAcl().hasAny([{ model: 'VnRole', props: '*', accessType: 'WRITE' }])" + v-if="useAcl().hasAcl('VnRole', '*', 'WRITE')" v-ripple clickable @click="showSyncDialog = true" diff --git a/src/pages/Account/Card/AccountDescriptorProxy.vue b/src/pages/Account/Card/AccountDescriptorProxy.vue new file mode 100644 index 000000000..6a4b3e267 --- /dev/null +++ b/src/pages/Account/Card/AccountDescriptorProxy.vue @@ -0,0 +1,14 @@ +<script setup> +import AccountDescriptor from './AccountDescriptor.vue'; +import AccountSummary from './AccountSummary.vue'; +</script> +<template> + <QPopupProxy style="max-width: 10px"> + <AccountDescriptor + v-if="$attrs.id" + v-bind="$attrs" + :summary="AccountSummary" + :proxy-render="true" + /> + </QPopupProxy> +</template> diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index f7a16e8c3..a098f10ee 100644 --- a/src/pages/Account/Card/AccountSummary.vue +++ b/src/pages/Account/Card/AccountSummary.vue @@ -5,6 +5,7 @@ import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import filter from './AccountFilter.js'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; const $props = defineProps({ id: { type: Number, default: 0 } }); @@ -17,7 +18,7 @@ const entityId = computed(() => $props.id || route.params.id); data-key="Account" ref="AccountSummary" url="VnUsers/preview" - :filter="filter" + :filter="{ ...filter, where: { id: entityId } }" > <template #header="{ entity }">{{ entity.id }} - {{ entity.nickname }}</template> <template #menu> @@ -26,13 +27,10 @@ const entityId = computed(() => $props.id || route.params.id); <template #body="{ entity }"> <QCard class="vn-one"> <QCardSection class="q-pa-none"> - <router-link - :to="{ name: 'AccountBasicData', params: { id: entityId } }" - class="header header-link" - > - {{ $t('globals.pageTitles.basicData') }} - <QIcon name="open_in_new" /> - </router-link> + <VnTitle + :url="`#/account/${entityId}/basic-data`" + :text="$t('globals.pageTitles.basicData')" + /> </QCardSection> <VnLv :label="$t('account.card.nickname')" :value="entity.name" /> <VnLv :label="$t('account.card.role')" :value="entity.role?.name" /> diff --git a/src/pages/Account/Role/AccountRolesFilter.vue b/src/pages/Account/Role/AccountRolesFilter.vue index cbe7a70c8..1358236c6 100644 --- a/src/pages/Account/Role/AccountRolesFilter.vue +++ b/src/pages/Account/Role/AccountRolesFilter.vue @@ -27,7 +27,7 @@ const props = defineProps({ :label="t('globals.name')" v-model="params.name" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -37,7 +37,7 @@ const props = defineProps({ :label="t('role.description')" v-model="params.description" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue index ef5b9db04..43ad22b90 100644 --- a/src/pages/Account/Role/Card/RoleCard.vue +++ b/src/pages/Account/Role/Card/RoleCard.vue @@ -1,9 +1,9 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import RoleDescriptor from './RoleDescriptor.vue'; </script> <template> - <VnCardBeta + <VnCard url="VnRoles" data-key="Role" :id-in-where="true" diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index 517517af0..698bea4fa 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -32,11 +32,12 @@ const removeRole = async () => { </script> <template> - <CardDescriptor + <EntityDescriptor url="VnRoles" :filter="{ where: { id: entityId } }" data-key="Role" :summary="$props.summary" + :to-module="{ name: 'AccountRoles' }" > <template #menu> <QItem v-ripple clickable @click="removeRole()"> @@ -46,7 +47,7 @@ const removeRole = async () => { <template #body="{ entity }"> <VnLv :label="t('role.description')" :value="entity.description" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style scoped> .q-item__label { diff --git a/src/pages/Account/Role/Card/RoleSummary.vue b/src/pages/Account/Role/Card/RoleSummary.vue index 410f90b17..baa4afeca 100644 --- a/src/pages/Account/Role/Card/RoleSummary.vue +++ b/src/pages/Account/Role/Card/RoleSummary.vue @@ -4,6 +4,7 @@ import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; const route = useRoute(); const { t } = useI18n(); @@ -29,13 +30,10 @@ const entityId = computed(() => $props.id || route.params.id); <template #body="{ entity }"> <QCard class="vn-one"> <QCardSection class="q-pa-none"> - <a - class="header header-link" - :href="`#/VnUser/${entityId}/basic-data`" - > - {{ t('globals.pageTitles.basicData') }} - <QIcon name="open_in_new" /> - </a> + <VnTitle + :url="`#/account/role/${entityId}/basic-data`" + :text="$t('globals.pageTitles.basicData')" + /> </QCardSection> <VnLv :label="t('role.id')" :value="entity.id" /> <VnLv :label="t('globals.name')" :value="entity.name" /> diff --git a/src/pages/Claim/Card/ClaimAction.vue b/src/pages/Claim/Card/ClaimAction.vue index baa36710c..a499d8b5d 100644 --- a/src/pages/Claim/Card/ClaimAction.vue +++ b/src/pages/Claim/Card/ClaimAction.vue @@ -328,7 +328,7 @@ async function post(query, params) { <QTd> <VnSelect v-model="row.shelvingFk" - :options="shelvings" + url="Shelvings" option-label="code" option-value="id" style="width: 100px" diff --git a/src/pages/Claim/Card/ClaimBasicData.vue b/src/pages/Claim/Card/ClaimBasicData.vue index 43941d1dc..7e7d42ae8 100644 --- a/src/pages/Claim/Card/ClaimBasicData.vue +++ b/src/pages/Claim/Card/ClaimBasicData.vue @@ -2,6 +2,7 @@ import { ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; +import { getDifferences, getUpdatedValues } from 'src/filters'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import FetchData from 'components/FetchData.vue'; @@ -9,12 +10,18 @@ import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; - import VnAvatar from 'src/components/ui/VnAvatar.vue'; const route = useRoute(); const { t } = useI18n(); const workersOptions = ref([]); + +function onBeforeSave(formData, originalData) { + return getUpdatedValues( + Object.keys(getDifferences(formData, originalData)), + formData, + ); +} </script> <template> <FetchData @@ -27,6 +34,7 @@ const workersOptions = ref([]); <FormModel model="Claim" :url-update="`Claims/updateClaim/${route.params.id}`" + :mapper="onBeforeSave" auto-load > <template #form="{ data, validate }"> diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 05f3b53a8..307a6df40 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -1,10 +1,10 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import ClaimDescriptor from './ClaimDescriptor.vue'; import filter from './ClaimFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Claim" url="Claims" :descriptor="ClaimDescriptor" diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index 4551c58fe..76ede81ed 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -5,7 +5,8 @@ import { useI18n } from 'vue-i18n'; import { toDateHourMinSec, toPercentage } from 'src/filters'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import { getUrl } from 'src/composables/getUrl'; @@ -43,7 +44,7 @@ onMounted(async () => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Claims/${entityId}`" :filter="filter" title="client.name" @@ -65,12 +66,12 @@ onMounted(async () => { </template> </VnLv> <VnLv :label="t('claim.created')" :value="toDateHourMinSec(entity.created)" /> - <VnLv :label="t('claim.commercial')"> + <VnLv :label="t('globals.department')"> <template #value> - <VnUserLink - :name="entity.client?.salesPersonUser?.name" - :worker-id="entity.client?.salesPersonFk" - /> + <span class="link"> + {{ entity?.client?.department?.name || '-' }} + <DepartmentDescriptorProxy :id="entity?.client?.departmentFk" /> + </span> </template> </VnLv> <VnLv @@ -146,7 +147,7 @@ onMounted(async () => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style scoped> .q-item__label { diff --git a/src/pages/Claim/Card/ClaimDescriptorProxy.vue b/src/pages/Claim/Card/ClaimDescriptorProxy.vue new file mode 100644 index 000000000..78e686745 --- /dev/null +++ b/src/pages/Claim/Card/ClaimDescriptorProxy.vue @@ -0,0 +1,14 @@ +<script setup> +import ClaimDescriptor from './ClaimDescriptor.vue'; +import ClaimSummary from './ClaimSummary.vue'; +</script> +<template> + <QPopupProxy style="max-width: 10px"> + <ClaimDescriptor + v-if="$attrs.id" + v-bind="$attrs.id" + :summary="ClaimSummary" + :proxy-render="true" + /> + </QPopupProxy> +</template> diff --git a/src/pages/Claim/Card/ClaimFilter.js b/src/pages/Claim/Card/ClaimFilter.js index 50cabe228..4f119544c 100644 --- a/src/pages/Claim/Card/ClaimFilter.js +++ b/src/pages/Claim/Card/ClaimFilter.js @@ -14,7 +14,7 @@ export default { relation: 'client', scope: { include: [ - { relation: 'salesPersonUser' }, + { relation: 'department' }, { relation: 'claimsRatio', scope: { diff --git a/src/pages/Claim/Card/ClaimLines.vue b/src/pages/Claim/Card/ClaimLines.vue index dee03b95d..7c948bb2f 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -117,7 +117,7 @@ const selected = ref([]); const mana = ref(0); async function fetchMana() { const ticketId = claim.value.ticketFk; - const response = await axios.get(`Tickets/${ticketId}/getSalesPersonMana`); + const response = await axios.get(`Tickets/${ticketId}/getDepartmentMana`); mana.value = response.data; } diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 210b0c982..67d57004f 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -19,7 +19,9 @@ import ClaimNotes from 'src/pages/Claim/Card/ClaimNotes.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; 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(); @@ -35,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(); @@ -172,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); } @@ -182,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`" @@ -200,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" /> @@ -252,13 +238,15 @@ function claimUrl(section) { </VnLv> <VnLv v-if="$route.name != 'ClaimSummary'" - :label="t('globals.salesPerson')" + :label="t('customer.summary.team')" > <template #value> - <VnUserLink - :name="claim.client?.salesPersonUser?.name" - :worker-id="claim.client?.salesPersonFk" - /> + <span class="link"> + {{ claim?.client?.department?.name || '-' }} + <DepartmentDescriptorProxy + :id="claim?.client?.departmentFk" + /> + </span> </template> </VnLv> <VnLv v-if="$route.name != 'ClaimSummary'" :label="t('claim.attendedBy')"> @@ -271,7 +259,7 @@ function claimUrl(section) { </VnLv> <VnLv v-if="$route.name != 'ClaimSummary'" :label="t('claim.customer')"> <template #value> - <span class="link cursor-pointer"> + <span class="link"> {{ claim.client?.name }} <CustomerDescriptorProxy :id="claim.clientFk" /> </span> diff --git a/src/pages/Claim/Card/ClaimSummaryAction.vue b/src/pages/Claim/Card/ClaimSummaryAction.vue index e5273902c..be3b9e896 100644 --- a/src/pages/Claim/Card/ClaimSummaryAction.vue +++ b/src/pages/Claim/Card/ClaimSummaryAction.vue @@ -80,7 +80,7 @@ const columns = [ :right-search="false" :column-search="false" :disable-option="{ card: true, table: true }" - search-url="actions" + :search-url="false" :filter="{ where: { claimFk: $props.id } }" :columns="columns" :limit="0" @@ -88,13 +88,13 @@ const columns = [ auto-load > <template #column-itemFk="{ row }"> - <span class="link"> + <span class="link" @click.stop> {{ row.itemFk }} <ItemDescriptorProxy :id="row.itemFk" /> </span> </template> <template #column-ticketFk="{ row }"> - <span class="link"> + <span class="link" @click.stop> {{ row.ticketFk }} <TicketDescriptorProxy :id="row.ticketFk" /> </span> diff --git a/src/pages/Claim/Card/__tests__/ClaimDescriptorMenu.spec.js b/src/pages/Claim/Card/__tests__/ClaimDescriptorMenu.spec.js index b208f1704..2142f41f2 100644 --- a/src/pages/Claim/Card/__tests__/ClaimDescriptorMenu.spec.js +++ b/src/pages/Claim/Card/__tests__/ClaimDescriptorMenu.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; describe('ClaimDescriptorMenu', () => { diff --git a/src/pages/Claim/Card/__tests__/ClaimLines.spec.js b/src/pages/Claim/Card/__tests__/ClaimLines.spec.js index ffbd6bf06..1ed5cccab 100644 --- a/src/pages/Claim/Card/__tests__/ClaimLines.spec.js +++ b/src/pages/Claim/Card/__tests__/ClaimLines.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import ClaimLines from '/src/pages/Claim/Card/ClaimLines.vue'; describe('ClaimLines', () => { diff --git a/src/pages/Claim/Card/__tests__/ClaimLinesImport.spec.js b/src/pages/Claim/Card/__tests__/ClaimLinesImport.spec.js index 1c4f367d4..cec4b1681 100644 --- a/src/pages/Claim/Card/__tests__/ClaimLinesImport.spec.js +++ b/src/pages/Claim/Card/__tests__/ClaimLinesImport.spec.js @@ -1,5 +1,6 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import ClaimLinesImport from 'pages/Claim/Card/ClaimLinesImport.vue'; describe('ClaimLinesImport', () => { diff --git a/src/pages/Claim/Card/__tests__/ClaimPhoto.spec.js b/src/pages/Claim/Card/__tests__/ClaimPhoto.spec.js index 8949e18a9..bf3548af3 100644 --- a/src/pages/Claim/Card/__tests__/ClaimPhoto.spec.js +++ b/src/pages/Claim/Card/__tests__/ClaimPhoto.spec.js @@ -1,7 +1,7 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import ClaimPhoto from 'pages/Claim/Card/ClaimPhoto.vue'; - describe('ClaimPhoto', () => { let vm; diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index 0fe7fc588..45eb89382 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -33,7 +33,7 @@ const props = defineProps({ :label="t('claim.customerId')" v-model="params.clientFk" lazy-rules - is-outlined + filled > <template #prepend> <QIcon name="badge" size="xs" /></template> </VnInput> @@ -41,18 +41,16 @@ const props = defineProps({ :label="t('Client Name')" v-model="params.clientName" lazy-rules - is-outlined + filled /> <VnSelect - :label="t('Salesperson')" - v-model="params.salesPersonFk" - url="Workers/activeWithInheritedRole" - :filter="{ where: { role: 'salesPerson' } }" - :use-like="false" - option-filter="firstName" dense - outlined - rounded + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> <VnSelect :label="t('claim.attendedBy')" @@ -62,8 +60,7 @@ const props = defineProps({ :use-like="false" option-filter="firstName" dense - outlined - rounded + filled /> <VnSelect :label="t('claim.state')" @@ -71,14 +68,12 @@ const props = defineProps({ :options="states" option-label="description" dense - outlined - rounded + filled /> <VnInputDate v-model="params.created" :label="t('claim.created')" - outlined - rounded + filled dense /> <VnSelect @@ -87,8 +82,7 @@ const props = defineProps({ url="Items/withName" :use-like="false" sort-by="id DESC" - outlined - rounded + filled dense /> <VnSelect @@ -99,15 +93,13 @@ const props = defineProps({ :use-like="false" option-filter="firstName" dense - outlined - rounded + filled /> <VnSelect :label="t('claim.zone')" v-model="params.zoneFk" url="Zones" - outlined - rounded + filled dense /> <QCheckbox @@ -123,10 +115,10 @@ const props = defineProps({ <i18n> en: params: + departmentFk: Department search: Contains clientFk: Customer clientName: Customer - salesPersonFk: Salesperson attenderFk: Attender claimResponsibleFk: Responsible claimStateFk: State @@ -136,10 +128,10 @@ en: zoneFk: Zone es: params: + departmentFk: Departamento search: Contiene clientFk: Cliente clientName: Cliente - salesPersonFk: Comercial attenderFk: Asistente claimResponsibleFk: Responsable claimStateFk: Estado @@ -148,6 +140,5 @@ es: itemFk: Artículo zoneFk: Zona Client Name: Nombre del cliente - Salesperson: Comercial Item: Artículo </i18n> diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index 41d0c5598..e0d9928f9 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'; import { toDate } from 'filters/index'; import ClaimFilter from './ClaimFilter.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import ClaimSummary from './Card/ClaimSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; @@ -48,6 +49,20 @@ const columns = computed(() => [ }, columnClass: 'expand', }, + { + align: 'left', + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', + }, + create: true, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), + }, { align: 'left', label: t('claim.attendedBy'), @@ -119,7 +134,7 @@ const columns = computed(() => [ const STATE_COLOR = { pending: 'bg-warning', - managed: 'bg-info', + loses: 'bg-negative', resolved: 'bg-positive', }; </script> @@ -152,6 +167,12 @@ const STATE_COLOR = { <CustomerDescriptorProxy :id="row.clientFk" /> </span> </template> + <template #column-departmentFk="{ row }"> + <span class="link" @click.stop> + {{ row.departmentName || '-' }} + <DepartmentDescriptorProxy :id="row?.departmentFk" /> + </span> + </template> <template #column-attendedBy="{ row }"> <span @click.stop> <VnUserLink :name="row.workerName" :worker-id="row.workerFk" /> diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue index 11db92eab..4855fadc0 100644 --- a/src/pages/Customer/Card/CustomerBalance.vue +++ b/src/pages/Customer/Card/CustomerBalance.vue @@ -20,11 +20,12 @@ 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(); const { t } = useI18n(); -const { hasAny } = useAcl(); +const { hasAcl } = useAcl(); const quasar = useQuasar(); const route = useRoute(); @@ -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 }) }} @@ -276,9 +276,7 @@ const showBalancePdf = ({ id }) => { > <VnInput v-model="scope.value" - :disable=" - !hasAny([{ model: 'Receipt', props: '*', accessType: 'WRITE' }]) - " + :disable="!hasAcl('Receipt', '*', 'WRITE')" @keypress.enter="scope.set" autofocus /> diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue index 36ec4763e..9c9d1b50b 100644 --- a/src/pages/Customer/Card/CustomerBasicData.vue +++ b/src/pages/Customer/Card/CustomerBasicData.vue @@ -8,7 +8,6 @@ import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import { getDifferences, getUpdatedValues } from 'src/filters'; const route = useRoute(); @@ -37,7 +36,7 @@ const exprBuilder = (param, value) => { function onBeforeSave(formData, originalData) { return getUpdatedValues( Object.keys(getDifferences(formData, originalData)), - formData + formData, ); } </script> @@ -119,16 +118,11 @@ function onBeforeSave(formData, originalData) { /> </VnRow> <VnRow> - <VnSelectWorker - :label="t('customer.summary.salesPerson')" - v-model="data.salesPersonFk" - :params="{ - departmentCodes: ['VT', 'shopping'], - }" - :has-avatar="true" - :rules="validate('client.salesPersonFk')" - :expr-builder="exprBuilder" - emit-value + <VnSelect + :label="t('globals.department')" + v-model="data.departmentFk" + url="Departments" + :fields="['id', 'name']" /> <VnSelect v-model="data.contactChannelFk" @@ -160,7 +154,7 @@ function onBeforeSave(formData, originalData) { <QIcon name="info" class="cursor-pointer"> <QTooltip>{{ t( - 'In case of a company succession, specify the grantor company' + 'In case of a company succession, specify the grantor company', ) }}</QTooltip> </QIcon> diff --git a/src/pages/Customer/Card/CustomerCard.vue b/src/pages/Customer/Card/CustomerCard.vue index 75fcb98fa..8c70646c1 100644 --- a/src/pages/Customer/Card/CustomerCard.vue +++ b/src/pages/Customer/Card/CustomerCard.vue @@ -1,10 +1,10 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import CustomerDescriptor from './CustomerDescriptor.vue'; </script> <template> - <VnCardBeta + <VnCard data-key="Customer" :url="`Clients/${$route.params.id}/getCard`" :descriptor="CustomerDescriptor" diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index e3156dd6d..c7461f890 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -3,14 +3,14 @@ import { onMounted, ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; +import { toCurrency, toDate } from 'src/filters'; import useCardDescription from 'src/composables/useCardDescription'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; -import VnUserLink from 'src/components/ui/VnUserLink.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import { useState } from 'src/composables/useState'; const state = useState(); @@ -54,7 +54,7 @@ const debtWarning = computed(() => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Clients/${entityId}/getCard`" :summary="$props.summary" data-key="Customer" @@ -84,14 +84,10 @@ const debtWarning = computed(() => { :value="toCurrency(entity.debt)" :info="t('customer.summary.riskInfo')" /> - <VnLv :label="t('customer.summary.salesPerson')"> + <VnLv :label="t('globals.department')"> <template #value> - <VnUserLink - v-if="entity.salesPersonUser" - :name="entity.salesPersonUser.name" - :worker-id="entity.salesPersonFk" - /> - <span v-else>{{ dashIfEmpty(entity.salesPersonUser) }}</span> + <span class="link" v-text="entity.department?.name" /> + <DepartmentDescriptorProxy :id="entity.department?.id" /> </template> </VnLv> <VnLv @@ -109,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" @@ -236,7 +223,7 @@ const debtWarning = computed(() => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Customer/Card/CustomerDescriptorMenu.vue b/src/pages/Customer/Card/CustomerDescriptorMenu.vue index aea45721c..fb78eab69 100644 --- a/src/pages/Customer/Card/CustomerDescriptorMenu.vue +++ b/src/pages/Customer/Card/CustomerDescriptorMenu.vue @@ -61,16 +61,6 @@ const openCreateForm = (type) => { .join('&'); useOpenURL(`/#/${type}/list?${params}`); }; -const updateSubstitutionAllowed = async () => { - try { - await axios.patch(`Clients/${route.params.id}`, { - substitutionAllowed: !$props.customer.substitutionAllowed, - }); - notify('globals.notificationSent', 'positive'); - } catch (error) { - notify(error.message, 'positive'); - } -}; </script> <template> @@ -79,13 +69,6 @@ const updateSubstitutionAllowed = async () => { {{ t('globals.pageTitles.createTicket') }} </QItemSection> </QItem> - <QItem v-ripple clickable> - <QItemSection @click="updateSubstitutionAllowed()">{{ - $props.customer.substitutionAllowed - ? t('Disable substitution') - : t('Allow substitution') - }}</QItemSection> - </QItem> <QItem v-ripple clickable> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> </QItem> diff --git a/src/pages/Customer/Card/CustomerFileManagement.vue b/src/pages/Customer/Card/CustomerFileManagement.vue index b565db6e7..419719251 100644 --- a/src/pages/Customer/Card/CustomerFileManagement.vue +++ b/src/pages/Customer/Card/CustomerFileManagement.vue @@ -86,12 +86,12 @@ const tableColumnComponents = { }, file: { component: QBtn, - props: () => ({ flat: true, color: 'blue' }), + props: () => ({ flat: true }), event: ({ row }) => downloadFile(row.dmsFk), }, employee: { component: QBtn, - props: () => ({ flat: true, color: 'blue' }), + props: () => ({ flat: true }), event: () => {}, }, created: { @@ -214,8 +214,17 @@ const toCustomerFileManagementCreate = () => { v-bind="tableColumnComponents[props.col.name].props(props)" > <template v-if="props.col.name !== 'original'"> - {{ props.value }} + <span + :class="{ + link: + props.col.name === 'employee' || + props.col.name === 'file', + }" + > + {{ props.value }} + </span> </template> + <WorkerDescriptorProxy :id="props.row.dms.workerFk" v-if="props.col.name === 'employee'" diff --git a/src/pages/Customer/Card/CustomerFiscalData.vue b/src/pages/Customer/Card/CustomerFiscalData.vue index 93909eb9c..baa728868 100644 --- a/src/pages/Customer/Card/CustomerFiscalData.vue +++ b/src/pages/Customer/Card/CustomerFiscalData.vue @@ -79,7 +79,7 @@ async function acceptPropagate({ isEqualizated }) { observe-form-changes @on-data-saved="checkEtChanges" > - <template #form="{ data, validate }"> + <template #form="{ data, validate, validations }"> <VnRow> <VnInput :label="t('Social name')" @@ -112,6 +112,7 @@ async function acceptPropagate({ isEqualizated }) { v-model="data.sageTaxTypeFk" data-cy="sageTaxTypeFk" :required="data.isTaxDataChecked" + :rules="[(val) => validations.required(data.isTaxDataChecked, val)]" /> <VnSelect :label="t('Sage transaction type')" @@ -122,6 +123,9 @@ async function acceptPropagate({ isEqualizated }) { data-cy="sageTransactionTypeFk" v-model="data.sageTransactionTypeFk" :required="data.isTaxDataChecked" + :rules="[ + (val) => validations.required(data.sageTransactionTypeFk, val), + ]" > <template #option="scope"> <QItem v-bind="scope.itemProps"> diff --git a/src/pages/Customer/Card/CustomerMandates.vue b/src/pages/Customer/Card/CustomerMandates.vue index 66cb44bc2..2511f5730 100644 --- a/src/pages/Customer/Card/CustomerMandates.vue +++ b/src/pages/Customer/Card/CustomerMandates.vue @@ -16,9 +16,7 @@ const filter = { { relation: 'mandateType', scope: { fields: ['id', 'code'] } }, { relation: 'company', scope: { fields: ['id', 'code'] } }, ], - where: { clientFk: route.params.id }, order: ['created DESC'], - limit: 20, }; const columns = computed(() => [ @@ -32,7 +30,7 @@ const columns = computed(() => [ { align: 'left', cardVisible: true, - format: ({ company }) => company.code, + format: ({ company }) => company?.code, label: t('globals.company'), name: 'company', }, @@ -65,7 +63,8 @@ const columns = computed(() => [ <VnTable data-key="Mandates" url="Mandates" - :filter="filter" + :user-filter="filter" + :filter="{ where: { clientFk: route.params.id } }" auto-load :columns="columns" class="full-width q-mt-md" diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index c98bf1ffb..9d30f0c6d 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -2,7 +2,6 @@ import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import VnUserLink from 'src/components/ui/VnUserLink.vue'; import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters'; import CardSummary from 'components/ui/CardSummary.vue'; @@ -13,6 +12,8 @@ import CustomerSummaryTable from 'src/pages/Customer/components/CustomerSummaryT import VnTitle from 'src/components/common/VnTitle.vue'; import VnRow from 'src/components/ui/VnRow.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; + const route = useRoute(); const { t } = useI18n(); const grafanaUrl = 'https://grafana.verdnatura.es'; @@ -83,39 +84,37 @@ const sumRisk = ({ clientRisks }) => { <VnLv :label="t('customer.summary.customerId')" :value="entity.id" /> <VnLv :label="t('globals.name')" :value="entity.name" /> <VnLv :label="t('customer.summary.contact')" :value="entity.contact" /> - <VnLv :value="entity.phone"> - <template #label> - {{ t('customer.extendedList.tableVisibleColumns.phone') }} + <VnLv :label="t('customer.extendedList.tableVisibleColumns.phone')"> + <template #value> <VnLinkPhone :phone-number="entity.phone" /> </template> </VnLv> - <VnLv :value="entity.mobile"> - <template #label> - {{ t('customer.summary.mobile') }} - <VnLinkPhone :phone-number="entity.mobile" /> + <VnLv :label="t('customer.summary.mobile')"> + <template #value> <VnLinkPhone + sip say-simple :phone-number="entity.mobile" :channel="entity.country?.saySimpleCountry?.channel" - class="q-ml-xs" /> </template> </VnLv> - <VnLv :value="entity.email" copy - ><template #label> - {{ t('globals.params.email') }} - <VnLinkMail email="entity.email"></VnLinkMail> </template - ></VnLv> <VnLv - :label="t('customer.summary.salesPerson')" - :value="entity?.salesPersonUser?.name" + :label="t('globals.params.email')" + :value="entity.email" + class="ellipsis" + copy > <template #value> - <VnUserLink - :name="entity.salesPersonUser?.name" - :worker-id="entity.salesPersonFk" - /> </template - ></VnLv> + <VnLinkMail :email="entity.email" /> + </template> + </VnLv> + <VnLv :label="t('globals.department')"> + <template #value> + <span class="link" v-text="entity.department?.name" /> + <DepartmentDescriptorProxy :id="entity?.department?.id" /> + </template> + </VnLv> <VnLv :label="t('customer.summary.contactChannel')" :value="entity?.contactChannel?.name" @@ -182,7 +181,7 @@ const sumRisk = ({ clientRisks }) => { <QCard class="vn-one"> <VnTitle :url="`#/customer/${entityId}/billing-data`" - :text="t('customer.summary.billingData')" + :text="t('customer.summary.payMethodFk')" /> <VnLv :label="t('customer.summary.payMethod')" @@ -290,7 +289,7 @@ const sumRisk = ({ clientRisks }) => { <VnLv v-if="entity.creditInsurance" :label="t('customer.summary.securedCredit')" - :value="toCurrency(entity.creditInsurance)" + :value="`${toCurrency(entity.creditInsurance)} (${entity.classifications[0]?.insurances[0]?.grade || ''})`" :info="t('customer.summary.securedCreditInfo')" /> diff --git a/src/pages/Customer/CustomerCreate.vue b/src/pages/Customer/CustomerCreate.vue deleted file mode 100644 index 79da63283..000000000 --- a/src/pages/Customer/CustomerCreate.vue +++ /dev/null @@ -1,146 +0,0 @@ -<script setup> -import { reactive, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; - -import FetchData from 'components/FetchData.vue'; -import FormModel from 'components/FormModel.vue'; -import VnRow from 'components/ui/VnRow.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import VnLocation from 'src/components/common/VnLocation.vue'; -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; - -const { t } = useI18n(); - -const initialData = reactive({ - active: true, - isEqualizated: false, -}); - -const workersOptions = ref([]); -const businessTypesOptions = ref([]); - -function handleLocation(data, location) { - const { town, code, provinceFk, countryFk } = location ?? {}; - data.postcode = code; - data.city = town; - data.provinceFk = provinceFk; - data.countryFk = countryFk; -} -</script> - -<template> - <FetchData - @on-fetch="(data) => (workersOptions = data)" - auto-load - url="Workers/search?departmentCodes" - /> - <FetchData - @on-fetch="(data) => (businessTypesOptions = data)" - auto-load - url="BusinessTypes" - /> - <QPage> - <VnSubToolbar /> - <FormModel - :form-initial-data="initialData" - model="client" - url-create="Clients/createWithUser" - > - <template #form="{ data, validate }"> - <VnRow> - <QInput :label="t('Comercial name')" v-model="data.name" /> - <VnSelect - :label="t('Salesperson')" - :options="workersOptions" - hide-selected - option-label="name" - option-value="id" - v-model="data.salesPersonFk" - /> - </VnRow> - <VnRow> - <VnSelect - :label="t('Business type')" - :options="businessTypesOptions" - hide-selected - option-label="description" - option-value="code" - v-model="data.businessTypeFk" - /> - <QInput v-model="data.fi" :label="t('Tax number')" /> - </VnRow> - <VnRow> - <QInput - :label="t('Business name')" - :rules="validate('client.socialName')" - v-model="data.socialName" - /> - </VnRow> - <VnRow> - <QInput - :label="t('Street')" - :rules="validate('client.street')" - v-model="data.street" - /> - </VnRow> - <VnRow> - <VnLocation - :rules="validate('Worker.postcode')" - :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]" - v-model="data.location" - @update:model-value="(location) => handleLocation(data, location)" - > - </VnLocation> - </VnRow> - - <VnRow> - <QInput v-model="data.userName" :label="t('Web user')" /> - <QInput - :label="t('Email')" - :rules="validate('client.email')" - clearable - type="email" - v-model="data.email" - > - <template #append> - <QIcon name="info" class="cursor-info"> - <QTooltip max-width="400px">{{ - t('customer.basicData.youCanSaveMultipleEmails') - }}</QTooltip> - </QIcon> - </template> - </QInput> - </VnRow> - <QCheckbox - :label="t('Is equalizated')" - v-model="initialData.isEqualizated" - /> - </template> - </FormModel> - </QPage> -</template> - -<style lang="scss" scoped> -.card { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - grid-gap: 20px; -} -</style> - -<i18n> -es: - Comercial name: Nombre comercial - Salesperson: Comercial - Business type: Tipo de negocio - Tax number: NIF / CIF - Business name: Razón social - Street: Dirección fiscal - Postcode: Código postal - City: Población - Province: Provincia - Country: País - Web user: Usuario web - Email: Email - Is equalizated: Recargo de equivalencia -</i18n> diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 1c5a08304..c30b11528 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -3,7 +3,6 @@ import { useI18n } from 'vue-i18n'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; -import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); defineProps({ @@ -42,7 +41,7 @@ const exprBuilder = (param, value) => { <template #body="{ params, searchFn }"> <QItem class="q-my-sm"> <QItemSection> - <VnInput :label="t('FI')" v-model="params.fi" is-outlined> + <VnInput :label="t('FI')" v-model="params.fi" filled> <template #prepend> <QIcon name="badge" size="xs" /> </template> @@ -51,7 +50,7 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Name')" v-model="params.name" is-outlined /> + <VnInput :label="t('Name')" v-model="params.name" filled /> </QItemSection> </QItem> <QItem class="q-mb-sm"> @@ -59,28 +58,21 @@ const exprBuilder = (param, value) => { <VnInput :label="t('customer.summary.socialName')" v-model="params.socialName" - is-outlined + filled /> </QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnSelectWorker - :label="t('Salesperson')" - v-model="params.salesPersonFk" - :params="{ - departmentCodes: ['VT'], - }" - :expr-builder="exprBuilder" - @update:model-value="searchFn()" - emit-value - map-options - use-input - hide-selected + <VnSelect dense - outlined - rounded - :input-debounce="0" + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" + no-one="true" /> </QItemSection> </QItem> @@ -97,8 +89,7 @@ const exprBuilder = (param, value) => { map-options hide-selected dense - outlined - rounded + filled auto-load :input-debounce="0" /> @@ -106,12 +97,12 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('City')" v-model="params.city" is-outlined /> + <VnInput :label="t('City')" v-model="params.city" filled /> </QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Phone')" v-model="params.phone" is-outlined> + <VnInput :label="t('Phone')" v-model="params.phone" filled> <template #prepend> <QIcon name="phone" size="xs" /> </template> @@ -120,7 +111,7 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Email')" v-model="params.email" is-outlined> + <VnInput :label="t('Email')" v-model="params.email" filled> <template #prepend> <QIcon name="email" size="sm" /> </template> @@ -140,19 +131,14 @@ const exprBuilder = (param, value) => { map-options hide-selected dense - outlined - rounded + filled auto-load sortBy="name ASC" /></QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput - :label="t('Postcode')" - v-model="params.postcode" - is-outlined - /> + <VnInput :label="t('Postcode')" v-model="params.postcode" filled /> </QItemSection> </QItem> </template> @@ -164,12 +150,12 @@ en: params: search: Contains fi: FI - salesPersonFk: Salesperson provinceFk: Province isActive: Is active city: City phone: Phone email: Email + departmentFk: Department isToBeMailed: Mailed isEqualizated: Equailized businessTypeFk: Business type @@ -182,6 +168,7 @@ en: postcode: Postcode es: params: + departmentFk: Departamento search: Contiene fi: NIF isActive: Activo @@ -191,7 +178,6 @@ es: sageTaxTypeFk: Tipo de impuesto Sage sageTransactionTypeFk: Tipo de impuesto Sage payMethodFk: Forma de pago - salesPersonFk: Comercial provinceFk: Provincia city: Ciudad phone: Teléfono @@ -201,7 +187,6 @@ es: name: Nombre postcode: CP FI: NIF - Salesperson: Comercial Province: Provincia City: Ciudad Phone: Teléfono diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 0bfca7910..b721a6ad9 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -10,7 +10,6 @@ import CustomerFilter from './CustomerFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import VnLocation from 'src/components/common/VnLocation.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; -import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); @@ -73,30 +72,17 @@ const columns = computed(() => [ }, { align: 'left', - name: 'salesPersonFk', - label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'), + name: 'departmentFk', + label: t('customer.summary.team'), component: 'select', attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name', 'firstName'], - where: { role: 'salesPerson' }, - optionFilter: 'firstName', + url: 'Departments', }, - columnFilter: { - component: 'select', - attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name', 'firstName'], - where: { role: 'salesPerson' }, - optionLabel: 'firstName', - optionValue: 'id', - }, - }, - create: false, + create: true, columnField: { component: null, }, - format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson), + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { align: 'left', @@ -155,6 +141,9 @@ const columns = computed(() => [ inWhere: true, }, columnClass: 'expand', + attrs: { + uppercase: true, + }, }, { align: 'left', @@ -446,36 +435,6 @@ function handleLocation(data, location) { redirect="customer" > <template #more-create-dialog="{ data }"> - <VnSelectWorker - :label="t('customer.summary.salesPerson')" - v-model="data.salesPersonFk" - :params="{ - departmentCodes: ['VT', 'shopping'], - }" - :has-avatar="true" - :id-value="data.salesPersonFk" - emit-value - auto-load - > - <template #prepend> - <VnAvatar - :worker-id="data.salesPersonFk" - color="primary" - :title="title" - /> - </template> - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel>{{ scope.opt?.name }}</QItemLabel> - <QItemLabel caption - >{{ scope.opt?.nickname }}, - {{ scope.opt?.code }}</QItemLabel - > - </QItemSection> - </QItem> - </template> - </VnSelectWorker> <VnLocation :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]" v-model="data.location" diff --git a/src/pages/Customer/Defaulter/CustomerDefaulter.vue b/src/pages/Customer/Defaulter/CustomerDefaulter.vue index dc4ac9162..296ad1eb4 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulter.vue @@ -32,28 +32,6 @@ const columns = computed(() => [ }, }, }, - { - align: 'left', - name: 'isWorker', - label: t('Is worker'), - }, - { - align: 'left', - name: 'salesPersonFk', - label: t('Salesperson'), - columnFilter: { - component: 'select', - attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - where: { role: 'salesPerson' }, - useLike: false, - optionValue: 'id', - optionLabel: 'name', - optionFilter: 'firstName', - }, - }, - }, { align: 'left', name: 'departmentFk', @@ -153,6 +131,11 @@ const columns = computed(() => [ label: t('Has recovery'), name: 'hasRecovery', }, + { + align: 'left', + name: 'isWorker', + label: t('customer.params.isWorker'), + }, ]); const viewAddObservation = (rowsSelected) => { @@ -167,7 +150,6 @@ const viewAddObservation = (rowsSelected) => { function exprBuilder(param, value) { switch (param) { - case 'salesPersonFk': case 'creditInsurance': case 'countryFk': return { [`c.${param}`]: value }; @@ -176,7 +158,7 @@ function exprBuilder(param, value) { case 'workerFk': return { [`co.${param}`]: value }; case 'departmentFk': - return { [`wd.${param}`]: value }; + return { [`c.${param}`]: value }; case 'amount': case 'clientFk': return { [`d.${param}`]: value }; @@ -241,12 +223,6 @@ function exprBuilder(param, value) { <template #column-observation="{ row }"> <VnInput type="textarea" v-model="row.observation" readonly dense rows="2" /> </template> - <template #column-salesPersonFk="{ row }"> - <span class="link" @click.stop> - {{ row.salesPersonName }} - <WorkerDescriptorProxy :id="row.salesPersonFk" /> - </span> - </template> <template #column-departmentFk="{ row }"> <span class="link" @click.stop> {{ row.departmentName }} @@ -265,8 +241,6 @@ function exprBuilder(param, value) { es: Add observation: Añadir observación Client: Cliente - Is worker: Es trabajador - Salesperson: Comercial Department: Departamento Country: País P. Method: F. Pago @@ -281,5 +255,5 @@ es: Credit I.: Crédito A. Credit insurance: Crédito asegurado From: Desde - Has recovery: Tiene recobro + Has recovery: Recobro </i18n> diff --git a/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue b/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue index ce86c6435..f7d4163d1 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulterFilter.vue @@ -15,19 +15,12 @@ const props = defineProps({ }, }); -const salespersons = ref(); const countries = ref(); const authors = ref(); const departments = ref(); </script> <template> - <FetchData - :filter="{ where: { role: 'salesPerson' } }" - @on-fetch="(data) => (salespersons = data)" - auto-load - url="Workers/activeWithInheritedRole" - /> <FetchData @on-fetch="(data) => (countries = data)" auto-load url="Countries" /> <FetchData @on-fetch="(data) => (authors = data)" @@ -52,8 +45,7 @@ const departments = ref(); dense option-label="name" option-value="id" - outlined - rounded + filled emit-value hide-selected map-options @@ -62,29 +54,6 @@ const departments = ref(); @update:model-value="searchFn()" /> </QItem> - <QItem class="q-mb-sm"> - <QItemSection v-if="salespersons"> - <VnSelect - :input-debounce="0" - :label="t('Salesperson')" - :options="salespersons" - dense - emit-value - hide-selected - map-options - option-label="name" - option-value="id" - outlined - rounded - use-input - v-model="params.salesPersonFk" - @update:model-value="searchFn()" - /> - </QItemSection> - <QItemSection v-else> - <QSkeleton class="full-width" type="QInput" /> - </QItemSection> - </QItem> <QItem class="q-mb-sm"> <QItemSection v-if="departments"> <VnSelect @@ -97,8 +66,7 @@ const departments = ref(); map-options option-label="name" option-value="id" - outlined - rounded + filled use-input v-model="params.departmentFk" @update:model-value="searchFn()" @@ -121,8 +89,7 @@ const departments = ref(); map-options option-label="name" option-value="id" - outlined - rounded + filled use-input v-model="params.countryFk" @update:model-value="searchFn()" @@ -138,7 +105,7 @@ const departments = ref(); <VnInput :label="t('P. Method')" clearable - is-outlined + filled v-model="params.paymentMethod" /> </QItemSection> @@ -149,7 +116,7 @@ const departments = ref(); <VnInput :label="t('Balance D.')" clearable - is-outlined + filled v-model="params.balance" /> </QItemSection> @@ -167,8 +134,7 @@ const departments = ref(); map-options option-label="name" option-value="id" - outlined - rounded + filled use-input v-model="params.workerFk" @update:model-value="searchFn()" @@ -184,7 +150,7 @@ const departments = ref(); <VnInputDate :label="t('L. O. Date')" clearable - is-outlined + filled v-model="params.date" /> </QItemSection> @@ -195,7 +161,7 @@ const departments = ref(); <VnInput :label="t('Credit I.')" clearable - is-outlined + filled v-model="params.credit" /> </QItemSection> @@ -205,7 +171,7 @@ const departments = ref(); <QItemSection> <VnInputDate :label="t('From')" - is-outlined + filled v-model="params.defaulterSinced" /> </QItemSection> @@ -219,7 +185,6 @@ const departments = ref(); en: params: clientFk: Client - salesPersonFk: Salesperson countryFk: Country paymentMethod: P. Method balance: Balance D. @@ -227,10 +192,11 @@ en: date: L. O. Date credit: Credit I. defaulterSinced: From + departmentFk: Department es: params: + departmentFk: Departamento clientFk: Cliente - salesPersonFk: Comercial countryFk: País paymentMethod: F. Pago balance: Saldo V. @@ -239,7 +205,6 @@ es: credit: Crédito A. defaulterSinced: Desde Client: Cliente - Salesperson: Comercial Departments: Departamentos Country: País P. Method: F. Pago diff --git a/src/pages/Customer/Notifications/CustomerNotifications.vue b/src/pages/Customer/Notifications/CustomerNotifications.vue index ce18739b4..cbbd6d205 100644 --- a/src/pages/Customer/Notifications/CustomerNotifications.vue +++ b/src/pages/Customer/Notifications/CustomerNotifications.vue @@ -69,17 +69,16 @@ const columns = computed(() => [ }, { align: 'left', - label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'), - name: 'salesPersonFk', + name: 'departmentFk', + label: t('customer.summary.team'), component: 'select', attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - where: { role: 'salesPerson' }, - optionFilter: 'firstName', - useLike: false, + url: 'Departments', }, - visible: false, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, ]); </script> @@ -96,7 +95,7 @@ const columns = computed(() => [ </VnSubToolbar> <VnTable :data-key="dataKey" - url="Clients" + url="Clients/filter" :table="{ 'row-key': 'id', selection: 'multiple', @@ -127,8 +126,8 @@ const columns = computed(() => [ es: Identifier: Identificador Social name: Razón social - Salesperson: Comercial Phone: Teléfono + Postcode: Código postal City: Población Email: Email Campaign consumption: Consumo campaña diff --git a/src/pages/Customer/Payments/CustomerPaymentsFilter.vue b/src/pages/Customer/Payments/CustomerPaymentsFilter.vue index 8982cba5a..ec20237b4 100644 --- a/src/pages/Customer/Payments/CustomerPaymentsFilter.vue +++ b/src/pages/Customer/Payments/CustomerPaymentsFilter.vue @@ -25,7 +25,7 @@ const props = defineProps({ <template #body="{ params }"> <QItem> <QItemSection> - <VnInput :label="t('Order ID')" v-model="params.orderFk" is-outlined> + <VnInput :label="t('Order ID')" v-model="params.orderFk" filled> <template #prepend> <QIcon name="vn:basket" size="xs" /> </template> @@ -34,11 +34,7 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnInput - :label="t('Customer ID')" - v-model="params.clientFk" - is-outlined - > + <VnInput :label="t('Customer ID')" v-model="params.clientFk" filled> <template #prepend> <QIcon name="vn:client" size="xs" /> </template> @@ -47,19 +43,15 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnInputNumber - :label="t('Amount')" - v-model="params.amount" - is-outlined - /> + <VnInputNumber :label="t('Amount')" v-model="params.amount" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate v-model="params.from" :label="t('From')" filled /> </QItemSection> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate v-model="params.to" :label="t('To')" filled /> </QItemSection> </QItem> </template> diff --git a/src/pages/Customer/Payments/__tests__/CustomerPayments.spec.js b/src/pages/Customer/Payments/__tests__/CustomerPayments.spec.js index 0b1457ece..238545050 100644 --- a/src/pages/Customer/Payments/__tests__/CustomerPayments.spec.js +++ b/src/pages/Customer/Payments/__tests__/CustomerPayments.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import CustomerPayments from 'src/pages/Customer/Payments/CustomerPayments.vue'; describe('CustomerPayments', () => { diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index f852c160a..bc76f5985 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -93,10 +93,26 @@ const updateAddressTicket = async () => { }; const updateObservations = async (payload) => { - await axios.post('AddressObservations/crud', payload); + await axios.post('AddressObservations/crud', cleanPayload(payload)); notes.value = []; deletes.value = []; }; + +function cleanPayload(payload) { + ['creates', 'deletes', 'updates'].forEach((prop) => { + if (prop === 'creates' || prop === 'updates') { + payload[prop] = payload[prop].filter( + (item) => item.description !== '' && item.observationTypeFk !== '', + ); + } else { + payload[prop] = payload[prop].filter( + (item) => item !== null && item !== undefined, + ); + } + }); + return payload; +} + async function updateAll({ data, payload }) { await updateObservations(payload); await updateAddress(data); diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index 6ecccc544..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 { 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' }, @@ -74,26 +76,24 @@ onBeforeMount(() => { urlCreate.value = `Clients/${route.params.id}/createReceipt`; }); -function setPaymentType(accounting) { +function setPaymentType(data, accounting) { + data.bankFk = accounting.id; if (!accounting) return; accountingType.value = accounting.accountingType; - initialData.description = []; - initialData.payed = Date.vnNew(); + data.description = []; + data.payed = Date.vnNew(); isCash.value = accountingType.value.code == 'cash'; viewReceipt.value = isCash.value; if (accountingType.value.daysInFuture) - initialData.payed.setDate( - initialData.payed.getDate() + accountingType.value.daysInFuture, - ); + data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture); maxAmount.value = accountingType.value && accountingType.value.maxAmount; - if (accountingType.value.code == 'compensation') - return (initialData.description = ''); + if (accountingType.value.code == 'compensation') return (data.description = ''); let descriptions = []; if (accountingType.value.receiptDescription) descriptions.push(accountingType.value.receiptDescription); - if (initialData.description) descriptions.push(initialData.description); - initialData.description = descriptions.join(', '); + if (data.description > 0) descriptions.push(data.description); + data.description = descriptions.join(', '); } const calculateFromAmount = (event) => { @@ -102,7 +102,7 @@ const calculateFromAmount = (event) => { }; const calculateFromDeliveredAmount = (event) => { - initialData.amountToReturn = parseFloat(event) - initialData.amountPaid; + amountToReturn.value = event - initialData.amountPaid; }; function onBeforeSave(data) { @@ -113,7 +113,6 @@ function onBeforeSave(data) { if (isCash.value && shouldSendEmail.value && !data.email) return notify(t('There is no assigned email for this client'), 'negative'); - data.bankFk = data.bankFk?.id; return data; } @@ -124,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', { @@ -181,42 +179,19 @@ async function getAmountPaid() { auto-load url="Clients/findOne" /> - <FormModel + <FormModelPopup ref="formModelRef" :form-initial-data="initialData" - :observe-form-changes="false" :url-create="urlCreate" :mapper="onBeforeSave" @on-data-saved="onDataSaved" - prevent-submit + :prevent-submit="true" > - <template #form="{ data, validate }"> - <span ref="closeButton" class="row justify-end close-icon" v-close-popup> - <QIcon name="close" size="sm" /> - </span> - + <template #form-inputs="{ data, validate }"> <h5 class="q-mt-none">{{ t('New payment') }}</h5> - - <VnRow> - <VnInputDate - :label="t('Date')" - :required="true" - v-model="data.payed" - /> - <VnSelect - :label="t('Company')" - :options="companyOptions" - :required="true" - :rules="validate('entry.companyFk')" - hide-selected - option-label="code" - option-value="id" - v-model="data.companyFk" - @update:model-value="getAmountPaid()" - /> - </VnRow> <VnRow> <VnSelect + autofocus :label="t('Bank')" v-model="data.bankFk" url="Accountings" @@ -225,9 +200,10 @@ async function getAmountPaid() { sort-by="id" :limit="0" @update:model-value=" - (value, options) => setPaymentType(value, options) + (value, options) => setPaymentType(data, value, options) " :emit-value="false" + data-cy="paymentBank" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -245,18 +221,37 @@ async function getAmountPaid() { @update:model-value="calculateFromAmount($event)" clearable v-model.number="data.amountPaid" + data-cy="paymentAmount" /> </VnRow> - <div v-if="data.bankFk?.accountingType?.code == 'compensation'"> + <VnRow> + <VnInputDate + :label="t('Date')" + v-model="data.payed" + :required="true" + /> + <VnSelect + :label="t('Company')" + :options="companyOptions" + :required="true" + :rules="validate('entry.companyFk')" + hide-selected + option-label="code" + option-value="id" + v-model="data.companyFk" + @update:model-value="getAmountPaid()" + /> + </VnRow> + <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> @@ -266,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 @@ -279,7 +273,7 @@ async function getAmountPaid() { <VnInputNumber :label="t('Amount to return')" disable - v-model="data.amountToReturn" + v-model="amountToReturn" /> </VnRow> <VnRow> @@ -287,27 +281,8 @@ async function getAmountPaid() { <QCheckbox v-model="shouldSendEmail" :label="t('Send email')" /> </VnRow> </div> - <div class="q-mt-lg row justify-end"> - <QBtn - :disabled="formModelRef.isLoading" - :label="t('globals.cancel')" - :loading="formModelRef.isLoading" - class="q-ml-sm" - color="primary" - flat - type="reset" - v-close-popup - /> - <QBtn - :disabled="formModelRef.isLoading" - :label="t('globals.save')" - :loading="formModelRef.isLoading" - color="primary" - @click="formModelRef.save()" - /> - </div> </template> - </FormModel> + </FormModelPopup> </QDialog> </template> diff --git a/src/pages/Customer/components/CustomerSummaryTable.vue b/src/pages/Customer/components/CustomerSummaryTable.vue index 09c7e714c..feb137065 100644 --- a/src/pages/Customer/components/CustomerSummaryTable.vue +++ b/src/pages/Customer/components/CustomerSummaryTable.vue @@ -191,7 +191,7 @@ const getItemPackagingType = (ticketSales) => { :without-header="true" auto-load :row-click="rowClick" - order="shipped DESC, id" + order="shipped DESC, id DESC" :disable-option="{ card: true, table: true }" class="full-width" :disable-infinite-scroll="true" diff --git a/src/pages/Customer/locale/en.yml b/src/pages/Customer/locale/en.yml index b6d495335..6724a5a7b 100644 --- a/src/pages/Customer/locale/en.yml +++ b/src/pages/Customer/locale/en.yml @@ -20,7 +20,7 @@ customer: name: Name contact: Contact mobile: Mobile - salesPerson: Sales person + team: Team contactChannel: Contact channel socialName: Social name fiscalId: Fiscal ID @@ -78,7 +78,6 @@ customer: id: Identifier socialName: Social name fi: Tax number - salesPersonFk: Salesperson creditInsurance: Credit insurance phone: Phone street: Street diff --git a/src/pages/Customer/locale/es.yml b/src/pages/Customer/locale/es.yml index f50d049da..4a266e07a 100644 --- a/src/pages/Customer/locale/es.yml +++ b/src/pages/Customer/locale/es.yml @@ -20,7 +20,7 @@ customer: name: Nombre contact: Contacto mobile: Móvil - salesPerson: Comercial + team: Equipo contactChannel: Canal de contacto socialName: Razón social fiscalId: NIF/CIF @@ -78,7 +78,6 @@ customer: id: Identificador socialName: Razón social fi: NIF / CIF - salesPersonFk: Comercial creditInsurance: Crédito asegurado phone: Teléfono street: Dirección fiscal diff --git a/src/pages/Entry/Card/EntryBasicData.vue b/src/pages/Entry/Card/EntryBasicData.vue index 6462ed24a..f6d15a977 100644 --- a/src/pages/Entry/Card/EntryBasicData.vue +++ b/src/pages/Entry/Card/EntryBasicData.vue @@ -13,6 +13,9 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; +import VnDmsInput from 'src/components/common/VnDmsInput.vue'; const route = useRoute(); const { t } = useI18n(); @@ -23,6 +26,7 @@ const user = state.getUser().fn(); const companiesOptions = ref([]); const currenciesOptions = ref([]); +const entryRef = ref({}); onMounted(() => { checkEntryLock(route.params.id, user.id); @@ -47,13 +51,14 @@ onMounted(() => { auto-load /> <FormModel - :url-update="`Entries/${route.params.id}`" + ref="entryRef" model="Entry" - auto-load + :url-update="`Entries/${route.params.id}`" :clear-store-on-unmount="false" + auto-load > <template #form="{ data }"> - <VnRow> + <VnRow class="q-py-sm"> <VnSelectTravelExtended :data="data" v-model="data.travelFk" @@ -65,15 +70,22 @@ onMounted(() => { :required="true" /> </VnRow> - <VnRow> - <VnInput v-model="data.reference" :label="t('globals.reference')" /> - <VnInputNumber - v-model="data.invoiceAmount" - :label="t('entry.summary.invoiceAmount')" - :positive="false" + <VnRow class="q-py-sm"> + <VnInput + v-model="data.reference" + :label="t('entry.list.tableVisibleColumns.reference')" + /> + <VnSelect + v-model="data.typeFk" + url="entryTypes" + :fields="['code', 'description']" + option-value="code" + optionLabel="description" + sortBy="description" + :label="t('entry.list.tableVisibleColumns.entryTypeDescription')" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInput v-model="data.invoiceNumber" :label="t('entry.summary.invoiceNumber')" @@ -84,12 +96,13 @@ onMounted(() => { :options="companiesOptions" option-value="id" option-label="code" + sort-by="code" map-options hide-selected :required="true" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInputNumber :label="t('entry.summary.commission')" v-model="data.commission" @@ -102,15 +115,15 @@ onMounted(() => { :options="currenciesOptions" option-value="id" option-label="code" + sort-by="code" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInputNumber v-model="data.initialTemperature" name="initialTemperature" :label="t('entry.basicData.initialTemperature')" :step="0.5" - :decimal-places="2" :positive="false" /> <VnInputNumber @@ -118,12 +131,21 @@ onMounted(() => { name="finalTemperature" :label="t('entry.basicData.finalTemperature')" :step="0.5" - :decimal-places="2" :positive="false" /> </VnRow> - <VnRow> - <QInput + <VnRow class="q-py-sm"> + <VnInputNumber + v-model="data.invoiceAmount" + :label="t('entry.list.tableVisibleColumns.invoiceAmount')" + :positive="false" + @update:model-value="data.buyerFk = user.id" + /> + <VnSelectWorker v-model="data.buyerFk" hide-selected /> + <VnDmsInput :data="data" :formRef="entryRef" :disable="false" /> + </VnRow> + <VnRow class="q-py-sm"> + <VnInputNumber :label="t('entry.basicData.observation')" type="textarea" v-model="data.observation" @@ -132,14 +154,20 @@ onMounted(() => { fill-input /> </VnRow> - <VnRow> - <QCheckbox v-model="data.isOrdered" :label="t('entry.summary.ordered')" /> - <QCheckbox v-model="data.isConfirmed" :label="t('globals.confirmed')" /> - <QCheckbox - v-model="data.isExcludedFromAvailable" - :label="t('entry.summary.excludedFromAvailable')" + <VnRow class="q-py-sm"> + <VnCheckbox + v-model="data.isOrdered" + :label="t('entry.list.tableVisibleColumns.isOrdered')" /> - <QCheckbox + <VnCheckbox + v-model="data.isConfirmed" + :label="t('entry.list.tableVisibleColumns.isConfirmed')" + /> + <VnCheckbox + v-model="data.isExcludedFromAvailable" + :label="t('entry.list.tableVisibleColumns.isExcludedFromAvailable')" + /> + <VnCheckbox :disable="!isAdministrative()" v-model="data.isBooked" :label="t('entry.basicData.booked')" diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue index 684ed5f59..b4c71ff2a 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -2,7 +2,7 @@ import { useStateStore } from 'stores/useStateStore'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { onMounted, ref } from 'vue'; +import { onMounted, ref, computed } from 'vue'; import { useState } from 'src/composables/useState'; @@ -16,6 +16,10 @@ import ItemDescriptor from 'src/pages/Item/Card/ItemDescriptor.vue'; import axios from 'axios'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import { checkEntryLock } from 'src/composables/checkEntryLock'; +import VnRow from 'src/components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import { beforeSave } from 'src/composables/updateMinPriceBeforeSave'; const $props = defineProps({ id: { @@ -42,6 +46,8 @@ const entityId = ref($props.id ?? route.params.id); const entryBuysRef = ref(); const footerFetchDataRef = ref(); const footer = ref({}); +const dialogRef = ref(false); +const newEntryRef = ref(null); const columns = [ { align: 'center', @@ -57,31 +63,6 @@ const columns = [ createOrder: 12, width: '25px', }, - { - label: t('Buyer'), - name: 'workerFk', - component: 'select', - attrs: { - url: 'TicketRequests/getItemTypeWorker', - fields: ['id', 'nickname'], - optionLabel: 'nickname', - sortBy: 'nickname ASC', - optionValue: 'id', - }, - visible: false, - }, - { - label: t('Family'), - name: 'itemTypeFk', - component: 'select', - attrs: { - url: 'itemTypes', - fields: ['id', 'name'], - optionLabel: 'name', - optionValue: 'id', - }, - visible: false, - }, { name: 'id', isId: true, @@ -111,16 +92,10 @@ const columns = [ }, }, { - align: 'center', + align: 'left', label: t('Article'), + component: 'input', name: 'name', - component: 'select', - attrs: { - url: 'Items', - fields: ['id', 'name'], - optionLabel: 'name', - optionValue: 'id', - }, width: '85px', isEditable: false, }, @@ -212,7 +187,6 @@ const columns = [ }, }, { - align: 'center', labelAbbreviation: 'GM', label: t('Grouping selector'), toolTip: t('Grouping selector'), @@ -240,7 +214,6 @@ const columns = [ }, }, { - align: 'center', labelAbbreviation: 'G', label: 'Grouping', toolTip: 'Grouping', @@ -281,6 +254,7 @@ const columns = [ component: 'number', attrs: { positive: false, + decimalPlaces: 3, }, cellEvent: { 'update:modelValue': async (value, oldValue, row) => { @@ -294,7 +268,7 @@ const columns = [ align: 'center', label: t('Amount'), name: 'amount', - width: '45px', + width: '75px', component: 'number', attrs: { positive: false, @@ -310,7 +284,9 @@ const columns = [ toolTip: t('Package'), name: 'price2', component: 'number', - createDisable: true, + createAttrs: { + disable: true, + }, width: '35px', create: true, format: (row) => parseFloat(row['price2']).toFixed(2), @@ -320,7 +296,9 @@ const columns = [ label: t('Box'), name: 'price3', component: 'number', - createDisable: true, + createAttrs: { + disable: true, + }, cellEvent: { 'update:modelValue': async (value, oldValue, row) => { row['price2'] = row['price2'] * (value / oldValue); @@ -340,13 +318,6 @@ const columns = [ toggleIndeterminate: false, }, component: 'checkbox', - cellEvent: { - 'update:modelValue': async (value, oldValue, row) => { - await axios.patch(`Items/${row['itemFk']}`, { - hasMinPrice: value, - }); - }, - }, width: '25px', }, { @@ -356,13 +327,6 @@ const columns = [ toolTip: t('Minimum price'), name: 'minPrice', component: 'number', - cellEvent: { - 'update:modelValue': async (value, oldValue, row) => { - await axios.patch(`Items/${row['itemFk']}`, { - minPrice: value, - }); - }, - }, width: '35px', style: (row) => { if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' }; @@ -425,6 +389,23 @@ const columns = [ }, }, ]; +const buyerFk = ref(null); +const itemTypeFk = ref(null); +const inkFk = ref(null); +const tag1 = ref(null); +const tag2 = ref(null); +const tag1Filter = ref(null); +const tag2Filter = ref(null); +const filter = computed(() => { + const where = {}; + where.workerFk = buyerFk.value; + where.itemTypeFk = itemTypeFk.value; + where.inkFk = inkFk.value; + where.tag1 = tag1.value; + where.tag2 = tag2.value; + + return { where }; +}); function getQuantityStyle(row) { if (row?.quantity !== row?.stickers * row?.packing) @@ -435,56 +416,6 @@ function getAmountStyle(row) { return { color: 'var(--vn-label-color)' }; } -async function beforeSave(data, getChanges) { - try { - const changes = data.updates; - if (!changes) return data; - const patchPromises = []; - - for (const change of changes) { - let patchData = {}; - - if ('hasMinPrice' in change.data) { - patchData.hasMinPrice = change.data?.hasMinPrice; - delete change.data.hasMinPrice; - } - if ('minPrice' in change.data) { - patchData.minPrice = change.data?.minPrice; - delete change.data.minPrice; - } - - if (Object.keys(patchData).length > 0) { - const promise = axios - .get('Buys/findOne', { - params: { - filter: { - fields: ['itemFk'], - where: { id: change.where.id }, - }, - }, - }) - .then((buy) => { - return axios.patch(`Items/${buy.data.itemFk}`, patchData); - }) - .catch((error) => { - console.error('Error processing change: ', change, error); - }); - - patchPromises.push(promise); - } - } - - await Promise.all(patchPromises); - - data.updates = changes.filter((change) => Object.keys(change.data).length > 0); - - return data; - } catch (error) { - console.error('Error in beforeSave:', error); - throw error; - } -} - function invertQuantitySign(rows, sign) { for (const row of rows) { if (sign > 0) row.quantity = Math.abs(row.quantity); @@ -521,6 +452,23 @@ async function setBuyUltimate(itemFk, data) { }); } +async function transferBuys(rows, newEntry) { + if (!newEntry) return; + + const promises = rows.map((row) => { + return axios.patch('Buys', { id: row.id, entryFk: newEntry }); + }); + + await Promise.all(promises); + + await axios.post(`Entries/${newEntry}/recalcEntryPrices`); + await axios.post(`Entries/${entityId.value}/recalcEntryPrices`); + + entryBuysRef.value.reload(); + newEntryRef.value = null; + dialogRef.value = false; +} + onMounted(() => { stateStore.rightDrawer = false; if ($props.editableMode) checkEntryLock(entityId.value, user.id); @@ -595,6 +543,47 @@ onMounted(() => { </QItem> </QList> </QBtnDropdown> + <QBtn + icon="move_group" + color="primary" + :title="t('Transfer buys')" + data-cy="transferBuys" + flat + @click="dialogRef = true" + :disable="!selectedRows.length" + /> + <QDialog v-model="dialogRef"> + <QCard> + <QCardSection> + <span>{{ t('Transfer buys') }}</span> + </QCardSection> + <QCardSection> + <VnInputNumber + v-model="newEntryRef" + :label="t('Entry')" + type="number" + data-cy="entryDestinyInput" + /> + </QCardSection> + <QCardSection> + <QCardActions> + <QBtn + label="Cancel" + flat + color="primary" + @click="dialogRef = false" + /> + <QBtn + label="Transfer" + data-cy="transferBuysBtn" + flat + color="primary" + @click="transferBuys(selectedRows, newEntryRef)" + /> + </QCardActions> + </QCardSection> + </QCard> + </QDialog> </QBtnGroup> </Teleport> <FetchData @@ -610,6 +599,7 @@ onMounted(() => { :url="`Entries/${entityId}/getBuyList`" search-url="EntryBuys" save-url="Buys/crud" + :filter="editableMode ? filter : {}" :disable-option="{ card: true }" v-model:selected="selectedRows" @on-fetch="() => footerFetchDataRef.fetch()" @@ -643,7 +633,7 @@ onMounted(() => { }, columnGridStyle: { 'max-width': '50%', - 'margin-right': '30px', + 'margin-right': '5%', flex: 1, }, previousStyle: { @@ -655,10 +645,10 @@ onMounted(() => { :is-editable="editableMode" :without-header="!editableMode" :with-filters="editableMode" - :right-search="editableMode" + :right-search="false" :row-click="false" :columns="columns" - :beforeSaveFn="beforeSave" + :beforeSaveFn="(data, getChanges) => beforeSave(data, getChanges, 'Buys')" class="buyList" :table-height="$props.tableHeight ?? '84vh'" auto-load @@ -666,6 +656,47 @@ onMounted(() => { data-cy="entry-buys" overlay > + <template #top-left> + <VnRow> + <VnSelect + :label="t('Buyer')" + v-model="buyerFk" + url="TicketRequests/getItemTypeWorker" + :fields="['id', 'nickname']" + option-label="nickname" + sort-by="nickname ASC" + :use-like="false" + /> + <VnSelect + :label="t('Family')" + v-model="itemTypeFk" + url="ItemTypes" + :fields="['id', 'name']" + option-label="name" + sort-by="name ASC" + /> + <VnSelect + :label="t('Color')" + v-model="inkFk" + url="Inks" + :fields="['id', 'name']" + option-label="name" + sort-by="name ASC" + /> + <VnInput + v-model="tag1Filter" + :label="t('Tag')" + @keyup.enter="tag1 = tag1Filter" + @remove="tag1 = null" + /> + <VnInput + v-model="tag2Filter" + :label="t('Tag')" + @keyup.enter="tag2 = tag2Filter" + @remove="tag2 = null" + /> + </VnRow> + </template> <template #column-hex="{ row }"> <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> </template> @@ -696,7 +727,7 @@ onMounted(() => { </div> </template> <template #column-footer-weight> - {{ footer?.weight }} + <span class="q-pr-xs">{{ footer?.weight }}</span> </template> <template #column-footer-quantity> <span :style="getQuantityStyle(footer)" data-cy="footer-quantity"> @@ -704,11 +735,10 @@ onMounted(() => { </span> </template> <template #column-footer-amount> - <span :style="getAmountStyle(footer)" data-cy="footer-amount"> - {{ footer?.amount }} - </span> + <span data-cy="footer-amount">{{ footer?.amount }} / </span> + <span style="color: var(--q-positive)">{{ footer?.checkedAmount }}</span> </template> - <template #column-create-itemFk="{ data }"> + <template #column-create-itemFk="{ data, validations }"> <VnSelect url="Items/search" v-model="data.itemFk" @@ -722,7 +752,8 @@ onMounted(() => { await setBuyUltimate(value, data); } " - :required="true" + required + :rules="[(val) => validations.required(true, val)]" data-cy="itemFk-create-popup" sort-by="nickname DESC" > @@ -767,6 +798,8 @@ onMounted(() => { </template> <i18n> es: + Buyer: Comprador + Family: Familia Article: Artículo Siz.: Med. Size: Medida @@ -798,6 +831,8 @@ es: Create buy: Crear compra Invert quantity value: Invertir valor de cantidad Check buy amount: Marcar como correcta la cantidad de compra + Transfer buys: Transferir compras + Entry: Entrada </i18n> <style lang="scss" scoped> .centered-container { diff --git a/src/pages/Entry/Card/EntryCard.vue b/src/pages/Entry/Card/EntryCard.vue index be82289f4..e9d07889f 100644 --- a/src/pages/Entry/Card/EntryCard.vue +++ b/src/pages/Entry/Card/EntryCard.vue @@ -1,13 +1,13 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import EntryDescriptor from './EntryDescriptor.vue'; import filter from './EntryFilter.js'; </script> <template> - <VnCardBeta + <VnCard 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 69b300cb2..2f9cfe0ff 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -6,7 +6,7 @@ import { toDate } from 'src/filters'; import { getUrl } from 'src/composables/getUrl'; import { useQuasar } from 'quasar'; import { usePrintService } from 'composables/usePrintService'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import axios from 'axios'; @@ -92,7 +92,7 @@ const getEntryRedirectionFilter = (entry) => { }; function showEntryReport() { - openReport(`Entries/${entityId.value}/entry-order-pdf`); + openReport(`Entries/${entityId.value}/entry-order-pdf`, {}, true); } function showNotification(type, message) { @@ -145,10 +145,9 @@ async function deleteEntry() { </script> <template> - <CardDescriptor - ref="entryDescriptorRef" + <EntityDescriptor :url="`Entries/${entityId}`" - :userFilter="entryFilter" + :filter="entryFilter" title="supplier.nickname" data-key="Entry" width="lg-width" @@ -265,7 +264,7 @@ async function deleteEntry() { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Entry/Card/EntryNotes.vue b/src/pages/Entry/Card/EntryNotes.vue index 459c3b069..4159ed5ca 100644 --- a/src/pages/Entry/Card/EntryNotes.vue +++ b/src/pages/Entry/Card/EntryNotes.vue @@ -2,153 +2,82 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; - -import FetchData from 'components/FetchData.vue'; -import CrudModel from 'components/CrudModel.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; const { params } = useRoute(); const { t } = useI18n(); - +const selectedRows = ref([]); const entryObservationsRef = ref(null); -const entryObservationsOptions = ref([]); -const selected = ref([]); - -const sortEntryObservationOptions = (data) => { - entryObservationsOptions.value = [...data].sort((a, b) => - a.description.localeCompare(b.description), - ); -}; - +const entityId = ref(params.id); const columns = computed(() => [ { - name: 'observationType', - label: t('entry.notes.observationType'), - field: (row) => row.observationTypeFk, - sortable: true, - options: entryObservationsOptions.value, - required: true, - model: 'observationTypeFk', - optionValue: 'id', - optionLabel: 'description', - tabIndex: 1, - align: 'left', + name: 'id', + isId: true, + visible: false, + isEditable: false, + columnFilter: false, }, { + name: 'observationTypeFk', + label: t('entry.notes.observationType'), + component: 'select', + columnFilter: { inWhere: true }, + attrs: { + inWhere: true, + url: 'ObservationTypes', + fields: ['id', 'description'], + optionValue: 'id', + optionLabel: 'description', + sortBy: 'description', + }, + width: '30px', + create: true, + }, + { + align: 'left', name: 'description', label: t('globals.description'), - field: (row) => row.description, - tabIndex: 2, - align: 'left', + component: 'input', + columnFilter: false, + attrs: { autogrow: true }, + create: true, }, ]); + +const filter = computed(() => ({ + fields: ['id', 'entryFk', 'observationTypeFk', 'description'], + include: ['observationType'], + where: { entryFk: entityId }, +})); </script> <template> - <FetchData - url="ObservationTypes" - @on-fetch="(data) => sortEntryObservationOptions(data)" + <VnTable + ref="entryObservationsRef" + data-key="EntryObservations" + :columns="columns" + url="EntryObservations" + :user-filter="filter" + order="id ASC" + :disable-option="{ card: true }" + :is-editable="true" + :right-search="true" + v-model:selected="selectedRows" + :create="{ + urlCreate: 'EntryObservations', + title: t('Create note'), + onDataSaved: () => { + entryObservationsRef.reload(); + }, + formInitialData: { entryFk: entityId }, + }" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" auto-load /> - <CrudModel - data-key="EntryAccount" - url="EntryObservations" - model="EntryAccount" - :filter="{ - fields: ['id', 'entryFk', 'observationTypeFk', 'description'], - where: { entryFk: params.id }, - }" - ref="entryObservationsRef" - :data-required="{ entryFk: params.id }" - v-model:selected="selected" - auto-load - > - <template #body="{ rows, validate }"> - <QTable - v-model:selected="selected" - :columns="columns" - :rows="rows" - :pagination="{ rowsPerPage: 0 }" - row-key="$index" - selection="multiple" - hide-pagination - :grid="$q.screen.lt.md" - table-header-class="text-left" - > - <template #body-cell-observationType="{ row, col }"> - <QTd> - <VnSelect - v-model="row[col.model]" - :options="col.options" - :option-value="col.optionValue" - :option-label="col.optionLabel" - :autofocus="col.tabIndex == 1" - input-debounce="0" - hide-selected - :required="true" - /> - </QTd> - </template> - <template #body-cell-description="{ row, col }"> - <QTd> - <VnInput - :label="t('globals.description')" - v-model="row[col.name]" - :rules="validate('EntryObservation.description')" - /> - </QTd> - </template> - <template #item="props"> - <div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition"> - <QCard bordered flat> - <QCardSection> - <QCheckbox v-model="props.selected" dense /> - </QCardSection> - <QSeparator /> - <QList dense> - <QItem> - <QItemSection> - <VnSelect - v-model="props.row.observationTypeFk" - :options="entryObservationsOptions" - option-value="id" - option-label="description" - input-debounce="0" - hide-selected - :required="true" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnInput - :label="t('globals.description')" - v-model="props.row.description" - :rules=" - validate('EntryObservation.description') - " - /> - </QItemSection> - </QItem> - </QList> - </QCard> - </div> - </template> - </QTable> - </template> - </CrudModel> - <QPageSticky position="bottom-right" :offset="[25, 25]"> - <QBtn - fab - color="primary" - icon="add" - v-shortcut="'+'" - @click="entryObservationsRef.insert()" - /> - </QPageSticky> </template> <i18n> es: - Add note: Añadir nota - Remove note: Quitar nota + Create note: Crear nota </i18n> diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index c40e2ba46..d5ebcde18 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -70,8 +70,8 @@ onMounted(async () => { :url="`#/entry/${entityId}/basic-data`" :text="t('globals.summary.basicData')" /> - <div class="card-group"> - <div class="card-content"> + <div class="vn-card-group"> + <div class="vn-card-content"> <VnLv :label="t('entry.summary.commission')" :value="entry?.commission" @@ -84,21 +84,24 @@ onMounted(async () => { :label="t('globals.company')" :value="entry?.company?.code" /> - <VnLv :label="t('globals.reference')" :value="entry?.reference" /> + <VnLv + :label="t('entry.list.tableVisibleColumns.reference')" + :value="entry?.reference" + /> <VnLv :label="t('entry.summary.invoiceNumber')" :value="entry?.invoiceNumber" /> </div> - <div class="card-content"> + <div class="vn-card-content"> <VnCheckbox - :label="t('entry.summary.ordered')" + :label="t('entry.list.tableVisibleColumns.isOrdered')" v-model="entry.isOrdered" :disable="true" size="xs" /> <VnCheckbox - :label="t('globals.confirmed')" + :label="t('entry.list.tableVisibleColumns.isConfirmed')" v-model="entry.isConfirmed" :disable="true" size="xs" @@ -110,7 +113,11 @@ onMounted(async () => { size="xs" /> <VnCheckbox - :label="t('entry.summary.excludedFromAvailable')" + :label=" + t( + 'entry.list.tableVisibleColumns.isExcludedFromAvailable', + ) + " v-model="entry.isExcludedFromAvailable" :disable="true" size="xs" @@ -123,8 +130,8 @@ onMounted(async () => { :url="`#/travel/${entry.travel.id}/summary`" :text="t('Travel')" /> - <div class="card-group"> - <div class="card-content"> + <div class="vn-card-group"> + <div class="vn-card-content"> <VnLv :label="t('entry.summary.travelReference')"> <template #value> <span class="link"> @@ -154,7 +161,8 @@ onMounted(async () => { :value="entry.travel.warehouseIn?.name" /> </div> - <div class="card-content"> + <div class="vn-card-content"> + <VnLv :label="t('travel.awbFk')" :value="entry.travel.awbFk" /> <VnCheckbox :label="t('entry.summary.travelDelivered')" v-model="entry.travel.isDelivered" @@ -185,31 +193,6 @@ onMounted(async () => { </template> </CardSummary> </template> -<style lang="scss" scoped> -.card-group { - display: flex; - flex-direction: column; -} - -.card-content { - display: flex; - flex-direction: column; - text-overflow: ellipsis; - > div { - max-height: 24px; - } -} - -@media (min-width: 1010px) { - .card-group { - flex-direction: row; - } - .card-content { - flex: 1; - margin-right: 16px; - } -} -</style> <i18n> es: Travel: Envío diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index c283e4a0b..19f4bca86 100644 --- a/src/pages/Entry/EntryFilter.vue +++ b/src/pages/Entry/EntryFilter.vue @@ -85,7 +85,7 @@ const entryFilterPanel = ref(); </QItemSection> <QItemSection> <QCheckbox - :label="t('entry.list.tableVisibleColumns.isConfirmed')" + label="LE" v-model="params.isConfirmed" toggle-indeterminate > @@ -101,13 +101,14 @@ const entryFilterPanel = ref(); :label="t('params.landed')" v-model="params.landed" @update:model-value="searchFn()" - is-outlined + filled + data-cy="landed" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput v-model="params.id" label="Id" is-outlined /> + <VnInput v-model="params.id" label="Id" filled /> </QItemSection> </QItem> <QItem> @@ -117,15 +118,7 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" hide-selected dense - outlined - rounded - /> - </QItemSection> - <QItemSection> - <VnInput - v-model="params.invoiceNumber" - :label="t('params.invoiceNumber')" - is-outlined + filled /> </QItemSection> </QItem> @@ -134,7 +127,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.reference" :label="t('entry.list.tableVisibleColumns.reference')" - is-outlined + filled /> </QItemSection> </QItem> @@ -149,8 +142,7 @@ const entryFilterPanel = ref(); :fields="['id', 'name']" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -159,7 +151,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.evaNotes" :label="t('params.evaNotes')" - is-outlined + filled /> </QItemSection> </QItem> @@ -171,10 +163,10 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" url="Warehouses" :fields="['id', 'name']" + sort-by="name ASC" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -186,10 +178,10 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" url="Warehouses" :fields="['id', 'name']" + sort-by="name ASC" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -211,7 +203,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.invoiceNumber" :label="t('params.invoiceNumber')" - is-outlined + filled /> </QItemSection> </QItem> @@ -228,8 +220,7 @@ const entryFilterPanel = ref(); option-label="description" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -238,7 +229,7 @@ const entryFilterPanel = ref(); <VnInput v-model="params.evaNotes" :label="t('params.evaNotes')" - is-outlined + filled /> </QItemSection> </QItem> @@ -267,7 +258,7 @@ en: hasToShowDeletedEntries: Show deleted entries es: params: - isExcludedFromAvailable: Inventario + isExcludedFromAvailable: Excluida isOrdered: Pedida isConfirmed: Confirmado isReceived: Recibida diff --git a/src/pages/Entry/EntryLatestBuys.vue b/src/pages/Entry/EntryLatestBuys.vue deleted file mode 100644 index 73fdcbbbf..000000000 --- a/src/pages/Entry/EntryLatestBuys.vue +++ /dev/null @@ -1,264 +0,0 @@ -<script setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import { useStateStore } from 'stores/useStateStore'; -import { toDate } from 'src/filters'; - -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue'; -import VnTable from 'components/VnTable/VnTable.vue'; -import VnImg from 'src/components/ui/VnImg.vue'; - -const stateStore = useStateStore(); -const { t } = useI18n(); -const tableRef = ref(); -const columns = [ - { - align: 'center', - label: t('entry.latestBuys.tableVisibleColumns.image'), - name: 'itemFk', - columnField: { - component: VnImg, - attrs: ({ row }) => { - return { - id: row.id, - size: '50x50', - }; - }, - }, - columnFilter: false, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.itemFk'), - name: 'itemFk', - isTitle: true, - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.summary.packing'), - name: 'packing', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.summary.grouping'), - name: 'grouping', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('globals.quantity'), - name: 'quantity', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('globals.description'), - name: 'description', - }, - { - align: 'left', - label: t('globals.size'), - name: 'size', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('globals.tags'), - name: 'tags', - }, - { - align: 'left', - label: t('globals.type'), - name: 'type', - }, - { - align: 'left', - label: t('globals.intrastat'), - name: 'intrastat', - }, - { - align: 'left', - label: t('globals.origin'), - name: 'origin', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.weightByPiece'), - name: 'weightByPiece', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.isActive'), - name: 'isActive', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.family'), - name: 'family', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.entryFk'), - name: 'entryFk', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.summary.buyingValue'), - name: 'buyingValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.freightValue'), - name: 'freightValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.comissionValue'), - name: 'comissionValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.packageValue'), - name: 'packageValue', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.isIgnored'), - name: 'isIgnored', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.price2'), - name: 'price2', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.price3'), - name: 'price3', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.minPrice'), - name: 'minPrice', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.ektFk'), - name: 'ektFk', - }, - { - align: 'left', - label: t('globals.weight'), - name: 'weight', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.buys.packagingFk'), - name: 'packagingFk', - columnFilter: { - component: 'number', - inWhere: true, - }, - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.packingOut'), - name: 'packingOut', - }, - { - align: 'left', - label: t('entry.latestBuys.tableVisibleColumns.landing'), - name: 'landing', - component: 'date', - columnField: { - component: null, - }, - format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)), - }, -]; - -onMounted(async () => { - stateStore.rightDrawer = true; -}); - -onUnmounted(() => (stateStore.rightDrawer = false)); -</script> - -<template> - <RightMenu> - <template #right-panel> - <EntryLatestBuysFilter data-key="LatestBuys" /> - </template> - </RightMenu> - <VnSubToolbar /> - <VnTable - ref="tableRef" - data-key="LatestBuys" - url="Buys/latestBuysFilter" - order="id DESC" - :columns="columns" - redirect="entry" - :row-click="({ entryFk }) => tableRef.redirect(entryFk)" - auto-load - :right-search="false" - /> -</template> diff --git a/src/pages/Entry/EntryLatestBuysFilter.vue b/src/pages/Entry/EntryLatestBuysFilter.vue deleted file mode 100644 index 658ba3847..000000000 --- a/src/pages/Entry/EntryLatestBuysFilter.vue +++ /dev/null @@ -1,161 +0,0 @@ -<script setup> -import { ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import VnInputDate from 'src/components/common/VnInputDate.vue'; -import VnInput from 'components/common/VnInput.vue'; -import VnSelect from 'components/common/VnSelect.vue'; -import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue'; -import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; - -const { t } = useI18n(); - -defineProps({ - dataKey: { - type: String, - required: true, - }, -}); - -const tagValues = ref([]); -</script> - -<template> - <ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']"> - <template #body="{ params, searchFn }"> - <QItem class="q-my-md"> - <QItemSection> - <VnSelect - :label="t('components.itemsFilterPanel.salesPersonFk')" - v-model="params.salesPersonFk" - url="TicketRequests/getItemTypeWorker" - option-label="nickname" - :fields="['id', 'nickname']" - sort-by="nickname ASC" - dense - outlined - rounded - use-input - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem class="q-my-md"> - <QItemSection> - <VnSelectSupplier - v-model="params.supplierFk" - url="Suppliers" - :fields="['id', 'name', 'nickname']" - sort-by="name ASC" - dense - outlined - rounded - /> - </QItemSection> - </QItem> - <QItem class="q-my-md"> - <QItemSection> - <VnInputDate - :label="t('components.itemsFilterPanel.started')" - v-model="params.from" - is-outlined - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem class="q-my-md"> - <QItemSection> - <VnInputDate - :label="t('components.itemsFilterPanel.ended')" - v-model="params.to" - is-outlined - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <QCheckbox - :label="t('components.itemsFilterPanel.active')" - v-model="params.active" - toggle-indeterminate - @update:model-value="searchFn()" - /> - </QItemSection> - <QItemSection> - <QCheckbox - :label="t('globals.visible')" - v-model="params.visible" - toggle-indeterminate - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <QCheckbox - :label="t('components.itemsFilterPanel.floramondo')" - v-model="params.floramondo" - toggle-indeterminate - @update:model-value="searchFn()" - /> - </QItemSection> - </QItem> - - <QItem - v-for="(value, index) in tagValues" - :key="value" - class="q-mt-md filter-value" - > - <QItemSection class="col"> - <VnSelect - :label="t('params.tag')" - v-model="value.selectedTag" - :options="tagOptions" - option-label="name" - dense - outlined - rounded - :emit-value="false" - use-input - :is-clearable="false" - @update:model-value="getSelectedTagValues(value)" - /> - </QItemSection> - <QItemSection class="col"> - <VnSelect - v-if="!value?.selectedTag?.isFree && value.valueOptions" - :label="t('params.value')" - v-model="value.value" - :options="value.valueOptions || []" - option-value="value" - option-label="value" - dense - outlined - rounded - emit-value - use-input - :disable="!value" - :is-clearable="false" - class="filter-input" - @update:model-value="applyTags(params, searchFn)" - /> - <VnInput - v-else - v-model="value.value" - :label="t('params.value')" - :disable="!value" - is-outlined - class="filter-input" - :is-clearable="false" - @keyup.enter="applyTags(params, searchFn)" - /> - </QItemSection> - <QIcon - name="delete" - class="filter-icon" - @click="removeTag(index, params, searchFn)" - /> - </QItem> - </template> - </ItemsFilterPanel> -</template> diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index f66151cc9..e42380fa3 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -107,9 +107,8 @@ const columns = computed(() => [ attrs: { url: 'suppliers', fields: ['id', 'name'], - where: { order: 'name DESC' }, + sortBy: 'name ASC', }, - format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), width: '110px', }, { @@ -145,6 +144,7 @@ const columns = computed(() => [ attrs: { url: 'agencyModes', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -158,7 +158,6 @@ const columns = computed(() => [ component: 'input', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.warehouseOutFk'), name: 'warehouseOutFk', cardVisible: true, @@ -166,6 +165,7 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -174,7 +174,6 @@ const columns = computed(() => [ width: '65px', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.warehouseInFk'), name: 'warehouseInFk', cardVisible: true, @@ -182,6 +181,7 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -190,7 +190,6 @@ const columns = computed(() => [ width: '65px', }, { - align: 'left', labelAbbreviation: t('Type'), label: t('entry.list.tableVisibleColumns.entryTypeDescription'), toolTip: t('entry.list.tableVisibleColumns.entryTypeDescription'), @@ -201,6 +200,7 @@ const columns = computed(() => [ fields: ['code', 'description'], optionValue: 'code', optionLabel: 'description', + sortBy: 'description', }, width: '65px', format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription), @@ -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' }; } @@ -283,7 +283,11 @@ onBeforeMount(async () => { </script> <template> - <VnSection :data-key="dataKey" prefix="entry"> + <VnSection + :data-key="dataKey" + prefix="entry" + :array-data-props="{ url: 'Entries/filter' }" + > <template #advanced-menu> <EntryFilter :data-key="dataKey" /> </template> diff --git a/src/pages/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue index 41f78617c..9e97e2ad5 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -1,24 +1,23 @@ <script setup> import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useState } from 'src/composables/useState'; -import { useQuasar } from 'quasar'; +import { useQuasar, date } from 'quasar'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import FetchData from 'components/FetchData.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnRow from 'components/ui/VnRow.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import EntryStockBoughtFilter from './EntryStockBoughtFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import EntryStockBoughtDetail from 'src/pages/Entry/EntryStockBoughtDetail.vue'; +import TravelDescriptorProxy from '../Travel/Card/TravelDescriptorProxy.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; +import axios from 'axios'; const { t } = useI18n(); const quasar = useQuasar(); -const state = useState(); -const user = state.getUser(); +const filterDate = ref(useFilterParams('StockBoughts').params); const footer = ref({ bought: 0, reserve: 0 }); const columns = computed(() => [ { @@ -46,7 +45,7 @@ const columns = computed(() => [ optionValue: 'id', }, columnFilter: false, - width: '50px', + width: '60%', }, { align: 'center', @@ -56,20 +55,20 @@ const columns = computed(() => [ create: true, component: 'number', summation: true, - width: '50px', format: ({ reserve }, dashIfEmpty) => dashIfEmpty(round(reserve)), + width: '20%', }, { - align: 'center', + align: 'right', label: t('entryStockBought.bought'), name: 'bought', summation: true, cardVisible: true, style: ({ reserve, bought }) => boughtStyle(bought, reserve), columnFilter: false, + width: '20%', }, { - align: 'left', label: t('entryStockBought.date'), name: 'dated', component: 'date', @@ -77,7 +76,7 @@ const columns = computed(() => [ create: true, }, { - align: 'left', + align: 'center', name: 'tableActions', actions: [ { @@ -90,7 +89,7 @@ const columns = computed(() => [ component: EntryStockBoughtDetail, componentProps: { workerFk: row.workerFk, - dated: userParams.value.dated, + dated: filterDate.value.dated, }, }); }, @@ -98,39 +97,30 @@ const columns = computed(() => [ ], }, ]); - const fetchDataRef = ref(); const travelDialogRef = ref(false); const tableRef = ref(); const travel = ref(null); -const userParams = ref({ - dated: Date.vnNew().toJSON(), -}); - -const filter = ref({ - fields: ['id', 'm3', 'warehouseInFk'], +const filter = computed(() => ({ + fields: ['id', 'm3', 'ref', 'warehouseInFk'], include: [ { relation: 'warehouseIn', scope: { - fields: ['code'], + fields: ['code', 'name'], }, }, ], where: { - shipped: (userParams.value.dated - ? new Date(userParams.value.dated) - : Date.vnNew() - ).setHours(0, 0, 0, 0), + shipped: date.adjustDate(filterDate.value.dated, { + hour: 0, + minute: 0, + second: 0, + milliseconds: 0, + }), m3: { neq: null }, }, -}); - -const setUserParams = async ({ dated }) => { - const shipped = (dated ? new Date(dated) : Date.vnNew()).setHours(0, 0, 0, 0); - filter.value.where.shipped = shipped; - fetchDataRef.value?.fetch(); -}; +})); function openDialog() { travelDialogRef.value = true; @@ -151,6 +141,31 @@ function round(value) { function boughtStyle(bought, reserve) { return reserve < bought ? { color: 'var(--q-negative)' } : ''; } + +async function beforeSave(data, getChanges) { + const changes = data.creates; + if (!changes) return data; + const patchPromises = []; + + for (const change of changes) { + if (change?.isReal === false && change?.reserve > 0) { + const postData = { + workerFk: change.workerFk, + reserve: change.reserve, + dated: filterDate.value.dated, + }; + const promise = axios.post('StockBoughts', postData).catch((error) => { + console.error('Error processing change: ', change, error); + }); + + patchPromises.push(promise); + } + } + + await Promise.all(patchPromises); + data.creates = []; + return data; +} </script> <template> <VnSubToolbar> @@ -158,18 +173,17 @@ function boughtStyle(bought, reserve) { <FetchData ref="fetchDataRef" url="Travels" - auto-load :filter="filter" @on-fetch=" (data) => { travel = data.find( - (data) => data.warehouseIn?.code.toLowerCase() === 'vnh', + (data) => data.warehouseIn?.code?.toLowerCase() === 'vnh', ); } " /> <VnRow class="travel"> - <div v-if="travel"> + <div v-show="travel"> <span style="color: var(--vn-label-color)"> {{ t('entryStockBought.purchaseSpaces') }}: </span> @@ -180,7 +194,7 @@ function boughtStyle(bought, reserve) { v-if="travel?.m3" style="max-width: 20%" flat - icon="edit" + icon="search" @click="openDialog()" :title="t('entryStockBought.editTravel')" color="primary" @@ -190,62 +204,47 @@ function boughtStyle(bought, reserve) { </VnRow> </template> </VnSubToolbar> - <QDialog v-model="travelDialogRef" :maximized="true" :class="['vn-row', 'wrap']"> + <QDialog v-model="travelDialogRef" :class="['vn-row', 'wrap']"> <FormModelPopup :url-update="`Travels/${travel?.id}`" model="travel" :title="t('Travel m3')" - :form-initial-data="{ id: travel?.id, m3: travel?.m3 }" + :form-initial-data="travel" @on-data-saved="fetchDataRef.fetch()" > <template #form-inputs="{ data }"> - <VnInput - v-model="data.id" - :label="t('id')" - type="number" - disable - readonly - /> + <span class="link"> + {{ data.ref }} + <TravelDescriptorProxy :id="data.id" /> + </span> <VnInput v-model="data.m3" :label="t('m3')" type="number" /> </template> </FormModelPopup> </QDialog> - <RightMenu> - <template #right-panel> - <EntryStockBoughtFilter - data-key="StockBoughts" - @set-user-params="setUserParams" - /> - </template> - </RightMenu> <div class="table-container"> <div class="column items-center"> <VnTable ref="tableRef" data-key="StockBoughts" url="StockBoughts/getStockBought" + :beforeSaveFn="beforeSave" save-url="StockBoughts/crud" search-url="StockBoughts" - order="reserve DESC" - :right-search="false" + order="bought DESC" :is-editable="true" - @on-fetch="(data) => setFooter(data)" - :create="{ - urlCreate: 'StockBoughts', - title: t('entryStockBought.reserveSomeSpace'), - onDataSaved: () => tableRef.reload(), - formInitialData: { - workerFk: user.id, - dated: Date.vnNow(), - }, - }" + @on-fetch=" + async (data) => { + setFooter(data); + await fetchDataRef.fetch(); + } + " :columns="columns" - :user-params="userParams" :footer="true" table-height="80vh" - auto-load :column-search="false" :without-header="true" + :user-params="{ dated: Date.vnNew() }" + auto-load > <template #column-workerFk="{ row }"> <span class="link" @click.stop> @@ -254,12 +253,15 @@ function boughtStyle(bought, reserve) { </span> </template> <template #column-footer-reserve> - <span> + <span class="q-pr-xs"> {{ round(footer.reserve) }} </span> </template> <template #column-footer-bought> - <span :style="boughtStyle(footer?.bought, footer?.reserve)"> + <span + :style="boughtStyle(footer?.bought, footer?.reserve)" + class="q-pr-xs" + > {{ round(footer.bought) }} </span> </template> @@ -277,10 +279,7 @@ function boughtStyle(bought, reserve) { } .column { min-width: 35%; - margin-top: 5%; - display: flex; - flex-direction: column; - align-items: center; + margin-top: 1%; } .text-negative { color: $negative !important; diff --git a/src/pages/Entry/EntryStockBoughtFilter.vue b/src/pages/Entry/EntryStockBoughtFilter.vue deleted file mode 100644 index 136881f17..000000000 --- a/src/pages/Entry/EntryStockBoughtFilter.vue +++ /dev/null @@ -1,70 +0,0 @@ -<script setup> -import { useI18n } from 'vue-i18n'; -import { onMounted } from 'vue'; -import { useStateStore } from 'stores/useStateStore'; - -import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; -import VnInputDate from 'src/components/common/VnInputDate.vue'; - -const { t } = useI18n(); -const props = defineProps({ - dataKey: { - type: String, - required: true, - }, -}); -const stateStore = useStateStore(); -const emit = defineEmits(['set-user-params']); -const setUserParams = (params) => { - emit('set-user-params', params); -}; -onMounted(async () => { - stateStore.rightDrawer = true; -}); -</script> - -<template> - <VnFilterPanel - :data-key="props.dataKey" - :search-button="true" - search-url="StockBoughts" - @set-user-params="setUserParams" - > - <template #tags="{ tag, formatFn }"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> - </div> - </template> - <template #body="{ params, searchFn }"> - <QItem class="q-my-sm"> - <QItemSection> - <VnInputDate - id="date" - v-model="params.dated" - @update:model-value=" - (value) => { - params.dated = value; - setUserParams(params); - searchFn(); - } - " - :label="t('Date')" - is-outlined - /> - </QItemSection> - </QItem> - </template> - </VnFilterPanel> -</template> -<i18n> - en: - params: - dated: Date - workerFk: Worker - es: - Date: Fecha - params: - dated: Fecha - workerFk: Trabajador -</i18n> diff --git a/src/pages/Entry/MyEntries.vue b/src/pages/Entry/EntrySupplier.vue similarity index 67% rename from src/pages/Entry/MyEntries.vue rename to src/pages/Entry/EntrySupplier.vue index 3f7566ae0..d8b17007f 100644 --- a/src/pages/Entry/MyEntries.vue +++ b/src/pages/Entry/EntrySupplier.vue @@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import { toDate } from 'src/filters/index'; import { useQuasar } from 'quasar'; -import EntryBuysTableDialog from './EntryBuysTableDialog.vue'; +import EntrySupplierlDetail from './EntrySupplierlDetail.vue'; import VnTable from 'components/VnTable/VnTable.vue'; const { t } = useI18n(); @@ -18,18 +18,28 @@ const columns = computed(() => [ { align: 'left', name: 'id', - label: t('myEntries.id'), + label: t('entrySupplier.id'), columnFilter: false, + isId: true, + chip: { + condition: () => true, + }, + }, + { + align: 'left', + name: 'supplierName', + label: t('entrySupplier.supplierName'), + cardVisible: true, isTitle: true, }, { visible: false, align: 'right', - label: t('myEntries.shipped'), + label: t('entrySupplier.shipped'), name: 'shipped', columnFilter: { name: 'fromShipped', - label: t('myEntries.fromShipped'), + label: t('entrySupplier.fromShipped'), component: 'date', }, format: ({ shipped }) => toDate(shipped), @@ -37,26 +47,26 @@ const columns = computed(() => [ { visible: false, align: 'left', - label: t('myEntries.shipped'), + label: t('entrySupplier.shipped'), name: 'shipped', columnFilter: { name: 'toShipped', - label: t('myEntries.toShipped'), + label: t('entrySupplier.toShipped'), component: 'date', }, format: ({ shipped }) => toDate(shipped), cardVisible: true, }, { - align: 'right', - label: t('myEntries.shipped'), + align: 'left', + label: t('entrySupplier.shipped'), name: 'shipped', columnFilter: false, format: ({ shipped }) => toDate(shipped), }, { - align: 'right', - label: t('myEntries.landed'), + align: 'left', + label: t('entrySupplier.landed'), name: 'landed', columnFilter: false, format: ({ landed }) => toDate(landed), @@ -64,15 +74,13 @@ const columns = computed(() => [ { align: 'right', - label: t('myEntries.wareHouseIn'), + label: t('entrySupplier.wareHouseIn'), name: 'warehouseInFk', - format: (row) => { - row.warehouseInName; - }, + format: ({ warehouseInName }) => warehouseInName, cardVisible: true, columnFilter: { name: 'warehouseInFk', - label: t('myEntries.warehouseInFk'), + label: t('entrySupplier.warehouseInFk'), component: 'select', attrs: { url: 'warehouses', @@ -86,13 +94,13 @@ const columns = computed(() => [ }, { align: 'left', - label: t('myEntries.daysOnward'), + label: t('entrySupplier.daysOnward'), name: 'daysOnward', visible: false, }, { align: 'left', - label: t('myEntries.daysAgo'), + label: t('entrySupplier.daysAgo'), name: 'daysAgo', visible: false, }, @@ -101,8 +109,8 @@ const columns = computed(() => [ name: 'tableActions', actions: [ { - title: t('myEntries.printLabels'), - icon: 'move_item', + title: t('entrySupplier.printLabels'), + icon: 'search', isPrimary: true, action: (row) => printBuys(row.id), }, @@ -112,7 +120,7 @@ const columns = computed(() => [ const printBuys = (rowId) => { quasar.dialog({ - component: EntryBuysTableDialog, + component: EntrySupplierlDetail, componentProps: { id: rowId, }, @@ -121,19 +129,18 @@ const printBuys = (rowId) => { </script> <template> <VnSearchbar - data-key="myEntriesList" + data-key="entrySupplierList" url="Entries/filter" - :label="t('myEntries.search')" - :info="t('myEntries.searchInfo')" + :label="t('entrySupplier.search')" + :info="t('entrySupplier.searchInfo')" /> <VnTable - data-key="myEntriesList" + data-key="entrySupplierList" url="Entries/filter" :columns="columns" :user-params="params" default-mode="card" order="shipped DESC" auto-load - chip-locale="myEntries" /> </template> diff --git a/src/pages/Entry/EntryBuysTableDialog.vue b/src/pages/Entry/EntrySupplierlDetail.vue similarity index 87% rename from src/pages/Entry/EntryBuysTableDialog.vue rename to src/pages/Entry/EntrySupplierlDetail.vue index 7a6c4ac43..01f6012c5 100644 --- a/src/pages/Entry/EntryBuysTableDialog.vue +++ b/src/pages/Entry/EntrySupplierlDetail.vue @@ -30,7 +30,7 @@ const entriesTableColumns = computed(() => [ align: 'left', name: 'itemFk', field: 'itemFk', - label: t('entry.latestBuys.tableVisibleColumns.itemFk'), + label: t('entrySupplier.itemId'), }, { align: 'left', @@ -65,7 +65,15 @@ const entriesTableColumns = computed(() => [ ]); function downloadCSV(rows) { - const headers = ['id', 'itemFk', 'name', 'stickers', 'packing', 'grouping', 'comment']; + const headers = [ + 'id', + 'itemFk', + 'name', + 'stickers', + 'packing', + 'grouping', + 'comment', + ]; const csvRows = rows.map((row) => { const buy = row; @@ -119,17 +127,18 @@ function downloadCSV(rows) { > <template #top-left> <QBtn - :label="t('myEntries.downloadCsv')" + :label="t('entrySupplier.downloadCsv')" color="primary" icon="csv" @click="downloadCSV(rows)" unelevated + data-cy="downloadCsvBtn" /> </template> <template #top-right> <QBtn class="q-mr-lg" - :label="t('myEntries.printLabels')" + :label="t('entrySupplier.printLabels')" color="primary" icon="print" @click=" @@ -148,13 +157,18 @@ function downloadCSV(rows) { v-if="props.row.stickers > 0" @click=" openReport( - `Entries/${props.row.id}/buy-label-supplier` + `Entries/${props.row.id}/buy-label-supplier`, + {}, + true, ) " unelevated + color="primary" + flat + data-cy="seeLabelBtn" > <QTooltip>{{ - t('myEntries.viewLabel') + t('entrySupplier.viewLabel') }}</QTooltip> </QBtn> </QTr> diff --git a/src/pages/Entry/EntryWasteRecalc.vue b/src/pages/Entry/EntryWasteRecalc.vue index 6ae200ed7..2fcd0f843 100644 --- a/src/pages/Entry/EntryWasteRecalc.vue +++ b/src/pages/Entry/EntryWasteRecalc.vue @@ -38,7 +38,7 @@ const recalc = async () => { <template> <div class="q-pa-lg row justify-center"> - <QCard class="bg-light" style="width: 300px"> + <QCard class="bg-light" style="width: 300px" data-cy="wasteRecalc"> <QCardSection> <VnInputDate class="q-mb-lg" @@ -46,6 +46,7 @@ const recalc = async () => { :label="$t('globals.from')" rounded dense + data-cy="dateFrom" /> <VnInputDate class="q-mb-lg" @@ -55,6 +56,7 @@ const recalc = async () => { :disable="!dateFrom" rounded dense + data-cy="dateTo" /> <QBtn color="primary" @@ -63,6 +65,7 @@ const recalc = async () => { :loading="isLoading" :disable="isLoading || !(dateFrom && dateTo)" @click="recalc()" + data-cy="recalc" /> </QCardSection> </QCard> diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index 88b16cb03..0bc92a5ea 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -6,7 +6,7 @@ entry: list: newEntry: New entry tableVisibleColumns: - isExcludedFromAvailable: Exclude from inventory + isExcludedFromAvailable: Excluded from available isOrdered: Ordered isConfirmed: Ready to label isReceived: Received @@ -33,7 +33,7 @@ entry: invoiceAmount: Invoice amount ordered: Ordered booked: Booked - excludedFromAvailable: Inventory + excludedFromAvailable: Excluded travelReference: Reference travelAgency: Agency travelShipped: Shipped @@ -55,7 +55,7 @@ entry: commission: Commission observation: Observation booked: Booked - excludedFromAvailable: Inventory + excludedFromAvailable: Excluded initialTemperature: Ini °C finalTemperature: Fin °C buys: @@ -65,27 +65,10 @@ entry: printedStickers: Printed stickers notes: observationType: Observation type - latestBuys: - tableVisibleColumns: - image: Picture - itemFk: Item ID - weightByPiece: Weight/Piece - isActive: Active - family: Family - entryFk: Entry - freightValue: Freight value - comissionValue: Commission value - packageValue: Package value - isIgnored: Is ignored - price2: Grouping - price3: Packing - minPrice: Min - ektFk: Ekt - packingOut: Package out - landing: Landing - isExcludedFromAvailable: Es inventory params: - isExcludedFromAvailable: Exclude from inventory + entryFk: Entry + observationTypeFk: Observation type + isExcludedFromAvailable: Excluded from available isOrdered: Ordered isConfirmed: Ready to label isReceived: Received @@ -127,13 +110,17 @@ entry: company_name: Company name itemTypeFk: Item type workerFk: Worker id + daysAgo: Days ago + toShipped: T. shipped + fromShipped: F. shipped + supplierName: Supplier search: Search entries searchInfo: You can search by entry reference descriptorMenu: showEntryReport: Show entry report entryFilter: params: - isExcludedFromAvailable: Exclude from inventory + isExcludedFromAvailable: Excluded from available invoiceNumber: Invoice number travelFk: Travel companyFk: Company @@ -155,7 +142,7 @@ entryFilter: warehouseOutFk: Origin warehouseInFk: Destiny entryTypeCode: Entry type -myEntries: +entrySupplier: id: ID landed: Landed shipped: Shipped @@ -170,6 +157,8 @@ myEntries: downloadCsv: Download CSV search: Search entries searchInfo: You can search by entry reference + supplierName: Supplier + itemId: Item id entryStockBought: travel: Travel editTravel: Edit travel diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index 3025d64cb..2c80299bc 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -6,7 +6,7 @@ entry: list: newEntry: Nueva entrada tableVisibleColumns: - isExcludedFromAvailable: Excluir del inventario + isExcludedFromAvailable: Excluir del disponible isOrdered: Pedida isConfirmed: Lista para etiquetar isReceived: Recibida @@ -25,7 +25,7 @@ entry: entryTypeDescription: Tipo entrada invoiceAmount: Importe dated: Fecha - inventoryEntry: Es inventario + inventoryEntry: Es inventario summary: commission: Comisión currency: Moneda @@ -33,7 +33,8 @@ entry: invoiceAmount: Importe ordered: Pedida booked: Contabilizada - excludedFromAvailable: Inventario + excludedFromAvailable: Excluir del disponible + isConfirmed: Lista para etiquetar travelReference: Referencia travelAgency: Agencia travelShipped: F. envio @@ -56,7 +57,7 @@ entry: observation: Observación commission: Comisión booked: Contabilizada - excludedFromAvailable: Inventario + excludedFromAvailable: Excluir del disponible initialTemperature: Ini °C finalTemperature: Fin °C buys: @@ -66,30 +67,12 @@ entry: printedStickers: Etiquetas impresas notes: observationType: Tipo de observación - latestBuys: - tableVisibleColumns: - image: Foto - itemFk: Id Artículo - weightByPiece: Peso (gramos)/tallo - isActive: Activo - family: Familia - entryFk: Entrada - freightValue: Porte - comissionValue: Comisión - packageValue: Embalaje - isIgnored: Ignorado - price2: Grouping - price3: Packing - minPrice: Min - ektFk: Ekt - packingOut: Embalaje envíos - landing: Llegada - isExcludedFromAvailable: Es inventario - search: Buscar entradas searchInfo: Puedes buscar por referencia de entrada params: - isExcludedFromAvailable: Excluir del inventario + entryFk: Entrada + observationTypeFk: Tipo de observación + isExcludedFromAvailable: Excluir del disponible isOrdered: Pedida isConfirmed: Lista para etiquetar isReceived: Recibida @@ -131,11 +114,15 @@ entry: company_name: Nombre empresa itemTypeFk: Familia workerFk: Comprador + daysAgo: Días atras + toShipped: F. salida(hasta) + fromShipped: F. salida(desde) + supplierName: Proveedor entryFilter: params: - isExcludedFromAvailable: Inventario + isExcludedFromAvailable: Excluir del disponible isOrdered: Pedida - isConfirmed: Confirmado + isConfirmed: Lista para etiquetar isReceived: Recibida isRaid: Raid landed: Fecha @@ -149,7 +136,7 @@ entryFilter: warehouseInFk: Destino entryTypeCode: Tipo de entrada hasToShowDeletedEntries: Mostrar entradas eliminadas -myEntries: +entrySupplier: id: ID landed: F. llegada shipped: F. salida @@ -164,10 +151,12 @@ myEntries: downloadCsv: Descargar CSV search: Buscar entradas searchInfo: Puedes buscar por referencia de la entrada + supplierName: Proveedor + itemId: Id artículo entryStockBought: travel: Envío editTravel: Editar envío - purchaseSpaces: Espacios de compra + purchaseSpaces: Camiones reservados buyer: Comprador reserve: Reservado bought: Comprado diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index 905ddebb2..c3b678678 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -121,42 +121,49 @@ function deleteFile(dmsFk) { hide-selected :is-clearable="false" :required="true" + data-cy="invoiceInBasicDataSupplier" /> <VnInput clearable clear-icon="close" :label="t('invoiceIn.supplierRef')" v-model="data.supplierRef" + data-cy="invoiceInBasicDataSupplierRef" /> </VnRow> <VnRow> - <VnInputDate :label="t('Expedition date')" v-model="data.issued" /> + <VnInputDate + :label="t('Expedition date')" + v-model="data.issued" + data-cy="invoiceInBasicDataIssued" + /> <VnInputDate :label="t('Operation date')" v-model="data.operated" autofocus + data-cy="invoiceInBasicDataOperated" /> </VnRow> <VnRow> - <VnInputDate :label="t('Entry date')" v-model="data.bookEntried" /> - <VnInputDate :label="t('Accounted date')" v-model="data.booked" /> + <VnInputDate + :label="t('Entry date')" + v-model="data.bookEntried" + data-cy="invoiceInBasicDatabookEntried" + /> + <VnInputDate + :label="t('Accounted date')" + v-model="data.booked" + data-cy="invoiceInBasicDataBooked" + /> </VnRow> <VnRow> <VnSelect - :label="t('Undeductible VAT')" - v-model="data.deductibleExpenseFk" - :options="expenses" + :label="t('invoiceIn.summary.sage')" + v-model="data.withholdingSageFk" + :options="sageWithholdings" option-value="id" - option-label="id" - :filter-options="['id', 'name']" - data-cy="UnDeductibleVatSelect" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - {{ `${scope.opt.id}: ${scope.opt.name}` }} - </QItem> - </template> - </VnSelect> + option-label="withholding" + /> <div class="row no-wrap"> <VnInput @@ -182,6 +189,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="downloadFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDownload" /> <QBtn :class="{ @@ -197,6 +205,7 @@ function deleteFile(dmsFk) { documentDialogRef.dms = data.dms; } " + data-cy="invoiceInBasicDataDmsEdit" > <QTooltip>{{ t('Edit document') }}</QTooltip> </QBtn> @@ -210,6 +219,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="deleteFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDelete" /> </div> <QBtn @@ -224,7 +234,7 @@ function deleteFile(dmsFk) { delete documentDialogRef.dms; } " - data-cy="dms-create" + data-cy="invoiceInBasicDataDmsAdd" > <QTooltip>{{ t('Create document') }}</QTooltip> </QBtn> @@ -237,9 +247,9 @@ function deleteFile(dmsFk) { :label="t('Currency')" v-model="data.currencyFk" :options="currencies" - option-value="id" option-label="code" sort-by="id" + data-cy="invoiceInBasicDataCurrencyFk" /> <VnSelect @@ -249,17 +259,8 @@ function deleteFile(dmsFk) { :label="t('Company')" v-model="data.companyFk" :options="companies" - option-value="id" option-label="code" - /> - </VnRow> - <VnRow> - <VnSelect - :label="t('invoiceIn.summary.sage')" - v-model="data.withholdingSageFk" - :options="sageWithholdings" - option-value="id" - option-label="withholding" + data-cy="invoiceInBasicDataCompanyFk" /> </VnRow> </template> @@ -313,7 +314,6 @@ function deleteFile(dmsFk) { supplierFk: Proveedor Expedition date: Fecha expedición Operation date: Fecha operación - Undeductible VAT: Iva no deducible Document: Documento Download file: Descargar archivo Entry date: Fecha asiento diff --git a/src/pages/InvoiceIn/Card/InvoiceInCard.vue b/src/pages/InvoiceIn/Card/InvoiceInCard.vue index 34cc26437..a1bae87a6 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInCard.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInCard.vue @@ -1,5 +1,5 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import InvoiceInDescriptor from './InvoiceInDescriptor.vue'; import { onBeforeRouteUpdate } from 'vue-router'; import { setRectificative } from '../composables/setRectificative'; @@ -9,7 +9,7 @@ onBeforeRouteUpdate(async (to) => await setRectificative(to)); </script> <template> - <VnCardBeta + <VnCard data-key="InvoiceIn" url="InvoiceIns" :descriptor="InvoiceInDescriptor" diff --git a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue index 1d0a8d078..775a2a72b 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue @@ -1,22 +1,16 @@ <script setup> import { ref, computed, capitalize } from 'vue'; -import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; import CrudModel from 'src/components/CrudModel.vue'; import FetchData from 'src/components/FetchData.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -const route = useRoute(); const { t } = useI18n(); const arrayData = useArrayData(); const invoiceIn = computed(() => arrayData.store.data); const invoiceInCorrectionRef = ref(); -const filter = { - include: { relation: 'invoiceIn' }, - where: { correctingFk: route.params.id }, -}; const columns = computed(() => [ { name: 'origin', @@ -92,7 +86,8 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); v-if="invoiceIn" data-key="InvoiceInCorrection" url="InvoiceInCorrections" - :filter="filter" + :user-filter="{ include: { relation: 'invoiceIn' } }" + :filter="{ where: { correctingFk: $route.params.id } }" auto-load primary-key="correctingFk" :default-remove="false" @@ -115,6 +110,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); :option-label="col.optionLabel" :disable="row.invoiceIn.isBooked" :filter-options="['description']" + data-cy="invoiceInCorrective_type" > <template #option="{ opt, itemProps }"> <QItem v-bind="itemProps"> @@ -137,6 +133,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); :rules="[requiredFieldRule]" :filter-options="['code', 'description']" :disable="row.invoiceIn.isBooked" + data-cy="invoiceInCorrective_class" > <template #option="{ opt, itemProps }"> <QItem v-bind="itemProps"> @@ -161,6 +158,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField'); :option-label="col.optionLabel" :rules="[requiredFieldRule]" :disable="row.invoiceIn.isBooked" + data-cy="invoiceInCorrective_reason" /> </QTd> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index 3843f5bf7..c5d79b045 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'; import axios from 'axios'; import { toCurrency, toDate } from 'src/filters'; import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import filter from './InvoiceInFilter.js'; import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; @@ -17,20 +17,16 @@ const { t } = useI18n(); const cardDescriptorRef = ref(); const entityId = computed(() => $props.id || +currentRoute.value.params.id); const totalAmount = ref(); -const config = ref(); -const cplusRectificationTypes = ref([]); -const siiTypeInvoiceIns = ref([]); -const invoiceCorrectionTypes = ref([]); const invoiceInCorrection = reactive({ correcting: [], corrected: null }); const routes = reactive({ getSupplier: (id) => { return { name: 'SupplierCard', params: { id } }; }, - getTickets: (id) => { + getInvoices: (id) => { return { name: 'InvoiceInList', query: { - params: JSON.stringify({ supplierFk: id }), + table: JSON.stringify({ supplierFk: id }), }, }; }, @@ -39,7 +35,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ correctedFk: entityId.value }), + table: JSON.stringify({ correctedFk: entityId.value }), }, }; } @@ -88,7 +84,7 @@ async function setInvoiceCorrection(id) { } </script> <template> - <CardDescriptor + <EntityDescriptor ref="cardDescriptorRef" data-key="InvoiceIn" :url="`InvoiceIns/${entityId}`" @@ -108,7 +104,7 @@ async function setInvoiceCorrection(id) { <VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" /> <VnLv :label="t('invoiceIn.list.supplier')"> <template #value> - <span class="link"> + <span class="link" data-cy="invoiceInDescriptor_supplier"> {{ entity?.supplier?.nickname }} <SupplierDescriptorProxy :id="entity?.supplierFk" /> </span> @@ -135,11 +131,11 @@ async function setInvoiceCorrection(id) { </QBtn> <QBtn size="md" - icon="vn:ticket" + icon="vn:invoice-in" color="primary" - :to="routes.getTickets(entity.supplierFk)" + :to="routes.getInvoices(entity.supplierFk)" > - <QTooltip>{{ t('globals.ticketList') }}</QTooltip> + <QTooltip>{{ t('invoiceIn.descriptor.invoices') }}</QTooltip> </QBtn> <QBtn v-if=" @@ -163,7 +159,7 @@ async function setInvoiceCorrection(id) { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <style lang="scss" scoped> .q-dialog { diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue index 8b039ec27..058f17d31 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, toRefs, reactive } from 'vue'; +import { ref, computed, toRefs, reactive, onBeforeMount } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -111,10 +111,9 @@ async function cloneInvoice() { } const isAgricultural = () => { - if (!config.value) return false; return ( - invoiceIn.value?.supplier?.sageFarmerWithholdingFk === - config?.value[0]?.sageWithholdingFk + invoiceIn.value?.supplier?.sageWithholdingFk == + config.value?.sageFarmerWithholdingFk ); }; function showPdfInvoice() { @@ -153,162 +152,183 @@ const createInvoiceInCorrection = async () => { ); push({ path: `/invoice-in/${correctingId}/summary` }); }; + +onBeforeMount(async () => { + config.value = ( + await axios.get('invoiceinConfigs/findOne', { + params: { fields: ['sageFarmerWithholdingFk'] }, + }) + ).data; +}); </script> - <template> - <FetchData - url="InvoiceCorrectionTypes" - @on-fetch="(data) => (invoiceCorrectionTypes = data)" - auto-load - /> - <FetchData - url="CplusRectificationTypes" - @on-fetch="(data) => (cplusRectificationTypes = data)" - auto-load - /> - <FetchData - url="SiiTypeInvoiceIns" - :where="{ code: { like: 'R%' } }" - @on-fetch="(data) => (siiTypeInvoiceIns = data)" - auto-load - /> - <FetchData - url="InvoiceInConfigs" - :where="{ fields: ['sageWithholdingFk'] }" - auto-load - @on-fetch="(data) => (config = data)" - /> - <InvoiceInToBook> - <template #content="{ book }"> - <QItem - v-if="!invoice?.isBooked && canEditProp('toBook')" - v-ripple - clickable - @click="book(entityId)" + <template v-if="config"> + <FetchData + url="InvoiceCorrectionTypes" + @on-fetch="(data) => (invoiceCorrectionTypes = data)" + auto-load + /> + <FetchData + url="CplusRectificationTypes" + @on-fetch="(data) => (cplusRectificationTypes = data)" + auto-load + /> + <FetchData + url="SiiTypeInvoiceIns" + :where="{ code: { like: 'R%' } }" + @on-fetch="(data) => (siiTypeInvoiceIns = data)" + auto-load + /> + <InvoiceInToBook> + <template #content="{ book }"> + <QItem + v-if="!invoice?.isBooked && canEditProp('toBook')" + v-ripple + clickable + @click="book(entityId)" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> + </QItem> + </template> + </InvoiceInToBook> + <QItem + v-if="invoice?.isBooked && canEditProp('toUnbook')" + v-ripple + clickable + @click="triggerMenu('unbook')" + > + <QItemSection> + {{ t('invoiceIn.descriptorMenu.unbook') }} + </QItemSection> + </QItem> + <QItem + v-if="canEditProp('deleteById')" + v-ripple + clickable + @click="triggerMenu('delete')" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection> + </QItem> + <QItem + v-if="canEditProp('clone')" + v-ripple + clickable + @click="triggerMenu('clone')" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> + <QItemSection>{{ + t('invoiceIn.descriptorMenu.showAgriculturalPdf') + }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> + <QItemSection + >{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection > - <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> - </QItem> - </template> - </InvoiceInToBook> - <QItem - v-if="invoice?.isBooked && canEditProp('toUnbook')" - v-ripple - clickable - @click="triggerMenu('unbook')" - > - <QItemSection> - {{ t('invoiceIn.descriptorMenu.unbook') }} - </QItemSection> - </QItem> - <QItem - v-if="canEditProp('deleteById')" - v-ripple - clickable - @click="triggerMenu('delete')" - > - <QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection> - </QItem> - <QItem v-if="canEditProp('clone')" v-ripple clickable @click="triggerMenu('clone')"> - <QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection> - </QItem> - <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> - <QItemSection>{{ - t('invoiceIn.descriptorMenu.showAgriculturalPdf') - }}</QItemSection> - </QItem> - <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> - <QItemSection - >{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection + </QItem> + <QItem + v-if="!invoiceInCorrection.corrected" + v-ripple + clickable + @click="triggerMenu('correct')" + data-cy="createCorrectiveItem" > - </QItem> - <QItem - v-if="!invoiceInCorrection.corrected" - v-ripple - clickable - @click="triggerMenu('correct')" - data-cy="createCorrectiveItem" - > - <QItemSection - >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection + <QItemSection + >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection + > + </QItem> + <QItem + v-if="invoice.dmsFk" + v-ripple + clickable + @click="downloadFile(invoice.dmsFk)" > - </QItem> - <QItem v-if="invoice.dmsFk" v-ripple clickable @click="downloadFile(invoice.dmsFk)"> - <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> - </QItem> - <QDialog ref="correctionDialogRef"> - <QCard> - <QCardSection> - <QItem class="q-px-none"> - <span class="text-primary text-h6 full-width"> - {{ t('Create rectificative invoice') }} - </span> - <QBtn icon="close" flat round dense v-close-popup /> - </QItem> - </QCardSection> - <QCardSection> - <QItem> - <QItemSection> - <QInput - :label="t('Original invoice')" - v-model="entityId" - readonly - /> - <VnSelect - :label="`${useCapitalize(t('globals.class'))}`" - v-model="correctionFormData.invoiceClass" - :options="siiTypeInvoiceIns" - option-value="id" - option-label="code" - :required="true" - /> - </QItemSection> - <QItemSection> - <VnSelect - :label="`${useCapitalize(t('globals.type'))}`" - v-model="correctionFormData.invoiceType" - :options="cplusRectificationTypes" - option-value="id" - option-label="description" - :required="true" - > - <template #option="{ itemProps, opt }"> - <QItem v-bind="itemProps"> - <QItemSection> - <QItemLabel - >{{ opt.id }} - - {{ opt.description }}</QItemLabel - > - </QItemSection> - </QItem> - <div></div> - </template> - </VnSelect> + <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> + </QItem> + <QDialog ref="correctionDialogRef"> + <QCard data-cy="correctiveInvoiceDialog"> + <QCardSection> + <QItem class="q-px-none"> + <span class="text-primary text-h6 full-width"> + {{ t('Create rectificative invoice') }} + </span> + <QBtn icon="close" flat round dense v-close-popup /> + </QItem> + </QCardSection> + <QCardSection> + <QItem> + <QItemSection> + <QInput + :label="t('Original invoice')" + v-model="entityId" + readonly + /> + <VnSelect + :label="`${useCapitalize(t('globals.class'))}`" + v-model="correctionFormData.invoiceClass" + :options="siiTypeInvoiceIns" + option-value="id" + option-label="code" + :required="true" + data-cy="invoiceInDescriptorMenu_class" + /> + </QItemSection> + <QItemSection> + <VnSelect + :label="`${useCapitalize(t('globals.type'))}`" + v-model="correctionFormData.invoiceType" + :options="cplusRectificationTypes" + option-value="id" + option-label="description" + :required="true" + data-cy="invoiceInDescriptorMenu_type" + > + <template #option="{ itemProps, opt }"> + <QItem v-bind="itemProps"> + <QItemSection> + <QItemLabel + >{{ opt.id }} - + {{ opt.description }}</QItemLabel + > + </QItemSection> + </QItem> + <div></div> + </template> + </VnSelect> - <VnSelect - :label="`${useCapitalize(t('globals.reason'))}`" - v-model="correctionFormData.invoiceReason" - :options="invoiceCorrectionTypes" - option-value="id" - option-label="description" - :required="true" - /> - </QItemSection> - </QItem> - </QCardSection> - <QCardActions class="justify-end q-mr-sm"> - <QBtn flat :label="t('globals.close')" color="primary" v-close-popup /> - <QBtn - :label="t('globals.save')" - color="primary" - v-close-popup - @click="createInvoiceInCorrection" - :disable="isNotFilled" - /> - </QCardActions> - </QCard> - </QDialog> + <VnSelect + :label="`${useCapitalize(t('globals.reason'))}`" + v-model="correctionFormData.invoiceReason" + :options="invoiceCorrectionTypes" + option-value="id" + option-label="description" + :required="true" + data-cy="invoiceInDescriptorMenu_reason" + /> + </QItemSection> + </QItem> + </QCardSection> + <QCardActions class="justify-end q-mr-sm"> + <QBtn + flat + :label="t('globals.close')" + color="primary" + v-close-popup + /> + <QBtn + :label="t('globals.save')" + color="primary" + v-close-popup + @click="createInvoiceInCorrection" + :disable="isNotFilled" + data-cy="saveCorrectiveInvoice" + /> + </QCardActions> + </QCard> + </QDialog> + </template> </template> - <i18n> en: isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries diff --git a/src/pages/InvoiceIn/Card/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 18602f043..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', @@ -198,6 +205,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; color="orange-11" text-color="black" @click="book(entityId)" + data-cy="invoiceInSummary_book" /> </template> </InvoiceIntoBook> @@ -206,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"> - {{ 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"> @@ -334,6 +338,15 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </QTh> </QTr> </template> + <template #body-cell-isDeductible="{ row }"> + <QTd align="center"> + <QCheckbox + v-model="row.isDeductible" + disable + data-cy="isDeductible_checkbox" + /> + </QTd> + </template> <template #body-cell-vat="{ value: vatCell }"> <QTd :title="vatCell" shrink> {{ vatCell }} diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index eae255120..61c3040ae 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -53,6 +53,13 @@ const columns = computed(() => [ sortable: true, align: 'left', }, + { + name: 'isDeductible', + label: t('invoiceIn.isDeductible'), + field: (row) => row.isDeductible, + model: 'isDeductible', + align: 'center', + }, { name: 'sageiva', label: t('Sage iva'), @@ -119,6 +126,7 @@ const filter = { 'foreignValue', 'taxTypeSageFk', 'transactionTypeSageFk', + 'isDeductible', ], where: { invoiceInFk: route.params.id, @@ -202,6 +210,9 @@ function setCursor(ref) { :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" + :acls="[ + { model: 'Expense', props: '*', accessType: 'WRITE' }, + ]" @keydown.tab.prevent=" autocompleteExpense( $event, @@ -227,6 +238,14 @@ function setCursor(ref) { </VnSelectDialog> </QTd> </template> + <template #body-cell-isDeductible="{ row }"> + <QTd align="center"> + <QCheckbox + v-model="row.isDeductible" + data-cy="isDeductible_checkbox" + /> + </QTd> + </template> <template #body-cell-taxablebase="{ row }"> <QTd shrink> <VnInputNumber @@ -321,6 +340,7 @@ function setCursor(ref) { </QTd> <QTd /> <QTd /> + <QTd /> <QTd> {{ toCurrency(taxRateTotal) }} </QTd> diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index e010a1edb..6551a7ca9 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -7,6 +7,7 @@ import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import { dateRange } from 'src/filters'; import { date } from 'quasar'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; defineProps({ dataKey: { type: String, required: true } }); const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; @@ -39,17 +40,13 @@ function handleDaysAgo(params, daysAgo) { <VnInputDate :label="$t('globals.from')" v-model="params.from" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - :label="$t('globals.to')" - v-model="params.to" - is-outlined - /> + <VnInputDate :label="$t('globals.to')" v-model="params.to" filled /> </QItemSection> </QItem> <QItem> @@ -57,7 +54,7 @@ function handleDaysAgo(params, daysAgo) { <VnInputNumber :label="$t('globals.daysAgo')" v-model="params.daysAgo" - is-outlined + filled :step="0" @update:model-value="(val) => handleDaysAgo(params, val)" @remove="(val) => handleDaysAgo(params, val)" @@ -66,12 +63,7 @@ function handleDaysAgo(params, daysAgo) { </QItem> <QItem> <QItemSection> - <VnSelectSupplier - v-model="params.supplierFk" - dense - outlined - rounded - /> + <VnSelectSupplier v-model="params.supplierFk" dense filled /> </QItemSection> </QItem> <QItem> @@ -79,7 +71,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('supplierRef')" v-model="params.supplierRef" - is-outlined + filled lazy-rules /> </QItemSection> @@ -89,7 +81,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('fi')" v-model="params.fi" - is-outlined + filled lazy-rules /> </QItemSection> @@ -99,7 +91,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('serial')" v-model="params.serial" - is-outlined + filled lazy-rules /> </QItemSection> @@ -109,7 +101,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('account')" v-model="params.account" - is-outlined + filled lazy-rules /> </QItemSection> @@ -119,7 +111,7 @@ function handleDaysAgo(params, daysAgo) { <VnInput :label="getLocale('globals.params.awbCode')" v-model="params.awbCode" - is-outlined + filled lazy-rules /> </QItemSection> @@ -129,7 +121,7 @@ function handleDaysAgo(params, daysAgo) { <VnInputNumber :label="$t('globals.amount')" v-model="params.amount" - is-outlined + filled /> </QItemSection> </QItem> @@ -141,19 +133,19 @@ function handleDaysAgo(params, daysAgo) { url="Companies" option-label="code" :fields="['id', 'code']" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <QCheckbox + <VnCheckbox :label="$t('invoiceIn.isBooked')" v-model="params.isBooked" @update:model-value="searchFn()" toggle-indeterminate /> - <QCheckbox + <VnCheckbox :label="getLocale('params.correctingFk')" v-model="params.correctingFk" @update:model-value="searchFn()" diff --git a/src/pages/InvoiceIn/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 5bdbe197b..28f54f040 100644 --- a/src/pages/InvoiceIn/InvoiceInToBook.vue +++ b/src/pages/InvoiceIn/InvoiceInToBook.vue @@ -4,7 +4,7 @@ import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import { useArrayData } from 'src/composables/useArrayData'; -import qs from 'qs'; + const { notify, dialog } = useQuasar(); const { t } = useI18n(); @@ -56,22 +56,21 @@ 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) { - let type = 'positive'; - let message = t('globals.dataSaved'); - + let err = false; try { await axios.post(`InvoiceIns/${id}/toBook`); store.data.isBooked = true; } catch (e) { - type = 'negative'; - message = t('It was not able to book the invoice'); + err = true; + throw e; } finally { - notify({ type, message }); + if (!err) notify({ type: 'positive', message: t('globals.dataSaved') }); } } </script> diff --git a/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue b/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue index 19ed73e50..66b7fa433 100644 --- a/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue +++ b/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue @@ -25,8 +25,7 @@ const { t } = useI18n(); <VnInputNumber v-model="params.daysAgo" :label="t('params.daysAgo')" - outlined - rounded + filled dense /> </QItemSection> @@ -36,8 +35,7 @@ const { t } = useI18n(); <VnInput v-model="params.serial" :label="t('params.serial')" - outlined - rounded + filled dense /> </QItemSection> diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 548e6c201..a7a8d2469 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 @@ -14,6 +15,7 @@ invoiceIn: amount: Amount descriptor: ticketList: Ticket list + invoices: Supplier invoices descriptorMenu: book: Book unbook: Unbook @@ -57,7 +59,6 @@ invoiceIn: bank: Bank foreignValue: Foreign value dueTotal: Due day - noMatch: Do not match code: Code net: Net stems: Stems @@ -68,3 +69,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..c593f5a08 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 @@ -13,7 +14,7 @@ invoiceIn: awb: AWB amount: Importe descriptor: - ticketList: Listado de tickets + invoices: Facturas de proveedor descriptorMenu: book: Contabilizar unbook: Descontabilizar @@ -66,3 +67,4 @@ invoiceIn: isBooked: Contabilizada account: Cuenta contable correctingFk: Rectificativa + noMatch: No cuadra con el iva({totalTaxableBase}) diff --git a/src/pages/InvoiceOut/Card/InvoiceOutCard.vue b/src/pages/InvoiceOut/Card/InvoiceOutCard.vue index a50c9d247..cdb736555 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutCard.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutCard.vue @@ -1,10 +1,10 @@ <script setup> import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue'; -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import filter from './InvoiceOutFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="InvoiceOut" url="InvoiceOuts" :filter="filter" diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue index 9b5215986..b93b8c8b7 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -3,7 +3,7 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; @@ -34,7 +34,7 @@ function ticketFilter(invoice) { </script> <template> - <CardDescriptor + <EntityDescriptor ref="descriptor" :url="`InvoiceOuts/${entityId}`" :filter="filter" @@ -46,6 +46,11 @@ function ticketFilter(invoice) { <InvoiceOutDescriptorMenu :invoice-out-data="entity" :menu-ref="menuRef" /> </template> <template #body="{ entity }"> + <VnLv + v-if="entity.externalRef" + :label="t('invoiceOut.externalRef')" + :value="entity.externalRef" + /> <VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" /> <VnLv :label="t('globals.amount')" :value="toCurrency(entity.amount)" /> <VnLv v-if="entity.client" :label="t('globals.client')"> @@ -88,5 +93,5 @@ function ticketFilter(invoice) { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/InvoiceOut/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index 648b8e4e6..93a343565 100644 --- a/src/pages/InvoiceOut/InvoiceOutFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutFilter.vue @@ -7,6 +7,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; const { t } = useI18n(); const props = defineProps({ @@ -30,23 +31,23 @@ const states = ref(); <QItem> <QItemSection> <VnInput - :label="t('Customer ID')" + :label="t('globals.params.clientFk')" v-model="params.clientFk" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput v-model="params.fi" :label="t('FI')" is-outlined /> + <VnInput v-model="params.fi" :label="t('globals.params.fi')" filled /> </QItemSection> </QItem> <QItem> <QItemSection> <VnInputNumber - :label="t('Amount')" + :label="t('globals.amount')" v-model="params.amount" - is-outlined + filled data-cy="InvoiceOutFilterAmountBtn" /> </QItemSection> @@ -54,22 +55,20 @@ const states = ref(); <QItem> <QItemSection> <QInput - :label="t('Min')" + :label="t('invoiceOut.params.min')" dense lazy-rules - outlined - rounded + filled type="number" v-model.number="params.min" /> </QItemSection> <QItemSection> <QInput - :label="t('Max')" + :label="t('invoiceOut.params.max')" dense lazy-rules - outlined - rounded + filled type="number" v-model.number="params.max" /> @@ -78,7 +77,7 @@ const states = ref(); <QItem> <QItemSection> <QCheckbox - :label="t('Has PDF')" + :label="t('invoiceOut.params.hasPdf')" toggle-indeterminate v-model="params.hasPdf" /> @@ -88,14 +87,30 @@ const states = ref(); <QItemSection> <VnInputDate v-model="params.created" - :label="t('Created')" - is-outlined + :label="t('invoiceOut.params.created')" + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.dued" :label="t('Dued')" is-outlined /> + <VnInputDate + v-model="params.dued" + :label="t('invoiceOut.params.dued')" + filled + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" + /> </QItemSection> </QItem> </template> diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue index 392256473..53433c56b 100644 --- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue +++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue @@ -26,7 +26,7 @@ const serialTypesOptions = ref([]); const handleInvoiceOutSerialsFetch = (data) => { serialTypesOptions.value = Array.from( - new Set(data.map((item) => item.type).filter((type) => type)) + new Set(data.map((item) => item.type).filter((type) => type)), ); }; @@ -99,8 +99,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled data-cy="InvoiceOutGlobalClientSelect" > <template #option="scope"> @@ -124,19 +123,18 @@ onMounted(async () => { option-label="type" hide-selected dense - outlined - rounded + filled data-cy="InvoiceOutGlobalSerialSelect" /> <VnInputDate v-model="formData.invoiceDate" :label="t('invoiceDate')" - is-outlined + filled /> <VnInputDate v-model="formData.maxShipped" :label="t('maxShipped')" - is-outlined + filled data-cy="InvoiceOutGlobalMaxShippedDate" /> <VnSelect @@ -145,8 +143,7 @@ onMounted(async () => { :options="companiesOptions" option-label="code" dense - outlined - rounded + filled data-cy="InvoiceOutGlobalCompanySelect" /> <VnSelect @@ -154,8 +151,7 @@ onMounted(async () => { v-model="formData.printer" :options="printersOptions" dense - outlined - rounded + filled data-cy="InvoiceOutGlobalPrinterSelect" /> </div> @@ -166,7 +162,7 @@ onMounted(async () => { color="primary" class="q-mt-md full-width" unelevated - rounded + filled dense /> <QBtn @@ -175,7 +171,7 @@ onMounted(async () => { color="primary" class="q-mt-md full-width" unelevated - rounded + filled dense @click="getStatus = 'stopping'" /> diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index a6ec9923e..c87428a94 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -8,7 +8,7 @@ import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { usePrintService } from 'src/composables/usePrintService'; import VnTable from 'src/components/VnTable/VnTable.vue'; import InvoiceOutSummary from './Card/InvoiceOutSummary.vue'; -import { toCurrency, toDate } from 'src/filters/index'; +import { toCurrency, toDate, dashIfEmpty } from 'src/filters/index'; import { QBtn } from 'quasar'; import axios from 'axios'; import InvoiceOutFilter from './InvoiceOutFilter.vue'; @@ -16,6 +16,7 @@ import VnRow from 'src/components/ui/VnRow.vue'; import VnRadio from 'src/components/common/VnRadio.vue'; import VnInput from 'src/components/common/VnInput.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from '../Worker/Department/Card/DepartmentDescriptorProxy.vue'; import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); @@ -54,6 +55,14 @@ const columns = computed(() => [ name: 'id', }, }, + { + align: 'left', + name: 'issued', + label: t('invoiceOut.summary.issued'), + component: 'date', + format: (row) => toDate(row.issued), + columnField: { component: null }, + }, { align: 'left', name: 'ref', @@ -70,6 +79,30 @@ const columns = computed(() => [ inWhere: true, }, }, + { + align: 'left', + name: 'issued', + label: t('invoiceOut.summary.issued'), + component: 'date', + format: (row) => toDate(row.issued), + columnField: { component: null }, + }, + { + align: 'left', + name: 'created', + label: t('globals.created'), + component: 'date', + columnField: { component: null }, + format: (row) => toDate(row.created), + }, + { + align: 'left', + name: 'dued', + label: t('invoiceOut.summary.expirationDate'), + component: 'date', + columnField: { component: null }, + format: (row) => toDate(row.dued), + }, { align: 'left', name: 'clientFk', @@ -86,6 +119,20 @@ const columns = computed(() => [ component: null, }, }, + { + align: 'left', + name: 'departmentFk', + label: t('customer.summary.team'), + cardVisible: true, + component: 'select', + attrs: { + url: 'Departments', + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), + }, { align: 'left', name: 'companyFk', @@ -109,22 +156,6 @@ const columns = computed(() => [ cardVisible: true, format: (row) => toCurrency(row.amount), }, - { - align: 'left', - name: 'created', - label: t('globals.created'), - component: 'date', - columnField: { component: null }, - format: (row) => toDate(row.created), - }, - { - align: 'left', - name: 'dued', - label: t('invoiceOut.summary.dued'), - component: 'date', - columnField: { component: null }, - format: (row) => toDate(row.dued), - }, { align: 'left', name: 'customsAgentFk', @@ -185,7 +216,7 @@ watchEffect(selectedRows); prefix="invoiceOut" :array-data-props="{ url: 'InvoiceOuts/filter', - order: ['id DESC'], + order: 'id DESC', }" > <template #advanced-menu> @@ -229,8 +260,14 @@ watchEffect(selectedRows); <CustomerDescriptorProxy :id="row.clientFk" /> </span> </template> + <template #column-departmentFk="{ row }"> + <span class="link" @click.stop> + {{ dashIfEmpty(row.departmentName) }} + <DepartmentDescriptorProxy :id="row?.departmentFk" /> + </span> + </template> <template #more-create-dialog="{ data }"> - <div class="row q-col-gutter-xs"> + <div class="row q-col-gutter-xs col-span-2"> <div class="col-12"> <div class="q-col-gutter-xs"> <VnRow fixed> @@ -396,7 +433,6 @@ watchEffect(selectedRows); :label=" t('invoiceOutList.tableVisibleColumns.taxArea') " - :options="taxAreasOptions" option-label="code" option-value="code" /> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue index b062678a0..432cd07d7 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue @@ -8,7 +8,7 @@ import { useInvoiceOutGlobalStore } from 'src/stores/invoiceOutGlobal.js'; import { useArrayData } from 'src/composables/useArrayData'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import TicketDescriptorProxy from '../Ticket/Card/TicketDescriptorProxy.vue'; -import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from '../Worker/Department/Card/DepartmentDescriptorProxy.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import InvoiceOutNegativeBasesFilter from './InvoiceOutNegativeBasesFilter.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; @@ -115,18 +115,16 @@ const columns = computed(() => [ }, { align: 'left', - label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'), - name: 'workerName', + name: 'departmentFk', + label: t('customer.summary.team'), component: 'select', attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - where: { role: 'salesPerson' }, + url: 'Departments', }, columnField: { component: null, }, - format: (row, dashIfEmpty) => dashIfEmpty(row.workerName), + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, ]); @@ -198,10 +196,10 @@ const downloadCSV = async () => { <TicketDescriptorProxy :id="row.ticketFk" /> </span> </template> - <template #column-workerName="{ row }"> + <template #column-departmentFk="{ row }"> <span class="link" @click.stop> - {{ row.workerName }} - <WorkerDescriptorProxy :id="row.comercialId" /> + {{ row.departmentName }} + <DepartmentDescriptorProxy :id="row.departmentFk" /> </span> </template> <template #moreFilterPanel="{ params }"> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue index cd9836bb7..1e2f80ec2 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue @@ -20,7 +20,7 @@ const props = defineProps({ <VnFilterPanel :data-key="props.dataKey" :search-button="true" - :un-removable-params="['from', 'to']" + :unremovable-params="['from', 'to']" :hidden-tags="['from', 'to']" > <template #tags="{ tag, formatFn }"> @@ -35,17 +35,13 @@ const props = defineProps({ <VnInputDate v-model="params.from" :label="t('globals.from')" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - v-model="params.to" - :label="t('globals.to')" - is-outlined - /> + <VnInputDate v-model="params.to" :label="t('globals.to')" filled /> </QItemSection> </QItem> <QItem> @@ -57,8 +53,7 @@ const props = defineProps({ option-label="code" option-value="code" dense - outlined - rounded + filled @update:model-value="searchFn()" > <template #option="scope"> @@ -84,9 +79,8 @@ const props = defineProps({ v-model="params.country" option-label="name" option-value="name" - outlined dense - rounded + filled @update:model-value="searchFn()" > <template #option="scope"> @@ -110,9 +104,8 @@ const props = defineProps({ url="Clients" :label="t('globals.client')" v-model="params.clientId" - outlined dense - rounded + filled @update:model-value="searchFn()" /> </QItemSection> @@ -122,19 +115,21 @@ const props = defineProps({ <VnInputNumber v-model="params.amount" :label="t('globals.amount')" - is-outlined + filled :positive="false" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnSelectWorker - :label="t('invoiceOut.negativeBases.comercial')" - v-model="params.workerName" - option-value="name" - is-outlined - @update:model-value="searchFn()" + <VnSelect + dense + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> </QItemSection> </QItem> diff --git a/src/pages/InvoiceOut/locale/en.yml b/src/pages/InvoiceOut/locale/en.yml index 17d198351..9d6a4a244 100644 --- a/src/pages/InvoiceOut/locale/en.yml +++ b/src/pages/InvoiceOut/locale/en.yml @@ -1,6 +1,7 @@ invoiceOut: search: Search invoice searchInfo: You can search by invoice reference + externalRef: External Ref. params: id: ID company: Company @@ -12,7 +13,6 @@ invoiceOut: isActive: Active hasToInvoice: Has to invoice hasVerifiedData: Verified data - workerName: Worker isTaxDataChecked: Verified data amount: Amount clientFk: Client @@ -26,6 +26,7 @@ invoiceOut: max: Max hasPdf: Has PDF search: Contains + departmentFk: Department card: issued: Issued customerCard: Customer card diff --git a/src/pages/InvoiceOut/locale/es.yml b/src/pages/InvoiceOut/locale/es.yml index 3df95d6b2..f9448cd9b 100644 --- a/src/pages/InvoiceOut/locale/es.yml +++ b/src/pages/InvoiceOut/locale/es.yml @@ -1,6 +1,7 @@ invoiceOut: search: Buscar factura emitida searchInfo: Puedes buscar por referencia de la factura + externalRef: Ref. externa params: id: ID company: Empresa @@ -12,7 +13,6 @@ invoiceOut: isActive: Activo hasToInvoice: Debe facturar hasVerifiedData: Datos verificados - workerName: Comercial isTaxDataChecked: Datos comprobados amount: Importe clientFk: Cliente @@ -26,6 +26,7 @@ invoiceOut: max: Max hasPdf: Tiene PDF search: Contiene + departmentFk: Departamento card: issued: Fecha emisión customerCard: Ficha del cliente diff --git a/src/pages/Item/Card/ItemCard.vue b/src/pages/Item/Card/ItemCard.vue index 610b77a02..ddd21fe36 100644 --- a/src/pages/Item/Card/ItemCard.vue +++ b/src/pages/Item/Card/ItemCard.vue @@ -1,9 +1,9 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import ItemDescriptor from './ItemDescriptor.vue'; </script> <template> - <VnCardBeta + <VnCard data-key="Item" :url="`Items/${$route.params.id}/getCard`" :descriptor="ItemDescriptor" diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue index 84e07a293..09f63a3b1 100644 --- a/src/pages/Item/Card/ItemDescriptor.vue +++ b/src/pages/Item/Card/ItemDescriptor.vue @@ -3,7 +3,7 @@ import { computed, ref, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; @@ -90,7 +90,7 @@ const updateStock = async () => { </script> <template> - <CardDescriptor + <EntityDescriptor data-key="Item" :summary="$props.summary" :url="`Items/${entityId}/getCard`" @@ -162,7 +162,7 @@ const updateStock = async () => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Item/Card/ItemDescriptorProxy.vue b/src/pages/Item/Card/ItemDescriptorProxy.vue index f686e8221..6e1f6d71f 100644 --- a/src/pages/Item/Card/ItemDescriptorProxy.vue +++ b/src/pages/Item/Card/ItemDescriptorProxy.vue @@ -22,7 +22,7 @@ const $props = defineProps({ }); </script> <template> - <QPopupProxy style="max-width: 10px"> + <QPopupProxy style="max-width: 10px" data-cy="ItemDescriptor"> <ItemDescriptor v-if="$props.id" :id="$props.id" diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index fdfa1d3d1..eb156ce9f 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -1,574 +1,386 @@ <script setup> -import { onMounted, ref, onUnmounted, nextTick, computed } from 'vue'; +import { onMounted, ref, onUnmounted, computed, watch } from 'vue'; import { useI18n } from 'vue-i18n'; -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; -import FetchedTags from 'components/ui/FetchedTags.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import VnInputDate from 'src/components/common/VnInputDate.vue'; -import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue'; -import ItemFixedPriceFilter from './ItemFixedPriceFilter.vue'; -import { useQuasar } from 'quasar'; -import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue'; -import { tMobile } from 'src/composables/tMobile'; -import VnConfirm from 'components/ui/VnConfirm.vue'; -import FetchData from 'src/components/FetchData.vue'; import { useStateStore } from 'stores/useStateStore'; -import { toDate } from 'src/filters'; -import { useVnConfirm } from 'composables/useVnConfirm'; import { useState } from 'src/composables/useState'; -import useNotify from 'src/composables/useNotify.js'; -import axios from 'axios'; -import { isLower, isBigger } from 'src/filters/date.js'; + +import { beforeSave } from 'src/composables/updateMinPriceBeforeSave'; + +import FetchedTags from 'components/ui/FetchedTags.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import EditFixedPriceForm from 'src/pages/Item/components/EditFixedPriceForm.vue'; +import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; -import { QCheckbox } from 'quasar'; +import VnColor from 'src/components/common/VnColor.vue'; + +import { toDate } from 'src/filters'; +import { isLower, isBigger } from 'src/filters/date.js'; +import ItemFixedPriceFilter from './ItemFixedPriceFilter.vue'; +import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue'; +import { toCurrency } from 'src/filters'; -const quasar = useQuasar(); const stateStore = useStateStore(); const { t } = useI18n(); -const { openConfirmationModal } = useVnConfirm(); -const state = useState(); -const { notify } = useNotify(); const tableRef = ref(); -const editTableCellDialogRef = ref(null); -const user = state.getUser(); -const fixedPrices = ref([]); -const warehousesOptions = ref([]); -const hasSelectedRows = computed(() => rowsSelected.value.length > 0); -const rowsSelected = ref([]); -const itemFixedPriceFilterRef = ref(); - +const editFixedPriceForm = ref(null); +const selectedRows = ref([]); +const hasSelectedRows = computed(() => selectedRows.value.length > 0); +const isToClone = ref(false); +const dateColor = 'var(--vn-label-text-color)'; +const state = useState(); +const user = state.getUser().fn(); +const warehouse = computed(() => user.warehouseFk); onMounted(async () => { stateStore.rightDrawer = true; }); onUnmounted(() => (stateStore.rightDrawer = false)); -const defaultColumnAttrs = { - align: 'left', - sortable: true, -}; const columns = computed(() => [ { - label: t('item.fixedPrice.itemFk'), name: 'itemFk', - ...defaultColumnAttrs, - isId: true, - columnField: { - component: 'input', - type: 'number', - }, - columnClass: 'shrink', - }, - { - label: t('globals.name'), - name: 'name', - ...defaultColumnAttrs, - create: true, + label: t('item.fixedPrice.itemFk'), + labelAbbreviation: 'Id', + toolTip: t('item.fixedPrice.itemFk'), + component: 'input', columnFilter: { - component: 'select', attrs: { + component: 'select', url: 'Items', fields: ['id', 'name', 'subName'], optionLabel: 'name', - optionValue: 'name', + optionValue: 'id', uppercase: false, }, + inWhere: true, }, + width: '55px', + isEditable: false, + }, + { + labelAbbreviation: '', + name: 'hex', + columnSearch: false, + isEditable: false, + width: '9px', + component: 'select', + attrs: { + url: 'Inks', + fields: ['id', 'name'], + }, + }, + { + align: 'left', + label: t('globals.name'), + name: 'name', + create: true, + component: 'input', + isEditable: false, }, { label: t('item.fixedPrice.groupingPrice'), + labelAbbreviation: 'P. Group', + toolTip: t('item.fixedPrice.groupingPrice'), name: 'rate2', - ...defaultColumnAttrs, - component: 'input', - type: 'number', + component: 'number', + create: true, + createOrder: 3, + createAttrs: { + required: true, + }, + width: '50px', + format: (row) => toCurrency(row.rate2), }, { label: t('item.fixedPrice.packingPrice'), + labelAbbreviation: 'P. Pack', + toolTip: t('item.fixedPrice.packingPrice'), name: 'rate3', - ...defaultColumnAttrs, - component: 'input', - type: 'number', + component: 'number', + create: true, + createOrder: 4, + createAttrs: { + required: true, + }, + width: '50px', + format: (row) => toCurrency(row.rate3), + }, + { + name: 'hasMinPrice', + label: t('item.fixedPrice.hasMinPrice'), + labelAbbreviation: t('item.fixedPrice.MP'), + toolTip: t('item.fixedPrice.hasMinPrice'), + label: t('item.fixedPrice.hasMinPrice'), + component: 'checkbox', + attrs: { + toggleIndeterminate: false, + }, + width: '50px', }, - { label: t('item.fixedPrice.minPrice'), + labelAbbreviation: 'Min.P', + toolTip: t('item.fixedPrice.minPrice'), name: 'minPrice', - ...defaultColumnAttrs, - component: 'input', - type: 'number', + component: 'number', + width: '50px', + style: (row) => { + if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' }; + }, + format: (row) => toCurrency(row.minPrice), }, { label: t('item.fixedPrice.started'), - field: 'started', name: 'started', - format: ({ started }) => toDate(started), - ...defaultColumnAttrs, - columnField: { - component: 'date', - class: 'shrink', - }, + component: 'date', columnFilter: { component: 'date', }, - columnClass: 'expand', + createAttrs: { + required: true, + }, + create: true, + createOrder: 5, + width: '65px', }, { label: t('item.fixedPrice.ended'), name: 'ended', - ...defaultColumnAttrs, - columnField: { - component: 'date', - class: 'shrink', - }, + component: 'date', columnFilter: { component: 'date', }, - columnClass: 'expand', - format: (row) => toDate(row.ended), + createAttrs: { + required: true, + }, + create: true, + createOrder: 6, + width: '65px', }, - { + align: 'center', label: t('globals.warehouse'), name: 'warehouseFk', - ...defaultColumnAttrs, - columnClass: 'shrink', component: 'select', - options: warehousesOptions, - columnFilter: { - name: 'warehouseFk', - inWhere: true, - component: 'select', - attrs: { - options: warehousesOptions, - 'option-label': 'name', - 'option-value': 'id', - }, + attrs: { + url: 'Warehouses', + fields: ['id', 'name'], + optionLabel: 'name', + optionValue: 'id', }, + create: true, + format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseName), + width: '80px', }, { align: 'right', name: 'tableActions', actions: [ { - title: t('delete'), - icon: 'delete', - action: (row) => confirmRemove(row), + title: t('globals.clone'), + icon: 'vn:clone', + action: (row) => openCloneFixedPriceForm(row), isPrimary: true, }, ], }, ]); -const editTableFieldsOptions = [ - { - field: 'rate2', - label: t('item.fixedPrice.groupingPrice'), - component: 'input', - attrs: { - type: 'number', - }, - }, - { - field: 'rate3', - label: t('item.fixedPrice.packingPrice'), - component: 'input', - attrs: { - type: 'number', - }, - }, - { - field: 'minPrice', - label: t('item.fixedPrice.minPrice'), - component: 'input', - attrs: { - type: 'number', - }, - }, - { - field: 'started', - label: t('item.fixedPrice.started'), - component: 'date', - }, - { - field: 'ended', - label: t('item.fixedPrice.ended'), - component: 'date', - }, - { - field: 'warehouseFk', - label: t('globals.warehouse'), - component: 'select', - attrs: { - options: [], - 'option-label': 'name', - 'option-value': 'id', - }, - }, -]; -const getRowUpdateInputEvents = (props, resetMinPrice, inputType = 'text') => { - return inputType === 'text' - ? { - 'keyup.enter': () => upsertPrice(props, resetMinPrice), - blur: () => upsertPrice(props, resetMinPrice), - } - : { 'update:modelValue': () => upsertPrice(props, resetMinPrice) }; +const openEditFixedPriceForm = () => { + editFixedPriceForm.value.show(); }; -const updateMinPrice = async (value, props) => { - props.row.hasMinPrice = value; - await upsertPrice({ - row: props.row, - col: { field: 'hasMinPrice' }, - rowIndex: props.rowIndex, - }); +const openCloneFixedPriceForm = (row) => { + tableRef.value.showForm = true; + isToClone.value = true; + tableRef.value.create.title = t('Clone fixed price'); + tableRef.value.create.formInitialData = (({ + itemFk, + rate2, + rate3, + started, + ended, + warehouseFk, + }) => ({ + itemFk, + rate2, + rate3, + started, + ended, + warehouseFk, + }))(JSON.parse(JSON.stringify(row))); }; -const validations = ({ row }) => { - const requiredFields = [ - 'itemFk', - 'started', - 'ended', - 'rate2', - 'rate3', - 'warehouseFk', - ]; - const isValid = requiredFields.every( - (field) => row[field] !== null && row[field] !== undefined - ); - return isValid; -}; -const upsertPrice = async (props, resetMinPrice = false) => { - const isValid = validations({ ...props }); - if (!isValid) { - return; - } - const { row } = props; - const changes = tableRef.value.CrudModelRef.getChanges(); - if (changes?.updates?.length > 0) { - if (resetMinPrice) row.hasMinPrice = 0; - } - if (!changes.updates && !changes.creates) return; - const data = await upsertFixedPrice(row); - Object.assign(tableRef.value.CrudModelRef.formData[props.rowIndex], data); - notify(t('globals.dataSaved'), 'positive'); - tableRef.value.reload(); -}; - -async function upsertFixedPrice(row) { - const { data } = await axios.patch('FixedPrices/upsertFixedPrice', row); - data.hasMinPrice = data.hasMinPrice ? 1 : 0; - return data; -} - -function checkLastVisibleRow() { - let lastVisibleRow = null; - - getTableRows().forEach((row, index) => { - const rect = row.getBoundingClientRect(); - if (rect.top >= 0 && rect.bottom <= window.innerHeight) { - lastVisibleRow = index; - } - }); - - return lastVisibleRow; -} - -const addRow = (original = null) => { - let copy = null; - const today = Date.vnNew(); - const millisecsInDay = 86400000; - const daysInWeek = 7; - const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay); - - copy = { - id: 0, - started: today, - ended: nextWeek, - hasMinPrice: 0, - $index: 0, - }; - return { original, copy }; -}; - -const getTableRows = () => - document.getElementsByClassName('q-table')[0].querySelectorAll('tr.cursor-pointer'); - -function highlightNewRow({ $index: index }) { - const row = getTableRows()[index]; - if (row) { - row.classList.add('highlight'); - setTimeout(() => { - row.classList.remove('highlight'); - }, 3000); - } -} -const openEditTableCellDialog = () => { - editTableCellDialogRef.value.show(); -}; - -const onEditCellDataSaved = async () => { - rowsSelected.value = []; - tableRef.value.reload(); -}; - -const removeFuturePrice = async () => { - rowsSelected.value.forEach(({ id }) => { - const rowIndex = fixedPrices.value.findIndex(({ id }) => id === id); - removePrice(id, rowIndex); - }); -}; - -function confirmRemove(item, isFuture) { - const promise = async () => - isFuture ? removeFuturePrice(item.id) : removePrice(item.id); - quasar.dialog({ - component: VnConfirm, - componentProps: { - title: t('globals.rowWillBeRemoved'), - message: t('globals.confirmDeletion'), - promise, - }, - }); -} - -const removePrice = async (id) => { - await axios.delete(`FixedPrices/${id}`); - notify(t('globals.dataSaved'), 'positive'); - tableRef.value.reload({}); -}; const dateStyle = (date) => date ? { - 'bg-color': 'warning', - 'is-outlined': true, + color: 'var(--vn-black-text-color)', } - : {}; + : { color: dateColor, 'background-color': 'transparent' }; -function handleOnDataSave({ CrudModelRef }) { - const { original, copy } = addRow(CrudModelRef.formData[checkLastVisibleRow()]); - if (original) { - CrudModelRef.formData.splice(original?.$index ?? 0, 0, copy); - } else { - CrudModelRef.insert(copy); +const onDataSaved = () => { + tableRef.value.CrudModelRef.saveChanges(); + selectedRows.value = []; +}; + +onMounted(() => { + if (tableRef.value) { + tableRef.value.showForm = false; } - nextTick(() => { - highlightNewRow(original ?? { $index: 0 }); - }); -} +}); + +watch( + () => tableRef.value?.showForm, + (newVal) => { + if (!newVal) { + tableRef.value.create.title = ''; + tableRef.value.create.formInitialData = { warehouseFk: warehouse }; + if (tableRef.value) { + isToClone.value = false; + tableRef.value.create.title = t('Create fixed price'); + } + } + }, +); </script> <template> - <FetchData - @on-fetch="(data) => (warehousesOptions = data)" - auto-load - url="Warehouses" - :filter="{ fields: ['id', 'name'], order: 'name ASC' }" - /> <RightMenu> <template #right-panel> - <ItemFixedPriceFilter - data-key="ItemFixedPrices" - ref="itemFixedPriceFilterRef" - /> + <ItemFixedPriceFilter data-key="ItemFixedPrices" /> </template> </RightMenu> - <VnSubToolbar> - <template #st-actions> + <VnSubToolbar /> + <Teleport to="#st-data" v-if="stateStore?.isSubToolbarShown()"> + <QBtnGroup push style="column-gap: 10px"> <QBtn :disable="!hasSelectedRows" - @click="openEditTableCellDialog()" + @click="openEditFixedPriceForm()" color="primary" icon="edit" + flat + :label="t('globals.edit')" + data-cy="FixedPriceToolbarEditBtn" > <QTooltip> {{ t('Edit fixed price(s)') }} </QTooltip> </QBtn> - <QBtn - :disable="!hasSelectedRows" - :label="tMobile('globals.remove')" - color="primary" - icon="delete" - flat - @click="(row) => confirmRemove(row, true)" - :title="t('globals.remove')" - /> - </template> - </VnSubToolbar> + </QBtnGroup> + </Teleport> <VnTable - :default-remove="false" - :default-reset="false" - :default-save="false" + ref="tableRef" data-key="ItemFixedPrices" url="FixedPrices/filter" - :order="['name DESC', 'itemFk DESC']" + :order="'name DESC'" save-url="FixedPrices/crud" - ref="tableRef" - dense - :filter="{ - where: { - warehouseFk: user.warehouseFk, - }, - }" :columns="columns" - default-mode="table" - auto-load :is-editable="true" :right-search="false" :table="{ 'row-key': 'id', selection: 'multiple', }" - v-model:selected="rowsSelected" - :create-as-dialog="false" + v-model:selected="selectedRows" :create="{ - onDataSaved: handleOnDataSave, + urlCreate: 'FixedPrices', + title: t('Create fixed price'), + formInitialData: { warehouseFk: warehouse }, + onDataSaved: () => tableRef.reload(), + showSaveAndContinueBtn: true, }" :disable-option="{ card: true }" - :has-sub-toolbar="false" + auto-load + :beforeSaveFn="(data, getChanges) => beforeSave(data, getChanges, 'FixedPrices')" > - <template #header-selection="scope"> - <QCheckbox v-model="scope.selected" /> + <template #column-hex="{ row }"> + <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> </template> - <template #body-selection="scope"> - {{ scope }} - <QCheckbox flat v-model="scope.selected" /> + <template #column-name="{ row }"> + <span class="link"> + {{ row.name }} + <ItemDescriptorProxy :id="row.itemFk" /> + </span> + <span class="subName">{{ row.subName }}</span> + <FetchedTags :item="row" :columns="6" /> </template> - - <template #column-itemFk="props"> + <template #column-started="{ row }"> + <div class="editable-text q-pb-xxs"> + <QBadge class="badge" :style="dateStyle(isLower(row?.ended))"> + {{ toDate(row?.started) }} + </QBadge> + </div> + </template> + <template #column-ended="{ row }"> + <div class="editable-text q-pb-xxs"> + <QBadge class="badge" :style="dateStyle(isBigger(row?.ended))"> + {{ toDate(row?.ended) }} + </QBadge> + </div> + </template> + <template #column-create-name="{ data }"> <VnSelect - style="max-width: 100px" - url="Items/withName" - hide-selected - option-label="id" + url="Items/search" + v-model="data.itemFk" + :label="t('item.fixedPrice.itemName')" + :fields="['id', 'name']" + :filter-options="['id', 'name']" + option-label="name" option-value="id" - v-model="props.row.itemFk" - v-on="getRowUpdateInputEvents(props, true, 'select')" + :required="true" + sort-by="name ASC" + data-cy="FixedPriceCreateNameSelect" > <template #option="scope"> <QItem v-bind="scope.itemProps"> <QItemSection> - <QItemLabel> #{{ scope.opt?.id }} </QItemLabel> - <QItemLabel caption>{{ scope.opt?.name }}</QItemLabel> + <QItemLabel> + {{ scope.opt.name }} + </QItemLabel> + <QItemLabel caption> #{{ scope.opt.id }} </QItemLabel> </QItemSection> </QItem> </template> </VnSelect> </template> - <template #column-name="{ row }"> - <span class="link"> - {{ row.name }} - </span> - <span class="subName">{{ row.subName }}</span> - <ItemDescriptorProxy :id="row.itemFk" /> - <FetchedTags :item="row" :columns="3" /> - </template> - <template #column-rate2="props"> - <QTd class="col"> - <VnInput - type="currency" - style="width: 75px" - v-model.number="props.row.rate2" - v-on="getRowUpdateInputEvents(props)" - > - <template #append>€</template> - </VnInput> - </QTd> - </template> - <template #column-rate3="props"> - <QTd class="col"> - <VnInput - style="width: 75px" - type="currency" - v-model.number="props.row.rate3" - v-on="getRowUpdateInputEvents(props)" - > - <template #append>€</template> - </VnInput> - </QTd> - </template> - <template #column-minPrice="props"> - <QTd class="col"> - <div class="row" style="align-items: center"> - <QCheckbox - :model-value="props.row.hasMinPrice" - @update:model-value="updateMinPrice($event, props)" - :false-value="0" - :true-value="1" - :toggle-indeterminate="false" - /> - <VnInput - class="col" - type="currency" - mask="###.##" - :disable="props.row.hasMinPrice === 0" - v-model.number="props.row.minPrice" - v-on="getRowUpdateInputEvents(props)" - > - <template #append>€</template> - </VnInput> - </div> - </QTd> - </template> - <template #column-started="props"> - <VnInputDate - class="vnInputDate" - :show-event="true" - v-model="props.row.started" - v-on="getRowUpdateInputEvents(props, false, 'date')" - v-bind="dateStyle(isBigger(props.row.started))" - /> - </template> - <template #column-ended="props"> - <VnInputDate - class="vnInputDate" - :show-event="true" - v-model="props.row.ended" - v-on="getRowUpdateInputEvents(props, false, 'date')" - v-bind="dateStyle(isLower(props.row.ended))" - /> - </template> - <template #column-warehouseFk="props"> - <QTd class="col"> - <VnSelect - style="max-width: 150px" - :options="warehousesOptions" - hide-selected - option-label="name" - option-value="id" - v-model="props.row.warehouseFk" - v-on="getRowUpdateInputEvents(props, false, 'select')" - /> - </QTd> - </template> - <template #column-deleteAction="{ row, rowIndex }"> - <QIcon - name="delete" - size="sm" - class="cursor-pointer fill-icon-on-hover" - color="primary" - @click.stop=" - openConfirmationModal( - t('globals.rowWillBeRemoved'), - t('Do you want to clone this item?'), - () => removePrice(row.id, rowIndex) - ) - " + <template #column-create-warehouseFk="{ data }"> + <VnSelect + :label="t('globals.warehouse')" + url="Warehouses" + v-model="data.warehouseFk" + :fields="['id', 'name']" + option-label="name" + option-value="id" + hide-selected + :required="true" + sort-by="name ASC" + data-cy="FixedPriceCreateWarehouseSelect" > - <QTooltip class="text-no-wrap"> - {{ t('globals.delete') }} - </QTooltip> - </QIcon> + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt.name }} + </QItemLabel> + <QItemLabel caption> #{{ scope.opt.id }} </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </template> </VnTable> - - <QDialog ref="editTableCellDialogRef"> - <EditTableCellValueForm + <QDialog ref="editFixedPriceForm"> + <EditFixedPriceForm edit-url="FixedPrices/editFixedPrice" - :rows="rowsSelected" - :fields-options="editTableFieldsOptions" - @on-data-saved="onEditCellDataSaved()" + :rows="selectedRows" + :fields-options=" + columns.filter( + ({ isEditable, component, name }) => + isEditable !== false && component && name !== 'itemFk', + ) + " + :beforeSave="beforeSave" + @on-data-saved="onDataSaved" /> </QDialog> </template> @@ -623,8 +435,17 @@ tbody tr.highlight .q-td { color: var(--vn-label-color); } </style> + +<style lang="scss" scoped> +.badge { + background-color: $warning; +} +</style> + <i18n> es: Add fixed price: Añadir precio fijado Edit fixed price(s): Editar precio(s) fijado(s) + Create fixed price: Crear precio fijado + Clone fixed price: Clonar precio fijado </i18n> diff --git a/src/pages/Item/ItemFixedPriceFilter.vue b/src/pages/Item/ItemFixedPriceFilter.vue index 8d92e245d..9c11a2e69 100644 --- a/src/pages/Item/ItemFixedPriceFilter.vue +++ b/src/pages/Item/ItemFixedPriceFilter.vue @@ -13,7 +13,6 @@ const props = defineProps({ required: true, }, }); - </script> <template> @@ -28,9 +27,9 @@ const props = defineProps({ :fields="['id', 'nickname']" option-label="nickname" dense - outlined - rounded + filled use-input + :use-like="false" @update:model-value="searchFn()" sort-by="nickname ASC" /> @@ -46,29 +45,26 @@ const props = defineProps({ :label="t('params.warehouseFk')" v-model="params.warehouseFk" dense - outlined - rounded + filled use-input @update:model-value="searchFn()" /> </QItemSection> </QItem> - <QItem class="q-my-md"> + <QItem> <QItemSection> <VnInputDate - :label="t('params.started')" v-model="params.started" - is-outlined + :label="t('params.started')" + filled @update:model-value="searchFn()" /> </QItemSection> - </QItem> - <QItem class="q-my-md"> <QItemSection> <VnInputDate - :label="t('params.ended')" v-model="params.ended" - is-outlined + :label="t('params.ended')" + filled @update:model-value="searchFn()" /> </QItemSection> diff --git a/src/pages/Item/ItemListFilter.vue b/src/pages/Item/ItemListFilter.vue index 22e948e06..ab9b91d06 100644 --- a/src/pages/Item/ItemListFilter.vue +++ b/src/pages/Item/ItemListFilter.vue @@ -7,7 +7,7 @@ import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; -import { QCheckbox } from 'quasar'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; import { useArrayData } from 'composables/useArrayData'; import { useValidator } from 'src/composables/useValidator'; @@ -177,11 +177,7 @@ onMounted(async () => { <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('params.search')" - is-outlined - /> + <VnInput v-model="params.search" :label="t('params.search')" filled /> </QItemSection> </QItem> <QItem> @@ -197,8 +193,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -213,8 +208,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -240,8 +234,7 @@ onMounted(async () => { option-label="nickname" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -252,16 +245,14 @@ onMounted(async () => { @update:model-value="searchFn()" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> <!-- Tags filter --> - <QItem class="row items-center"> - <QItemLabel> - {{ t('params.tags') }} - </QItemLabel> + <QItemLabel header> + {{ t('params.tags') }} + <QIcon name="add_circle" class="fill-icon-on-hover q-ml-md" @@ -269,7 +260,7 @@ onMounted(async () => { color="primary" @click="tagValues.push({})" /> - </QItem> + </QItemLabel> <QItem v-for="(tag, index) in tagValues" :key="index" @@ -277,13 +268,13 @@ onMounted(async () => { > <QItemSection class="col"> <VnSelect + class="full-width" :label="t('params.tag')" v-model="tag.selectedTag" :options="tagOptions" option-label="name" dense - outlined - rounded + filled :emit-value="false" use-input :is-clearable="false" @@ -299,8 +290,7 @@ onMounted(async () => { option-value="value" option-label="value" dense - outlined - rounded + filled emit-value use-input :disable="!tag" @@ -312,7 +302,7 @@ onMounted(async () => { v-model="tag.value" :label="t('params.value')" :disable="!tag" - is-outlined + filled :is-clearable="false" @keydown.enter.prevent="applyTags(params, searchFn)" /> @@ -326,33 +316,26 @@ onMounted(async () => { /> </QItem> <!-- Filter fields --> - <QItem class="row items-center"> - <QItemLabel> - {{ t('More fields') }} - </QItemLabel> + <QItemLabel header + >{{ t('More fields') }} <QIcon name="add_circle" class="fill-icon-on-hover q-ml-md" size="sm" color="primary" @click="fieldFiltersValues.push({})" - /> - </QItem> - <QItem - v-for="(fieldFilter, index) in fieldFiltersValues" - :key="index" - class="row items-center" - > + /></QItemLabel> + <QItem v-for="(fieldFilter, index) in fieldFiltersValues" :key="index"> <QItemSection class="col"> <VnSelect + class="full-width" :label="t('params.tag')" :model-value="fieldFilter.selectedField" :options="moreFields" option-label="label" option-value="label" dense - outlined - rounded + filled :emit-value="false" use-input :is-clearable="false" @@ -366,7 +349,7 @@ onMounted(async () => { /> </QItemSection> <QItemSection class="col"> - <QCheckbox + <VnCheckbox v-if="fieldFilter.selectedField?.type === 'boolean'" v-model="fieldFilter.value" :label="t('params.value')" @@ -377,17 +360,18 @@ onMounted(async () => { v-model="fieldFilter.value" :label="t('params.value')" :disable="!fieldFilter.selectedField" - is-outlined + filled @keydown.enter="applyFieldFilters(params, searchFn)" /> </QItemSection> - <QIcon - name="delete" - class="fill-icon-on-hover q-ml-xs" - size="sm" - color="primary" - @click="removeFieldFilter(index, params, searchFn)" - /> + <QItemSection side + ><QIcon + name="delete" + class="fill-icon-on-hover q-ml-xs" + size="sm" + color="primary" + @click="removeFieldFilter(index, params, searchFn)" + /></QItemSection> </QItem> <QItem> <QItemSection> diff --git a/src/pages/Item/ItemRequest.vue b/src/pages/Item/ItemRequest.vue index 43fc611d8..ccae98025 100644 --- a/src/pages/Item/ItemRequest.vue +++ b/src/pages/Item/ItemRequest.vue @@ -3,6 +3,7 @@ import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import { useStateStore } from 'stores/useStateStore'; import { toCurrency } from 'filters/index'; import useNotify from 'src/composables/useNotify.js'; @@ -61,6 +62,7 @@ const columns = computed(() => [ columnClass: 'expand', }, { + align: 'left', label: t('item.buyRequest.requester'), name: 'requesterName', component: 'select', @@ -77,6 +79,19 @@ const columns = computed(() => [ }, columnClass: 'shrink', }, + { + align: 'left', + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), + }, { label: t('item.buyRequest.requested'), name: 'quantity', @@ -107,6 +122,7 @@ const columns = computed(() => [ }, columnClass: 'shrink', }, + { label: t('globals.item'), name: 'item', @@ -262,6 +278,12 @@ const onDenyAccept = (_, responseData) => { <WorkerDescriptorProxy :id="row.requesterFk" /> </span> </template> + <template #column-departmentFk="{ row }"> + <span class="link" @click.stop> + {{ row.departmentName }} + <DepartmentDescriptorProxy :id="row.departmentFk" /> + </span> + </template> <template #column-item="{ row }"> <span> diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue index c2a63ddd9..68f36c566 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -87,11 +87,7 @@ onMounted(async () => { <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('params.search')" - is-outlined - /> + <VnInput v-model="params.search" :label="t('params.search')" filled /> </QItemSection> </QItem> <QItem> @@ -99,7 +95,7 @@ onMounted(async () => { <VnInput v-model="params.ticketFk" :label="t('params.ticketFk')" - is-outlined + filled /> </QItemSection> </QItem> @@ -114,8 +110,7 @@ onMounted(async () => { option-label="nickname" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -124,7 +119,7 @@ onMounted(async () => { <VnInput v-model="params.clientFk" :label="t('params.clientFk')" - is-outlined + filled /> </QItemSection> </QItem> @@ -139,8 +134,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -153,25 +147,16 @@ onMounted(async () => { :params="{ departmentCodes: ['VT'] }" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - v-model="params.from" - :label="t('params.from')" - is-outlined - /> + <VnInputDate v-model="params.from" :label="t('params.from')" filled /> </QItemSection> <QItemSection> - <VnInputDate - v-model="params.to" - :label="t('params.to')" - is-outlined - /> + <VnInputDate v-model="params.to" :label="t('params.to')" filled /> </QItemSection> </QItem> <QItem> @@ -180,7 +165,7 @@ onMounted(async () => { :label="t('params.daysOnward')" v-model="params.daysOnward" lazy-rules - is-outlined + filled /> </QItemSection> </QItem> @@ -195,8 +180,7 @@ onMounted(async () => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -221,7 +205,7 @@ en: attenderFk: Atender clientFk: Client id warehouseFk: Warehouse - requesterFk: Salesperson + requesterFk: Requester from: From to: To mine: For me @@ -239,7 +223,7 @@ es: attenderFk: Comprador clientFk: Id cliente warehouseFk: Almacén - requesterFk: Comercial + requesterFk: Solicitante from: Desde to: Hasta mine: Para mi diff --git a/src/pages/Item/ItemType/Card/ItemTypeCard.vue b/src/pages/Item/ItemType/Card/ItemTypeCard.vue index 84e810de5..bd41b1be2 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeCard.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeCard.vue @@ -1,11 +1,11 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue'; import filter from './ItemTypeFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="ItemType" url="ItemTypes" :filter="filter" diff --git a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue index 725fb30aa..106b005bf 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import filter from './ItemTypeFilter.js'; @@ -25,11 +25,12 @@ const entityId = computed(() => { }); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`ItemTypes/${entityId}`" :filter="filter" title="code" data-key="ItemType" + :to-module="{ name: 'ItemTypeList' }" > <template #body="{ entity }"> <VnLv :label="$t('itemType.shared.code')" :value="entity.code" /> @@ -45,5 +46,5 @@ const entityId = computed(() => { :value="entity.category?.name" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Item/ItemType/Card/ItemTypeSummary.vue b/src/pages/Item/ItemType/Card/ItemTypeSummary.vue index 3b63c4b63..ba294e144 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeSummary.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeSummary.vue @@ -7,6 +7,7 @@ import filter from './ItemTypeFilter.js'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; onUpdated(() => summaryRef.value.fetch()); @@ -62,13 +63,10 @@ async function setItemTypeData(data) { </template> <template #body> <QCard class="vn-one"> - <router-link - :to="{ name: 'ItemTypeBasicData', params: { id: entityId } }" - class="header header-link" - > - {{ t('globals.summary.basicData') }} - <QIcon name="open_in_new" /> - </router-link> + <VnTitle + :url="`#/item/item-type/${entityId}/basic-data`" + :text="$t('globals.summary.basicData')" + /> <VnLv :label="t('itemType.summary.id')" :value="itemType.id" /> <VnLv :label="t('itemType.shared.code')" :value="itemType.code" /> <VnLv :label="t('itemType.shared.name')" :value="itemType.name" /> diff --git a/src/components/EditTableCellValueForm.vue b/src/pages/Item/components/EditFixedPriceForm.vue similarity index 81% rename from src/components/EditTableCellValueForm.vue rename to src/pages/Item/components/EditFixedPriceForm.vue index 172866191..9c6a63893 100644 --- a/src/components/EditTableCellValueForm.vue +++ b/src/pages/Item/components/EditFixedPriceForm.vue @@ -8,11 +8,6 @@ import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnRow from 'components/ui/VnRow.vue'; import { QCheckbox } from 'quasar'; -import axios from 'axios'; -import useNotify from 'src/composables/useNotify.js'; - -const emit = defineEmits(['onDataSaved']); - const $props = defineProps({ rows: { type: Array, @@ -26,10 +21,14 @@ const $props = defineProps({ type: String, default: '', }, + beforeSave: { + type: Function, + default: () => {}, + }, }); const { t } = useI18n(); -const { notify } = useNotify(); +const emit = defineEmits(['onDataSaved']); const inputs = { input: markRaw(VnInput), @@ -44,24 +43,13 @@ const selectedField = ref(null); const closeButton = ref(null); const isLoading = ref(false); -const onDataSaved = () => { - notify('globals.dataSaved', 'positive'); - emit('onDataSaved'); - closeForm(); -}; - const onSubmit = async () => { isLoading.value = true; - const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk })); - const payload = { - field: selectedField.value.field, - newValue: newValue.value, - lines: rowsToEdit, - }; - - await axios.post($props.editUrl, payload); - onDataSaved(); - isLoading.value = false; + $props.rows.forEach((row) => { + row[selectedField.value.name] = newValue.value; + }); + emit('onDataSaved', $props.rows); + closeForm(); }; const closeForm = () => { @@ -78,21 +66,24 @@ const closeForm = () => { <span class="title">{{ t('Edit') }}</span> <span class="countLines">{{ ` ${rows.length} ` }}</span> <span class="title">{{ t('buy(s)') }}</span> - <VnRow> + <VnRow class="q-mt-md"> <VnSelect + class="editOption" :label="t('Field to edit')" :options="fieldsOptions" hide-selected option-label="label" v-model="selectedField" - data-cy="field-to-edit" + data-cy="EditFixedPriceSelectOption" + @update:model-value="newValue = null" + :class="{ 'is-select': selectedField?.component === 'select' }" /> <component :is="inputs[selectedField?.component || 'input']" v-bind="selectedField?.attrs || {}" v-model="newValue" :label="t('Value')" - data-cy="value-to-edit" + data-cy="EditFixedPriceValueOption" style="width: 200px" /> </VnRow> @@ -140,6 +131,15 @@ const closeForm = () => { } </style> +<style lang="scss"> +.editOption .q-field__inner .q-field__control { + padding: 0 !important; +} +.editOption.is-select .q-field__inner .q-field__control { + padding: 0 !important; +} +</style> + <i18n> es: Edit: Editar diff --git a/src/pages/Item/components/__tests__/EditFixedPriceForm.spec.js b/src/pages/Item/components/__tests__/EditFixedPriceForm.spec.js new file mode 100644 index 000000000..6ac8f372d --- /dev/null +++ b/src/pages/Item/components/__tests__/EditFixedPriceForm.spec.js @@ -0,0 +1,46 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import EditFixedPriceForm from 'src/pages/Item/components/EditFixedPriceForm.vue'; + +describe('EditFixedPriceForm.vue', () => { + let wrapper; + let vm; + + const mockRows = [ + { id: 1, itemFk: 101 }, + { id: 2, itemFk: 102 }, + ]; + + const mockFieldsOptions = [ + { + name: 'price', + label: 'Price', + component: 'input', + attrs: { type: 'number' }, + }, + ]; + + beforeEach(() => { + wrapper = createWrapper(EditFixedPriceForm, { + props: { + rows: JSON.parse(JSON.stringify(mockRows)), + fieldsOptions: mockFieldsOptions, + }, + }); + wrapper = wrapper.wrapper; + vm = wrapper.vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should emit "onDataSaved" with updated rows on submit', async () => { + vm.selectedField = mockFieldsOptions[0]; + vm.newValue = 199.99; + + await vm.onSubmit(); + + expect(wrapper.emitted('onDataSaved')).toBeTruthy(); + }); +}); diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 9d27fc96e..017f6b11f 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -84,7 +84,7 @@ item: attenderFk: Atender clientFk: Client id warehouseFk: Warehouse - requesterFk: Salesperson + requesterFk: Requester from: From to: To mine: For me @@ -167,6 +167,8 @@ item: started: Started ended: Ended warehouse: Warehouse + MP: MP + itemName: Item create: name: Name tag: Tag diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 935f5160b..a06695fe9 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -93,7 +93,7 @@ item: attenderFk: Comprador clientFk: Id cliente warehouseFk: Almacén - requesterFk: Comercial + requesterFk: Solicitante from: Desde to: Hasta mine: Para mi @@ -173,6 +173,8 @@ item: started: Inicio ended: Fin warehouse: Almacén + MP: PM + itemName: Nombre create: name: Nombre tag: Etiqueta diff --git a/src/pages/Login/__tests__/Login.spec.js b/src/pages/Login/__tests__/Login.spec.js index e90a8ee53..b25246f52 100644 --- a/src/pages/Login/__tests__/Login.spec.js +++ b/src/pages/Login/__tests__/Login.spec.js @@ -1,6 +1,7 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; import Login from 'pages/Login/LoginMain.vue'; +import axios from 'axios'; describe('Login', () => { let vm; diff --git a/src/pages/Monitor/MonitorClients.vue b/src/pages/Monitor/MonitorClients.vue index c1958cdcb..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 'salesPersonFk': - return { [`c.${param}`]: value }; } } @@ -62,25 +67,21 @@ const columns = computed(() => [ columnFilter: false, }, { - label: t('salesClientsTable.salesPerson'), - name: 'salesPersonFk', - field: 'salesPerson', align: 'left', - columnField: { - component: null, - }, - optionFilter: 'firstName', + name: 'departmentFk', + label: t('customer.summary.team'), columnFilter: { component: 'select', attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - sortBy: 'nickname ASC', - where: { role: 'salesPerson' }, - useLike: false, + url: 'Departments', }, + alias: 'c', + inWhere: true, }, - columnClass: 'no-padding', + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { label: t('salesClientsTable.client'), @@ -102,6 +103,7 @@ const columns = computed(() => [ columnClass: 'no-padding', }, ]); +const openTab = (id) => useOpenURL(`#/customer/${id}/summary`); </script> <template> @@ -121,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> @@ -128,13 +132,17 @@ const columns = computed(() => [ <VnInputDate v-model="to" :label="$t('globals.to')" dense /> </VnRow> </template> - <template #column-salesPersonFk="{ row }"> - <span class="link" :title="row.salesPerson" v-text="row.salesPerson" /> - <WorkerDescriptorProxy :id="row.salesPersonFk" dense /> + <template #column-departmentFk="{ row }"> + <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 873f8abb4..bdfcf3837 100644 --- a/src/pages/Monitor/MonitorOrders.vue +++ b/src/pages/Monitor/MonitorOrders.vue @@ -1,14 +1,15 @@ <script setup> import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnTable from 'components/VnTable/VnTable.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; 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(); @@ -20,8 +21,8 @@ function exprBuilder(param, value) { switch (param) { case 'clientFk': return { [`c.id`]: value }; - case 'salesPersonFk': - return { [`c.salesPersonFk`]: value }; + case 'departmentFk': + return { [`c.departmentFk`]: value }; } } @@ -63,20 +64,18 @@ const columns = computed(() => [ columnFilter: false, }, { - label: t('salesClientsTable.salesPerson'), - name: 'salesPersonFk', align: 'left', - optionFilter: 'firstName', - columnFilter: { - component: 'select', - attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - sortBy: 'nickname ASC', - where: { role: 'salesPerson' }, - useLike: false, - }, + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', }, + create: true, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { label: t('salesOrdersTable.import'), @@ -110,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 @@ -131,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 }" > @@ -179,17 +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-salesPersonFk="{ row }"> - <QTd @click.stop> - <span class="link" v-text="row.salesPerson" /> - <WorkerDescriptorProxy :id="row.salesPersonFk" dense /> - </QTd> + <template #column-departmentFk="{ row }"> + <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 48710d696..1cadd4cb4 100644 --- a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue +++ b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue @@ -9,7 +9,6 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import FetchData from 'src/components/FetchData.vue'; import { dateRange } from 'src/filters'; -import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; defineProps({ dataKey: { type: String, required: true } }); const { t, te } = useI18n(); @@ -78,7 +77,7 @@ const getLocale = (label) => { <VnInput :label="t('globals.params.clientFk')" v-model="params.clientFk" - is-outlined + filled /> </QItemSection> </QItem> @@ -87,7 +86,7 @@ const getLocale = (label) => { <VnInput :label="t('params.orderFk')" v-model="params.orderFk" - is-outlined + filled /> </QItemSection> </QItem> @@ -96,7 +95,7 @@ const getLocale = (label) => { <VnInputNumber :label="t('params.scopeDays')" v-model="params.scopeDays" - is-outlined + filled @update:model-value="(val) => handleScopeDays(params, val)" @remove="(val) => handleScopeDays(params, val)" /> @@ -107,66 +106,54 @@ const getLocale = (label) => { <VnInput :label="t('params.nickname')" v-model="params.nickname" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnSelectWorker - outlined + <VnSelect dense - rounded - :label="t('globals.params.salesPersonFk')" - v-model="params.salesPersonFk" - :params="{ departmentCodes: ['VT'] }" - :no-one="true" - > - </VnSelectWorker> + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + url="Departments" + /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('params.refFk')" - v-model="params.refFk" - is-outlined - /> + <VnInput :label="t('params.refFk')" v-model="params.refFk" filled /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('params.agencyModeFk')" v-model="params.agencyModeFk" url="AgencyModes/isActive" - is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.stateFk')" v-model="params.stateFk" url="States" - is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('params.groupedStates')" v-model="params.alertLevel" :options="groupedStates" @@ -177,9 +164,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.warehouseFk')" v-model="params.warehouseFk" :options="warehouses" @@ -189,9 +175,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.countryFk')" v-model="params.countryFk" url="Countries" @@ -201,9 +186,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded + filled :label="t('globals.params.provinceFk')" v-model="params.provinceFk" url="Provinces" @@ -213,23 +197,8 @@ const getLocale = (label) => { <QItem> <QItemSection> <VnSelect - outlined dense - rounded - :label="t('globals.params.departmentFk')" - v-model="params.department" - option-label="name" - option-value="name" - url="Departments" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnSelect - outlined - dense - rounded + filled :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 782175cd6..b46eb5bfa 100644 --- a/src/pages/Monitor/Ticket/MonitorTickets.vue +++ b/src/pages/Monitor/Ticket/MonitorTickets.vue @@ -2,7 +2,7 @@ import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; -import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; @@ -49,8 +49,8 @@ function exprBuilder(param, value) { switch (param) { case 'stateFk': return { 'ts.stateFk': value }; - case 'salesPersonFk': - return { 'c.salesPersonFk': !value ? null : value }; + case 'departmentFk': + return { 'c.departmentFk': !value ? null : value }; case 'provinceFk': return { 'a.provinceFk': value }; case 'theoreticalHour': @@ -108,19 +108,18 @@ const columns = computed(() => [ }, }, { - label: t('salesClientsTable.salesPerson'), - name: 'salesPersonFk', - field: 'userName', align: 'left', - columnFilter: { - component: 'select', - attrs: { - url: 'Workers/search?departmentCodes=["VT"]', - fields: ['id', 'name', 'nickname', 'code'], - sortBy: 'nickname ASC', - optionLabel: 'nickname', - }, + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', }, + create: true, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { label: t('salesClientsTable.date'), @@ -437,10 +436,10 @@ const openTab = (id) => useOpenURL(`#/ticket/${id}/sale`); <CustomerDescriptorProxy :id="row.clientFk" /> </div> </template> - <template #column-salesPersonFk="{ row }"> - <div @click.stop :title="row.userName"> - <span class="link" v-text="dashIfEmpty(row.userName)" /> - <WorkerDescriptorProxy :id="row.salesPersonFk" /> + <template #column-departmentFk="{ row }"> + <div @click.stop :title="row.departmentName"> + <span class="link" v-text="dashIfEmpty(row.departmentName)" /> + <DepartmentDescriptorProxy :id="row.departmentFk" /> </div> </template> <template #column-shippedDate="{ row }"> @@ -450,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/Monitor/locale/en.yml b/src/pages/Monitor/locale/en.yml index c049a5e53..a9ce36ffd 100644 --- a/src/pages/Monitor/locale/en.yml +++ b/src/pages/Monitor/locale/en.yml @@ -7,7 +7,6 @@ salesClientsTable: to: To date: Date hour: Hour - salesPerson: Salesperson client: Client salesOrdersTable: delete: Delete diff --git a/src/pages/Monitor/locale/es.yml b/src/pages/Monitor/locale/es.yml index a02d7f36f..6086eda6b 100644 --- a/src/pages/Monitor/locale/es.yml +++ b/src/pages/Monitor/locale/es.yml @@ -7,7 +7,6 @@ salesClientsTable: to: Hasta date: Fecha hour: Hora - salesPerson: Comercial client: Cliente salesOrdersTable: delete: Eliminar diff --git a/src/pages/Order/Card/CatalogFilterValueDialog.vue b/src/pages/Order/Card/CatalogFilterValueDialog.vue index d1bd48c9e..10273a254 100644 --- a/src/pages/Order/Card/CatalogFilterValueDialog.vue +++ b/src/pages/Order/Card/CatalogFilterValueDialog.vue @@ -57,9 +57,8 @@ const getSelectedTagValues = async (tag) => { option-value="id" option-label="name" dense - outlined class="q-mb-md" - rounded + filled :emit-value="false" use-input @update:model-value="getSelectedTagValues" @@ -79,8 +78,7 @@ const getSelectedTagValues = async (tag) => { option-value="value" option-label="value" dense - outlined - rounded + filled emit-value use-input :disable="!value || !selectedTag" @@ -92,16 +90,14 @@ const getSelectedTagValues = async (tag) => { v-model="value.value" :label="t('components.itemsFilterPanel.value')" :disable="!value" - is-outlined class="col" data-cy="catalogFilterValueDialogValueInput" /> <QBtn icon="delete" size="md" - outlined dense - rounded + filled flat class="filter-icon col-2" @click="tagValues.splice(index, 1)" diff --git a/src/pages/Order/Card/OrderBasicData.vue b/src/pages/Order/Card/OrderBasicData.vue index 9c02d7494..73b8b6fc8 100644 --- a/src/pages/Order/Card/OrderBasicData.vue +++ b/src/pages/Order/Card/OrderBasicData.vue @@ -64,17 +64,7 @@ const orderFilter = { { relation: 'client', scope: { - fields: [ - 'salesPersonFk', - 'name', - 'isActive', - 'isFreezed', - 'isTaxDataChecked', - ], - include: { - relation: 'salesPersonUser', - scope: { fields: ['id', 'name'] }, - }, + fields: ['name', 'isActive', 'isFreezed', 'isTaxDataChecked'], }, }, ], @@ -167,7 +157,7 @@ const onClientChange = async (clientId) => { !data.isConfirmed && agencyList?.length && agencyList.some( - (agency) => agency.agencyModeFk === data.agency_id + (agency) => agency.agencyModeFk === data.agency_id, ) ? data.agencyModeFk : null diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue index ad5c73a87..11dbbe532 100644 --- a/src/pages/Order/Card/OrderCard.vue +++ b/src/pages/Order/Card/OrderCard.vue @@ -1,14 +1,16 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue'; import filter from './OrderFilter.js'; </script> <template> - <VnCardBeta - data-key="Order" + <VnCard + :data-key="$attrs['data-key'] ?? 'Order'" url="Orders" :filter="filter" :descriptor="OrderDescriptor" + v-bind="$attrs" + v-on="$attrs" /> </template> diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue index d16a92017..cb380c48f 100644 --- a/src/pages/Order/Card/OrderCatalogFilter.vue +++ b/src/pages/Order/Card/OrderCatalogFilter.vue @@ -221,8 +221,7 @@ function addOrder(value, field, params) { option-value="id" option-label="name" dense - outlined - rounded + filled emit-value use-input sort-by="name ASC" @@ -251,8 +250,7 @@ function addOrder(value, field, params) { v-model="orderBySelected" :options="orderByList" dense - outlined - rounded + filled @update:model-value="(value) => addOrder(value, 'field', params)" /> </QItemSection> @@ -264,8 +262,7 @@ function addOrder(value, field, params) { v-model="orderWaySelected" :options="orderWayList" dense - outlined - rounded + filled @update:model-value="(value) => addOrder(value, 'way', params)" /> </QItemSection> @@ -275,8 +272,7 @@ function addOrder(value, field, params) { <VnInput :label="t('components.itemsFilterPanel.value')" dense - outlined - rounded + filled :is-clearable="false" v-model="searchByTag" @keyup.enter="(val) => onSearchByTag(val, params)" diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue index 0d18864dc..ee66bb57e 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -4,11 +4,11 @@ import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { toCurrency, toDate } from 'src/filters'; import { useState } from 'src/composables/useState'; -import filter from './OrderFilter.js'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import FetchData from 'components/FetchData.vue'; -import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import OrderCard from './OrderCard.vue'; +import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; const DEFAULT_ITEMS = 0; @@ -24,11 +24,14 @@ const route = useRoute(); const state = useState(); const { t } = useI18n(); const getTotalRef = ref(); +const total = ref(0); const entityId = computed(() => { return $props.id || route.params.id; }); +const orderTotal = computed(() => state.get('orderTotal') ?? 0); + const setData = (entity) => { if (!entity) return; getTotalRef.value && getTotalRef.value.fetch(); @@ -38,9 +41,6 @@ const setData = (entity) => { const getConfirmationValue = (isConfirmed) => { return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed'); }; - -const orderTotal = computed(() => state.get('orderTotal') ?? 0); -const total = ref(0); </script> <template> @@ -54,23 +54,23 @@ const total = ref(0); " /> <CardDescriptor - ref="descriptor" - :url="`Orders/${entityId}`" - :filter="filter" + v-bind="$attrs" + :id="entityId" + :card="OrderCard" title="client.name" @on-fetch="setData" - data-key="Order" + module="Order" > <template #body="{ entity }"> <VnLv :label="t('globals.state')" :value="getConfirmationValue(entity.isConfirmed)" /> - <VnLv :label="t('order.field.salesPersonFk')"> + <VnLv :label="t('customer.summary.team')"> <template #value> <span class="link"> - {{ entity?.client?.salesPersonUser?.name || '-' }} - <WorkerDescriptorProxy :id="entity?.client?.salesPersonFk" /> + {{ entity?.client?.department?.name || '-' }} + <DepartmentDescriptorProxy :id="entity?.client?.departmentFk" /> </span> </template> </VnLv> diff --git a/src/pages/Order/Card/OrderDescriptorProxy.vue b/src/pages/Order/Card/OrderDescriptorProxy.vue index 04ebb054a..1dff1b620 100644 --- a/src/pages/Order/Card/OrderDescriptorProxy.vue +++ b/src/pages/Order/Card/OrderDescriptorProxy.vue @@ -12,6 +12,11 @@ const $props = defineProps({ <template> <QPopupProxy> - <OrderDescriptor v-if="$props.id" :id="$props.id" :summary="OrderSummary" /> + <OrderDescriptor + v-if="$props.id" + :id="$props.id" + :summary="OrderSummary" + data-key="OrderDescriptor" + /> </QPopupProxy> </template> diff --git a/src/pages/Order/Card/OrderFilter.js b/src/pages/Order/Card/OrderFilter.js index 3e521b92c..d45578529 100644 --- a/src/pages/Order/Card/OrderFilter.js +++ b/src/pages/Order/Card/OrderFilter.js @@ -10,14 +10,14 @@ export default { relation: 'client', scope: { fields: [ - 'salesPersonFk', + 'departmentFk', 'name', 'isActive', 'isFreezed', 'isTaxDataChecked', ], include: { - relation: 'salesPersonUser', + relation: 'department', scope: { fields: ['id', 'name'] }, }, }, diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue index c387be241..5f91153ac 100644 --- a/src/pages/Order/Card/OrderFilter.vue +++ b/src/pages/Order/Card/OrderFilter.vue @@ -6,7 +6,6 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'components/common/VnSelect.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; -import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const props = defineProps({ @@ -50,8 +49,7 @@ const sourceList = ref([]); v-model="params.clientFk" lazy-rules dense - outlined - rounded + filled /> <VnSelect :label="t('agency')" @@ -59,38 +57,29 @@ const sourceList = ref([]); :options="agencyList" :input-debounce="0" dense - outlined - rounded + filled /> - <VnSelectWorker - :label="t('globals.salesPerson')" - v-model="params.workerFk" - :params="{ - departmentCodes: ['VT'], - }" + <VnSelect dense - outlined - rounded + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> <VnInputDate v-model="params.from" :label="t('fromLanded')" dense - outlined - rounded - /> - <VnInputDate - v-model="params.to" - :label="t('toLanded')" - dense - outlined - rounded + filled /> + <VnInputDate v-model="params.to" :label="t('toLanded')" dense filled /> <VnInput :label="t('orderId')" v-model="params.orderFk" lazy-rules - is-outlined + filled /> <VnSelect :label="t('application')" @@ -99,8 +88,7 @@ const sourceList = ref([]); option-label="value" option-value="value" dense - outlined - rounded + filled :input-debounce="0" /> <QCheckbox @@ -125,7 +113,6 @@ en: search: Includes clientFk: Client agencyModeFk: Agency - salesPersonFk: Sales Person from: From to: To orderFk: Order @@ -136,7 +123,6 @@ en: showEmpty: Show Empty customerId: Customer ID agency: Agency - salesPerson: Sales Person fromLanded: From Landed toLanded: To Landed orderId: Order ID @@ -144,12 +130,13 @@ en: myTeam: My Team isConfirmed: Order Confirmed showEmpty: Show Empty + departmentFk: Department es: params: + departmentFk: Departamento search: Búsqueda clientFk: Cliente agencyModeFk: Agencia - salesPersonFk: Comercial from: Desde to: Hasta orderFk: Cesta @@ -160,7 +147,6 @@ es: showEmpty: Mostrar vacías customerId: ID Cliente agency: Agencia - salesPerson: Comercial fromLanded: Desde F. entrega toLanded: Hasta F. entrega orderId: ID Cesta diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue index 1b864de6f..231efbcd9 100644 --- a/src/pages/Order/Card/OrderLines.vue +++ b/src/pages/Order/Card/OrderLines.vue @@ -295,13 +295,11 @@ watch( :user-filter="lineFilter" > <template #column-image="{ row }"> - <div class="image-wrapper"> - <VnImg - :id="parseInt(row?.item?.image)" - class="rounded" - zoom-resolution="1600x900" - /> - </div> + <VnImg + :id="parseInt(row?.item?.image)" + class="rounded" + zoom-resolution="1600x900" + /> </template> <template #column-id="{ row }"> <span class="link" @click.stop> @@ -361,12 +359,6 @@ watch( } } -.image-wrapper { - height: 50px; - width: 50px; - margin-left: 30%; -} - .header { color: $primary; font-weight: bold; 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 091275e32..f59ce6585 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -10,12 +10,12 @@ import axios from 'axios'; import OrderSummary from 'pages/Order/Card/OrderSummary.vue'; import OrderFilter from './Card/OrderFilter.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; -import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSection from 'src/components/common/VnSection.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import { getAddresses } from '../Customer/composables/getAddresses'; const { t } = useI18n(); @@ -59,22 +59,16 @@ const columns = computed(() => [ }, { align: 'left', - name: 'salesPersonFk', - label: t('module.salesPerson'), - columnFilter: { - component: 'select', - inWhere: true, - attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - where: { role: 'salesPerson' }, - useLike: false, - optionValue: 'id', - optionLabel: 'name', - optionFilter: 'firstName', - }, + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', }, - format: (row) => row?.name, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { align: 'center', @@ -147,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, }, ], @@ -156,9 +150,7 @@ const columns = computed(() => [ onMounted(async () => { if (!route.query) return; if (route.query?.createForm) { - const query = JSON.parse(route.query?.createForm); - formInitialData.value = query; - await onClientSelected({ ...formInitialData.value, clientFk: query?.clientFk }); + await onClientSelected(JSON.parse(route.query?.createForm)); } else if (route.query?.table) { const query = JSON.parse(route.query?.table); const clientFk = query?.clientFk; @@ -177,7 +169,6 @@ watch( tableRef.value.create.formInitialData = formInitialData.value; } }, - { immediate: true }, ); async function onClientSelected({ clientFk }, formData = {}) { @@ -191,13 +182,17 @@ async function onClientSelected({ clientFk }, formData = {}) { addressOptions.value = data; formData.defaultAddressFk = data[0].client.defaultAddressFk; formData.addressId = formData.defaultAddressFk; - - formInitialData.value = { addressId: formData.addressId, clientFk }; + formInitialData.value = { ...formData, clientFk }; await fetchAgencies(formData); } -async function fetchAgencies({ landed, addressId }) { - if (!landed || !addressId) return (agencyList.value = []); +async function fetchAgencies(formData) { + const { landed, addressId } = formData; + if (!landed || !addressId) { + formData.defaultAddressFk = formInitialData.value.defaultAddressFk; + + return (agencyList.value = []); + } const { data } = await axios.get('Agencies/landsThatDay', { params: { @@ -220,6 +215,11 @@ const getDateColor = (date) => { if (difference == 0) return 'bg-warning'; if (difference < 0) return 'bg-success'; }; + +const isDefaultAddress = (opt, data) => { + const addressId = data.defaultAddressFk ?? data.addressId; + return addressId === opt.id && opt.isActive; +}; </script> <template> @@ -258,10 +258,10 @@ const getDateColor = (date) => { <CustomerDescriptorProxy :id="row?.clientFk" /> </span> </template> - <template #column-salesPersonFk="{ row }"> + <template #column-departmentFk="{ row }"> <span class="link" @click.stop> - {{ row?.name }} - <WorkerDescriptorProxy :id="row?.salesPersonFk" /> + {{ row?.departmentName }} + <DepartmentDescriptorProxy :id="row?.departmentFk" /> </span> </template> <template #column-landed="{ row }"> @@ -310,10 +310,7 @@ const getDateColor = (date) => { > <QItemSection style="min-width: min-content" avatar> <QIcon - v-if=" - scope.opt.isActive && - data.defaultAddressFk === scope.opt.id - " + v-if="isDefaultAddress(scope.opt, data)" size="sm" color="grey" name="star" diff --git a/src/pages/Order/locale/en.yml b/src/pages/Order/locale/en.yml index 14e41c559..877a3c380 100644 --- a/src/pages/Order/locale/en.yml +++ b/src/pages/Order/locale/en.yml @@ -8,7 +8,6 @@ module: hour: Hour agency: Agency total: Total - salesPerson: Sales Person address: Address cerateOrder: Create order lines: @@ -22,8 +21,6 @@ lines: params: tagGroups: Tags order: - field: - salesPersonFk: Sales Person form: clientFk: Client addressFk: Address diff --git a/src/pages/Order/locale/es.yml b/src/pages/Order/locale/es.yml index 44e243ad1..f7528ec28 100644 --- a/src/pages/Order/locale/es.yml +++ b/src/pages/Order/locale/es.yml @@ -8,7 +8,6 @@ module: hour: Hora agency: Agencia total: Total - salesPerson: Comercial address: Dirección cerateOrder: Crear cesta lines: @@ -22,8 +21,6 @@ lines: params: tagGroups: Tags order: - field: - salesPersonFk: Comercial form: clientFk: Cliente addressFk: Dirección diff --git a/src/pages/Route/Agency/AgencyList.vue b/src/pages/Route/Agency/AgencyList.vue index 5c2904bf3..c01dd272c 100644 --- a/src/pages/Route/Agency/AgencyList.vue +++ b/src/pages/Route/Agency/AgencyList.vue @@ -2,10 +2,13 @@ import { computed } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnTable from 'components/VnTable/VnTable.vue'; import VnSection from 'src/components/common/VnSection.vue'; +import AgencySummary from 'pages/Route/Agency/Card/AgencySummary.vue'; const { t } = useI18n(); +const { viewSummary } = useSummaryDialog(); const router = useRouter(); const dataKey = 'AgencyList'; function navigate(id) { @@ -40,16 +43,22 @@ const columns = computed(() => [ }, { align: 'left', - label: t('isOwn'), + label: t('agency.isOwn'), name: 'isOwn', component: 'checkbox', + columnFilter: { + inWhere: true, + }, cardVisible: true, }, { align: 'left', - label: t('isAnyVolumeAllowed'), + label: t('agency.isAnyVolumeAllowed'), name: 'isAnyVolumeAllowed', component: 'checkbox', + columnFilter: { + inWhere: true, + }, cardVisible: true, }, { @@ -58,9 +67,10 @@ const columns = computed(() => [ name: 'tableActions', actions: [ { - title: t('Client ticket list'), + title: t('globals.pageTitles.summary'), icon: 'preview', - action: (row) => navigate(row.id), + action: (row) => viewSummary(row?.id, AgencySummary), + isPrimary: true, }, ], }, @@ -82,7 +92,7 @@ const columns = computed(() => [ <VnTable :data-key :columns="columns" - is-editable="false" + :is-editable="false" :right-search="false" :use-model="true" redirect="route/agency" @@ -103,11 +113,3 @@ const columns = computed(() => [ justify-content: center; } </style> -<i18n> - es: - isOwn: Tiene propietario - isAnyVolumeAllowed: Permite cualquier volumen - en: - isOwn: Has owner - isAnyVolumeAllowed: Allows any volume -</i18n> diff --git a/src/pages/Route/Agency/Card/AgencyBasicData.vue b/src/pages/Route/Agency/Card/AgencyBasicData.vue index 4270b136c..4f8f17163 100644 --- a/src/pages/Route/Agency/Card/AgencyBasicData.vue +++ b/src/pages/Route/Agency/Card/AgencyBasicData.vue @@ -21,7 +21,7 @@ const warehouses = ref([]); @on-fetch="(data) => (warehouses = data)" auto-load /> - <FormModel :update-url="`Agencies/${routeId}`" model="Agency" auto-load> + <FormModel :url-update="`Agencies/${routeId}`" model="Agency" auto-load> <template #form="{ data }"> <VnRow> <VnInput v-model="data.name" :label="t('globals.name')" /> diff --git a/src/pages/Route/Agency/Card/AgencyCard.vue b/src/pages/Route/Agency/Card/AgencyCard.vue index 7dc31f8ba..9fd3fe5e5 100644 --- a/src/pages/Route/Agency/Card/AgencyCard.vue +++ b/src/pages/Route/Agency/Card/AgencyCard.vue @@ -1,7 +1,7 @@ <script setup> import AgencyDescriptor from 'pages/Route/Agency/Card/AgencyDescriptor.vue'; -import VnCardBeta from 'src/components/common/VnCardBeta.vue'; +import VnCard from 'src/components/common/VnCard.vue'; </script> <template> - <VnCardBeta 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/Agency/Card/AgencyDescriptor.vue b/src/pages/Route/Agency/Card/AgencyDescriptor.vue index a0472c6c3..64b33cc06 100644 --- a/src/pages/Route/Agency/Card/AgencyDescriptor.vue +++ b/src/pages/Route/Agency/Card/AgencyDescriptor.vue @@ -3,7 +3,7 @@ import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; const props = defineProps({ @@ -17,18 +17,19 @@ const props = defineProps({ const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => props.id || route.params.id); -const { store } = useArrayData('Parking'); +const { store } = useArrayData(); const card = computed(() => store.data); </script> <template> - <CardDescriptor + <EntityDescriptor data-key="Agency" :url="`Agencies/${entityId}`" :title="card?.name" :subtitle="props.id" + :to-module="{ name: 'RouteAgency' }" > <template #body="{ entity: agency }"> <VnLv :label="t('globals.name')" :value="agency.name" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Route/Agency/Card/AgencyDescriptorProxy.vue b/src/pages/Route/Agency/Card/AgencyDescriptorProxy.vue new file mode 100644 index 000000000..e5c1249b2 --- /dev/null +++ b/src/pages/Route/Agency/Card/AgencyDescriptorProxy.vue @@ -0,0 +1,20 @@ +<script setup> +import AgencyDescriptor from 'pages/Route/Agency/Card/AgencyDescriptor.vue'; +import AgencySummary from './AgencySummary.vue'; + +const $props = defineProps({ + id: { + type: Number, + required: true, + }, + summary: { + type: Object, + default: null, + }, +}); +</script> +<template> + <QPopupProxy> + <AgencyDescriptor v-if="$props.id" :id="$props.id" :summary="AgencySummary" /> + </QPopupProxy> +</template> diff --git a/src/pages/Route/Agency/Card/AgencySummary.vue b/src/pages/Route/Agency/Card/AgencySummary.vue index 71a6d1066..ab274939a 100644 --- a/src/pages/Route/Agency/Card/AgencySummary.vue +++ b/src/pages/Route/Agency/Card/AgencySummary.vue @@ -6,29 +6,31 @@ import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import VnCheckbox from 'components/common/VnCheckbox.vue'; +const route = useRoute(); const $props = defineProps({ id: { type: Number, default: 0 } }); const { t } = useI18n(); -const entityId = computed(() => $props.id || useRoute().params.id); +const entityId = computed(() => $props.id || route.params.id); </script> <template> <div class="q-pa-md"> - <CardSummary :url="`Agencies/${entityId}`" data-key="Agency"> + <CardSummary :url="`Agencies/${entityId}`" data-key="Agency" module-name="Agency"> <template #header="{ entity: agency }">{{ agency.name }}</template> <template #body="{ entity: agency }"> <QCard class="vn-one"> <VnTitle - :url="`#/agency/${entityId}/basic-data`" + :url="`#/${route.meta.moduleName.toLowerCase()}/agency/${entityId}/basic-data`" :text="t('globals.pageTitles.basicData')" /> <VnLv :label="t('globals.name')" :value="agency.name" /> - <QCheckbox + <VnCheckbox :label="t('agency.isOwn')" v-model="agency.isOwn" :disable="true" /> - <QCheckbox + <VnCheckbox :label="t('agency.isAnyVolumeAllowed')" v-model="agency.isAnyVolumeAllowed" :disable="true" diff --git a/src/pages/Route/Agency/Card/AgencyWorkcenter.vue b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue index 9a9213868..d33c9f753 100644 --- a/src/pages/Route/Agency/Card/AgencyWorkcenter.vue +++ b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue @@ -80,6 +80,7 @@ async function deleteWorCenter(id) { color="primary" round flat + data-cy="removeWorkCenterBtn" /> </QItemSection> </QItem> diff --git a/src/pages/Route/Agency/locale/en.yml b/src/pages/Route/Agency/locale/en.yml index 93f8b4aaa..78a687f2e 100644 --- a/src/pages/Route/Agency/locale/en.yml +++ b/src/pages/Route/Agency/locale/en.yml @@ -1,11 +1,12 @@ agency: search: Search agency - searchInfo: You can search by name + searchInfo: You can search by name and by id isOwn: Own isAnyVolumeAllowed: Any volume allowed + removeItem: Agency removed successfully notification: - removeItemError: Error removing agency - removeItem: WorkCenter removed successfully + removeItemError: Error removing work center + removeItem: Work center removed successfully pageTitles: agency: Agency searchBar: diff --git a/src/pages/Route/Agency/locale/es.yml b/src/pages/Route/Agency/locale/es.yml index 1efed0e9c..b6237a9f7 100644 --- a/src/pages/Route/Agency/locale/es.yml +++ b/src/pages/Route/Agency/locale/es.yml @@ -1,15 +1,14 @@ agency: search: Buscar agencia - searchInfo: Puedes buscar por nombre + searchInfo: Puedes buscar por nombre y por id isOwn: Propio isAnyVolumeAllowed: Cualquier volumen removeItem: Agencia eliminada correctamente notification: - removeItemError: Error al eliminar la agencia + removeItemError: Error al eliminar la el centro de trabajo removeItem: Centro de trabajo eliminado correctamente pageTitles: agency: Agencia searchBar: info: Puedes buscar por nombre o id label: Buscar agencia... - diff --git a/src/pages/Route/Card/RouteAutonomousFilter.vue b/src/pages/Route/Card/RouteAutonomousFilter.vue index f70f60e1c..fe631a0be 100644 --- a/src/pages/Route/Card/RouteAutonomousFilter.vue +++ b/src/pages/Route/Card/RouteAutonomousFilter.vue @@ -71,7 +71,7 @@ const exprBuilder = (param, value) => { <QList dense> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.routeFk" :label="t('ID')" is-outlined /> + <VnInput v-model="params.routeFk" :label="t('ID')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm" v-if="agencyList"> @@ -83,8 +83,7 @@ const exprBuilder = (param, value) => { option-value="id" option-label="name" dense - outlined - rounded + filled emit-value map-options use-input @@ -102,8 +101,7 @@ const exprBuilder = (param, value) => { option-value="id" option-label="name" dense - outlined - rounded + filled emit-value map-options use-input @@ -123,8 +121,7 @@ const exprBuilder = (param, value) => { option-value="name" option-label="name" dense - outlined - rounded + filled emit-value map-options use-input @@ -135,20 +132,12 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate - v-model="params.dated" - :label="t('Date')" - is-outlined - /> + <VnInputDate v-model="params.dated" :label="t('Date')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate - v-model="params.from" - :label="t('From')" - is-outlined - /> + <VnInputDate v-model="params.from" :label="t('From')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -156,7 +145,7 @@ const exprBuilder = (param, value) => { <VnInputDate v-model="params.to" :label="t('To')" - is-outlined + filled is-clearable /> </QItemSection> @@ -166,23 +155,23 @@ const exprBuilder = (param, value) => { <VnInput v-model="params.packages" :label="t('Packages')" - is-outlined + filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.m3" :label="t('m3')" is-outlined /> + <VnInput v-model="params.m3" :label="t('m3')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.kmTotal" :label="t('Km')" is-outlined /> + <VnInput v-model="params.kmTotal" :label="t('Km')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.price" :label="t('Price')" is-outlined /> + <VnInput v-model="params.price" :label="t('Price')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -190,7 +179,7 @@ const exprBuilder = (param, value) => { <VnInput v-model="params.invoiceInFk" :label="t('Received')" - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Route/Card/RouteCard.vue b/src/pages/Route/Card/RouteCard.vue index c178dc6bf..b71f7d088 100644 --- a/src/pages/Route/Card/RouteCard.vue +++ b/src/pages/Route/Card/RouteCard.vue @@ -1,10 +1,10 @@ <script setup> import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue'; -import VnCardBeta from 'src/components/common/VnCardBeta.vue'; +import VnCard from 'src/components/common/VnCard.vue'; import filter from './RouteFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Route" url="Routes" :filter="filter" diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index 503cd1941..ee42d8e76 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -1,12 +1,12 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; +import useCardDescription from 'composables/useCardDescription'; import VnLv from 'components/ui/VnLv.vue'; import { dashIfEmpty, toDate } from 'src/filters'; import RouteDescriptorMenu from 'pages/Route/Card/RouteDescriptorMenu.vue'; import filter from './RouteFilter.js'; -import useCardDescription from 'src/composables/useCardDescription'; import axios from 'axios'; const $props = defineProps({ @@ -27,23 +27,26 @@ const getZone = async () => { const filter = { where: { routeFk: $props.id ? $props.id : route.params.id }, }; - const { data } = await axios.get('Tickets/findOne', { + const { data } = await axios.get('Tickets/filter', { params: { filter: JSON.stringify(filter), }, }); - zoneId.value = data.zoneFk; + + if (!data.length) return; + const firstRecord = data[0]; + + zoneId.value = firstRecord.zoneFk; const { data: zoneData } = await axios.get(`Zones/${zoneId.value}`); zone.value = zoneData.name; }; const data = ref(useCardDescription()); -const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); onMounted(async () => { getZone(); }); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Routes/${entityId}`" :filter="filter" :title="null" @@ -65,7 +68,7 @@ onMounted(async () => { <template #menu="{ entity }"> <RouteDescriptorMenu :route="entity" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Route/Card/RouteDescriptorProxy.vue b/src/pages/Route/Card/RouteDescriptorProxy.vue index 1ff39a51e..7553469f3 100644 --- a/src/pages/Route/Card/RouteDescriptorProxy.vue +++ b/src/pages/Route/Card/RouteDescriptorProxy.vue @@ -7,6 +7,10 @@ const $props = defineProps({ type: Number, required: true, }, + summary: { + type: Object, + default: null, + }, }); </script> <template> diff --git a/src/pages/Route/Card/RouteFilter.vue b/src/pages/Route/Card/RouteFilter.vue index 21858102b..f830b83e2 100644 --- a/src/pages/Route/Card/RouteFilter.vue +++ b/src/pages/Route/Card/RouteFilter.vue @@ -25,7 +25,7 @@ const emit = defineEmits(['search']); > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`route.params.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> @@ -33,10 +33,10 @@ const emit = defineEmits(['search']); <QItem class="q-my-sm"> <QItemSection> <VnSelectWorker + :label="t('globals.worker')" v-model="params.workerFk" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> @@ -44,15 +44,14 @@ const emit = defineEmits(['search']); <QItem class="q-my-sm"> <QItemSection> <VnSelect - :label="t('Agency')" + :label="t('globals.agency')" v-model="params.agencyModeFk" url="AgencyModes/isActive" sort-by="name ASC" option-value="id" option-label="name" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> @@ -61,8 +60,8 @@ const emit = defineEmits(['search']); <QItemSection> <VnInputDate v-model="params.from" - :label="t('From')" - is-outlined + :label="t('globals.from')" + filled :disable="Boolean(params.scopeDays)" @update:model-value="params.scopeDays = null" /> @@ -72,8 +71,8 @@ const emit = defineEmits(['search']); <QItemSection> <VnInputDate v-model="params.to" - :label="t('To')" - is-outlined + :label="t('globals.to')" + filled :disable="Boolean(params.scopeDays)" @update:model-value="params.scopeDays = null" /> @@ -84,8 +83,8 @@ const emit = defineEmits(['search']); <VnInput v-model="params.scopeDays" type="number" - :label="t('Days Onward')" - is-outlined + :label="t('globals.daysOnward')" + filled clearable :disable="Boolean(params.from || params.to)" @update:model-value=" @@ -98,7 +97,7 @@ const emit = defineEmits(['search']); <QItem class="q-my-sm"> <QItemSection> <VnSelect - :label="t('Vehicle')" + :label="t('globals.vehicle')" v-model="params.vehicleFk" url="Vehicles/active" sort-by="numberPlate ASC" @@ -106,28 +105,26 @@ const emit = defineEmits(['search']); option-label="numberPlate" option-filter-value="numberPlate" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInput v-model="params.m3" label="m³" is-outlined clearable /> + <VnInput v-model="params.m3" label="m³" filled clearable /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> <VnSelect - :label="t('Warehouse')" + :label="t('globals.warehouse')" v-model="params.warehouseFk" url="Warehouses" option-value="id" option-label="name" dense - outlined - rounded + filled :input-debounce="0" /> </QItemSection> @@ -136,8 +133,8 @@ const emit = defineEmits(['search']); <QItemSection> <VnInput v-model="params.description" - :label="t('Description')" - is-outlined + :label="t('globals.description')" + filled clearable /> </QItemSection> @@ -146,7 +143,7 @@ const emit = defineEmits(['search']); <QItemSection> <QCheckbox v-model="params.isOk" - :label="t('Served')" + :label="t('route.filter.Served')" toggle-indeterminate /> </QItemSection> @@ -154,38 +151,3 @@ const emit = defineEmits(['search']); </template> </VnFilterPanel> </template> - -<i18n> -en: - params: - warehouseFk: Warehouse - description: Description - m3: m³ - scopeDays: Days Onward - vehicleFk: Vehicle - agencyModeFk: Agency - workerFk: Worker - from: From - to: To - Served: Served -es: - params: - warehouseFk: Almacén - description: Descripción - m3: m³ - scopeDays: Días en adelante - vehicleFk: Vehículo - agencyModeFk: Agencia - workerFk: Trabajador - from: Desde - to: Hasta - Warehouse: Almacén - Description: Descripción - Vehicle: Vehículo - Agency: Agencia - Worker: Trabajador - From: Desde - To: Hasta - Served: Servida - Days Onward: Días en adelante -</i18n> diff --git a/src/pages/Route/Card/RouteSummary.vue b/src/pages/Route/Card/RouteSummary.vue index 3051972b2..86bdbb5c5 100644 --- a/src/pages/Route/Card/RouteSummary.vue +++ b/src/pages/Route/Card/RouteSummary.vue @@ -135,77 +135,82 @@ const ticketColumns = ref([ <template #body="{ entity }"> <QCard class="vn-max"> <VnTitle - :url="`#/route/${entityId}/basic-data`" + :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 @@ -221,7 +226,7 @@ const ticketColumns = ref([ <template #body-cell-city="{ value, row }"> <QTd auto-width> <span - class="link cursor-pointer" + class="link" @click="openBuscaman(entity?.route?.vehicleFk, [row])" > {{ value }} @@ -230,7 +235,7 @@ const ticketColumns = ref([ </template> <template #body-cell-client="{ value, row }"> <QTd auto-width> - <span class="link cursor-pointer"> + <span class="link"> {{ value }} <CustomerDescriptorProxy :id="row?.clientFk" /> </span> @@ -238,7 +243,7 @@ const ticketColumns = ref([ </template> <template #body-cell-ticket="{ value, row }"> <QTd auto-width class="text-center"> - <span class="link cursor-pointer"> + <span class="link"> {{ value }} <TicketDescriptorProxy :id="row?.id" /> </span> diff --git a/src/pages/Route/Cmr/CmrList.vue b/src/pages/Route/Cmr/CmrList.vue index b3eaf3b48..170f73bc0 100644 --- a/src/pages/Route/Cmr/CmrList.vue +++ b/src/pages/Route/Cmr/CmrList.vue @@ -2,28 +2,37 @@ import { onBeforeMount, onMounted, computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { Notify } from 'quasar'; +import { useRoute } from 'vue-router'; import { useSession } from 'src/composables/useSession'; import { toDateHourMin } from 'filters/index'; import { useStateStore } from 'src/stores/useStateStore'; -import axios from 'axios'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnTable from 'components/VnTable/VnTable.vue'; +import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; +const route = useRoute(); const { t } = useI18n(); const { getTokenMultimedia } = useSession(); const token = getTokenMultimedia(); const state = useStateStore(); -const warehouses = ref([]); const selectedRows = ref([]); +const dataKey = 'CmrList'; +const shipped = Date.vnNew(); +shipped.setHours(0, 0, 0, 0); +shipped.setDate(shipped.getDate() - 1); +const userParams = { + shipped: null, +}; + const columns = computed(() => [ { align: 'left', name: 'cmrFk', - label: t('route.cmr.list.cmrFk'), + label: t('route.cmr.params.cmrFk'), chip: { condition: () => true, }, @@ -32,62 +41,67 @@ const columns = computed(() => [ { align: 'center', name: 'hasCmrDms', - label: t('route.cmr.list.hasCmrDms'), + label: t('route.cmr.params.hasCmrDms'), component: 'checkbox', cardVisible: true, }, { align: 'left', - label: t('route.cmr.list.ticketFk'), + label: t('route.cmr.params.ticketFk'), name: 'ticketFk', }, { align: 'left', - label: t('route.cmr.list.routeFk'), + label: t('route.cmr.params.routeFk'), name: 'routeFk', }, { align: 'left', - label: t('route.cmr.list.clientFk'), + label: t('route.cmr.params.clientFk'), name: 'clientFk', }, { align: 'right', - label: t('route.cmr.list.country'), + label: t('route.cmr.params.countryFk'), name: 'countryFk', - cardVisible: true, + component: 'select', attrs: { url: 'countries', fields: ['id', 'name'], - optionLabel: 'name', - optionValue: 'id', }, columnFilter: { - inWhere: true, - component: 'select', + name: 'countryFk', + attrs: { + url: 'countries', + fields: ['id', 'name'], + }, }, format: ({ countryName }) => countryName, }, { align: 'right', - label: t('route.cmr.list.shipped'), + label: t('route.cmr.params.shipped'), name: 'shipped', cardVisible: true, - columnFilter: { - component: 'date', - inWhere: true, - }, + component: 'date', format: ({ shipped }) => toDateHourMin(shipped), }, { align: 'right', + label: t('route.cmr.params.warehouseFk'), name: 'warehouseFk', - label: t('globals.warehouse'), - columnFilter: { - component: 'select', - }, + component: 'select', attrs: { - options: warehouses.value, + url: 'warehouses', + fields: ['id', 'name'], + }, + columnFilter: { + inWhere: true, + name: 'warehouseFk', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, }, format: ({ warehouseName }) => warehouseName, }, @@ -96,7 +110,7 @@ const columns = computed(() => [ name: 'tableActions', actions: [ { - title: t('Ver cmr'), + title: t('route.cmr.params.viewCmr'), icon: 'visibility', isPrimary: true, action: (row) => window.open(getCmrUrl(row?.cmrFk), '_blank'), @@ -105,13 +119,17 @@ const columns = computed(() => [ }, ]); -onBeforeMount(async () => { - const { data } = await axios.get('Warehouses'); - warehouses.value = data; +onBeforeMount(() => { + initializeFromQuery(); }); onMounted(() => (state.rightDrawer = true)); +const initializeFromQuery = () => { + const query = route.query.table ? JSON.parse(route.query.table) : {}; + shipped.value = query.shipped || shipped.toISOString(); + Object.assign(userParams, { shipped }); +}; function getApiUrl() { return new URL(window.location).origin; } @@ -133,6 +151,11 @@ function downloadPdfs() { } </script> <template> + <VnSearchbar + :data-key + :label="t('route.cmr.search')" + :info="t('route.cmr.searchInfo')" + /> <VnSubToolbar> <template #st-actions> <QBtn @@ -142,16 +165,17 @@ function downloadPdfs() { :disable="!selectedRows?.length" @click="downloadPdfs" > - <QTooltip>{{ t('route.cmr.list.downloadCmrs') }}</QTooltip> + <QTooltip>{{ t('route.cmr.params.downloadCmrs') }}</QTooltip> </QBtn> </template> </VnSubToolbar> <VnTable ref="tableRef" - data-key="CmrList" + :data-key url="Cmrs/filter" :columns="columns" - :right-search="true" + :order="['shipped DESC', 'cmrFk ASC']" + :user-params="userParams" default-mode="table" v-model:selected="selectedRows" table-height="85vh" diff --git a/src/pages/Route/Roadmap/RoadmapBasicData.vue b/src/pages/Route/Roadmap/RoadmapBasicData.vue index a9e6059c3..3e9b8df6c 100644 --- a/src/pages/Route/Roadmap/RoadmapBasicData.vue +++ b/src/pages/Route/Roadmap/RoadmapBasicData.vue @@ -17,7 +17,7 @@ const onSave = (data, response) => { </script> <template> <FormModel - :update-url="`Roadmaps/${$route.params?.id}`" + :url-update="`Roadmaps/${$route.params?.id}`" :url="`Roadmaps/${$route.params?.id}`" observe-form-changes model="Roadmap" diff --git a/src/pages/Route/Roadmap/RoadmapCard.vue b/src/pages/Route/Roadmap/RoadmapCard.vue index 48ba516a1..af08bc9d4 100644 --- a/src/pages/Route/Roadmap/RoadmapCard.vue +++ b/src/pages/Route/Roadmap/RoadmapCard.vue @@ -1,7 +1,7 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import RoadmapDescriptor from 'pages/Route/Roadmap/RoadmapDescriptor.vue'; </script> <template> - <VnCardBeta data-key="Roadmap" url="Roadmaps" :descriptor="RoadmapDescriptor" /> + <VnCard data-key="Roadmap" url="Roadmaps" :descriptor="RoadmapDescriptor" /> </template> diff --git a/src/pages/Route/Roadmap/RoadmapDescriptor.vue b/src/pages/Route/Roadmap/RoadmapDescriptor.vue index baa864a15..dfa692feb 100644 --- a/src/pages/Route/Roadmap/RoadmapDescriptor.vue +++ b/src/pages/Route/Roadmap/RoadmapDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import { dashIfEmpty, toDateHourMin } from 'src/filters'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; @@ -15,6 +15,10 @@ const $props = defineProps({ required: false, default: null, }, + summary: { + type: Object, + default: null, + }, }); const route = useRoute(); @@ -26,7 +30,13 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor :url="`Roadmaps/${entityId}`" :filter="filter" data-key="Roadmap"> + <EntityDescriptor + :url="`Roadmaps/${entityId}`" + :filter="filter" + data-key="Roadmap" + :summary="summary" + :to-module="{ name: 'RouteRoadmap' }" + > <template #body="{ entity }"> <VnLv :label="t('Roadmap')" :value="entity?.name" /> <VnLv :label="t('ETD')" :value="toDateHourMin(entity?.etd)" /> @@ -42,7 +52,7 @@ const entityId = computed(() => { <template #menu="{ entity }"> <RoadmapDescriptorMenu :route="entity" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Route/Roadmap/RoadmapFilter.vue b/src/pages/Route/Roadmap/RoadmapFilter.vue index 982f1efba..9acbfb740 100644 --- a/src/pages/Route/Roadmap/RoadmapFilter.vue +++ b/src/pages/Route/Roadmap/RoadmapFilter.vue @@ -31,12 +31,12 @@ const emit = defineEmits(['search']); <template #body="{ params }"> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate v-model="params.from" :label="t('From')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate v-model="params.to" :label="t('To')" filled /> </QItemSection> </QItem> <QItem class="q-my-sm"> @@ -44,7 +44,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.tractorPlate" :label="t('Tractor Plate')" - is-outlined + filled clearable /> </QItemSection> @@ -54,7 +54,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.trailerPlate" :label="t('Trailer Plate')" - is-outlined + filled clearable /> </QItemSection> @@ -66,8 +66,7 @@ const emit = defineEmits(['search']); :fields="['id', 'nickname']" v-model="params.supplierFk" dense - outlined - rounded + filled emit-value map-options use-input @@ -81,7 +80,7 @@ const emit = defineEmits(['search']); v-model="params.price" :label="t('Price')" type="number" - is-outlined + filled clearable /> </QItemSection> @@ -91,7 +90,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.driverName" :label="t('Driver name')" - is-outlined + filled clearable /> </QItemSection> @@ -101,7 +100,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.phone" :label="t('Phone')" - is-outlined + filled clearable /> </QItemSection> diff --git a/src/pages/Route/Roadmap/RoadmapSummary.vue b/src/pages/Route/Roadmap/RoadmapSummary.vue index 0c1c2b903..dcd02d98e 100644 --- a/src/pages/Route/Roadmap/RoadmapSummary.vue +++ b/src/pages/Route/Roadmap/RoadmapSummary.vue @@ -112,12 +112,9 @@ const filter = { :label="t('Trailer Plate')" :value="dashIfEmpty(entity?.trailerPlate)" /> - <VnLv :label="t('Phone')" :value="dashIfEmpty(entity?.phone)"> + <VnLv :label="t('Phone')"> <template #value> - <span> - {{ dashIfEmpty(entity?.phone) }} - <VnLinkPhone :phone-number="entity?.phone" /> - </span> + <VnLinkPhone :phone-number="entity?.phone" /> </template> </VnLv> <VnLv diff --git a/src/pages/Route/RouteAutonomous.vue b/src/pages/Route/RouteAutonomous.vue index 3047cdf86..15db2a55f 100644 --- a/src/pages/Route/RouteAutonomous.vue +++ b/src/pages/Route/RouteAutonomous.vue @@ -13,6 +13,7 @@ import RouteSummary from 'pages/Route/Card/RouteSummary.vue'; import RouteDescriptorProxy from 'pages/Route/Card/RouteDescriptorProxy.vue'; import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; +import AgencyDescriptorProxy from 'pages/Route/Agency/Card/AgencyDescriptorProxy.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnDms from 'components/common/VnDms.vue'; import VnTable from 'components/VnTable/VnTable.vue'; @@ -236,10 +237,16 @@ onUnmounted(() => (stateStore.rightDrawer = false)); selection: 'multiple', }" > - <template #column-id="{ row }"> + <template #column-agencyModeName="{ row }"> <span class="link" @click.stop> - {{ row.routeFk }} - <RouteDescriptorProxy :id="row.route.id" /> + {{ row?.agencyModeName }} + <AgencyDescriptorProxy :id="row?.agencyModeFk" v-if="row?.agencyModeFk" /> + </span> + </template> + <template #column-agencyAgreement="{ row }"> + <span class="link" @click.stop> + {{ row?.agencyAgreement }} + <AgencyDescriptorProxy :id="row?.agencyFk" v-if="row?.agencyFk" /> </span> </template> <template #column-invoiceInFk="{ row }"> diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue index a7e192765..c69492836 100644 --- a/src/pages/Route/RouteExtendedList.vue +++ b/src/pages/Route/RouteExtendedList.vue @@ -38,7 +38,7 @@ const routeFilter = { }; const columns = computed(() => [ { - align: 'center', + align: 'right', name: 'id', label: 'Id', chip: { @@ -48,9 +48,8 @@ const columns = computed(() => [ columnFilter: false, }, { - align: 'center', name: 'workerFk', - label: t('route.Worker'), + label: t('globals.worker'), create: true, component: 'select', attrs: { @@ -71,9 +70,8 @@ const columns = computed(() => [ format: (row, dashIfEmpty) => dashIfEmpty(row.workerUserName), }, { - align: 'center', name: 'agencyModeFk', - label: t('route.Agency'), + label: t('globals.agency'), isTitle: true, cardVisible: true, create: true, @@ -90,9 +88,8 @@ const columns = computed(() => [ format: (row, dashIfEmpty) => dashIfEmpty(row.agencyName), }, { - align: 'center', name: 'vehicleFk', - label: t('route.Vehicle'), + label: t('globals.vehicle'), cardVisible: true, create: true, component: 'select', @@ -111,9 +108,8 @@ const columns = computed(() => [ format: (row, dashIfEmpty) => dashIfEmpty(row.vehiclePlateNumber), }, { - align: 'center', name: 'dated', - label: t('route.Date'), + label: t('globals.date'), columnFilter: false, cardVisible: true, create: true, @@ -122,9 +118,8 @@ const columns = computed(() => [ dated === '0000-00-00' ? dashIfEmpty(null) : toDate(dated), }, { - align: 'center', name: 'from', - label: t('route.From'), + label: t('globals.from'), visible: false, cardVisible: true, create: true, @@ -132,9 +127,8 @@ const columns = computed(() => [ format: ({ from }) => toDate(from), }, { - align: 'center', name: 'to', - label: t('route.To'), + label: t('globals.to'), visible: false, cardVisible: true, create: true, @@ -142,14 +136,13 @@ const columns = computed(() => [ format: ({ date }) => toDate(date), }, { - align: 'center', + align: 'right', name: 'm3', label: 'm3', cardVisible: true, columnClass: 'shrink', }, { - align: 'center', name: 'started', label: t('route.hourStarted'), component: 'time', @@ -157,7 +150,6 @@ const columns = computed(() => [ format: ({ started }) => toHour(started), }, { - align: 'center', name: 'finished', label: t('route.hourFinished'), component: 'time', @@ -165,7 +157,7 @@ const columns = computed(() => [ format: ({ finished }) => toHour(finished), }, { - align: 'center', + align: 'right', name: 'kmStart', label: t('route.KmStart'), columnClass: 'shrink', @@ -173,7 +165,7 @@ const columns = computed(() => [ visible: false, }, { - align: 'center', + align: 'right', name: 'kmEnd', label: t('route.KmEnd'), columnClass: 'shrink', @@ -181,16 +173,15 @@ const columns = computed(() => [ visible: false, }, { - align: 'center', + align: 'left', name: 'description', - label: t('route.Description'), + label: t('globals.description'), isTitle: true, create: true, component: 'input', field: 'description', }, { - align: 'center', name: 'isOk', label: t('route.Served'), component: 'checkbox', @@ -202,7 +193,7 @@ const columns = computed(() => [ name: 'tableActions', actions: [ { - title: t('route.Add tickets'), + title: t('route.addTicket'), icon: 'vn:ticketAdd', action: (row) => openTicketsDialog(row?.id), isPrimary: true, @@ -214,7 +205,7 @@ const columns = computed(() => [ isPrimary: true, }, { - title: t('route.Route summary'), + title: t('route.routeSummary'), icon: 'arrow_forward', action: (row) => navigate(row?.id), isPrimary: true, @@ -276,11 +267,13 @@ const openTicketsDialog = (id) => { <QDialog v-model="confirmationDialog"> <QCard style="min-width: 350px"> <QCardSection> - <p class="text-h6 q-ma-none">{{ t('route.Select the starting date') }}</p> + <p class="text-h6 q-ma-none"> + {{ t('route.extendedList.selectStartingDate') }} + </p> </QCardSection> <QCardSection class="q-pt-none"> <VnInputDate - :label="t('route.Starting date')" + :label="t('route.extendedList.startingDate')" v-model="startingDate" autofocus /> @@ -288,7 +281,7 @@ const openTicketsDialog = (id) => { <QCardActions align="right"> <QBtn flat - :label="t('route.Cancel')" + :label="t('globals.cancel')" v-close-popup class="text-primary" /> @@ -335,29 +328,34 @@ const openTicketsDialog = (id) => { <QBtn icon="vn:clone" color="primary" + flat class="q-mr-sm" :disable="!selectedRows?.length" @click="confirmationDialog = true" > - <QTooltip>{{ t('route.Clone Selected Routes') }}</QTooltip> + <QTooltip>{{ t('route.extendedList.cloneSelectedRoutes') }}</QTooltip> </QBtn> <QBtn icon="cloud_download" color="primary" + flat class="q-mr-sm" :disable="!selectedRows?.length" @click="showRouteReport" > - <QTooltip>{{ t('route.Download selected routes as PDF') }}</QTooltip> + <QTooltip>{{ + t('route.extendedList.downloadSelectedRoutes') + }}</QTooltip> </QBtn> <QBtn icon="check" color="primary" + flat class="q-mr-sm" :disable="!selectedRows?.length" @click="markAsServed()" > - <QTooltip>{{ t('route.Mark as served') }}</QTooltip> + <QTooltip>{{ t('route.extendedList.markServed') }}</QTooltip> </QBtn> </template> </VnTable> diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index 5723e2f0d..810157683 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -3,14 +3,18 @@ import { computed, ref, markRaw } from 'vue'; import { useI18n } from 'vue-i18n'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { toHour } from 'src/filters'; +import { useRouter } from 'vue-router'; import RouteSummary from 'pages/Route/Card/RouteSummary.vue'; import RouteFilter from 'pages/Route/Card/RouteFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import AgencyDescriptorProxy from 'src/pages/Route/Agency/Card/AgencyDescriptorProxy.vue'; +import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue'; import VnSection from 'src/components/common/VnSection.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); +const router = useRouter(); const { viewSummary } = useSummaryDialog(); const tableRef = ref([]); const dataKey = 'RouteList'; @@ -24,6 +28,14 @@ const routeFilter = { }, ], }; + +function redirectToTickets(id) { + router.push({ + name: 'RouteTickets', + params: { id }, + }); +} + const columns = computed(() => [ { align: 'right', @@ -38,7 +50,7 @@ const columns = computed(() => [ { align: 'left', name: 'workerFk', - label: t('route.Worker'), + label: t('globals.worker'), component: markRaw(VnSelectWorker), create: true, cardVisible: true, @@ -46,13 +58,7 @@ const columns = computed(() => [ columnFilter: false, }, { - align: 'left', - name: 'agencyName', - label: t('route.Agency'), - cardVisible: true, - }, - { - label: t('route.Agency'), + label: t('globals.agency'), name: 'agencyModeFk', component: 'select', attrs: { @@ -64,19 +70,12 @@ const columns = computed(() => [ }, }, create: true, - columnFilter: false, - visible: false, - }, - { - align: 'left', - name: 'vehiclePlateNumber', - label: t('route.Vehicle'), + columnFilter: true, cardVisible: true, }, { name: 'vehicleFk', - label: t('route.Vehicle'), - cardVisible: true, + label: t('globals.vehicle'), component: 'select', attrs: { url: 'vehicles', @@ -89,29 +88,29 @@ const columns = computed(() => [ }, }, create: true, - columnFilter: false, - visible: false, + columnFilter: true, + cardVisible: true, }, { - align: 'left', + align: 'center', name: 'started', label: t('route.hourStarted'), cardVisible: true, columnFilter: false, - format: (row) => toHour(row.started), + format: ({ started }) => toHour(started), }, { - align: 'left', + align: 'center', name: 'finished', label: t('route.hourFinished'), cardVisible: true, columnFilter: false, - format: (row) => toHour(row.started), + format: ({ finished }) => toHour(finished), }, { align: 'left', name: 'description', - label: t('route.Description'), + label: t('globals.description'), cardVisible: true, isTitle: true, create: true, @@ -119,7 +118,6 @@ const columns = computed(() => [ columnFilter: false, }, { - align: 'left', name: 'isOk', label: t('route.Served'), component: 'checkbox', @@ -130,6 +128,12 @@ const columns = computed(() => [ align: 'right', name: 'tableActions', actions: [ + { + title: t('globals.pageTitles.tickets'), + icon: 'vn:ticket', + action: (row) => redirectToTickets(row?.id), + isPrimary: true, + }, { title: t('components.smartCard.viewSummary'), icon: 'preview', @@ -155,9 +159,10 @@ const columns = computed(() => [ </template> <template #body> <VnTable + :with-filters="false" :data-key - :columns="columns" ref="tableRef" + :columns="columns" :right-search="false" redirect="route" :create="{ @@ -174,6 +179,24 @@ const columns = computed(() => [ <WorkerDescriptorProxy :id="row?.workerFk" v-if="row?.workerFk" /> </span> </template> + <template #column-agencyModeFk="{ row }"> + <span class="link" @click.stop> + {{ row?.agencyName }} + <AgencyDescriptorProxy + :id="row?.agencyModeFk" + v-if="row?.agencyModeFk" + /> + </span> + </template> + <template #column-vehicleFk="{ row }"> + <span class="link" @click.stop> + {{ row?.vehiclePlateNumber }} + <VehicleDescriptorProxy + :id="row?.vehicleFk" + v-if="row?.vehicleFk" + /> + </span> + </template> </VnTable> </template> </VnSection> diff --git a/src/pages/Route/RouteRoadmap.vue b/src/pages/Route/RouteRoadmap.vue index 23b1b1d5b..bdb3d12c4 100644 --- a/src/pages/Route/RouteRoadmap.vue +++ b/src/pages/Route/RouteRoadmap.vue @@ -2,13 +2,11 @@ import { useI18n } from 'vue-i18n'; import { computed, ref } from 'vue'; import { dashIfEmpty } from 'src/filters'; -import { toDate, toDateHourMin } from 'filters/index'; +import { toDate, toDateHourMin, toCurrency } from 'filters/index'; import { useQuasar } from 'quasar'; import { useSummaryDialog } from 'composables/useSummaryDialog'; -import toCurrency from 'filters/toCurrency'; import axios from 'axios'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import RoadmapSummary from 'pages/Route/Roadmap/RoadmapSummary.vue'; @@ -17,6 +15,8 @@ import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputTime from 'src/components/common/VnInputTime.vue'; import VnSection from 'src/components/common/VnSection.vue'; import RoadmapFilter from './Roadmap/RoadmapFilter.vue'; +import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue'; +import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; const { viewSummary } = useSummaryDialog(); const { t } = useI18n(); @@ -33,7 +33,7 @@ const columns = computed(() => [ { align: 'left', name: 'name', - label: t('Roadmap'), + label: t('route.roadmap.roadmap'), create: true, cardVisible: true, columnFilter: { @@ -41,9 +41,9 @@ const columns = computed(() => [ }, }, { - align: 'left', + align: 'center', name: 'etd', - label: t('ETD'), + label: t('route.roadmap.etd'), component: 'date', columnFilter: { inWhere: true, @@ -54,7 +54,7 @@ const columns = computed(() => [ { align: 'left', name: 'supplierFk', - label: t('Carrier'), + label: t('route.roadmap.carrier'), component: 'select', attrs: { url: 'suppliers', @@ -65,21 +65,21 @@ const columns = computed(() => [ }, { name: 'tractorPlate', - label: t('Plate'), + label: t('route.roadmap.vehicle'), field: (row) => row.tractorPlate, sortable: true, align: 'left', }, { name: 'price', - label: t('Price'), - field: (row) => toCurrency(row.price), + label: t('route.roadmap.price'), + format: ({ price }) => toCurrency(price), sortable: true, - align: 'left', + align: 'right', }, { name: 'observations', - label: t('Observations'), + label: t('route.roadmap.observations'), field: (row) => dashIfEmpty(row.observations), sortable: true, align: 'left', @@ -89,7 +89,7 @@ const columns = computed(() => [ name: 'tableActions', actions: [ { - title: t('Ver cmr'), + title: t('route.roadmap.seeCmr'), icon: 'preview', isPrimary: true, action: (row) => viewSummary(row?.id, RoadmapSummary), @@ -124,8 +124,8 @@ function confirmRemove() { .dialog({ component: VnConfirm, componentProps: { - title: t('Selected roadmaps will be removed'), - message: t('Are you sure you want to continue?'), + title: t('route.roadmap.selectedRoadmapsRemoved'), + message: t('route.roadmap.areYouSure'), promise: removeSelection, }, }) @@ -157,15 +157,24 @@ function exprBuilder(param, value) { <QCard style="min-width: 350px"> <QCardSection> <p class="text-h6 q-ma-none"> - {{ t('Select the estimated date of departure (ETD)') }} + {{ t('route.roadmap.selectEtd') }} </p> </QCardSection> <QCardSection class="q-pt-none"> - <VnInputDate :label="t('ETD')" v-model="etdDate" autofocus /> + <VnInputDate + :label="t('route.roadmap.etd')" + v-model="etdDate" + autofocus + /> </QCardSection> <QCardActions align="right"> - <QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" /> + <QBtn + flat + :label="t('globals.cancel')" + v-close-popup + class="text-primary" + /> <QBtn color="primary" v-close-popup @click="cloneSelection"> {{ t('globals.clone') }} </QBtn> @@ -181,7 +190,7 @@ function exprBuilder(param, value) { :disable="!selectedRows?.length" @click="isCloneDialogOpen = true" > - <QTooltip>{{ t('Clone Selected Routes') }}</QTooltip> + <QTooltip>{{ t('route.roadmap.cloneSelected') }}</QTooltip> </QBtn> <QBtn icon="delete" @@ -190,7 +199,7 @@ function exprBuilder(param, value) { :disable="!selectedRows?.length" @click="confirmRemove" > - <QTooltip>{{ t('Delete roadmap(s)') }}</QTooltip> + <QTooltip>{{ t('route.roadmap.deleteRoadmap') }}</QTooltip> </QBtn> </template> </VnSubToolbar> @@ -204,7 +213,7 @@ function exprBuilder(param, value) { }" > <template #advanced-menu> - <RoadmapFilter :dataKey /> + <RoadmapFilter :data-key /> </template> <template #body> <VnTable @@ -222,7 +231,7 @@ function exprBuilder(param, value) { redirect="route/roadmap" :create="{ urlCreate: 'Roadmaps', - title: t('Create routemap'), + title: t('route.roadmap.createRoadmap'), onDataSaved: ({ id }) => tableRef.redirect(id), formInitialData: {}, }" @@ -232,7 +241,10 @@ function exprBuilder(param, value) { {{ toDateHourMin(row.etd) }} </template> <template #column-supplierFk="{ row }"> - {{ row.supplierFk }} + <span class="link" @click.stop> + {{ row.driverName }} + <SupplierDescriptorProxy :id="row.supplierFk" /> + </span> </template> <template #more-create-dialog="{ data }"> <VnInputDate v-model="data.etd" /> @@ -251,21 +263,3 @@ function exprBuilder(param, value) { gap: 12px; } </style> -<i18n> -es: - Create routemap: Crear troncal - Search roadmaps: Buscar troncales - You can search by roadmap reference: Puedes buscar por referencia del troncal - Delete roadmap(s): Eliminar troncal(es) - Selected roadmaps will be removed: Los troncales seleccionadas serán eliminados - Are you sure you want to continue?: ¿Seguro que quieres continuar? - The date can't be empty: La fecha no puede estar vacía - Clone Selected Routes: Clonar rutas seleccionadas - Create roadmap: Crear troncal - Roadmap: Troncal - Carrier: Transportista - Plate: Matrícula - Price: Precio - Observations: Observaciones - Select the estimated date of departure (ETD): Selecciona la fecha estimada de salida -</i18n> diff --git a/src/pages/Route/RouteTickets.vue b/src/pages/Route/RouteTickets.vue index adc7dfdaa..5e28bb689 100644 --- a/src/pages/Route/RouteTickets.vue +++ b/src/pages/Route/RouteTickets.vue @@ -30,16 +30,16 @@ const columns = computed(() => [ align: 'center', }, { - name: 'street', - label: t('Street'), - field: (row) => row?.street, + name: 'client', + label: t('Client'), + field: (row) => row?.nickname, sortable: false, align: 'left', }, { - name: 'city', - label: t('City'), - field: (row) => row?.city, + name: 'street', + label: t('Street'), + field: (row) => row?.street, sortable: false, align: 'left', }, @@ -51,9 +51,9 @@ const columns = computed(() => [ align: 'center', }, { - name: 'client', - label: t('Client'), - field: (row) => row?.nickname, + name: 'city', + label: t('City'), + field: (row) => row?.city, sortable: false, align: 'left', }, @@ -199,12 +199,22 @@ const confirmRemove = (ticket) => { const openSmsDialog = async () => { const clientsId = []; const clientsPhone = []; - + const clientWithoutPhone = []; for (let ticket of selectedRows.value) { clientsId.push(ticket?.clientFk); const { data: client } = await axios.get(`Clients/${ticket?.clientFk}`); + if (!client.phone) { + clientWithoutPhone.push(ticket?.clientFk); + continue; + } clientsPhone.push(client.phone); } + if (clientWithoutPhone.length) { + quasar.notify({ + type: 'warning', + message: t('components.VnNotes.clientWithoutPhone', { clientWithoutPhone }), + }); + } quasar.dialog({ component: SendSmsDialog, @@ -319,7 +329,7 @@ const openSmsDialog = async () => { selection="multiple" > <template #body-cell-order="{ row }"> - <QTd class="order-field"> + <QTd class="order-field" auto-width> <div class="flex no-wrap items-center"> <QIcon name="low_priority" @@ -341,7 +351,7 @@ const openSmsDialog = async () => { </QTd> </template> <template #body-cell-city="{ value, row }"> - <QTd auto-width> + <QTd> <span class="link" @click="goToBuscaman(row)"> {{ value }} <QTooltip>{{ t('Open buscaman') }}</QTooltip> @@ -349,7 +359,7 @@ const openSmsDialog = async () => { </QTd> </template> <template #body-cell-client="{ value, row }"> - <QTd auto-width> + <QTd> <span class="link"> {{ value }} <CustomerDescriptorProxy :id="row?.clientFk" /> diff --git a/src/pages/Route/Vehicle/Card/VehicleCard.vue b/src/pages/Route/Vehicle/Card/VehicleCard.vue index f59420aa2..b6038c24c 100644 --- a/src/pages/Route/Vehicle/Card/VehicleCard.vue +++ b/src/pages/Route/Vehicle/Card/VehicleCard.vue @@ -1,10 +1,10 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import VehicleDescriptor from './VehicleDescriptor.vue'; import VehicleFilter from '../VehicleFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Vehicle" url="Vehicles" :filter="VehicleFilter" diff --git a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue index d9a2434ab..bab7fa998 100644 --- a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue +++ b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue @@ -1,17 +1,30 @@ <script setup> +import { computed } from 'vue'; +import { useRoute } from 'vue-router'; import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; const { notify } = useNotify(); + +const props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); + +const route = useRoute(); +const entityId = computed(() => props.id || route.params.id); </script> <template> - <CardDescriptor - :url="`Vehicles/${$route.params.id}`" + <EntityDescriptor + :url="`Vehicles/${entityId}`" data-key="Vehicle" title="numberPlate" - :to-module="{ name: 'VehicleList' }" + :to-module="{ name: 'RouteVehicle' }" > <template #menu="{ entity }"> <QItem @@ -41,7 +54,7 @@ const { notify } = useNotify(); <VnLv :label="$t('globals.model')" :value="entity.model" /> <VnLv :label="$t('globals.country')" :value="entity.countryCodeFk" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> es: diff --git a/src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue b/src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue new file mode 100644 index 000000000..cc0943cb8 --- /dev/null +++ b/src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue @@ -0,0 +1,20 @@ +<script setup> +import VehicleDescriptor from 'pages/Route/Vehicle/Card/VehicleDescriptor.vue'; +import VehicleSummary from './VehicleSummary.vue'; + +const $props = defineProps({ + id: { + type: Number, + required: true, + }, + summary: { + type: Object, + default: null, + }, +}); +</script> +<template> + <QPopupProxy> + <VehicleDescriptor v-if="$props.id" :id="$props.id" :summary="VehicleSummary" /> + </QPopupProxy> +</template> 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/Vehicle/Card/VehicleSummary.vue b/src/pages/Route/Vehicle/Card/VehicleSummary.vue index e4b0a9497..13d4bbc9d 100644 --- a/src/pages/Route/Vehicle/Card/VehicleSummary.vue +++ b/src/pages/Route/Vehicle/Card/VehicleSummary.vue @@ -13,12 +13,13 @@ const props = defineProps({ id: { type: [Number, String], default: null } }); const route = useRoute(); const entityId = computed(() => props.id || +route.params.id); +const baseLink = `#/${route.meta.moduleName.toLowerCase()}/vehicle/${entityId.value}`; const links = { - 'basic-data': `#/vehicle/${entityId.value}/basic-data`, - notes: `#/vehicle/${entityId.value}/notes`, - dms: `#/vehicle/${entityId.value}/dms`, - 'invoice-in': `#/vehicle/${entityId.value}/invoice-in`, - events: `#/vehicle/${entityId.value}/events`, + 'basic-data': `${baseLink}/basic-data`, + notes: `${baseLink}/notes`, + dms: `${baseLink}/dms`, + 'invoice-in': `${baseLink}/invoice-in`, + events: `${baseLink}/events`, }; </script> <template> @@ -54,7 +55,10 @@ const links = { <template #value> <span class="link"> {{ entity.supplier?.name }} - <SupplierDescriptorProxy :id="entity.supplierFk" /> + <SupplierDescriptorProxy + v-if="entity.supplierFk" + :id="entity.supplierFk" + /> </span> </template> </VnLv> @@ -63,6 +67,7 @@ const links = { <span class="link"> {{ entity.supplierCooler?.name }} <SupplierDescriptorProxy + v-if="entity.supplierCoolerFk" :id="entity.supplierCoolerFk" /> </span> diff --git a/src/pages/Route/Vehicle/VehicleList.vue b/src/pages/Route/Vehicle/VehicleList.vue index e5b945010..a79cc2e35 100644 --- a/src/pages/Route/Vehicle/VehicleList.vue +++ b/src/pages/Route/Vehicle/VehicleList.vue @@ -116,6 +116,7 @@ const columns = computed(() => [ title: t('components.smartCard.openSummary'), icon: 'preview', action: (row) => viewSummary(row.id, VehicleSummary), + isPrimary: true, }, ], }, diff --git a/src/pages/Route/locale/en.yml b/src/pages/Route/locale/en.yml index cc445f412..e7e2d691e 100644 --- a/src/pages/Route/locale/en.yml +++ b/src/pages/Route/locale/en.yml @@ -1,48 +1,79 @@ route: + filter: + Served: Served + extendedList: + selectStartingDate: Select the starting date + startingDate: Starting date + cloneSelectedRoutes: Clone selected routes + downloadSelectedRoutes: Download selected routes as PDF + markServed: Mark as served roadmap: + roadmap: Roadmap + carrier: Carrier + vehicle: Vehicle + price: Price + observations: Observations + etd: ETD + dateCantEmpty: The date can't be empty + createRoadmap: Create roadmap + deleteRoadmap: Delete roadmap(s) + cloneSelected: Clone selected routes + selectedRoadmapsRemoved: Selected roadmaps will be removed + areYouSure: Are you sure you want to continue? + selectEtd: Select the estimated date of departure (ETD) search: Search roadmap searchInfo: You can search by roadmap reference params: + warehouseFk: Warehouse + description: Description + m3: m³ + scopeDays: Days Onward + vehicleFk: Vehicle + agencyModeFk: Agency + workerFk: Worker + from: From + to: To + isOk: Served etd: ETD tractorPlate: Plate price: Price observations: Observations - id: ID + id: Id name: Name cmrFk: CMR id hasCmrDms: Attached in gestdoc ticketFk: Ticketd id routeFk: Route id + clientFk: Client id + countryFk: Country shipped: Shipped agencyAgreement: Agency agreement agencyModeName: Agency route + isOwn: Own + isAnyVolumeAllowed: Any volume allowed Worker: Worker Agency: Agency Vehicle: Vehicle Description: Description hourStarted: H.Start hourFinished: H.End - dated: Dated - From: From - To: To + createRoute: Create route Date: Date KmStart: Km start KmEnd: Km end Served: Served - Clone Selected Routes: Clone selected routes - Select the starting date: Select the starting date - Stating date: Starting date - Cancel: Cancel - Mark as served: Mark as served - Download selected routes as PDF: Download selected routes as PDF - Add ticket: Add ticket - Summary: Summary + addTicket: Add ticket + routeSummary: Go to summary Route is closed: Route is closed Route is not served: Route is not served search: Search route searchInfo: You can search by route reference + dated: Dated + preview: Preview cmr: - list: + search: Search Cmr + searchInfo: You can search Cmr by Id + params: results: results cmrFk: CMR id hasCmrDms: Attached in gestdoc @@ -50,8 +81,10 @@ route: 'false': 'No' ticketFk: Ticketd id routeFk: Route id - country: Country + countryFk: Country clientFk: Client id + warehouseFk: Warehouse shipped: Preparation date viewCmr: View CMR - downloadCmrs: Download CMRs \ No newline at end of file + downloadCmrs: Download CMRs + search: General search diff --git a/src/pages/Route/locale/es.yml b/src/pages/Route/locale/es.yml index 51d43774a..2785ded31 100644 --- a/src/pages/Route/locale/es.yml +++ b/src/pages/Route/locale/es.yml @@ -1,21 +1,57 @@ route: + filter: + Served: Servida + extendedList: + selectStartingDate: Seleccione la fecha de inicio + statingDate: Fecha de inicio + cloneSelectedRoutes: Clonar rutas seleccionadas + downloadSelectedRoutes: Descargar rutas seleccionadas como PDF + markServed: Marcar como servidas roadmap: + roadmap: Troncal + carrier: Transportista + vehicle: Vehículo + price: Precio + observations: Observaciones + etd: ETD + dateCantEmpty: La fecha no puede estar vacía + createRoadmap: Crear troncal + deleteRoadmap: Eliminar troncal(es) + cloneSelected: Clonar rutas seleccionadas + selectedRoadmapsRemoved: Los troncales seleccionadas serán eliminados + areYouSure: ¿Seguro que quieres continuar? + selectEtd: Selecciona la fecha estimada de salida search: Buscar troncales searchInfo: Puedes buscar por referencia del troncal params: - agencyModeName: Agencia Ruta - agencyAgreement: Agencia Acuerdo - id: Id - name: Troncal + warehouseFk: Almacén + description: Descripción + m3: m³ + scopeDays: Días adelante + vehicleFk: Vehículo + agencyModeFk: Agencia + workerFk: Trabajador + from: Desde + to: Hasta + isOk: Servida etd: ETD tractorPlate: Matrícula price: Precio observations: Observaciones + id: Id + name: Troncal cmrFk: Id CMR hasCmrDms: Gestdoc + search: Búsqueda general ticketFk: Id ticket - routeFK: Id ruta + routeFk: Id ruta + clientFk: Id cliente + countryFk: Pais shipped: Fecha preparación + agencyModeName: Agencia Ruta + agencyAgreement: Agencia Acuerdo + isOwn: Propio + isAnyVolumeAllowed: Cualquier volumen Worker: Trabajador Agency: Agencia Vehicle: Vehículo @@ -23,25 +59,18 @@ route: hourStarted: H.Inicio hourFinished: H.Fin createRoute: Crear ruta - From: Desde - To: Hasta Date: Fecha KmStart: Km inicio KmEnd: Km fin Served: Servida - Clone Selected Routes: Clonar rutas seleccionadas - Select the starting date: Seleccione la fecha de inicio - Stating date: Fecha de inicio - Cancel: Cancelar - Mark as served: Marcar como servidas - Download selected routes as PDF: Descargar rutas seleccionadas como PDF - Add ticket: Añadir tickets - preview: Vista previa - Summary: Resumen + addTicket: Añadir tickets + routeSummary: Ir a vista previa Route is closed: La ruta está cerrada Route is not served: La ruta no está servida search: Buscar rutas searchInfo: Puedes buscar por referencia de la ruta + dated: Fecha + preview: Vista previa cmr: list: results: resultados @@ -55,4 +84,4 @@ route: clientFk: Id cliente shipped: Fecha preparación viewCmr: Ver CMR - downloadCmrs: Descargar CMRs \ No newline at end of file + downloadCmrs: Descargar CMRs diff --git a/src/pages/Shelving/Card/ShelvingCard.vue b/src/pages/Shelving/Card/ShelvingCard.vue index 9e0ac8ad2..e2fb79fb0 100644 --- a/src/pages/Shelving/Card/ShelvingCard.vue +++ b/src/pages/Shelving/Card/ShelvingCard.vue @@ -1,11 +1,11 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue'; import filter from './ShelvingFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Shelving" url="Shelvings" :filter="filter" diff --git a/src/pages/Shelving/Card/ShelvingDescriptor.vue b/src/pages/Shelving/Card/ShelvingDescriptor.vue index 5e618aa7f..2405467da 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptor.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import ShelvingDescriptorMenu from 'pages/Shelving/Card/ShelvingDescriptorMenu.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; @@ -24,7 +24,7 @@ const entityId = computed(() => { }); </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Shelvings/${entityId}`" :filter="filter" title="code" @@ -45,5 +45,5 @@ const entityId = computed(() => { <template #menu="{ entity }"> <ShelvingDescriptorMenu :shelving="entity" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Shelving/Card/ShelvingFilter.vue b/src/pages/Shelving/Card/ShelvingFilter.vue index 56cf4f58c..35657a972 100644 --- a/src/pages/Shelving/Card/ShelvingFilter.vue +++ b/src/pages/Shelving/Card/ShelvingFilter.vue @@ -2,6 +2,7 @@ import { useI18n } from 'vue-i18n'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const props = defineProps({ @@ -38,27 +39,14 @@ const emit = defineEmits(['search']); option-label="code" :filter-options="['id', 'code']" dense - outlined - rounded + filled sort-by="code ASC" /> </QItemSection> </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnSelect - dense - outlined - rounded - :label="t('params.userFk')" - v-model="params.userFk" - url="Workers/activeWithInheritedRole" - option-value="id" - option-label="firstName" - :where="{ role: 'salesPerson' }" - sort-by="firstName ASC" - :use-like="false" - /> + <VnSelectWorker v-model="params.userFk" filled /> </QItemSection> </QItem> <QItem class="q-mb-md"> diff --git a/src/pages/Shelving/Card/ShelvingSummary.vue b/src/pages/Shelving/Card/ShelvingSummary.vue index f89ff4d78..4a6669624 100644 --- a/src/pages/Shelving/Card/ShelvingSummary.vue +++ b/src/pages/Shelving/Card/ShelvingSummary.vue @@ -6,6 +6,7 @@ import VnLv from 'components/ui/VnLv.vue'; import VnUserLink from 'components/ui/VnUserLink.vue'; import filter from './ShelvingFilter.js'; import ShelvingDescriptorMenu from './ShelvingDescriptorMenu.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; const $props = defineProps({ id: { @@ -38,13 +39,10 @@ const entityId = computed(() => $props.id || route.params.id); </template> <template #body="{ entity }"> <QCard class="vn-one"> - <RouterLink - class="header header-link" - :to="{ name: 'ShelvingBasicData', params: { id: entityId } }" - > - {{ $t('globals.pageTitles.basicData') }} - <QIcon name="open_in_new" /> - </RouterLink> + <VnTitle + :url="`#/shelving/${entityId}/basic-data`" + :text="$t('globals.pageTitles.basicData')" + /> <VnLv :label="$t('globals.code')" :value="entity.code" /> <VnLv :label="$t('shelving.list.parking')" diff --git a/src/pages/Shelving/Parking/Card/ParkingCard.vue b/src/pages/Shelving/Parking/Card/ParkingCard.vue index b32c1b7d3..c8b3c60d7 100644 --- a/src/pages/Shelving/Parking/Card/ParkingCard.vue +++ b/src/pages/Shelving/Parking/Card/ParkingCard.vue @@ -1,11 +1,11 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import ParkingDescriptor from 'pages/Shelving/Parking/Card/ParkingDescriptor.vue'; import filter from './ParkingFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Parking" url="Parkings" :filter="filter" diff --git a/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue b/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue index 46c9f8ea0..0e01238a0 100644 --- a/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue +++ b/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue @@ -1,7 +1,7 @@ <script setup> import { computed } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import filter from './ParkingFilter.js'; const props = defineProps({ @@ -16,17 +16,17 @@ const route = useRoute(); const entityId = computed(() => props.id || route.params.id); </script> <template> - <CardDescriptor + <EntityDescriptor data-key="Parking" :url="`Parkings/${entityId}`" title="code" :filter="filter" - :to-module="{ name: 'ParkingList' }" + :to-module="{ name: 'ParkingMain' }" > <template #body="{ entity }"> <VnLv :label="$t('globals.code')" :value="entity.code" /> <VnLv :label="$t('parking.pickingOrder')" :value="entity.pickingOrder" /> <VnLv :label="$t('parking.sector')" :value="entity.sector?.description" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Shelving/Parking/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/Shelving/Parking/Card/ParkingSummary.vue b/src/pages/Shelving/Parking/Card/ParkingSummary.vue index 7188ebeb6..1365c71ca 100644 --- a/src/pages/Shelving/Parking/Card/ParkingSummary.vue +++ b/src/pages/Shelving/Parking/Card/ParkingSummary.vue @@ -4,6 +4,7 @@ import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'components/ui/VnLv.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; const $props = defineProps({ id: { @@ -28,13 +29,10 @@ const filter = { <template #body="{ entity }"> <QCard class="vn-one"> <QCardSection class="q-pa-none"> - <a - class="header header-link" - :href="`#/parking/${entityId}/basic-data`" - > - {{ t('globals.pageTitles.basicData') }} - <QIcon name="open_in_new" /> - </a> + <VnTitle + :url="`#/shelving/parking/${entityId}/basic-data`" + :text="$t('globals.pageTitles.basicData')" + /> </QCardSection> <VnLv :label="t('globals.code')" :value="entity.code" /> <VnLv diff --git a/src/pages/Shelving/Parking/ParkingFilter.vue b/src/pages/Shelving/Parking/ParkingFilter.vue index 1d7c3a4b6..59cb49459 100644 --- a/src/pages/Shelving/Parking/ParkingFilter.vue +++ b/src/pages/Shelving/Parking/ParkingFilter.vue @@ -36,11 +36,7 @@ const emit = defineEmits(['search']); <template #body="{ params }"> <QItem> <QItemSection> - <VnInput - :label="t('params.code')" - v-model="params.code" - is-outlined - /> + <VnInput :label="t('params.code')" v-model="params.code" filled /> </QItemSection> </QItem> <QItem> @@ -51,8 +47,7 @@ const emit = defineEmits(['search']); option-label="description" :label="t('params.sectorFk')" dense - outlined - rounded + filled :options="sectors" use-input input-debounce="0" diff --git a/src/pages/Supplier/Card/SupplierBalanceFilter.vue b/src/pages/Supplier/Card/SupplierBalanceFilter.vue index c4b63d9c8..c727688ad 100644 --- a/src/pages/Supplier/Card/SupplierBalanceFilter.vue +++ b/src/pages/Supplier/Card/SupplierBalanceFilter.vue @@ -33,7 +33,7 @@ defineProps({ :label="t('params.from')" v-model="params.from" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -47,8 +47,7 @@ defineProps({ :include="{ relation: 'accountingType' }" sort-by="id" dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -74,8 +73,7 @@ defineProps({ option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Supplier/Card/SupplierCard.vue b/src/pages/Supplier/Card/SupplierCard.vue index e30f79f96..74b3520bf 100644 --- a/src/pages/Supplier/Card/SupplierCard.vue +++ b/src/pages/Supplier/Card/SupplierCard.vue @@ -1,10 +1,10 @@ <script setup> import SupplierDescriptor from './SupplierDescriptor.vue'; -import VnCardBeta from 'src/components/common/VnCardBeta.vue'; +import VnCard from 'src/components/common/VnCard.vue'; import filter from './SupplierFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Supplier" url="Suppliers" :descriptor="SupplierDescriptor" diff --git a/src/pages/Supplier/Card/SupplierConsumption.vue b/src/pages/Supplier/Card/SupplierConsumption.vue index 718de95dd..259561f4c 100644 --- a/src/pages/Supplier/Card/SupplierConsumption.vue +++ b/src/pages/Supplier/Card/SupplierConsumption.vue @@ -203,7 +203,7 @@ onMounted(async () => { </QTr> <QTr v-for="(buy, index) in row.buys" :key="index"> <QTd no-hover> - <QBtn flat color="blue" dense no-caps>{{ buy.itemName }}</QBtn> + <QBtn flat class="link" dense no-caps>{{ buy.itemName }}</QBtn> <ItemDescriptorProxy :id="buy.itemFk" /> </QTd> diff --git a/src/pages/Supplier/Card/SupplierConsumptionFilter.vue b/src/pages/Supplier/Card/SupplierConsumptionFilter.vue index 390f7d9ff..e21e37eb3 100644 --- a/src/pages/Supplier/Card/SupplierConsumptionFilter.vue +++ b/src/pages/Supplier/Card/SupplierConsumptionFilter.vue @@ -25,20 +25,12 @@ defineProps({ <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('params.search')" - is-outlined - /> + <VnInput v-model="params.search" :label="t('params.search')" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.itemId" - :label="t('params.itemId')" - is-outlined - /> + <VnInput v-model="params.itemId" :label="t('params.itemId')" filled /> </QItemSection> </QItem> <QItem> @@ -54,8 +46,7 @@ defineProps({ option-label="nickname" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -73,8 +64,7 @@ defineProps({ option-label="name" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -102,8 +92,7 @@ defineProps({ option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -113,7 +102,7 @@ defineProps({ :label="t('params.from')" v-model="params.from" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -123,7 +112,7 @@ defineProps({ :label="t('params.to')" v-model="params.to" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 462bdf853..2511edf11 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toDateString } from 'src/filters'; @@ -61,7 +61,7 @@ const getEntryQueryParams = (supplier) => { </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Suppliers/${entityId}`" :filter="filter" data-key="Supplier" @@ -106,7 +106,7 @@ const getEntryQueryParams = (supplier) => { <QBtn :to="{ name: 'EntryList', - query: { params: JSON.stringify(getEntryQueryParams(entity)) }, + query: { table: JSON.stringify(getEntryQueryParams(entity)) }, }" size="md" icon="vn:entry" @@ -136,7 +136,7 @@ const getEntryQueryParams = (supplier) => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index d1d437a19..ec89d77e0 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -172,6 +172,7 @@ const filterColumns = computed(() => { > <template #more-create-dialog="{ data }"> <VnInput + class="col-span-2" :label="t('globals.name')" v-model="data.socialName" :uppercase="true" diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue index 055c9a0ff..83c621b20 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -91,7 +91,7 @@ const totalPrice = computed(() => { const totalNewPrice = computed(() => { return rows.value.reduce( (acc, item) => acc + item.component.newPrice * item.quantity, - 0 + 0, ); }); @@ -210,18 +210,18 @@ onMounted(async () => { flat > <template #body-cell-item="{ row }"> - <QTd @click.stop class="link"> - <QBtn flat> + <QTd align="center"> + <span @click.stop class="link"> {{ row.itemFk }} <ItemDescriptorProxy :id="row.itemFk" /> - </QBtn> + </span> </QTd> </template> <template #body-cell-description="{ row }"> <QTd style="min-width: 120px; max-width: 120px"> <div class="column q-pb-xs" style="min-width: 120px"> <span>{{ row.item.name }}</span> - <FetchedTags :item="row.item" class="full-width" /> + <FetchedTags :item="row.item" class="full-width" :columns="6" /> </div> </QTd> </template> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue index 9d70fea38..61932468c 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue @@ -25,9 +25,7 @@ const { validate } = useValidator(); const { notify } = useNotify(); const router = useRouter(); const { t } = useI18n(); -const canEditZone = useAcl().hasAny([ - { model: 'Ticket', props: 'editZone', accessType: 'WRITE' }, -]); +const canEditZone = useAcl().hasAcl('Ticket', 'editZone', 'WRITE'); const agencyFetchRef = ref(); const warehousesOptions = ref([]); diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue index ef2eb75d6..76191b099 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue @@ -45,7 +45,7 @@ const getPriceDifference = async () => { }; const { data } = await axios.post( `tickets/${ticket.value.id}/priceDifference`, - params + params, ); ticket.value.sale = data; }; @@ -72,7 +72,7 @@ const submit = async () => { const { data } = await axios.post( `tickets/${ticket.value.id}/componentUpdate`, - params + params, ); if (!data) return; @@ -99,7 +99,7 @@ const onNextStep = async () => { openConfirmationModal( t('basicData.negativesConfirmTitle'), t('basicData.negativesConfirmMessage'), - submitWithNegatives + submitWithNegatives, ); else submit(); } diff --git a/src/pages/Ticket/Card/TicketCard.vue b/src/pages/Ticket/Card/TicketCard.vue index e22d5799a..19dbd608c 100644 --- a/src/pages/Ticket/Card/TicketCard.vue +++ b/src/pages/Ticket/Card/TicketCard.vue @@ -1,10 +1,10 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import TicketDescriptor from './TicketDescriptor.vue'; import filter from './TicketFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Ticket" url="Tickets" :descriptor="TicketDescriptor" diff --git a/src/pages/Ticket/Card/TicketCreateTracking.vue b/src/pages/Ticket/Card/TicketCreateTracking.vue deleted file mode 100644 index 5c1e916f2..000000000 --- a/src/pages/Ticket/Card/TicketCreateTracking.vue +++ /dev/null @@ -1,59 +0,0 @@ -<script setup> -import { ref } from 'vue'; -import { useRoute } from 'vue-router'; -import { useI18n } from 'vue-i18n'; - -import FormModelPopup from 'components/FormModelPopup.vue'; -import VnRow from 'components/ui/VnRow.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import FetchData from 'components/FetchData.vue'; - -import { useState } from 'src/composables/useState'; -import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; - -const emit = defineEmits(['onRequestCreated']); - -const route = useRoute(); -const { t } = useI18n(); -const state = useState(); -const user = state.getUser(); -const stateFetchDataRef = ref(null); -const statesOptions = ref([]); - -const onStateFkChange = (formData) => (formData.userFk = user.value.id); -</script> -<template> - <FetchData - ref="stateFetchDataRef" - url="States" - auto-load - @on-fetch="(data) => (statesOptions = data)" - /> - <FormModelPopup - :title="t('Create tracking')" - url-create="Tickets/state" - model="CreateTicketTracking" - :form-initial-data="{ ticketFk: route.params.id }" - @on-data-saved="() => emit('onRequestCreated')" - > - <template #form-inputs="{ data }"> - <VnRow> - <VnSelect - v-model="data.stateFk" - :label="t('ticketList.state')" - :options="statesOptions" - @update:model-value="onStateFkChange(data)" - hide-selected - option-label="name" - option-value="id" - /> - <VnSelectWorker v-model="data.userFk" :fields="['id', 'name']" /> - </VnRow> - </template> - </FormModelPopup> -</template> - -<i18n> - es: - Create tracking: Crear estado -</i18n> diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index 1e585592f..96920231c 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -3,14 +3,15 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import DepartmentDescriptorProxy from 'pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import VnLv from 'src/components/ui/VnLv.vue'; -import VnUserLink from 'src/components/ui/VnUserLink.vue'; import { toDateTimeFormat } from 'src/filters/date'; import filter from './TicketFilter.js'; import FetchData from 'src/components/FetchData.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; +import axios from 'axios'; const $props = defineProps({ id: { @@ -31,23 +32,37 @@ const entityId = computed(() => { return $props.id || route.params.id; }); const problems = ref({}); +const originalTicket = ref(); function ticketFilter(ticket) { return JSON.stringify({ clientFk: ticket.clientFk }); } +async function getClaims() { + const userFilter = { where: { refundTicketFk: entityId.value } }; + const { data } = await axios.get(`TicketRefunds`, { + params: { filter: JSON.stringify(userFilter) }, + }); + if (!data) return; + originalTicket.value = data[0]?.originalTicketFk; +} +async function getProblems() { + const { data } = await axios.get(`Tickets/${entityId.value}/getTicketProblems`); + if (!data) return; + problems.value = data[0]; +} +function getInfo() { + getClaims(); + getProblems(); +} </script> <template> - <FetchData - :url="`Tickets/${entityId}/getTicketProblems`" - auto-load - @on-fetch="(data) => ([problems] = data)" - /> - <CardDescriptor + <EntityDescriptor :url="`Tickets/${entityId}`" :filter="filter" data-key="Ticket" :summary="$props.summary" + @on-fetch="getInfo" width="lg-width" > <template #menu="{ entity }"> @@ -73,12 +88,12 @@ function ticketFilter(ticket) { </QBadge> </template> </VnLv> - <VnLv :label="t('globals.salesPerson')"> + <VnLv :label="t('customer.summary.team')"> <template #value> - <VnUserLink - :name="entity.client?.salesPersonUser?.name" - :worker-id="entity.client?.salesPersonFk" - /> + <span class="link"> + {{ entity?.client?.department?.name || '-' }} + <DepartmentDescriptorProxy :id="entity?.client?.departmentFk" /> + </span> </template> </VnLv> <VnLv @@ -95,7 +110,7 @@ function ticketFilter(ticket) { </template> <template #icons="{ entity }"> <QCardActions class="q-gutter-x-xs"> - <TicketProblems :row="{ ...entity?.client, ...problems }" /> + <TicketProblems :row="{ ...entity?.client, ...problems, ...entity }" /> </QCardActions> </template> <template #actions="{ entity }"> @@ -129,9 +144,18 @@ function ticketFilter(ticket) { > <QTooltip>{{ t('ticket.card.newOrder') }}</QTooltip> </QBtn> + <QBtn + v-if="originalTicket" + size="md" + icon="vn:claims" + color="primary" + :to="{ name: 'TicketCard', params: { id: originalTicket } }" + > + <QTooltip>{{ t('ticket.card.ticketClaimed') }}</QTooltip> + </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 63e45c8ab..f7389b592 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -32,7 +32,7 @@ onMounted(() => { watch( () => props.ticket, - () => restoreTicket + () => restoreTicket, ); const { push, currentRoute } = useRouter(); @@ -58,7 +58,7 @@ const hasDocuwareFile = ref(); const quasar = useQuasar(); const canRestoreTicket = ref(false); -const onClientSelected = async(clientId) =>{ +const onClientSelected = async (clientId) => { client.value = clientId; await fetchClient(); await fetchAddresses(); @@ -66,10 +66,10 @@ const onClientSelected = async(clientId) =>{ const onAddressSelected = (addressId) => { address.value = addressId; -} +}; const fetchClient = async () => { - const response = await getClient(client.value) + const response = await getClient(client.value); if (!response) return; const [retrievedClient] = response.data; selectedClient.value = retrievedClient; @@ -151,7 +151,7 @@ function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') { recipientId: ticket.value.clientFk, type: type, }, - '_blank' + '_blank', ); } @@ -297,8 +297,8 @@ async function transferClient() { clientFk: client.value, addressFk: address.value, }; - - await axios.patch( `Tickets/${ticketId.value}/transferClient`, params ); + + await axios.patch(`Tickets/${ticketId.value}/transferClient`, params); window.location.reload(); } @@ -339,7 +339,7 @@ async function changeShippedHour(time) { const { data } = await axios.post( `Tickets/${ticketId.value}/updateEditableTicket`, - params + params, ); if (data) window.location.reload(); @@ -405,8 +405,7 @@ async function uploadDocuware(force) { uploadDocuware(true); }); - const { data } = await axios.post(`Docuwares/upload`, { - fileCabinet: 'deliveryNote', + const { data } = await axios.post(`Docuwares/upload-delivery-note`, { ticketIds: [parseInt(ticketId.value)], }); @@ -500,7 +499,7 @@ async function ticketToRestore() { </QItem> </template> </VnSelect> - <VnSelect + <VnSelect :disable="!client" :options="addressesOptions" :fields="['id', 'nickname']" @@ -815,7 +814,7 @@ async function ticketToRestore() { en: addTurn: Add turn invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}" - + es: Show Delivery Note...: Ver albarán... Send Delivery Note...: Enviar albarán... diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index ff40a6592..266c82ccd 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -33,7 +33,7 @@ const save = (sale = $props.sale) => { }; const getMana = async () => { - const { data } = await axios.get(`Tickets/${route.params.id}/getSalesPersonMana`); + const { data } = await axios.get(`Tickets/${route.params.id}/getDepartmentMana`); mana.value = data; await getUsesMana(); }; diff --git a/src/pages/Ticket/Card/TicketFilter.js b/src/pages/Ticket/Card/TicketFilter.js index 7846f1658..daa204a7a 100644 --- a/src/pages/Ticket/Card/TicketFilter.js +++ b/src/pages/Ticket/Card/TicketFilter.js @@ -12,7 +12,7 @@ export default { fields: [ 'id', 'name', - 'salesPersonFk', + 'departmentFk', 'phone', 'mobile', 'email', @@ -29,7 +29,7 @@ export default { fields: ['id', 'lang'], }, }, - { relation: 'salesPersonUser' }, + { relation: 'department' }, ], }, }, diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index e69d489c0..e721e2d1c 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, computed, watch } from 'vue'; +import { onMounted, ref, computed, watch, inject } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter, useRoute } from 'vue-router'; import { useQuasar } from 'quasar'; @@ -25,7 +25,7 @@ import VnTable from 'src/components/VnTable/VnTable.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; - +const app = inject('app'); const route = useRoute(); const router = useRouter(); const { t } = useI18n(); @@ -99,6 +99,7 @@ const columns = computed(() => [ align: 'left', label: t('globals.quantity'), name: 'quantity', + class: 'shrink', format: (row) => toCurrency(row.quantity), }, { @@ -174,29 +175,39 @@ const getSaleTotal = (sale) => { return price - discount; }; -const getRowUpdateInputEvents = (sale) => ({ - 'keyup.enter': () => { - changeQuantity(sale); - }, - blur: () => { - changeQuantity(sale); - }, -}); +const getRowUpdateInputEvents = (sale) => { + return { + 'keyup.enter': () => { + changeQuantity(sale); + }, + blur: () => { + changeQuantity(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) => { if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity) return; - if (!sale.id) return addSale(sale); + else sale.originalQuantity = sale.quantity; + try { + if (!sale.id) await addSale(sale); + } catch (e) { + app.config.errorHandler(e); + return; + } if (await isSalePrepared(sale)) { await confirmUpdate(() => updateQuantity(sale)); } else await updateQuantity(sale); }; - const updateQuantity = async (sale) => { try { let { quantity, id } = sale; @@ -209,7 +220,7 @@ const updateQuantity = async (sale) => { (s) => s.id === sale.id, ); sale.quantity = quantity; - throw e; + app.config.errorHandler(e); } }; @@ -218,24 +229,27 @@ const addSale = async (sale) => { barcode: sale.itemFk, quantity: sale.quantity, }; + try { + const { data } = await axios.post(`tickets/${route.params.id}/addSale`, params); - const { data } = await axios.post(`tickets/${route.params.id}/addSale`, params); + if (!data) return; - if (!data) return; + const newSale = data; + sale.id = newSale.id; + sale.image = newSale.item.image; + sale.subName = newSale.item.subName; + sale.concept = newSale.concept; + sale.quantity = newSale.quantity; + sale.discount = newSale.discount; + sale.price = newSale.price; + sale.item = newSale.item; - const newSale = data; - sale.id = newSale.id; - sale.image = newSale.item.image; - sale.subName = newSale.item.subName; - sale.concept = newSale.concept; - sale.quantity = newSale.quantity; - sale.discount = newSale.discount; - sale.price = newSale.price; - sale.item = newSale.item; - - notify('globals.dataSaved', 'positive'); - sale.isNew = false; - arrayData.fetch({}); + notify('globals.dataSaved', 'positive'); + sale.isNew = false; + resetChanges(); + } catch (e) { + app.config.errorHandler(e); + } }; const changeConcept = async (sale) => { if (await isSalePrepared(sale)) { @@ -244,10 +258,14 @@ const changeConcept = async (sale) => { }; const updateConcept = async (sale) => { - const data = { newConcept: sale.concept }; - await axios.post(`Sales/${sale.id}/updateConcept`, data); - notify('globals.dataSaved', 'positive'); - resetChanges(); + try { + const data = { newConcept: sale.concept }; + await axios.post(`Sales/${sale.id}/updateConcept`, data); + notify('globals.dataSaved', 'positive'); + resetChanges(); + } catch (e) { + app.config.errorHandler(e); + } }; const DEFAULT_EDIT = { @@ -294,11 +312,15 @@ const changePrice = async (sale) => { } }; const updatePrice = async (sale, newPrice) => { - await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); - sale.price = newPrice; - edit.value = { ...DEFAULT_EDIT }; - notify('globals.dataSaved', 'positive'); - resetChanges(); + try { + await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); + sale.price = newPrice; + edit.value = { ...DEFAULT_EDIT }; + notify('globals.dataSaved', 'positive'); + resetChanges(); + } catch (e) { + app.config.errorHandler(e); + } }; const changeDiscount = async (sale) => { @@ -310,7 +332,7 @@ const changeDiscount = async (sale) => { } }; -const updateDiscounts = async (sales, newDiscount = null) => { +const updateDiscounts = async (sales, newDiscount) => { const salesTracking = await fetchSalesTracking(); const someSaleIsPrepared = salesTracking.some((sale) => @@ -320,17 +342,21 @@ const updateDiscounts = async (sales, newDiscount = null) => { else updateDiscount(sales, newDiscount); }; -const updateDiscount = async (sales, newDiscount = null) => { - const saleIds = sales.map((sale) => sale.id); - const _newDiscount = newDiscount || edit.value.discount; - const params = { - salesIds: saleIds, - newDiscount: _newDiscount, - manaCode: manaCode.value, - }; - await axios.post(`Tickets/${route.params.id}/updateDiscount`, params); - notify('globals.dataSaved', 'positive'); - resetChanges(); +const updateDiscount = async (sales, newDiscount = 0) => { + try { + const salesIds = sales.map(({ id }) => id); + const params = { + salesIds, + newDiscount, + manaCode: manaCode.value, + }; + await axios.post(`Tickets/${route.params.id}/updateDiscount`, params); + notify('globals.dataSaved', 'positive'); + resetChanges(); + } catch (e) { + app.config.errorHandler(e); + return; + } }; const getNewPrice = computed(() => { @@ -352,30 +378,40 @@ const getNewPrice = computed(() => { }); const newOrderFromTicket = async () => { - const { data } = await axios.post(`Orders/newFromTicket`, { - ticketFk: Number(route.params.id), - }); - const routeData = router.resolve({ name: 'OrderCatalog', params: { id: data } }); - window.open(routeData.href, '_blank'); + try { + const { data } = await axios.post(`Orders/newFromTicket`, { + ticketFk: Number(route.params.id), + }); + const routeData = router.resolve({ name: 'OrderCatalog', params: { id: data } }); + window.open(routeData.href, '_blank'); + } catch (e) { + app.config.errorHandler(e); + } }; 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, + }), }, }); }; const changeTicketState = async (val) => { - stateBtnDropdownRef.value.hide(); - const params = { ticketFk: route.params.id, code: val }; - await axios.post('Tickets/state', params); - notify('globals.dataSaved', 'positive'); - await resetChanges(); + try { + stateBtnDropdownRef.value.hide(); + const params = { ticketFk: route.params.id, code: val }; + await axios.post('Tickets/state', params); + notify('globals.dataSaved', 'positive'); + resetChanges(); + } catch (e) { + app.config.errorHandler(e); + } }; const removeSelectedSales = () => { @@ -395,10 +431,14 @@ const removeSales = async () => { .forEach((sale) => tableRef.value.CrudModelRef.formData.splice(sale.$index, 1)); if (params.sales.length == 0) return; - await axios.post('Sales/deleteSales', params); - removeSelectedSales(); - notify('globals.dataSaved', 'positive'); - resetChanges(); + try { + await axios.post('Sales/deleteSales', params); + removeSelectedSales(); + notify('globals.dataSaved', 'positive'); + resetChanges(); + } catch (e) { + app.config.errorHandler(e); + } }; const setTransferParams = async () => { @@ -474,7 +514,7 @@ const endNewRow = (row) => { }; async function confirmUpdate(cb) { - await quasar + quasar .dialog({ component: VnConfirm, componentProps: { @@ -664,6 +704,7 @@ watch( selection: 'multiple', }" :right-search="false" + :search-url="false" :column-search="false" :disable-option="{ card: true }" auto-load @@ -703,7 +744,7 @@ watch( </template> <template #column-image="{ row }"> <div class="image-wrapper"> - <VnImg :id="parseInt(row?.item?.id)" class="rounded" /> + <VnImg v-if="row.item" :id="parseInt(row?.item?.id)" class="rounded" /> </div> </template> <template #column-visible="{ row }"> @@ -751,7 +792,7 @@ watch( {{ row?.item?.subName.toUpperCase() }} </div> </div> - <FetchedTags :item="row.item" :max-length="6" /> + <FetchedTags v-if="row.item" :item="row.item" :columns="6" :max-length="6" /> <QPopupProxy v-if="row.id && isTicketEditable"> <VnInput v-model="row.concept" diff --git a/src/pages/Ticket/Card/TicketSaleMoreActions.vue b/src/pages/Ticket/Card/TicketSaleMoreActions.vue index 840b62507..37441b44f 100644 --- a/src/pages/Ticket/Card/TicketSaleMoreActions.vue +++ b/src/pages/Ticket/Card/TicketSaleMoreActions.vue @@ -55,14 +55,11 @@ const isClaimable = computed(() => { if (ticket.value) { const landedPlusWeek = new Date(ticket.value.landed); landedPlusWeek.setDate(landedPlusWeek.getDate() + 7); - const createAfterDeadline = acl.hasAny([ - { model: 'Claim', props: 'createAfterDeadline', accessType: 'WRITE' }, - ]); + const createAfterDeadline = acl.hasAcl('Claim', 'createAfterDeadline', 'WRITE'); return landedPlusWeek >= Date.vnNew() || createAfterDeadline; } return false; }); -const hasReserves = computed(() => props.sales.some((sale) => sale.reserved == true)); const sendSms = async (params) => { await axios.post(`Tickets/${ticket.value.id}/sendSms`, params); @@ -144,14 +141,6 @@ const onCreateClaimAccepted = async () => { push({ name: 'ClaimBasicData', params: { id: data.id } }); }; -const setReserved = async (reserved) => { - const params = { ticketId: ticket.value.id, sales: props.sales, reserved: reserved }; - await axios.post(`Sales/reserve`, params); - props.sales.forEach((sale) => { - sale.reserved = reserved; - }); -}; - const createRefund = async (withWarehouse) => { if (!props.ticket) return; @@ -240,30 +229,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 - v-if="isTicketEditable && hasReserves" - clickable - v-close-popup - v-ripple - @click="setReserved(false)" - data-cy="unmarkAsReservedItem" - > - <QItemSection> - <QItemLabel>{{ t('Unmark as reserved') }}</QItemLabel> - </QItemSection> - </QItem> <QItem clickable v-ripple data-cy="ticketSaleRefundItem"> <QItemSection> <QItemLabel>{{ t('Refund') }}</QItemLabel> @@ -309,8 +274,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 5838efa88..119b867ed 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -13,14 +13,15 @@ import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import { getUrl } from 'src/composables/getUrl'; import useNotify from 'src/composables/useNotify.js'; import { useArrayData } from 'composables/useArrayData'; -import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; 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,142 +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" - > - {{ entity.ticketState.state.name }} - </QBadge> - </template> - </VnLv> - <VnLv :label="t('globals.salesPerson')"> - <template #value> - <VnUserLink - :name="entity.client?.salesPersonUser?.name" - :worker-id="entity.client?.salesPersonFk" + <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" /> - </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" + <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.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/Card/TicketTracking.vue b/src/pages/Ticket/Card/TicketTracking.vue index 00610de44..06171366d 100644 --- a/src/pages/Ticket/Card/TicketTracking.vue +++ b/src/pages/Ticket/Card/TicketTracking.vue @@ -1,27 +1,23 @@ <script setup> -import { ref, computed, watch, reactive } from 'vue'; +import { ref, reactive, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; -import TicketCreateTracking from './TicketCreateTracking.vue'; -import VnPaginate from 'components/ui/VnPaginate.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; +import { useState } from 'src/composables/useState'; import { toDateTimeFormat } from 'src/filters/date.js'; +import VnTable from 'src/components/VnTable/VnTable.vue'; +const state = useState(); + +const user = state.getUser(); const route = useRoute(); const { t } = useI18n(); -const createTrackingDialogRef = ref(null); -const paginateRef = ref(null); - -watch( - () => route.params.id, - async (val) => { - paginateFilter.where.ticketFk = val; - paginateRef.value.fetch(); - }, -); - +const tableRef = ref(null); +const onStateFkChange = (formData) => (formData.userFk = user.value.id); const paginateFilter = reactive({ include: [ { @@ -56,75 +52,68 @@ const columns = computed(() => [ name: 'state', field: 'state', align: 'left', - format: (val) => val.name, + format: (row) => row.state?.name, }, { label: t('expedition.worker'), name: 'worker', align: 'left', + field: 'user', }, { label: t('expedition.created'), name: 'created', field: 'created', align: 'left', - format: (val) => toDateTimeFormat(val), + format: ({ created }) => toDateTimeFormat(created), }, ]); - -const openCreateModal = () => createTrackingDialogRef.value.show(); </script> <template> - <QPage class="column items-center q-pa-md"> - <VnPaginate - ref="paginateRef" - data-key="TicketTracking" - :user-filter="paginateFilter" - search-url="table" - url="TicketTrackings" - auto-load - order="created DESC" - :limit="0" - > - <template #body="{ rows }"> - <QTable - :rows="rows" - :columns="columns" - row-key="id" - :pagination="{ rowsPerPage: 0 }" - class="full-width q-mt-md" - :no-data-label="t('globals.noResults')" - > - <template #body-cell-worker="{ row }"> - <QTd> - <QBtn flat class="link" @click.stop> - {{ row.user?.name }} - <WorkerDescriptorProxy :id="row.user?.worker?.id" /> - </QBtn> - </QTd> - </template> - </QTable> - </template> - </VnPaginate> - <QDialog - ref="createTrackingDialogRef" - transition-show="scale" - transition-hide="scale" - > - <TicketCreateTracking @on-request-created="paginateRef.fetch()" /> - </QDialog> - <QPageSticky :offset="[20, 20]"> - <QBtn - @click="openCreateModal()" - color="primary" - fab - icon="add" - v-shortcut="'+'" + <VnTable + ref="tableRef" + :right-search="false" + :column-search="false" + :disable-option="{ card: true, table: true }" + :search-url="false" + :columns="columns" + data-key="TicketTracking" + :user-filter="paginateFilter" + url="TicketTrackings" + auto-load + order="created DESC" + :limit="0" + :without-header="true" + :create="{ + urlCreate: 'Tickets/state', + title: t('Create tracking'), + onDataSaved: () => tableRef.reload(), + formInitialData: { + ticketFk: route.params.id, + }, + }" + > + <template #more-create-dialog="{ data }"> + <VnSelect + url="States" + v-model="data.stateFk" + :label="t('ticketList.state')" + auto-load + @update:model-value="onStateFkChange(data)" + hide-selected /> - <QTooltip class="text-no-wrap"> - {{ t('tracking.addState') }} - </QTooltip> - </QPageSticky> - </QPage> + <VnSelectWorker v-model="data.userFk" :fields="['id', 'name']" /> + </template> + <template #column-worker="{ row }"> + <span class="link" @click.stop> + {{ row.user.name }} + <WorkerDescriptorProxy :id="row.user?.worker?.id" /> + </span> + </template> + </VnTable> </template> +<i18n> + es: + Create tracking: Crear estado +</i18n> diff --git a/src/pages/Ticket/Card/TicketVolume.vue b/src/pages/Ticket/Card/TicketVolume.vue index db78094cf..690ae9063 100644 --- a/src/pages/Ticket/Card/TicketVolume.vue +++ b/src/pages/Ticket/Card/TicketVolume.vue @@ -134,7 +134,7 @@ onMounted(() => (stateStore.rightDrawer = true)); auto-load > <template #column-itemFk="{ row }"> - <span class="link"> + <span class="link" @click.stop> {{ row.itemFk }} <ItemDescriptorProxy :id="row.itemFk" /> </span> diff --git a/src/pages/Ticket/Card/__tests__/TicketBoxing.spec.js b/src/pages/Ticket/Card/__tests__/TicketBoxing.spec.js index 8fd62d8c2..a1dd7775d 100644 --- a/src/pages/Ticket/Card/__tests__/TicketBoxing.spec.js +++ b/src/pages/Ticket/Card/__tests__/TicketBoxing.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import TicketBoxing from 'pages/Ticket/Card/TicketBoxing.vue'; // #4836 - Investigate how to test q-drawer outside @@ -21,7 +22,11 @@ describe('TicketBoxing', () => { min: 1, max: 2, }; - const videoList = ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4']; + const videoList = [ + '2022-01-01T01-01-00.mp4', + '2022-02-02T02-02-00.mp4', + '2022-03-03T03-03-00.mp4', + ]; vi.spyOn(axios, 'get').mockResolvedValue({ data: videoList }); vi.spyOn(vm.quasar, 'notify'); @@ -44,7 +49,9 @@ describe('TicketBoxing', () => { await vm.getVideoList(expeditionId, timed); - expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'negative' })); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'negative' }) + ); }); }); }); diff --git a/src/pages/Ticket/Negative/TicketLackFilter.vue b/src/pages/Ticket/Negative/TicketLackFilter.vue index 3762f453d..73d53b247 100644 --- a/src/pages/Ticket/Negative/TicketLackFilter.vue +++ b/src/pages/Ticket/Negative/TicketLackFilter.vue @@ -81,7 +81,7 @@ const setUserParams = (params) => { v-model="params.days" :label="t('negative.days')" dense - is-outlined + filled type="number" @update:model-value=" (value) => { @@ -97,7 +97,7 @@ const setUserParams = (params) => { v-model="params.id" :label="t('negative.id')" dense - is-outlined + filled /> </QItemSection> </QItem> @@ -107,7 +107,7 @@ const setUserParams = (params) => { v-model="params.producer" :label="t('negative.producer')" dense - is-outlined + filled /> </QItemSection> </QItem> @@ -117,7 +117,7 @@ const setUserParams = (params) => { v-model="params.origen" :label="t('negative.origen')" dense - is-outlined + filled /> </QItemSection> </QItem ><QItem> @@ -133,8 +133,7 @@ const setUserParams = (params) => { option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection ><QItemSection v-else> <QSkeleton class="full-width" type="QSelect" /> @@ -151,8 +150,7 @@ const setUserParams = (params) => { option-label="name" hide-selected dense - outlined - rounded + filled > <template #option="scope"> <QItem v-bind="scope.itemProps"> diff --git a/src/pages/Ticket/Negative/TicketLackTable.vue b/src/pages/Ticket/Negative/TicketLackTable.vue index 176e8f7ad..c9c2a7cad 100644 --- a/src/pages/Ticket/Negative/TicketLackTable.vue +++ b/src/pages/Ticket/Negative/TicketLackTable.vue @@ -225,7 +225,7 @@ function onBuysFetched(data) { /> </div> <div class="flex column left" style="align-items: flex-start"> - <QBtn flat class="link text-blue"> + <QBtn flat class="link"> {{ item?.longName ?? item.name }} <ItemDescriptorProxy :id="entityId" /> <FetchedTags class="q-ml-md" :item="item" :columns="7" /> diff --git a/src/pages/Ticket/TicketAdvance.vue b/src/pages/Ticket/TicketAdvance.vue index 94b4623aa..bf3593acd 100644 --- a/src/pages/Ticket/TicketAdvance.vue +++ b/src/pages/Ticket/TicketAdvance.vue @@ -259,7 +259,7 @@ const moveTicketsAdvance = async () => { destinationId: ticket.id, originShipped: ticket.futureShipped, destinationShipped: ticket.shipped, - salesPersonFk: ticket.workerFk, + departmentFk: ticket.departmentFk, }); } const params = { tickets: ticketsToMove }; @@ -285,7 +285,7 @@ const progressAdd = () => { t('advanceTickets.moveTicketSuccess', { ticketsNumber: progressLength.value - splitErrors.value.length, }), - 'positive' + 'positive', ); } }; @@ -345,16 +345,16 @@ watch( originElRef.value.setAttribute('colspan', '9'); destinationElRef.value.textContent = `${t( - 'advanceTickets.destination' + 'advanceTickets.destination', )} ${toDateFormat(vnTableRef.value.params.dateToAdvance)}`; originElRef.value.textContent = `${t('advanceTickets.origin')} ${toDateFormat( - vnTableRef.value.params.dateFuture + vnTableRef.value.params.dateFuture, )}`; newRow.append(destinationElRef.value, originElRef.value); head.insertBefore(newRow, firstRow); }, - { once: true, inmmediate: true } + { once: true, inmmediate: true }, ); watch( @@ -362,14 +362,14 @@ watch( () => { if (originElRef.value && destinationElRef.value) { destinationElRef.value.textContent = `${t( - 'advanceTickets.destination' + 'advanceTickets.destination', )} ${toDateFormat(vnTableRef.value.params.dateToAdvance)}`; originElRef.value.textContent = `${t('advanceTickets.origin')} ${toDateFormat( - vnTableRef.value.params.dateFuture + vnTableRef.value.params.dateFuture, )}`; } }, - { deep: true } + { deep: true }, ); </script> <template> @@ -405,7 +405,7 @@ watch( t(`advanceTickets.advanceTitleSubtitle`, { selectedTickets: selectedTickets.length, }), - moveTicketsAdvance + moveTicketsAdvance, ) " > @@ -423,7 +423,7 @@ watch( t(`advanceTickets.advanceWithoutNegativeSubtitle`, { selectedTickets: selectedTickets.length, }), - splitTickets + splitTickets, ) " > diff --git a/src/pages/Ticket/TicketAdvanceFilter.vue b/src/pages/Ticket/TicketAdvanceFilter.vue index 6d5c7726e..f065eaf2e 100644 --- a/src/pages/Ticket/TicketAdvanceFilter.vue +++ b/src/pages/Ticket/TicketAdvanceFilter.vue @@ -71,7 +71,7 @@ onMounted(async () => await getItemPackingTypes()); <VnInputDate v-model="params.dateFuture" :label="t('params.dateFuture')" - is-outlined + filled /> </QItemSection> </QItem> @@ -80,7 +80,7 @@ onMounted(async () => await getItemPackingTypes()); <VnInputDate v-model="params.dateToAdvance" :label="t('params.dateToAdvance')" - is-outlined + filled /> </QItemSection> </QItem> @@ -95,8 +95,7 @@ onMounted(async () => await getItemPackingTypes()); :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled :use-like="false" > </VnSelect> @@ -113,8 +112,7 @@ onMounted(async () => await getItemPackingTypes()); :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled :use-like="false" > </VnSelect> @@ -125,7 +123,7 @@ onMounted(async () => await getItemPackingTypes()); <VnInputNumber v-model="params.scopeDays" :label="t('Days onward')" - is-outlined + filled /> </QItemSection> </QItem> @@ -147,8 +145,7 @@ onMounted(async () => await getItemPackingTypes()); url="Departments" :fields="['id', 'name']" dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -162,8 +159,7 @@ onMounted(async () => await getItemPackingTypes()); option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index bdf75c826..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" @@ -63,18 +52,10 @@ const getGroupedStates = (data) => { <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.clientFk" - :label="t('Customer ID')" - is-outlined - /> + <VnInput v-model="params.clientFk" :label="t('Customer ID')" filled /> </QItemSection> <QItemSection> - <VnInput - v-model="params.orderFk" - :label="t('Order ID')" - is-outlined - /> + <VnInput v-model="params.orderFk" :label="t('Order ID')" filled /> </QItemSection> </QItem> <QItem> @@ -82,7 +63,7 @@ const getGroupedStates = (data) => { <VnInputDate v-model="params.from" :label="t('From')" - is-outlined + filled data-cy="From_date" /> </QItemSection> @@ -90,22 +71,21 @@ const getGroupedStates = (data) => { <VnInputDate v-model="params.to" :label="t('To')" - is-outlined + filled data-cy="To_date" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnSelectWorker - :label="t('globals.salesPerson')" - v-model="params.salesPersonFk" - :params="{ - departmentCodes: ['VT'], - }" + <VnSelect dense - outlined - rounded + filled + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> </QItemSection> </QItem> @@ -125,8 +105,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -136,29 +115,23 @@ 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 dense - outlined - rounded + filled sort-by="name ASC" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.refFk" - :label="t('Invoice Ref.')" - is-outlined - /> + <VnInput v-model="params.refFk" :label="t('Invoice Ref.')" filled /> </QItemSection> </QItem> <QItem> @@ -166,17 +139,13 @@ const getGroupedStates = (data) => { <VnInput v-model="params.scopeDays" :label="t('Days onward')" - is-outlined + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.nickname" - :label="t('Nickname')" - is-outlined - /> + <VnInput v-model="params.nickname" :label="t('Nickname')" filled /> </QItemSection> </QItem> <QItem> @@ -241,8 +210,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -260,8 +228,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -281,8 +248,7 @@ const getGroupedStates = (data) => { map-options use-input dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -291,7 +257,7 @@ const getGroupedStates = (data) => { <VnInput v-model="params.collectionFk" :label="t('Collection')" - is-outlined + filled /> </QItemSection> </QItem> @@ -308,7 +274,6 @@ en: from: From shipped: Shipped to: To - salesPersonFk: Salesperson stateFk: State groupedStates: Grouped State refFk: Invoice Ref. @@ -327,7 +292,7 @@ en: ON_PREPARATION: On preparation PACKED: Packed DELIVERED: Delivered - ON_PREVIOUS: ON_PREVIOUS + ON_PREVIOUS: On previous es: params: search: Contiene @@ -336,7 +301,6 @@ es: from: Desde shipped: F. envío to: Hasta - salesPersonFk: Comercial stateFk: Estado groupedStates: Estado agrupado refFk: Ref. Factura @@ -355,7 +319,6 @@ es: Order ID: ID Pedido From: Desde To: Hasta - Salesperson: Comercial State: Estado Invoice Ref.: Ref. Factura My team: Mi equipo @@ -374,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/TicketFuture.vue b/src/pages/Ticket/TicketFuture.vue index 92911cd25..588379ed9 100644 --- a/src/pages/Ticket/TicketFuture.vue +++ b/src/pages/Ticket/TicketFuture.vue @@ -160,7 +160,7 @@ const moveTicketsFuture = async () => { destinationId: ticket.futureId, originShipped: ticket.shipped, destinationShipped: ticket.futureShipped, - salesPersonFk: ticket.salesPersonFk, + departmentFk: ticket.departmentFk, }; }); diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue index 64e060a39..033b47f72 100644 --- a/src/pages/Ticket/TicketFutureFilter.vue +++ b/src/pages/Ticket/TicketFutureFilter.vue @@ -73,7 +73,7 @@ onMounted(async () => { <VnInputDate v-model="params.originScopeDays" :label="t('params.originScopeDays')" - is-outlined + filled /> </QItemSection> </QItem> @@ -82,7 +82,7 @@ onMounted(async () => { <VnInputDate v-model="params.futureScopeDays" :label="t('params.futureScopeDays')" - is-outlined + filled /> </QItemSection> </QItem> @@ -91,7 +91,7 @@ onMounted(async () => { <VnInput :label="t('params.litersMax')" v-model="params.litersMax" - is-outlined + filled /> </QItemSection> </QItem> @@ -100,7 +100,7 @@ onMounted(async () => { <VnInput :label="t('params.linesMax')" v-model="params.linesMax" - is-outlined + filled /> </QItemSection> </QItem> @@ -115,8 +115,7 @@ onMounted(async () => { :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -132,8 +131,7 @@ onMounted(async () => { :info="t('iptInfo')" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -148,8 +146,7 @@ onMounted(async () => { option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -164,8 +161,7 @@ onMounted(async () => { option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> @@ -191,8 +187,7 @@ onMounted(async () => { option-label="name" @update:model-value="searchFn()" dense - outlined - rounded + filled > </VnSelect> </QItemSection> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index b2e13fcb6..634b8e50a 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -17,6 +17,7 @@ import TicketFilter from './TicketFilter.vue'; import VnInput from 'src/components/common/VnInput.vue'; import FetchData from 'src/components/FetchData.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import { toTimeFormat } from 'src/filters/date'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; @@ -54,8 +55,7 @@ onBeforeMount(() => { onMounted(async () => { if (!route.query) return; if (route.query?.createForm) { - formInitialData.value = JSON.parse(route.query?.createForm); - await onClientSelected(formInitialData.value); + await onClientSelected(JSON.parse(route.query?.createForm)); } else if (route.query?.table) { const query = route.query?.table; const clientId = +JSON.parse(query)?.clientFk; @@ -100,26 +100,20 @@ const columns = computed(() => [ }, { align: 'left', - label: t('ticketList.salesPerson'), - name: 'salesPersonFk', + name: 'departmentFk', + label: t('customer.summary.team'), component: 'select', attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - where: { role: 'salesPerson' }, - optionFilter: 'firstName', - useLike: false, + url: 'Departments', }, columnField: { component: null, }, - columnClass: 'expand', - cardVisible: true, - format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson), + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { align: 'left', - name: 'shipped', + name: 'shippedDate', cardVisible: true, label: t('ticketList.shipped'), columnFilter: { @@ -129,7 +123,7 @@ const columns = computed(() => [ }, { align: 'left', - name: 'shipped', + name: 'shippedHour', component: 'time', columnFilter: false, label: t('ticketList.hour'), @@ -254,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'); }, }, ], @@ -273,12 +267,18 @@ const fetchAddresses = async (formData) => { return; } const { data } = await getAddresses(formData.clientId); - formInitialData.value = { clientId: formData.clientId }; - if (!data) return; + + if (!data) { + formInitialData.value = { clientId: formData.clientId }; + return; + } addressesOptions.value = data; selectedClient.value = data[0].client; formData.addressId = selectedClient.value.defaultAddressFk; - formInitialData.value.addressId = formData.addressId; + formInitialData.value = { + clientId: formData.clientId, + addressId: formData.addressId, + }; }; watch( () => route.query.table, @@ -340,25 +340,20 @@ async function makeInvoice(ticket) { }); } -async function sendDocuware(ticket) { - try { - let ticketIds = ticket.map((item) => item.id); +async function sendDocuware(tickets) { + let ticketIds = tickets.map((item) => item.id); - const { data } = await axios.post(`Docuwares/upload`, { - fileCabinet: 'deliveryNote', - ticketIds, - }); + const { data } = await axios.post(`Docuwares/upload-delivery-note`, { + ticketIds, + }); - for (let ticket of ticketIds) { - ticket.stateFk = data.id; - ticket.state = data.name; - ticket.alertLevel = data.alertLevel; - ticket.alertLevelCode = data.code; - } - notify('globals.dataSaved', 'positive'); - } catch (err) { - console.err('err: ', err); + for (let ticket of tickets) { + ticket.stateFk = data.id; + ticket.state = data.name; + ticket.alertLevel = data.alertLevel; + ticket.alertLevelCode = data.code; } + notify('globals.dataSaved', 'positive'); } function openBalanceDialog(ticket) { @@ -514,10 +509,10 @@ function setReference(data) { <template #column-statusIcons="{ row }"> <TicketProblems :row="row" /> </template> - <template #column-salesPersonFk="{ row }"> + <template #column-departmentFk="{ row }"> <span class="link" @click.stop> - {{ dashIfEmpty(row.userName) }} - <CustomerDescriptorProxy :id="row.salesPersonFk" /> + {{ dashIfEmpty(row.departmentName) }} + <DepartmentDescriptorProxy :id="row.departmentFk" /> </span> </template> <template #column-shippedDate="{ row }"> diff --git a/src/pages/Ticket/TicketWeekly.vue b/src/pages/Ticket/TicketWeekly.vue index 0e18fe028..d6493550b 100644 --- a/src/pages/Ticket/TicketWeekly.vue +++ b/src/pages/Ticket/TicketWeekly.vue @@ -5,7 +5,7 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectCache from 'src/components/common/VnSelectCache.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; -import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import { useStateStore } from 'stores/useStateStore'; import { useVnConfirm } from 'composables/useVnConfirm'; @@ -112,23 +112,17 @@ const columns = computed(() => [ }, { align: 'left', - name: 'id', - label: t('weeklyTickets.salesperson'), - columnFilter: { - component: 'select', - alias: 'u', - attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - where: { role: 'salesperson' }, - }, - inWhere: true, + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', }, + create: true, columnField: { component: null, }, - cardVisible: true, - format: (row) => row.userName, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { align: 'right', @@ -142,9 +136,9 @@ const columns = computed(() => [ openConfirmationModal( t('You are going to delete this weekly ticket'), t( - 'This ticket will be removed from weekly tickets! Continue anyway?' + 'This ticket will be removed from weekly tickets! Continue anyway?', ), - () => deleteWeekly(row.ticketFk) + () => deleteWeekly(row.ticketFk), ), isPrimary: true, }, @@ -219,10 +213,10 @@ onMounted(async () => { <CustomerDescriptorProxy :id="row.clientFk" /> </span> </template> - <template #column-id="{ row }"> + <template #column-departmentFk="{ row }"> <span class="link" @click.stop> - {{ row.userName }} - <WorkerDescriptorProxy :id="row.workerFk" /> + {{ row.departmentName }} + <DepartmentDescriptorProxy :id="row.departmentFk" /> </span> </template> </VnTable> diff --git a/src/pages/Ticket/__tests__/TicketAdvance.spec.js b/src/pages/Ticket/__tests__/TicketAdvance.spec.js index ab1a47544..cfe5f86c5 100644 --- a/src/pages/Ticket/__tests__/TicketAdvance.spec.js +++ b/src/pages/Ticket/__tests__/TicketAdvance.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import TicketAdvance from 'pages/Ticket/TicketAdvance.vue'; import { Notify } from 'quasar'; import { nextTick } from 'vue'; diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index cdbb22d9b..2e44df7aa 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -136,7 +136,6 @@ purchaseRequest: weeklyTickets: id: Ticket ID shipment: Shipment - salesperson: Salesperson search: Search weekly tickets searchInfo: Search weekly tickets by id or client id ticketSaleTracking: @@ -172,7 +171,7 @@ tracking: addState: Add state package: package: Package - added: Added + added: D. Added addPackage: Add package removePackage: Remove package ticketList: @@ -181,7 +180,6 @@ ticketList: state: State shipped: Shipped zone: Zone - salesPerson: Sales person totalWithVat: Total with VAT summary: Summary client: Customer @@ -207,6 +205,7 @@ ticketList: toLines: Go to lines addressNickname: Address nickname ref: Reference + hour: Hour rounding: Rounding noVerifiedData: No verified data purchaseRequest: Purchase request diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index 75d3c6a2b..62013d9c5 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -58,7 +58,6 @@ basicData: weeklyTickets: id: ID Ticket shipment: Salida - salesperson: Comercial search: Buscar por tickets programados searchInfo: Buscar tickets programados por el identificador o el identificador del cliente advanceTickets: @@ -162,7 +161,7 @@ expedition: removeExpedition: Eliminar expedición package: package: Embalaje - added: Añadido + added: F. Creacion addPackage: Añadir embalaje removePackage: Quitar embalaje ticketSaleTracking: @@ -186,7 +185,6 @@ ticketList: state: Estado shipped: F. Envío zone: Zona - salesPerson: Comercial totalWithVat: Total con IVA summary: Resumen client: Cliente diff --git a/src/pages/Travel/Card/TravelBasicData.vue b/src/pages/Travel/Card/TravelBasicData.vue index b1adc8126..a6ef8ad19 100644 --- a/src/pages/Travel/Card/TravelBasicData.vue +++ b/src/pages/Travel/Card/TravelBasicData.vue @@ -36,7 +36,7 @@ const warehousesOptionsIn = ref([]); auto-load :filter="{ where: { isDestiny: TRUE } }" /> - <FormModel :url-update="`Travels/${route.params.id}`" model="Travel" auto-load> + <FormModel :url-update="`Travels/${route.params.id}`" model="Travel"> <template #form="{ data }"> <VnRow> <VnInput v-model="data.ref" :label="t('globals.reference')" /> @@ -57,8 +57,8 @@ const warehousesOptionsIn = ref([]); <VnRow> <VnInputDate v-model="data.availabled" - :label="t('travel.summary.availabled')" - /> + :label="t('travel.summary.availabled')" + /> <VnInputTime v-model="data.availabled" :label="t('travel.summary.availabledHour')" @@ -96,6 +96,7 @@ const warehousesOptionsIn = ref([]); </QIcon> </template> </VnInput> + <VnInput v-model="data.awbFk" :label="t('travel.awbFk')" /> </VnRow> <VnRow> <QCheckbox v-model="data.isRaid" :label="t('travel.basicData.isRaid')" /> diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index cb09eafd6..d452f5287 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -1,13 +1,13 @@ <script setup> import TravelDescriptor from './TravelDescriptor.vue'; -import VnCardBeta from 'src/components/common/VnCardBeta.vue'; +import VnCard from 'src/components/common/VnCard.vue'; import filter from './TravelFilter.js'; </script> <template> - <VnCardBeta + <VnCard data-key="Travel" url="Travels" :descriptor="TravelDescriptor" - :filter="filter" + :filter="{ ...filter, where: { id: $route.params.id } }" /> </template> diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue index 922f89f33..d57046bae 100644 --- a/src/pages/Travel/Card/TravelDescriptor.vue +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -3,7 +3,7 @@ import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue'; @@ -31,7 +31,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. </script> <template> - <CardDescriptor + <EntityDescriptor :url="`Travels/${entityId}`" :title="data.title" :subtitle="data.subtitle" @@ -66,7 +66,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. :to="{ name: 'TravelList', query: { - params: JSON.stringify({ + table: JSON.stringify({ agencyModeFk: entity.agencyModeFk, }), }, @@ -79,7 +79,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Travel/Card/TravelDescriptorMenuItems.vue b/src/pages/Travel/Card/TravelDescriptorMenuItems.vue index 14d824b86..f8828bffe 100644 --- a/src/pages/Travel/Card/TravelDescriptorMenuItems.vue +++ b/src/pages/Travel/Card/TravelDescriptorMenuItems.vue @@ -37,7 +37,7 @@ const cloneTravelWithEntries = async () => { router.push({ name: 'TravelBasicData', params: { id: data.id } }); }; -const canDelete = computed(() => useAcl().hasAny('Travel', '*', 'WRITE')); +const canDelete = computed(() => useAcl().hasAcl('Travel', '*', 'WRITE')); const openDeleteEntryDialog = (id) => { quasar diff --git a/src/pages/Travel/Card/TravelFilter.js b/src/pages/Travel/Card/TravelFilter.js index 05436834f..0799e449c 100644 --- a/src/pages/Travel/Card/TravelFilter.js +++ b/src/pages/Travel/Card/TravelFilter.js @@ -12,6 +12,7 @@ export default { 'isRaid', 'daysInForward', 'availabled', + 'awbFk', ], include: [ { diff --git a/src/pages/Travel/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..849eeee5b 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; @@ -547,7 +527,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 +546,6 @@ watch(route, () => { ]" v-text="col.value" /> - <!-- Main Row Descriptors --> <TravelDescriptorProxy v-if="col.name === 'id'" :id="props.row.id" @@ -597,58 +576,46 @@ 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> - <QTd> - <QBtn flat class="link" dense>{{ entry.supplierName }}</QBtn> - <SupplierDescriptorProxy :id="entry.supplierFk" /> + <QTd :colspan="2"> + <div style="display: flex"> + <span class="link"> + {{ entry.supplierName }} + <SupplierDescriptorProxy :id="entry.supplierFk" /> + </span> + <QIcon + v-if="entry.isCustomInspectionRequired" + name="warning" + color="negative" + size="md" + :title="t('extraCommunity.requiresInspection')" + /> + </div> </QTd> - <QTd> - <QIcon - v-if="entry.isCustomInspectionRequired" - name="warning" - color="negative" - size="md" - :title="t('extraCommunity.requiresInspection')" - > - </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,18 +629,21 @@ 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; } - &:nth-child(4) { - padding: 0; - } + padding: 0 5px 0; + } + thead > tr > th { + padding: 3px; + color: var(--vn-label-color); } } diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index ae6e695be..76ffdba20 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -87,7 +87,7 @@ warehouses(); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput label="id" v-model="params.id" is-outlined /> + <VnInput label="id" v-model="params.id" filled /> </QItemSection> </QItem> <QItem> @@ -95,7 +95,7 @@ warehouses(); <VnInput :label="t('extraCommunity.filter.reference')" v-model="params.reference" - is-outlined + filled /> </QItemSection> </QItem> @@ -106,8 +106,7 @@ warehouses(); type="number" :label="t('extraCommunity.filter.totalEntries')" dense - outlined - rounded + filled min="0" class="input-number" > @@ -141,8 +140,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -152,7 +150,7 @@ warehouses(); :label="t('extraCommunity.filter.shippedFrom')" v-model="params.shippedFrom" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -162,7 +160,7 @@ warehouses(); :label="t('extraCommunity.filter.landedTo')" v-model="params.landedTo" @update:model-value="searchFn()" - is-outlined + filled /> </QItemSection> </QItem> @@ -176,8 +174,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -191,8 +188,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -206,8 +202,7 @@ warehouses(); option-label="name" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -218,8 +213,7 @@ warehouses(); v-model="params.cargoSupplierFk" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -229,8 +223,7 @@ warehouses(); v-model="params.entrySupplierFk" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> @@ -245,8 +238,7 @@ warehouses(); :filter-options="['code', 'name']" hide-selected dense - outlined - rounded + filled /> </QItemSection> </QItem> diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index 4a9c80952..5498fd269 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -33,19 +33,14 @@ defineExpose({ states }); </template> <template #body="{ params, searchFn }"> <div class="q-pa-sm q-gutter-y-sm"> - <VnInput - :label="t('travel.Id')" - v-model="params.id" - lazy-rules - is-outlined - > + <VnInput :label="t('travel.Id')" v-model="params.id" lazy-rules filled> <template #prepend> <QIcon name="badge" size="xs" /></template> </VnInput> <VnInput :label="t('travel.ref')" v-model="params.ref" lazy-rules - is-outlined + filled /> <VnSelect :label="t('travel.agency')" @@ -56,8 +51,7 @@ defineExpose({ states }); :use-like="false" option-filter="name" dense - outlined - rounded + filled /> <VnSelect :label="t('travel.warehouseInFk')" @@ -69,27 +63,24 @@ defineExpose({ states }); option-label="name" option-filter="name" dense - outlined - rounded + filled /> <VnInputDate :label="t('travel.shipped')" v-model="params.shipped" @update:model-value="searchFn()" - outlined - rounded + filled /> <VnInputTime v-model="params.shipmentHour" @update:model-value="searchFn()" :label="t('travel.shipmentHour')" - outlined - rounded + filled dense /> <VnSelect :label="t('travel.warehouseOut')" - v-model="params.warehouseOut" + v-model="params.warehouseOutFk" @update:model-value="searchFn()" url="warehouses" :use-like="false" @@ -97,36 +88,33 @@ defineExpose({ states }); option-label="name" option-filter="name" dense - outlined - rounded + filled /> <VnInputDate :label="t('travel.landed')" v-model="params.landed" @update:model-value="searchFn()" dense - outlined - rounded + filled /> <VnInputTime v-model="params.landingHour" @update:model-value="searchFn()" :label="t('travel.landingHour')" - outlined - rounded + filled dense /> <VnInput :label="t('travel.totalEntries')" v-model="params.totalEntries" lazy-rules - is-outlined + filled /> <VnInput :label="t('travel.daysOnward')" v-model="params.daysOnward" lazy-rules - is-outlined + filled /> </div> </template> @@ -140,6 +128,7 @@ en: ref: Reference agency: Agency warehouseInFk: Warehouse In + warehouseOutFk: Warehouse Out shipped: Shipped shipmentHour: Shipment Hour warehouseOut: Warehouse Out @@ -153,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/Wagon/Card/WagonCard.vue b/src/pages/Wagon/Card/WagonCard.vue index 644a30ffa..19f0a682a 100644 --- a/src/pages/Wagon/Card/WagonCard.vue +++ b/src/pages/Wagon/Card/WagonCard.vue @@ -1,6 +1,6 @@ <script setup> -import VnCard from 'components/common/VnCard.vue'; +import VnCard from 'src/components/common/VnCard.vue'; </script> <template> - <VnCard data-key="Wagon" url="Wagons" /> + <VnCard data-key="Wagon" url="Wagons" :descriptor="{}" /> </template> diff --git a/src/pages/Wagon/WagonList.vue b/src/pages/Wagon/WagonList.vue index ccfdb89d0..16c5fca63 100644 --- a/src/pages/Wagon/WagonList.vue +++ b/src/pages/Wagon/WagonList.vue @@ -8,6 +8,7 @@ import VnTable from 'src/components/VnTable/VnTable.vue'; import { computed, ref } from 'vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const quasar = useQuasar(); const arrayData = useArrayData('WagonList'); @@ -15,6 +16,7 @@ const store = arrayData.store; const router = useRouter(); const { t } = useI18n(); const tableRef = ref(); +const dataKey = 'WagonList'; const filter = { include: { relation: 'type', @@ -75,95 +77,106 @@ function navigate(id) { } async function remove(row) { - try { - await axios.delete(`Wagons/${row.id}`).then(async () => { - quasar.notify({ - message: t('wagon.list.removeItem'), - type: 'positive', - }); - store.data.splice(store.data.indexOf(row), 1); - window.location.reload(); + await axios.delete(`Wagons/${row.id}`).then(async () => { + quasar.notify({ + message: t('wagon.list.removeItem'), + type: 'positive', }); - } catch (error) { - // - } + store.data.splice(store.data.indexOf(row), 1); + window.location.reload(); + }); } </script> - <template> <QPage class="column items-center q-pa-md"> - <VnTable - ref="tableRef" - data-key="WagonList" - url="Wagons" - :filter="filter" + <VnSection + :data-key="dataKey" :columns="columns" - order="id DESC" - :column-search="false" - :default-mode="'card'" - :disable-option="{ table: true }" - :create="{ - urlCreate: 'Wagons', - title: t('Create new wagon'), - onDataSaved: () => tableRef.reload(), - formInitialData: {}, + prefix="card" + :array-data-props="{ + url: 'Wagons', + exprBuilder, + order: 'id DESC', }" > - <template #more-create-dialog="{ data }"> - <VnInput - filled - v-model="data.label" - :label="t('wagon.create.label')" - type="number" - min="0" - :rules="[(val) => !!val || t('wagon.warnings.labelNotEmpty')]" - /> - <VnInput - filled - v-model="data.plate" - :label="t('wagon.list.plate')" - :rules="[(val) => !!val || t('wagon.warnings.plateNotEmpty')]" - /> - <VnInput - filled - v-model="data.volume" - :label="t('wagon.list.volume')" - type="number" - min="0" - :rules="[(val) => !!val || t('wagon.warnings.volumeNotEmpty')]" - /> - <VnSelect - url="WagonTypes" - filled - v-model="data.typeFk" - use-input - fill-input - hide-selected - input-debounce="0" - option-label="name" - option-value="id" - emit-value - map-options - :label="t('globals.type')" - :rules="[(val) => !!val || t('wagon.warnings.typeNotEmpty')]" + <template #body> + <VnTable + ref="tableRef" + :data-key="dataKey" + :create="{ + urlCreate: 'Wagons', + title: t('Create new wagon'), + onDataSaved: () => tableRef.reload(), + formInitialData: {}, + }" + :filter="filter" + :columns="columns" + :column-search="false" + :default-mode="'card'" + :disable-option="{ table: true }" + :right-search="false" > - <template v-if="data.typeFk" #append> - <QIcon - name="cancel" - @click.stop.prevent="data.typeFk = null" - class="cursor-pointer" + <template #more-create-dialog="{ data }"> + <VnInput + filled + v-model="data.label" + :label="t('wagon.create.label')" + type="number" + min="0" + :rules="[(val) => !!val || t('wagon.warnings.labelNotEmpty')]" /> + <VnInput + filled + v-model="data.plate" + :label="t('wagon.list.plate')" + :rules="[(val) => !!val || t('wagon.warnings.plateNotEmpty')]" + /> + <VnInput + filled + v-model="data.volume" + :label="t('wagon.list.volume')" + type="number" + min="0" + :rules="[ + (val) => !!val || t('wagon.warnings.volumeNotEmpty'), + ]" + /> + <VnSelect + url="WagonTypes" + filled + v-model="data.typeFk" + use-input + fill-input + hide-selected + input-debounce="0" + option-label="name" + option-value="id" + emit-value + map-options + :label="t('globals.type')" + :options="filteredWagonTypes" + :rules="[(val) => !!val || t('wagon.warnings.typeNotEmpty')]" + @filter="filterType" + > + <template v-if="data.typeFk" #append> + <QIcon + name="cancel" + @click.stop.prevent="data.typeFk = null" + class="cursor-pointer" + /> + </template> + <template #no-option> + <QItem> + <QItemSection class="text-grey"> + {{ t('wagon.warnings.noData') }} + </QItemSection> + </QItem> + </template> + </VnSelect> </template> - <template #no-option> - <QItem> - <QItemSection class="text-grey"> - {{ t('wagon.warnings.noData') }} - </QItemSection> - </QItem> - </template> - </VnSelect> + </VnTable> </template> - </VnTable> + </VnSection> </QPage> </template> diff --git a/src/pages/Wagon/__tests__/WagonCreate.spec.js b/src/pages/Wagon/__tests__/WagonCreate.spec.js index f195c183f..9be19e027 100644 --- a/src/pages/Wagon/__tests__/WagonCreate.spec.js +++ b/src/pages/Wagon/__tests__/WagonCreate.spec.js @@ -1,5 +1,6 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import axios from 'axios'; +import { createWrapper } from 'app/test/vitest/helper'; import WagonCreate from 'pages/Wagon/WagonCreate.vue'; describe('WagonCreate', () => { diff --git a/src/pages/Worker/Card/WorkerBasicData.vue b/src/pages/Worker/Card/WorkerBasicData.vue index a78983e5c..f2a16b7e1 100644 --- a/src/pages/Worker/Card/WorkerBasicData.vue +++ b/src/pages/Worker/Card/WorkerBasicData.vue @@ -1,5 +1,6 @@ <script setup> -import { ref, nextTick } from 'vue'; +import { ref, nextTick, onMounted } from 'vue'; +import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import FetchData from 'components/FetchData.vue'; @@ -17,12 +18,12 @@ const maritalStatus = [ { code: 'M', name: t('Married') }, { code: 'S', name: t('Single') }, ]; -async function setAdvancedSummary(data) { - const advanced = (await useAdvancedSummary('Workers', data.id)) ?? {}; + +onMounted(async () => { + const advanced = await useAdvancedSummary('Workers', useRoute().params.id); Object.assign(form.value.formData, advanced); - await nextTick(); - if (form.value) form.value.hasChanges = false; -} + nextTick(() => (form.value.hasChanges = false)); +}); </script> <template> <FetchData @@ -42,7 +43,6 @@ async function setAdvancedSummary(data) { :url-update="`Workers/${$route.params.id}`" auto-load model="Worker" - @on-fetch="setAdvancedSummary" > <template #form="{ data }"> <VnRow> @@ -96,6 +96,7 @@ async function setAdvancedSummary(data) { option-label="name" option-value="code" v-model="data.maritalStatus" + data-cy="MaritalStatus" /> </VnRow> @@ -107,6 +108,7 @@ async function setAdvancedSummary(data) { option-label="name" option-value="id" v-model="data.originCountryFk" + data-cy="country" /> <VnSelect :label="t('Education level')" @@ -132,7 +134,7 @@ async function setAdvancedSummary(data) { <VnInputDate :label="t('seniority')" v-model="data.seniority" /> </VnRow> <VnRow> - <VnInput v-model="data.fi" :label="t('fi')" /> + <VnInput v-model="data.fi" :label="t('fi')" data-cy="fi" /> <VnInputDate :label="t('birth')" v-model="data.birth" /> </VnRow> <VnRow wrap> diff --git a/src/pages/Worker/Card/WorkerCalendar.vue b/src/pages/Worker/Card/WorkerCalendar.vue index df4616011..05ebb4687 100644 --- a/src/pages/Worker/Card/WorkerCalendar.vue +++ b/src/pages/Worker/Card/WorkerCalendar.vue @@ -18,9 +18,7 @@ const router = useRouter(); const route = useRoute(); const { t } = useI18n(); const acl = useAcl(); -const canSeeNotes = computed(() => - acl.hasAny([{ model: 'Worker', props: '__get__business', accessType: 'READ' }]), -); +const canSeeNotes = computed(() => acl.hasAcl('Worker', '__get__business', 'READ')); const workerIsFreelance = ref(); const WorkerFreelanceRef = ref(); const workerCalendarFilterRef = ref(null); diff --git a/src/pages/Worker/Card/WorkerCalendarFilter.vue b/src/pages/Worker/Card/WorkerCalendarFilter.vue index 48fc4094b..32edaa6e9 100644 --- a/src/pages/Worker/Card/WorkerCalendarFilter.vue +++ b/src/pages/Worker/Card/WorkerCalendarFilter.vue @@ -40,7 +40,7 @@ watch( (newValue) => { checkHolidays(newValue); }, - { deep: true, immediate: true } + { deep: true, immediate: true }, ); const emit = defineEmits(['update:businessFk', 'update:year', 'update:absenceType']); @@ -166,52 +166,44 @@ const yearList = ref(generateYears()); }} </QCardSection> </div> - <QList dense class="list q-gutter-y-sm q-my-lg"> - <QItem> - <QItemSection> - <VnSelect - :label="t('Year')" - v-model="selectedYear" - :options="yearList" - dense - outlined - rounded - use-input - :is-clearable="false" - /> - </QItemSection> - <QItemSection> - <VnSelect - :label="t('Contract')" - v-model="selectedBusinessFk" - :options="contractList" - option-value="businessFk" - option-label="businessFk" - dense - outlined - rounded - use-input - :is-clearable="false" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel># {{ scope.opt?.businessFk }}</QItemLabel> - <QItemLabel caption> - {{ toDateFormat(scope.opt?.started) }} - - {{ - scope.opt?.ended - ? toDateFormat(scope.opt?.ended) - : 'Indef.' - }} - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - </QItemSection> - </QItem> - </QList> + <div dense class="column q-gutter-y-sm q-px-md"> + <VnSelect + :label="t('Year')" + v-model="selectedYear" + :options="yearList" + dense + filled + use-input + :is-clearable="false" + /> + <VnSelect + :label="t('Contract')" + v-model="selectedBusinessFk" + :options="contractList" + option-value="businessFk" + option-label="businessFk" + dense + filled + use-input + :is-clearable="false" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel># {{ scope.opt?.businessFk }}</QItemLabel> + <QItemLabel caption> + {{ toDateFormat(scope.opt?.started) }} - + {{ + scope.opt?.ended + ? toDateFormat(scope.opt?.ended) + : 'Indef.' + }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </div> <QList dense class="list q-gutter-y-xs q-my-md"> <QItem v-for="type in absenceTypeList" :key="type.id"> <WorkerEventLabel diff --git a/src/pages/Worker/Card/WorkerCard.vue b/src/pages/Worker/Card/WorkerCard.vue index 3b7a62025..591dadcd2 100644 --- a/src/pages/Worker/Card/WorkerCard.vue +++ b/src/pages/Worker/Card/WorkerCard.vue @@ -1,9 +1,9 @@ <script setup> import WorkerDescriptor from './WorkerDescriptor.vue'; -import VnCardBeta from 'src/components/common/VnCardBeta.vue'; +import VnCard from 'src/components/common/VnCard.vue'; </script> <template> - <VnCardBeta + <VnCard data-key="Worker" url="Workers/summary" :id-in-where="true" diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index dc2c3f197..9576e7e84 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -2,7 +2,7 @@ import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; @@ -52,9 +52,10 @@ const handlePhotoUpdated = (evt = false) => { }; </script> <template> - <CardDescriptor + <EntityDescriptor ref="cardDescriptorRef" :data-key="dataKey" + :summary="$props.summary" url="Workers/summary" :filter="{ where: { id: entityId } }" title="user.nickname" @@ -115,27 +116,25 @@ const handlePhotoUpdated = (evt = false) => { <template #body="{ entity }"> <VnLv :label="t('globals.user')" :value="entity.user?.name" /> <VnLv - class="ellipsis-text" + class="ellipsis" :label="t('globals.params.email')" :value="entity.user?.emailUser?.email" copy /> - <VnLv :label="t('worker.list.department')"> + <VnLv :label="t('globals.department')"> <template #value> <span class="link" v-text="entity.department?.department?.name" /> <DepartmentDescriptorProxy :id="entity.department?.department?.id" /> </template> </VnLv> - <VnLv :value="entity.phone"> - <template #label> - {{ t('globals.phone') }} + <VnLv :label="t('globals.phone')"> + <template #value> <VnLinkPhone :phone-number="entity.phone" /> </template> </VnLv> - <VnLv :value="entity?.sip?.extension"> - <template #label> - {{ t('worker.summary.sipExtension') }} + <VnLv :label="t('worker.summary.sipExtension')"> + <template #value> <VnLinkPhone :phone-number="entity?.sip?.extension" /> </template> </VnLv> @@ -166,7 +165,7 @@ const handlePhotoUpdated = (evt = false) => { </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> <VnChangePassword ref="changePassRef" :submit-fn=" @@ -189,9 +188,3 @@ const handlePhotoUpdated = (evt = false) => { white-space: nowrap; } </style> - -<i18n> -es: - Click to allow the user to be disabled: Marcar para deshabilitar - Click to exclude the user from getting disabled: Marcar para no deshabilitar -</i18n> diff --git a/src/pages/Worker/Card/WorkerDescriptorMenu.vue b/src/pages/Worker/Card/WorkerDescriptorMenu.vue index 0dcb4fd71..3b6b144d6 100644 --- a/src/pages/Worker/Card/WorkerDescriptorMenu.vue +++ b/src/pages/Worker/Card/WorkerDescriptorMenu.vue @@ -63,3 +63,8 @@ const showChangePasswordDialog = () => { </QItemSection> </QItem> </template> +<i18n> + es: + Click to allow the user to be disabled: Marcar para deshabilitar + Click to exclude the user from getting disabled: Marcar para no deshabilitar + </i18n> diff --git a/src/pages/Worker/Card/WorkerDescriptorProxy.vue b/src/pages/Worker/Card/WorkerDescriptorProxy.vue index 5f71abbea..baa9aa571 100644 --- a/src/pages/Worker/Card/WorkerDescriptorProxy.vue +++ b/src/pages/Worker/Card/WorkerDescriptorProxy.vue @@ -11,7 +11,7 @@ const $props = defineProps({ </script> <template> - <QPopupProxy> + <QPopupProxy data-cy="WorkerDescriptor"> <WorkerDescriptor v-if="$props.id" :id="$props.id" diff --git a/src/pages/Worker/Card/WorkerLocker.vue b/src/pages/Worker/Card/WorkerLocker.vue index 015bced35..62891070d 100644 --- a/src/pages/Worker/Card/WorkerLocker.vue +++ b/src/pages/Worker/Card/WorkerLocker.vue @@ -9,7 +9,7 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import { useArrayData } from 'src/composables/useArrayData'; import FetchData from 'components/FetchData.vue'; -const { hasAny } = useAcl(); +const { hasAcl } = useAcl(); const { t } = useI18n(); const fetchData = ref(); const originaLockerId = ref(); @@ -58,11 +58,7 @@ const init = async (data) => { option-label="code" option-value="id" hide-selected - :readonly=" - !hasAny([ - { model: 'Worker', props: '__get__locker', accessType: 'READ' }, - ]) - " + :readonly="!hasAcl('Worker', '__get__locker', 'READ')" /> </template> </FormModel> diff --git a/src/pages/Worker/Card/WorkerOperator.vue b/src/pages/Worker/Card/WorkerOperator.vue index 8ab802b9f..34d9ba020 100644 --- a/src/pages/Worker/Card/WorkerOperator.vue +++ b/src/pages/Worker/Card/WorkerOperator.vue @@ -98,12 +98,14 @@ watch( <VnInput :label="t('worker.operator.numberOfWagons')" v-model="row.numberOfWagons" + data-cy="numberOfWagons" /> <VnSelect :label="t('worker.operator.train')" :options="trainsData" hide-selected v-model="row.trainFk" + data-cy="train" :required="true" /> </VnRow> @@ -116,6 +118,7 @@ watch( option-value="code" v-model="row.itemPackingTypeFk" :required="true" + data-cy="itemPackingType" /> <VnSelect :label="t('worker.operator.warehouse')" @@ -123,6 +126,7 @@ watch( hide-selected v-model="row.warehouseFk" :required="true" + data-cy="warehouse" /> </VnRow> <VnRow> @@ -132,6 +136,7 @@ watch( hide-selected option-label="description" v-model="row.sectorFk" + data-cy="sector" /> <VnSelect :label="t('worker.operator.labeler')" @@ -139,6 +144,7 @@ watch( hide-selected option-label="name" v-model="row.labelerFk" + data-cy="labeler" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -160,11 +166,13 @@ watch( :label="t('worker.operator.linesLimit')" v-model="row.linesLimit" lazy-rules + data-cy="linesLimit" /> <VnInput :label="t('worker.operator.volumeLimit')" v-model="row.volumeLimit" lazy-rules + data-cy="volumeLimit" /> </VnRow> <VnRow> @@ -172,6 +180,7 @@ watch( :label="t('worker.operator.sizeLimit')" v-model="row.sizeLimit" lazy-rules + data-cy="sizeLimit" /> <VnInput :label="t('worker.operator.isOnReservationMode')" diff --git a/src/pages/Worker/Card/WorkerPda.vue b/src/pages/Worker/Card/WorkerPda.vue index d32941494..408260256 100644 --- a/src/pages/Worker/Card/WorkerPda.vue +++ b/src/pages/Worker/Card/WorkerPda.vue @@ -5,24 +5,26 @@ import { ref, computed } from 'vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import { useVnConfirm } from 'composables/useVnConfirm'; +import { useArrayData } from 'src/composables/useArrayData'; +import { downloadDocuware } from 'src/composables/downloadFile'; + import FetchData from 'components/FetchData.vue'; import FormModelPopup from 'src/components/FormModelPopup.vue'; -import { useVnConfirm } from 'composables/useVnConfirm'; - -import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -import VnInput from 'src/components/common/VnInput.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; const { t } = useI18n(); const { notify } = useNotify(); - -const paginate = ref(); +const loadingDocuware = ref(true); +const tableRef = ref(); const dialog = ref(); +const getAvailablePdaRef = ref(); const route = useRoute(); const { openConfirmationModal } = useVnConfirm(); const routeId = computed(() => route.params.id); - +const worker = computed(() => useArrayData('Worker').store.data); const initialData = computed(() => { return { userFk: routeId.value, @@ -31,154 +33,271 @@ const initialData = computed(() => { }; }); -const deallocatePDA = async (deviceProductionFk) => { - await axios.post(`Workers/${route.params.id}/deallocatePDA`, { - pda: deviceProductionFk, - }); - notify(t('PDA deallocated'), 'positive'); - - paginate.value.fetch(); -}; +const columns = computed(() => [ + { + align: 'center', + label: t('globals.state'), + name: 'state', + format: (row) => row?.docuware?.state, + columnFilter: false, + chip: { + condition: (_, row) => !!row.docuware, + color: (row) => (isSigned(row) ? 'bg-positive' : 'bg-warning'), + }, + visible: false, + }, + { + align: 'right', + label: t('worker.pda.currentPDA'), + name: 'deviceProductionFk', + columnClass: 'shrink', + cardVisible: true, + }, + { + align: 'left', + label: t('Model'), + name: 'modelFk', + format: ({ deviceProduction }) => deviceProduction.modelFk, + cardVisible: true, + }, + { + align: 'right', + label: t('Serial number'), + name: 'serialNumber', + format: ({ deviceProduction }) => deviceProduction.serialNumber, + cardVisible: true, + }, + { + align: 'left', + label: t('Current SIM'), + name: 'simFk', + cardVisible: true, + }, + { + align: 'right', + name: 'actions', + columnFilter: false, + cardVisible: true, + }, +]); function reloadData() { initialData.value.deviceProductionFk = null; initialData.value.simFk = null; - paginate.value.fetch(); + tableRef.value.reload(); + getAvailablePdaRef.value.fetch(); +} + +async function fetchDocuware() { + loadingDocuware.value = true; + + const id = `${worker.value?.lastName} ${worker.value?.firstName}`; + const rows = tableRef.value.CrudModelRef.formData; + + const promises = rows.map(async (row) => { + const { data } = await axios.post(`Docuwares/${id}/checkFile`, { + fileCabinet: 'hr', + signed: false, + mergeFilter: [ + { + DBName: 'TIPO_DOCUMENTO', + Value: ['PDA'], + }, + { + DBName: 'OBSERVACIONES', + Value: [row.deviceProductionFk], + }, + ], + }); + row.docuware = data; + }); + + await Promise.allSettled(promises); + loadingDocuware.value = false; +} + +async function sendToTablet(rows) { + const promises = rows.map(async (row) => { + await axios.post(`Docuwares/upload-pda-pdf`, { + ids: [row.deviceProductionFk], + }); + row.docuware = true; + }); + await Promise.allSettled(promises); + notify(t('PDF sended to signed'), 'positive'); + tableRef.value.reload(); +} + +async function deallocatePDA(deviceProductionFk) { + await axios.post(`Workers/${route.params.id}/deallocatePDA`, { + pda: deviceProductionFk, + }); + const index = tableRef.value.CrudModelRef.formData.findIndex( + (data) => data?.deviceProductionFk == deviceProductionFk, + ); + delete tableRef.value.CrudModelRef.formData[index]; + notify(t('PDA deallocated'), 'positive'); + await getAvailablePdaRef.value.fetch(); +} + +function isSigned(row) { + return row.docuware?.state === 'Firmado'; } </script> <template> - <QPage class="column items-center q-pa-md centerCard"> - <FetchData - url="workers/getAvailablePda" - @on-fetch="(data) => (deviceProductions = data)" - auto-load - /> - <VnPaginate - ref="paginate" - data-key="WorkerPda" - url="DeviceProductionUsers" - :user-filter="{ where: { userFk: routeId } }" - order="id" - search-url="pda" - auto-load - > - <template #body="{ rows }"> - <QCard - flat - bordered - :key="row.id" - v-for="row of rows" - class="card q-px-md q-mb-sm container" - > - <VnRow> - <VnInput - :label="t('worker.pda.currentPDA')" - :model-value="row?.deviceProductionFk" - disable - /> - <VnInput - :label="t('Model')" - :model-value="row?.deviceProduction?.modelFk" - disable - /> - <VnInput - :label="t('Serial number')" - :model-value="row?.deviceProduction?.serialNumber" - disable - /> - <VnInput - :label="t('Current SIM')" - :model-value="row?.simFk" - disable - /> - <QBtn - flat - icon="delete" - color="primary" - class="btn-delete" - @click=" - openConfirmationModal( - t(`Remove PDA`), - t('Do you want to remove this PDA?'), - () => deallocatePDA(row.deviceProductionFk), - ) - " - > - <QTooltip> - {{ t('worker.pda.removePDA') }} - </QTooltip> - </QBtn> - </VnRow> - </QCard> - </template> - </VnPaginate> - <QPageSticky :offset="[18, 18]"> + <FetchData + ref="getAvailablePdaRef" + url="workers/getAvailablePda" + @on-fetch="(data) => (deviceProductions = data)" + auto-load + /> + <VnTable + ref="tableRef" + data-key="WorkerPda" + url="DeviceProductionUsers" + :user-filter="{ order: 'id' }" + :filter="{ where: { userFk: routeId } }" + search-url="pda" + auto-load + :columns="columns" + @onFetch="fetchDocuware" + :hasSubToolbar="true" + :default-remove="false" + :default-reset="false" + :default-save="false" + :table="{ + 'row-key': 'deviceProductionFk', + selection: 'multiple', + }" + :table-filter="{ hiddenTags: ['userFk'] }" + > + <template #moreBeforeActions> <QBtn - @click.stop="dialog.show()" + :label="t('globals.refresh')" + icon="refresh" + @click="tableRef.reload()" + /> + <QBtn + :disable="!tableRef?.selected?.length" + :label="t('globals.send')" + icon="install_mobile" + @click="sendToTablet(tableRef?.selected)" + class="bg-primary" + /> + </template> + <template #column-actions="{ row }"> + <QBtn + flat + icon="delete" color="primary" - fab - icon="add" - v-shortcut="'+'" + @click=" + openConfirmationModal( + t(`Remove PDA`), + t('Do you want to remove this PDA?'), + () => deallocatePDA(row.deviceProductionFk), + ) + " + data-cy="workerPda-remove" > - <QDialog ref="dialog"> - <FormModelPopup - :title="t('Add new device')" - url-create="DeviceProductionUsers" - model="DeviceProductionUser" - :form-initial-data="initialData" - @on-data-saved="reloadData()" - > - <template #form-inputs="{ data }"> - <VnRow> - <VnSelect - :label="t('worker.pda.newPDA')" - v-model="data.deviceProductionFk" - :options="deviceProductions" - option-label="id" - option-value="id" - id="deviceProductionFk" - hide-selected - data-cy="pda-dialog-select" - :required="true" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel - >ID: {{ scope.opt?.id }}</QItemLabel - > - <QItemLabel caption> - {{ scope.opt?.modelFk }}, - {{ scope.opt?.serialNumber }} - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - <VnInput - v-model="data.simFk" - :label="t('SIM serial number')" - id="simSerialNumber" - use-input - /> - </VnRow> - </template> - </FormModelPopup> - </QDialog> + <QTooltip> + {{ t('worker.pda.removePDA') }} + </QTooltip> </QBtn> - <QTooltip> - {{ t('globals.new') }} - </QTooltip> - </QPageSticky> - </QPage> + <QBtn + v-if="!isSigned(row)" + :loading="loadingDocuware" + icon="install_mobile" + flat + color="primary" + @click=" + openConfirmationModal( + t('Sign PDA'), + t('Are you sure you want to send it?'), + () => sendToTablet([row]), + ) + " + data-cy="workerPda-send" + > + <QTooltip> + {{ t('worker.pda.sendToTablet') }} + </QTooltip> + </QBtn> + <QBtn + v-if="isSigned(row)" + :loading="loadingDocuware" + icon="cloud_download" + flat + color="primary" + @click=" + downloadDocuware('Docuwares/download-pda-pdf', { + file: row.deviceProductionFk + '-pda', + worker: worker?.lastName + ' ' + worker?.firstName, + }) + " + data-cy="workerPda-download" + > + <QTooltip> + {{ t('globals.downloadPdf') }} + </QTooltip> + </QBtn> + </template> + </VnTable> + <QPageSticky :offset="[18, 18]"> + <QBtn @click.stop="dialog.show()" color="primary" fab icon="add" v-shortcut="'+'"> + <QDialog ref="dialog"> + <FormModelPopup + :title="t('Add new device')" + url-create="DeviceProductionUsers" + model="DeviceProductionUser" + :form-initial-data="initialData" + @on-data-saved="reloadData()" + > + <template #form-inputs="{ data }"> + <VnRow> + <VnSelect + :label="t('PDA')" + v-model="data.deviceProductionFk" + :options="deviceProductions" + option-label="modelFk" + option-value="id" + id="deviceProductionFk" + hide-selected + data-cy="pda-dialog-select" + :required="true" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel + >ID: {{ scope.opt?.id }}</QItemLabel + > + <QItemLabel caption> + {{ scope.opt?.modelFk }}, + {{ scope.opt?.serialNumber }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + <VnSelect + url="Sims" + option-label="line" + option-value="code" + v-model="data.simFk" + :label="t('SIM serial number')" + id="simSerialNumber" + /> + </VnRow> + </template> + </FormModelPopup> + </QDialog> + </QBtn> + <QTooltip> + {{ t('globals.new') }} + </QTooltip> + </QPageSticky> </template> -<style lang="scss" scoped> -.btn-delete { - max-width: 4%; - margin-top: 30px; -} -</style> <i18n> es: Model: Modelo @@ -190,4 +309,7 @@ es: Do you want to remove this PDA?: ¿Desea eliminar este PDA? You can only have one PDA: Solo puedes tener un PDA si no eres autonomo This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario + Are you sure you want to send it?: ¿Seguro que quieres enviarlo? + Sign PDA: Firmar PDA + PDF sended to signed: PDF enviado para firmar </i18n> diff --git a/src/pages/Worker/Card/WorkerPit.vue b/src/pages/Worker/Card/WorkerPit.vue index 3de60d6a0..cb07c1f1d 100644 --- a/src/pages/Worker/Card/WorkerPit.vue +++ b/src/pages/Worker/Card/WorkerPit.vue @@ -68,8 +68,14 @@ const deleteRelative = async (id) => { :label="t('familySituation')" clearable v-model="data.familySituation" + data-cy="familySituation" + /> + <VnInput + :label="t('spouseNif')" + clearable + v-model="data.spouseNif" + data-cy="spouseNif" /> - <VnInput :label="t('spouseNif')" clearable v-model="data.spouseNif" /> </VnRow> <VnRow> <VnSelect @@ -93,11 +99,13 @@ const deleteRelative = async (id) => { clearable v-model="data.childPension" :label="t(`childPension`)" + data-cy="childPension" /> <VnInput clearable v-model="data.spousePension" :label="t(`spousePension`)" + data-cy="spousePension" /> </VnRow> <VnRow wrap> @@ -190,12 +198,14 @@ const deleteRelative = async (id) => { type="number" v-model="row.birthed" :label="t(`birthed`)" + data-cy="birthed" /> <VnInput type="number" v-model="row.adoptionYear" :label="t(`adoptionYear`)" + data-cy="adoptionYear" /> <QCheckbox v-model="row.isDependend" diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 78c5dfd82..96d5220f5 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -39,6 +39,7 @@ onBeforeMount(async () => { url="Workers/summary" :user-filter="{ where: { id: entityId } }" data-key="Worker" + module-name="Worker" > <template #header="{ entity }"> <div>{{ entity.id }} - {{ entity.firstName }} {{ entity.lastName }}</div> @@ -47,82 +48,91 @@ 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('worker.list.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 :label="t('worker.summary.phoneExtension')"> + <template #value> + <VnLinkPhone :phone-number="worker.mobileExtension" /> + </template> + </VnLv> + <VnLv :label="t('worker.summary.entPhone')"> + <template #value> + <VnLinkPhone :phone-number="worker.phone" /> + </template> + </VnLv> + <VnLv :label="t('worker.summary.personalPhone')"> + <template #value> + <VnLinkPhone + :phone-number="advancedSummary?.client?.phone" + /> + </template> + </VnLv> + </div> + <div class="vn-card-content" v-if="advancedSummary"> + <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')" /> <VnLv :label="t('globals.name')" :value="worker?.user?.nickname" /> <VnLv + class="ellipsis" :label="t('globals.params.email')" :value="worker.user?.emailUser?.email" copy @@ -135,9 +145,8 @@ onBeforeMount(async () => { </span> </template> </VnLv> - <VnLv :value="worker?.sip?.extension"> - <template #label> - {{ t('worker.summary.sipExtension') }} + <VnLv :label="t('worker.summary.sipExtension')"> + <template #value> <VnLinkPhone :phone-number="worker?.sip?.extension" /> </template> </VnLv> diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 9c0fa6758..b64166c7d 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -68,13 +68,9 @@ const arrayData = useArrayData('Worker'); const acl = useAcl(); const selectedDateYear = computed(() => moment(selectedDate.value).isoWeekYear()); const worker = computed(() => arrayData.store?.data); -const canSend = computed(() => - acl.hasAny([{ model: 'WorkerTimeControl', props: 'sendMail', accessType: 'WRITE' }]), -); +const canSend = computed(() => acl.hasAcl('WorkerTimeControl', 'sendMail', 'WRITE')); const canUpdate = computed(() => - acl.hasAny([ - { model: 'WorkerTimeControl', props: 'updateMailState', accessType: 'WRITE' }, - ]), + acl.hasAcl('WorkerTimeControl', 'updateMailState', 'WRITE'), ); const isHimself = computed(() => user.value.id === Number(route.params.id)); diff --git a/src/pages/Worker/Card/WorkerTimeForm.vue b/src/pages/Worker/Card/WorkerTimeForm.vue index 3250e3180..ea9d89144 100644 --- a/src/pages/Worker/Card/WorkerTimeForm.vue +++ b/src/pages/Worker/Card/WorkerTimeForm.vue @@ -53,7 +53,7 @@ const title = computed(() => (isEditMode.value ? t('Edit entry') : t('Add time') const urlCreate = computed(() => isEditMode.value ? `WorkerTimeControls/${$props.entryId}/updateTimeEntry` - : `WorkerTimeControls/${route.params.id}/addTimeEntry` + : `WorkerTimeControls/${route.params.id}/addTimeEntry`, ); onBeforeMount(() => { @@ -83,6 +83,7 @@ onBeforeMount(() => { autofocus :required="true" :is-clearable="false" + data-cy="entryHour" /> <VnSelect :label="t('Type')" @@ -91,6 +92,7 @@ onBeforeMount(() => { option-value="code" option-label="description" hide-selected + data-cy="entryType" /> </template> </FormModelPopup> diff --git a/src/pages/Worker/Department/Card/DepartmentCard.vue b/src/pages/Worker/Department/Card/DepartmentCard.vue index 2e3f11521..0fbc90332 100644 --- a/src/pages/Worker/Department/Card/DepartmentCard.vue +++ b/src/pages/Worker/Department/Card/DepartmentCard.vue @@ -1,9 +1,9 @@ <script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; +import VnCard from 'components/common/VnCard.vue'; import DepartmentDescriptor from 'pages/Worker/Department/Card/DepartmentDescriptor.vue'; </script> <template> - <VnCardBeta + <VnCard class="q-pa-md column items-center" v-bind="{ ...$attrs }" data-key="Department" diff --git a/src/pages/Worker/Department/Card/DepartmentDescriptor.vue b/src/pages/Worker/Department/Card/DepartmentDescriptor.vue index 4b7dfd9b8..820658593 100644 --- a/src/pages/Worker/Department/Card/DepartmentDescriptor.vue +++ b/src/pages/Worker/Department/Card/DepartmentDescriptor.vue @@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'src/components/ui/EntityDescriptor.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -40,7 +40,7 @@ const removeDepartment = async () => { const { openConfirmationModal } = useVnConfirm(); </script> <template> - <CardDescriptor + <EntityDescriptor ref="DepartmentDescriptorRef" :url="`Departments/${entityId}`" :summary="$props.summary" @@ -95,7 +95,7 @@ const { openConfirmationModal } = useVnConfirm(); </QBtn> </QCardActions> </template> - </CardDescriptor> + </EntityDescriptor> </template> <i18n> diff --git a/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue b/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue index 5b556f655..c793e9319 100644 --- a/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue +++ b/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue @@ -16,6 +16,7 @@ const $props = defineProps({ v-if="$props.id" :id="$props.id" :summary="DepartmentSummary" + data-key="DepartmentDescriptorProxy" /> </QPopupProxy> </template> diff --git a/src/pages/Worker/WorkerFilter.vue b/src/pages/Worker/WorkerFilter.vue index 8210ba0e3..c40afe57e 100644 --- a/src/pages/Worker/WorkerFilter.vue +++ b/src/pages/Worker/WorkerFilter.vue @@ -35,7 +35,7 @@ const getLocale = (label) => { <template #body="{ params }"> <QItem> <QItemSection> - <VnInput :label="t('FI')" v-model="params.fi" is-outlined + <VnInput :label="t('FI')" v-model="params.fi" filled ><template #prepend> <QIcon name="badge" size="xs"></QIcon> </template ></VnInput> @@ -43,29 +43,17 @@ const getLocale = (label) => { </QItem> <QItem> <QItemSection> - <VnInput - :label="t('First Name')" - v-model="params.firstName" - is-outlined - /> + <VnInput :label="t('First Name')" v-model="params.firstName" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('Last Name')" - v-model="params.lastName" - is-outlined - /> + <VnInput :label="t('Last Name')" v-model="params.lastName" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('User Name')" - v-model="params.userName" - is-outlined - /> + <VnInput :label="t('User Name')" v-model="params.userName" filled /> </QItemSection> </QItem> <QItem> @@ -79,23 +67,18 @@ const getLocale = (label) => { emit-value map-options dense - outlined - rounded + filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput :label="t('Email')" v-model="params.email" is-outlined /> + <VnInput :label="t('Email')" v-model="params.email" filled /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - :label="t('Extension')" - v-model="params.extension" - is-outlined - /> + <VnInput :label="t('Extension')" v-model="params.extension" filled /> </QItemSection> </QItem> <QItem> @@ -119,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 79eb26881..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, }, ], @@ -223,7 +223,7 @@ async function autofillBic(worker) { :right-search="false" > <template #more-create-dialog="{ data }"> - <div class="q-pa-lg full-width"> + <div class="col-span-2"> <VnRadio v-model="data.isFreelance" :val="false" diff --git a/src/pages/Zone/Card/ZoneCard.vue b/src/pages/Zone/Card/ZoneCard.vue index 41daff5c0..80b209fe3 100644 --- a/src/pages/Zone/Card/ZoneCard.vue +++ b/src/pages/Zone/Card/ZoneCard.vue @@ -1,38 +1,15 @@ <script setup> -import { useRoute } from 'vue-router'; -import { computed } from 'vue'; - -import VnCard from 'components/common/VnCard.vue'; +import VnCard from 'src/components/common/VnCard.vue'; import ZoneDescriptor from './ZoneDescriptor.vue'; -import ZoneFilterPanel from '../ZoneFilterPanel.vue'; -import filter from './ZoneFilter.js'; - +import filter from 'src/pages/Zone/Card/ZoneFilter.js'; +import { useRoute } from 'vue-router'; const route = useRoute(); -const routeName = computed(() => route.name); - -function notIsLocations(ifIsFalse, ifIsTrue) { - if (routeName.value != 'ZoneLocations') return ifIsFalse; - return ifIsTrue; -} </script> - <template> <VnCard data-key="Zone" - :url="notIsLocations('Zones', undefined)" + :url="`Zones/${route.params.id}`" :descriptor="ZoneDescriptor" :filter="filter" - :filter-panel="notIsLocations(ZoneFilterPanel, undefined)" - :search-data-key="notIsLocations('ZoneList', undefined)" - :searchbar-props="{ - url: notIsLocations('Zones', 'ZoneLocations'), - label: notIsLocations($t('list.searchZone'), $t('list.searchLocation')), - info: $t('list.searchInfo'), - whereFilter: notIsLocations((value) => { - return /^\d+$/.test(value) - ? { id: value } - : { name: { like: `%${value}%` } }; - }), - }" /> </template> diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue index 27676212e..f2bcc1247 100644 --- a/src/pages/Zone/Card/ZoneDescriptor.vue +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import EntityDescriptor from 'components/ui/EntityDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toTimeFormat } from 'src/filters/date'; import { toCurrency } from 'filters/index'; @@ -25,7 +25,7 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> + <EntityDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> <template #menu="{ entity }"> <ZoneDescriptorMenuItems :zone="entity" /> </template> @@ -36,5 +36,5 @@ const entityId = computed(() => { <VnLv :label="$t('list.price')" :value="toCurrency(entity.price)" /> <VnLv :label="$t('zone.bonus')" :value="toCurrency(entity.bonus)" /> </template> - </CardDescriptor> + </EntityDescriptor> </template> diff --git a/src/pages/Zone/Card/ZoneDescriptorProxy.vue b/src/pages/Zone/Card/ZoneDescriptorProxy.vue index 27102ac07..a16d231e6 100644 --- a/src/pages/Zone/Card/ZoneDescriptorProxy.vue +++ b/src/pages/Zone/Card/ZoneDescriptorProxy.vue @@ -11,7 +11,7 @@ const $props = defineProps({ </script> <template> - <QPopupProxy> + <QPopupProxy data-cy="ZoneDescriptor"> <ZoneDescriptor v-if="$props.id" :id="$props.id" :summary="ZoneSummary" /> </QPopupProxy> </template> diff --git a/src/pages/Zone/Card/ZoneEventExclusionForm.vue b/src/pages/Zone/Card/ZoneEventExclusionForm.vue index 4b6aa52bd..582a8bbad 100644 --- a/src/pages/Zone/Card/ZoneEventExclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventExclusionForm.vue @@ -1,16 +1,18 @@ <script setup> -import { ref, computed, onMounted, reactive } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; +import { useQuasar } from 'quasar'; +import axios from 'axios'; +import moment from 'moment'; import VnRow from 'components/ui/VnRow.vue'; import FormPopup from 'components/FormPopup.vue'; import ZoneLocationsTree from './ZoneLocationsTree.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; - import { useArrayData } from 'src/composables/useArrayData'; import { useVnConfirm } from 'composables/useVnConfirm'; -import axios from 'axios'; +import { toDateFormat } from 'src/filters/date'; const props = defineProps({ date: { @@ -34,18 +36,25 @@ const props = defineProps({ type: Array, default: () => [], }, + isMasiveEdit: { + type: Boolean, + default: false, + }, + zoneIds: { + type: Array, + default: () => [], + }, }); const emit = defineEmits(['onSubmit', 'closeForm']); - +const quasar = useQuasar(); const route = useRoute(); const { t } = useI18n(); const { openConfirmationModal } = useVnConfirm(); const isNew = computed(() => props.isNewMode); -const dated = reactive(props.date); +const dated = ref(props.date || Date.vnNew()); const tickedNodes = ref(); - const _excludeType = ref('all'); const excludeType = computed({ get: () => _excludeType.value, @@ -63,16 +72,46 @@ const exclusionGeoCreate = async () => { geoIds: tickedNodes.value, }; await axios.post('Zones/exclusionGeo', params); + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); await refetchEvents(); }; const exclusionCreate = async () => { - const url = `Zones/${route.params.id}/exclusions`; + const defaultMonths = await axios.get('ZoneConfigs'); + const nMonths = defaultMonths.data[0].defaultMonths; const body = { - dated, + dated: dated.value, }; - if (isNew.value || props.event?.type) await axios.post(`${url}`, [body]); - else await axios.put(`${url}/${props.event?.id}`, body); + const zoneIds = props.zoneIds?.length ? props.zoneIds : [route.params.id]; + for (const id of zoneIds) { + const url = `Zones/${id}/exclusions`; + let today = moment(dated.value); + let lastDay = today.clone().add(nMonths, 'months').endOf('month'); + + const { data } = await axios.get(`Zones/getEventsFiltered`, { + params: { + zoneFk: id, + started: today, + ended: lastDay, + }, + }); + const existsEvent = data.events.find( + (event) => toDateFormat(event.dated) === toDateFormat(dated.value), + ); + if (existsEvent) { + await axios.delete(`Zones/${existsEvent?.zoneFk}/events/${existsEvent?.id}`); + } + + if (isNew.value || props.event?.type) await axios.post(`${url}`, [body]); + else await axios.put(`${url}/${props.event?.id}`, body); + } + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); await refetchEvents(); }; @@ -129,6 +168,7 @@ onMounted(() => { :label="t('eventsExclusionForm.all')" /> <QRadio + v-if="!props.isMasiveEdit" v-model="excludeType" dense val="specificLocations" @@ -171,9 +211,10 @@ onMounted(() => { openConfirmationModal( t('eventsPanel.deleteTitle'), t('eventsPanel.deleteSubtitle'), - () => deleteEvent() + () => deleteEvent(), ) " + data-cy="ZoneEventExclusionDeleteBtn" /> <QBtn :label="isNew ? t('globals.add') : t('globals.save')" diff --git a/src/pages/Zone/Card/ZoneEventInclusionForm.vue b/src/pages/Zone/Card/ZoneEventInclusionForm.vue index 88f8b30e4..8b02c2d84 100644 --- a/src/pages/Zone/Card/ZoneEventInclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventInclusionForm.vue @@ -2,6 +2,13 @@ import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; +import { useQuasar } from 'quasar'; +import axios from 'axios'; +import moment from 'moment'; + +import { useArrayData } from 'src/composables/useArrayData'; +import { useWeekdayStore } from 'src/stores/useWeekdayStore'; +import { useVnConfirm } from 'composables/useVnConfirm'; import VnRow from 'components/ui/VnRow.vue'; import FormPopup from 'components/FormPopup.vue'; @@ -9,16 +16,11 @@ import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnWeekdayPicker from 'src/components/common/VnWeekdayPicker.vue'; import VnInputTime from 'components/common/VnInputTime.vue'; import VnInput from 'src/components/common/VnInput.vue'; - -import { useArrayData } from 'src/composables/useArrayData'; -import { useWeekdayStore } from 'src/stores/useWeekdayStore'; -import { useVnConfirm } from 'composables/useVnConfirm'; -import axios from 'axios'; +import { toDateFormat } from 'src/filters/date'; const props = defineProps({ date: { type: Date, - required: true, default: null, }, event: { @@ -33,6 +35,14 @@ const props = defineProps({ type: Boolean, default: true, }, + isMasiveEdit: { + type: Boolean, + default: false, + }, + zoneIds: { + type: Array, + default: () => [], + }, }); const emit = defineEmits(['onSubmit', 'closeForm']); @@ -41,10 +51,10 @@ const route = useRoute(); const { t } = useI18n(); const weekdayStore = useWeekdayStore(); const { openConfirmationModal } = useVnConfirm(); - +const quasar = useQuasar(); const isNew = computed(() => props.isNewMode); const eventInclusionFormData = ref({ wdays: [] }); - +const dated = ref(props.date || Date.vnNew()); const _inclusionType = ref('indefinitely'); const inclusionType = computed({ get: () => _inclusionType.value, @@ -57,8 +67,12 @@ const inclusionType = computed({ const arrayData = useArrayData('ZoneEvents'); const createEvent = async () => { + const defaultMonths = await axios.get('ZoneConfigs'); + const nMonths = defaultMonths.data[0].defaultMonths; + eventInclusionFormData.value.weekDays = weekdayStore.toSet( eventInclusionFormData.value.wdays, + eventInclusionFormData.value.wdays, ); if (inclusionType.value == 'day') eventInclusionFormData.value.weekDays = ''; @@ -69,14 +83,43 @@ const createEvent = async () => { eventInclusionFormData.value.ended = null; } - if (isNew.value) - await axios.post(`Zones/${route.params.id}/events`, eventInclusionFormData.value); - else - await axios.put( - `Zones/${route.params.id}/events/${props.event?.id}`, - eventInclusionFormData.value, - ); + const zoneIds = props.zoneIds?.length ? props.zoneIds : [route.params.id]; + for (const id of zoneIds) { + let today = eventInclusionFormData.value.dated + ? moment(eventInclusionFormData.value.dated) + : moment(dated.value); + let lastDay = today.clone().add(nMonths, 'months').endOf('month'); + const { data } = await axios.get(`Zones/getEventsFiltered`, { + params: { + zoneFk: id, + started: today, + ended: lastDay, + }, + }); + const existsExclusion = data.exclusions.find( + (exclusion) => + toDateFormat(exclusion.dated) === + toDateFormat(eventInclusionFormData.value.dated), + ); + if (existsExclusion) { + await axios.delete( + `Zones/${existsExclusion?.zoneFk}/exclusions/${existsExclusion?.id}`, + ); + } + + if (isNew.value) + await axios.post(`Zones/${id}/events`, eventInclusionFormData.value); + else + await axios.put( + `Zones/${id}/events/${props.event?.id}`, + eventInclusionFormData.value, + ); + } + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); await refetchEvents(); emit('onSubmit'); }; @@ -98,9 +141,11 @@ const refetchEvents = async () => { onMounted(() => { if (props.event) { + dated.value = props.event?.dated; eventInclusionFormData.value = { ...props.event }; inclusionType.value = props.event?.type || 'day'; } else if (props.date) { + dated.value = props.date; eventInclusionFormData.value.dated = props.date; inclusionType.value = 'day'; } else inclusionType.value = 'indefinitely'; @@ -126,6 +171,7 @@ onMounted(() => { data-cy="ZoneEventInclusionDayRadio" /> <QRadio + v-if="!props.isMasiveEdit" v-model="inclusionType" dense val="indefinitely" @@ -133,6 +179,7 @@ onMounted(() => { data-cy="ZoneEventInclusionIndefinitelyRadio" /> <QRadio + v-if="!props.isMasiveEdit" v-model="inclusionType" dense val="range" @@ -159,12 +206,10 @@ onMounted(() => { <VnInputDate :label="t('eventsInclusionForm.from')" v-model="eventInclusionFormData.started" - data-cy="ZoneEventsFromDate" /> <VnInputDate :label="t('eventsInclusionForm.to')" v-model="eventInclusionFormData.ended" - data-cy="ZoneEventsToDate" /> </VnRow> <VnRow> diff --git a/src/pages/Zone/Card/ZoneEvents.vue b/src/pages/Zone/Card/ZoneEvents.vue index 1e6debd25..2fa7dfb43 100644 --- a/src/pages/Zone/Card/ZoneEvents.vue +++ b/src/pages/Zone/Card/ZoneEvents.vue @@ -1,18 +1,14 @@ <script setup> -import { ref } from 'vue'; +import { ref, reactive } from 'vue'; import { useI18n } from 'vue-i18n'; import ZoneEventsPanel from './ZoneEventsPanel.vue'; import ZoneCalendarGrid from '../ZoneCalendarGrid.vue'; import ZoneEventInclusionForm from './ZoneEventInclusionForm.vue'; import ZoneEventExclusionForm from './ZoneEventExclusionForm.vue'; - -import { useStateStore } from 'stores/useStateStore'; -import { reactive } from 'vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; const { t } = useI18n(); -const stateStore = useStateStore(); - const firstDay = ref(); const lastDay = ref(); @@ -43,14 +39,16 @@ const onZoneEventFormClose = () => { </script> <template> - <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> - <ZoneEventsPanel - :first-day="firstDay" - :last-day="lastDay" - :events="events" - v-model:formModeName="formModeName" - /> - </Teleport> + <RightMenu> + <template #right-panel> + <ZoneEventsPanel + :first-day="firstDay" + :last-day="lastDay" + :events="events" + v-model:formModeName="formModeName" + /> + </template> + </RightMenu> <QPage class="q-pa-md flex justify-center"> <ZoneCalendarGrid v-model:events="events" diff --git a/src/pages/Zone/Card/ZoneEventsPanel.vue b/src/pages/Zone/Card/ZoneEventsPanel.vue index bb8c15934..82b34e3a2 100644 --- a/src/pages/Zone/Card/ZoneEventsPanel.vue +++ b/src/pages/Zone/Card/ZoneEventsPanel.vue @@ -14,12 +14,10 @@ import { useVnConfirm } from 'composables/useVnConfirm'; const props = defineProps({ firstDay: { type: Date, - required: true, default: null, }, lastDay: { type: Date, - required: true, default: null, }, events: { @@ -67,7 +65,7 @@ watch( async () => { await fetchData(); }, - { immediate: true, deep: true } + { immediate: true, deep: true }, ); const formatWdays = (event) => { @@ -178,9 +176,10 @@ onMounted(async () => { openConfirmationModal( t('zone.deleteTitle'), t('zone.deleteSubtitle'), - () => deleteEvent(event.id) + () => deleteEvent(event.id), ) " + data-cy="ZoneEventsPanelDeleteBtn" > <QTooltip>{{ t('eventsPanel.delete') }}</QTooltip> </QBtn> diff --git a/src/pages/Zone/Card/ZoneLocations.vue b/src/pages/Zone/Card/ZoneLocations.vue index 08b99df60..add9f6f5b 100644 --- a/src/pages/Zone/Card/ZoneLocations.vue +++ b/src/pages/Zone/Card/ZoneLocations.vue @@ -34,9 +34,10 @@ const onSelected = async (val, node) => { node.selected ? '--checked' : node.selected == false - ? '--unchecked' - : '--indeterminate', + ? '--unchecked' + : '--indeterminate', ]" + data-cy="ZoneLocationTreeCheckbox" /> </template> </ZoneLocationsTree> diff --git a/src/pages/Zone/Card/ZoneLocationsTree.vue b/src/pages/Zone/Card/ZoneLocationsTree.vue index 5c87faf99..d5d7d52b6 100644 --- a/src/pages/Zone/Card/ZoneLocationsTree.vue +++ b/src/pages/Zone/Card/ZoneLocationsTree.vue @@ -1,6 +1,7 @@ <script setup> import { onMounted, ref, computed, watch, onUnmounted } from 'vue'; import { useRoute } from 'vue-router'; +import { useStateStore } from 'stores/useStateStore'; import VnInput from 'src/components/common/VnInput.vue'; import { useState } from 'src/composables/useState'; import axios from 'axios'; @@ -30,7 +31,7 @@ const emit = defineEmits(['update:tickedNodes']); const route = useRoute(); const state = useState(); - +const stateStore = useStateStore(); const treeRef = ref(); const expanded = ref([]); @@ -72,6 +73,7 @@ const onNodeExpanded = async (nodeKeysArray) => { const response = await axios.get(`Zones/${route.params.id}/getLeaves`, { params, }); + response.data = JSON.parse(response.data); if (response.data) { node.childs = response.data.map((n) => { if (n.sons > 0) n.childs = [{}]; @@ -82,7 +84,7 @@ const onNodeExpanded = async (nodeKeysArray) => { await fetchNodeLeaves(lastNodeKey, true); } else { const difference = new Set( - [...previousExpandedNodes.value].filter((x) => !nodeKeysSet.has(x)) + [...previousExpandedNodes.value].filter((x) => !nodeKeysSet.has(x)), ); const collapsedNode = Array.from(difference).pop(); const node = treeRef.value?.getNodeByKey(collapsedNode); @@ -125,17 +127,20 @@ watch( async (val) => { if (!val) return; // // Se triggerea cuando se actualiza el store.data, el cual es el resultado del fetch de la searchbar + val = JSON.parse(val); if (!nodes.value[0]) nodes.value = [defaultNode]; nodes.value[0].childs = [...val]; const fetchedNodeKeys = val.flatMap(getNodeIds); state.set('Tree', [...fetchedNodeKeys]); expanded.value = [null, ...fetchedNodeKeys]; + const fetchs = []; for (let n of state.get('Tree')) { - await fetchNodeLeaves(n); + fetchs.push(fetchNodeLeaves(n)); } + await Promise.all(fetchs); previousExpandedNodes.value = new Set(expanded.value); }, - { immediate: true } + { immediate: true }, ); const reFetch = async () => { @@ -153,6 +158,17 @@ onUnmounted(() => { </script> <template> + <Teleport to="#section-searchbar" v-if="stateStore.isHeaderMounted()"> + <VnSearchbar + v-if="!showSearchBar" + :data-key="datakey" + :url="url" + :redirect="false" + :search-remove-params="false" + :label="$t('zone.searchLocations')" + :info="$t('zone.searchLocationsInfo')" + /> + </Teleport> <VnInput v-if="showSearchBar" v-model="store.userParams.search" @@ -163,13 +179,6 @@ onUnmounted(() => { <QBtn color="primary" icon="search" dense flat @click="reFetch()" /> </template> </VnInput> - <VnSearchbar - v-if="!showSearchBar" - :data-key="datakey" - :url="url" - :redirect="false" - :search-remove-params="false" - /> <QTree ref="treeRef" :nodes="nodes" diff --git a/src/pages/Zone/Card/ZoneLog.vue b/src/pages/Zone/Card/ZoneLog.vue index 373d210b5..99ea0912f 100644 --- a/src/pages/Zone/Card/ZoneLog.vue +++ b/src/pages/Zone/Card/ZoneLog.vue @@ -2,5 +2,5 @@ import VnLog from 'src/components/common/VnLog.vue'; </script> <template> - <VnLog model="Zone" url="/ZoneLogs"></VnLog> + <VnLog model="Zone" /> </template> diff --git a/src/pages/Zone/Card/ZoneSearchbar.vue b/src/pages/Zone/Card/ZoneSearchbar.vue deleted file mode 100644 index d1188a1e8..000000000 --- a/src/pages/Zone/Card/ZoneSearchbar.vue +++ /dev/null @@ -1,74 +0,0 @@ -<script setup> -import { useI18n } from 'vue-i18n'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; - -const { t } = useI18n(); - -const exprBuilder = (param, value) => { - switch (param) { - case 'name': - return { - name: { like: `%${value}%` }, - }; - case 'code': - return { - code: { like: `%${value}%` }, - }; - case 'agencyModeFk': - return { - agencyModeFk: value, - }; - case 'search': - return /^\d+$/.test(value) ? { id: value } : { name: { like: `%${value}%` } }; - } -}; - -const tableFilter = { - include: [ - { - relation: 'agencyMode', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'address', - scope: { - fields: ['id', 'nickname', 'provinceFk', 'postalCode'], - include: [ - { - relation: 'province', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'postcode', - scope: { - fields: ['code', 'townFk'], - include: { - relation: 'town', - scope: { - fields: ['id', 'name'], - }, - }, - }, - }, - ], - }, - }, - ], -}; -</script> - -<template> - <VnSearchbar - data-key="ZonesList" - url="Zones" - :filter="tableFilter" - :expr-builder="exprBuilder" - :label="t('list.searchZone')" - :info="t('list.searchInfo')" - custom-route-redirect-name="ZoneSummary" - /> -</template> diff --git a/src/pages/Zone/Card/ZoneSummary.vue b/src/pages/Zone/Card/ZoneSummary.vue index 5b29b495b..5958fe27a 100644 --- a/src/pages/Zone/Card/ZoneSummary.vue +++ b/src/pages/Zone/Card/ZoneSummary.vue @@ -60,10 +60,11 @@ onMounted(async () => { <template> <CardSummary - data-key="Zone" + data-key="ZoneSummary" ref="summary" :url="`Zones/${entityId}`" :filter="filter" + :entity-id="entityId" > <template #header="{ entity }"> <div>#{{ entity.id }} - {{ entity.name }}</div> @@ -74,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="vn-card-group"> + <div class="vn-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="vn-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"> + <QCard class="vn-max"> <VnTitle :url="zoneUrl + `warehouses`" :text="t('list.warehouse')" /> <QTable :columns="columns" diff --git a/src/pages/Zone/ZoneCalendarGrid.vue b/src/pages/Zone/ZoneCalendarGrid.vue index 91d2cc7eb..1ef687b3f 100644 --- a/src/pages/Zone/ZoneCalendarGrid.vue +++ b/src/pages/Zone/ZoneCalendarGrid.vue @@ -42,7 +42,7 @@ const refreshEvents = () => { days.value = {}; if (!data.value) return; - let day = new Date(firstDay.value.getTime()); + let day = new Date(firstDay?.value?.getTime()); while (day <= lastDay.value) { let stamp = day.getTime(); @@ -156,7 +156,7 @@ watch( (value) => { data.value = value; }, - { immediate: true } + { immediate: true }, ); const getMonthNameAndYear = (date) => { diff --git a/src/pages/Zone/ZoneDeliveryDays.vue b/src/pages/Zone/ZoneDeliveryDays.vue index d95c64d8b..ddde3f6b3 100644 --- a/src/pages/Zone/ZoneDeliveryDays.vue +++ b/src/pages/Zone/ZoneDeliveryDays.vue @@ -3,7 +3,6 @@ import { ref } from 'vue'; import ZoneDeliveryPanel from './ZoneDeliveryPanel.vue'; import ZoneCalendarGrid from './ZoneCalendarGrid.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; -import ZoneSearchbar from './Card/ZoneSearchbar.vue'; const firstDay = ref(null); const lastDay = ref(null); @@ -11,7 +10,6 @@ const events = ref([]); </script> <template> - <ZoneSearchbar /> <RightMenu> <template #right-panel> <ZoneDeliveryPanel /> diff --git a/src/pages/Zone/ZoneDeliveryPanel.vue b/src/pages/Zone/ZoneDeliveryPanel.vue index 993ec274f..fc5c04b41 100644 --- a/src/pages/Zone/ZoneDeliveryPanel.vue +++ b/src/pages/Zone/ZoneDeliveryPanel.vue @@ -89,14 +89,13 @@ watch( v-model="formData.geoFk" url="Postcodes/location" :fields="['geoFk', 'code', 'townFk', 'countryFk']" - :sort-by="['code ASC']" + :sort-by="'code ASC'" option-value="geoFk" option-label="code" :filter-options="['code']" hide-selected dense - outlined - rounded + filled map-key="geoFk" data-cy="ZoneDeliveryDaysPostcodeSelect" > @@ -128,8 +127,7 @@ watch( option-label="name" hide-selected dense - outlined - rounded + filled data-cy="ZoneDeliveryDaysAgencySelect" /> <VnSelect @@ -144,7 +142,6 @@ watch( option-label="name" hide-selected dense - outlined rounded /> </div> diff --git a/src/pages/Zone/ZoneFilterPanel.vue b/src/pages/Zone/ZoneFilterPanel.vue deleted file mode 100644 index ff65479e4..000000000 --- a/src/pages/Zone/ZoneFilterPanel.vue +++ /dev/null @@ -1,69 +0,0 @@ -<script setup> -import { ref } from 'vue'; -import { useI18n } from 'vue-i18n'; -import VnInput from 'components/common/VnInput.vue'; -import FetchData from 'components/FetchData.vue'; -import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; -import VnSelect from 'components/common/VnSelect.vue'; -import order from 'src/router/modules/order'; - -const { t } = useI18n(); -const props = defineProps({ - dataKey: { - type: String, - required: true, - }, - exprBuilder: { - type: Function, - default: null, - }, -}); - -const agencies = ref([]); -</script> - -<template> - <FetchData - url="AgencyModes" - :filter="{ fields: ['id', 'name'], order: ['name ASC'] }" - @on-fetch="(data) => (agencies = data)" - auto-load - /> - <VnFilterPanel :data-key="props.dataKey" :search-button="true"> - <template #tags="{ tag }"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`filterPanel.${tag.label}`) }}: </strong> - <span>{{ tag.value }}</span> - </div> - </template> - <template #body="{ params, searchFn }"> - <QItem> - <QItemSection> - <VnInput - :label="t('list.name')" - v-model="params.name" - is-outlined - data-cy="zoneFilterPanelNameInput" - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnSelect - :label="t('filterPanel.agencyModeFk')" - v-model="params.agencyModeFk" - :options="agencies" - option-value="id" - option-label="name" - @update:model-value="searchFn()" - dense - outlined - rounded - data-cy="zoneFilterPanelAgencySelect" - > - </VnSelect> - </QItemSection> - </QItem> - </template> - </VnFilterPanel> -</template> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index eb54ec15b..8d7c4a165 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -14,9 +14,11 @@ import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputTime from 'src/components/common/VnInputTime.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import ZoneFilterPanel from './ZoneFilterPanel.vue'; -import ZoneSearchbar from './Card/ZoneSearchbar.vue'; + +import VnSection from 'src/components/common/VnSection.vue'; +import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; +import ZoneEventInclusionForm from './Card/ZoneEventInclusionForm.vue'; +import ZoneEventExclusionForm from './Card/ZoneEventExclusionForm.vue'; const { t } = useI18n(); const router = useRouter(); @@ -25,7 +27,12 @@ const { viewSummary } = useSummaryDialog(); const { openConfirmationModal } = useVnConfirm(); const tableRef = ref(); const warehouseOptions = ref([]); - +const dataKey = 'ZoneList'; +const selectedRows = ref([]); +const hasSelectedRows = computed(() => selectedRows.value.length > 0); +const openInclusionForm = ref(); +const showZoneEventForm = ref(false); +const zoneIds = ref({}); const tableFilter = { include: [ { @@ -114,6 +121,7 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + columnClass: 'shrink-column', }, { align: 'left', @@ -169,77 +177,180 @@ function formatRow(row) { return dashIfEmpty(`${row?.address?.nickname}, ${row?.address?.postcode?.town?.name} (${row?.address?.province?.name})`); } + +const exprBuilder = (param, value) => { + switch (param) { + case 'name': + return { + name: { like: `%${value}%` }, + }; + case 'code': + return { + code: { like: `%${value}%` }, + }; + case 'agencyModeFk': + return { + agencyModeFk: value, + }; + case 'search': + return /^\d+$/.test(value) ? { id: value } : { name: { like: `%${value}%` } }; + case 'price': + return { + price: value, + }; + } +}; + +function openForm(value, rows) { + zoneIds.value = rows.map((row) => row.id); + openInclusionForm.value = value; + showZoneEventForm.value = true; +} + +const closeEventForm = () => { + showZoneEventForm.value = false; +}; </script> <template> - <ZoneSearchbar /> - <RightMenu> - <template #right-panel> - <ZoneFilterPanel data-key="ZonesList" /> - </template> - </RightMenu> - <VnTable - ref="tableRef" - data-key="ZonesList" - url="Zones" - :create="{ - urlCreate: 'Zones', - title: t('list.createZone'), - onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`), - formInitialData: {}, - }" - :user-filter="tableFilter" + <VnSection + :data-key="dataKey" :columns="columns" - redirect="zone" - :right-search="false" + prefix="zone" + :array-data-props="{ + url: 'Zones', + order: ['id ASC'], + userFilter: tableFilter, + exprBuilder, + }" > - <template #column-addressFk="{ row }"> - {{ dashIfEmpty(formatRow(row)) }} + <template #body> + <VnSubToolbar> + <template #st-actions> + <QBtnGroup style="column-gap: 10px"> + <QBtn + color="primary" + icon-right="event_available" + :disable="!hasSelectedRows" + @click="openForm(true, selectedRows)" + > + <QTooltip>{{ t('list.includeEvent') }}</QTooltip> + </QBtn> + <QBtn + color="primary" + icon-right="event_busy" + :disable="!hasSelectedRows" + @click="openForm(false, selectedRows)" + > + <QTooltip>{{ t('list.excludeEvent') }}</QTooltip> + </QBtn> + </QBtnGroup> + </template> + </VnSubToolbar> + <div class="table-container"> + <div class="column items-center"> + <VnTable + ref="tableRef" + :data-key="dataKey" + :columns="columns" + redirect="Zone" + :create="{ + urlCreate: 'Zones', + title: t('list.createZone'), + onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`), + formInitialData: {}, + }" + table-height="85vh" + v-model:selected="selectedRows" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" + > + <template #column-addressFk="{ row }"> + {{ dashIfEmpty(formatRow(row)) }} + </template> + <template #more-create-dialog="{ data }"> + <VnSelect + url="AgencyModes" + v-model="data.agencyModeFk" + option-value="id" + option-label="name" + :label="t('list.agency')" + /> + <VnInput + v-model="data.price" + :label="t('list.price')" + min="0" + type="number" + required="true" + /> + <VnInput + v-model="data.bonus" + :label="t('zone.bonus')" + min="0" + type="number" + /> + <VnInput + v-model="data.travelingDays" + :label="t('zone.travelingDays')" + type="number" + min="0" + /> + <VnInputTime v-model="data.hour" :label="t('list.close')" /> + <VnSelect + url="Warehouses" + v-model="data.warehouseFK" + option-value="id" + option-label="name" + :label="t('list.warehouse')" + :options="warehouseOptions" + /> + <QCheckbox + v-model="data.isVolumetric" + :label="t('list.isVolumetric')" + :toggle-indeterminate="false" + /> + </template> + </VnTable> + </div> + </div> </template> - <template #more-create-dialog="{ data }"> - <VnSelect - url="AgencyModes" - sort-by="name ASC" - v-model="data.agencyModeFk" - :label="t('list.agency')" - /> - <VnInput - v-model="data.price" - :label="t('list.price')" - min="0" - type="number" - required="true" - /> - <VnInput - v-model="data.bonus" - :label="t('zone.bonus')" - min="0" - type="number" - /> - <VnInput - v-model="data.travelingDays" - :label="t('zone.travelingDays')" - type="number" - min="0" - /> - <VnInputTime v-model="data.hour" :label="t('list.close')" /> - <VnSelect - url="Warehouses" - v-model="data.warehouseFK" - option-value="id" - option-label="name" - :label="t('list.warehouse')" - :options="warehouseOptions" - /> - <QCheckbox - v-model="data.isVolumetric" - :label="t('list.isVolumetric')" - :toggle-indeterminate="false" - /> - </template> - </VnTable> + </VnSection> + <QDialog v-model="showZoneEventForm" @hide="closeEventForm()"> + <ZoneEventInclusionForm + v-if="openInclusionForm" + :event="'event'" + :is-masive-edit="true" + :zone-ids="zoneIds" + @close-form="closeEventForm" + /> + <ZoneEventExclusionForm + v-else + :zone-ids="zoneIds" + :is-masive-edit="true" + @close-form="closeEventForm" + /> + </QDialog> </template> +<style lang="scss" scoped> +.table-container { + display: flex; + justify-content: center; +} +.column { + display: flex; + flex-direction: column; + align-items: center; + min-width: 70%; +} + +:deep(.shrink-column) { + width: 8%; +} +</style> + <i18n> es: Search zone: Buscar zona diff --git a/src/pages/Zone/ZoneUpcoming.vue b/src/pages/Zone/ZoneUpcoming.vue index adcdfbc04..b8664dc2f 100644 --- a/src/pages/Zone/ZoneUpcoming.vue +++ b/src/pages/Zone/ZoneUpcoming.vue @@ -7,7 +7,6 @@ import FetchData from 'components/FetchData.vue'; import { toDateFormat } from 'src/filters/date.js'; import { useWeekdayStore } from 'src/stores/useWeekdayStore'; -import ZoneSearchbar from './Card/ZoneSearchbar.vue'; const { t } = useI18n(); const weekdayStore = useWeekdayStore(); @@ -31,7 +30,7 @@ const columns = computed(() => [ label: t('list.id'), name: 'id', field: 'zoneFk', - align: 'left', + align: 'center', }, ]); @@ -53,7 +52,6 @@ onMounted(() => weekdayStore.initStore()); @on-fetch="(data) => (details = data)" auto-load /> - <ZoneSearchbar /> <VnSubToolbar /> <QPage class="column items-center q-pa-md"> <QCard class="containerShrinked q-pa-md"> diff --git a/src/pages/Zone/locale/en.yml b/src/pages/Zone/locale/en.yml index e53e7b560..f46a98ee6 100644 --- a/src/pages/Zone/locale/en.yml +++ b/src/pages/Zone/locale/en.yml @@ -11,10 +11,13 @@ zone: m3Max: Max m³ deleteTitle: This item will be deleted deleteSubtitle: Are you sure you want to continue? - volumetric: Volumetric bonus: Bonus closing: Closing travelingDays: Traveling days + search: Search zone + searchInfo: Search zone by id or name + searchLocations: Search locations + searchLocationsInfo: Search locations by post code list: clone: Clone id: Id @@ -22,6 +25,7 @@ list: agency: Agency close: Close price: Price + priceOptimum: Optimal price create: Create zone openSummary: Details searchZone: Search zones @@ -30,9 +34,12 @@ list: confirmCloneTitle: All it's properties will be copied confirmCloneSubtitle: Do you want to clone this zone? warehouse: Warehouse + isVolumetric: Volumetric createZone: Create zone zoneSummary: Summary addressFk: Address + includeEvent: Include event + excludeEvent: Exclude event create: name: Name closingHour: Closing hour diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml index bc31e74a9..7a23bdc02 100644 --- a/src/pages/Zone/locale/es.yml +++ b/src/pages/Zone/locale/es.yml @@ -15,6 +15,10 @@ zone: bonus: Bonificación closing: Cierre travelingDays: Días de viaje + search: Buscar zona + searchInfo: Buscar zona por Id o nombre + searchLocations: Buscar localización + searchLocationsInfo: Buscar localización por código postal list: clone: Clonar id: Id @@ -35,6 +39,8 @@ list: createZone: Crear zona zoneSummary: Resumen addressFk: Consignatario + includeEvent: Incluir evento + excludeEvent: Excluir evento create: closingHour: Hora de cierre itemMaxSize: Medida máxima diff --git a/src/router/__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/customer.js b/src/router/modules/customer.js index 67b00b161..a33ed6be5 100644 --- a/src/router/modules/customer.js +++ b/src/router/modules/customer.js @@ -4,8 +4,8 @@ const customerCard = { name: 'CustomerCard', path: ':id', component: () => import('src/pages/Customer/Card/CustomerCard.vue'), - redirect: { name: 'CustomerSummary' }, - meta: { + redirect: { name: 'CustomerSummary' }, + meta: { menu: [ 'CustomerBasicData', 'CustomerFiscalData', @@ -40,8 +40,7 @@ const customerCard = { title: 'basicData', icon: 'vn:settings', }, - component: () => - import('src/pages/Customer/Card/CustomerBasicData.vue'), + component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'), }, { path: 'fiscal-data', @@ -50,8 +49,7 @@ const customerCard = { title: 'fiscalData', icon: 'vn:dfiscales', }, - component: () => - import('src/pages/Customer/Card/CustomerFiscalData.vue'), + component: () => import('src/pages/Customer/Card/CustomerFiscalData.vue'), }, { path: 'billing-data', @@ -60,8 +58,7 @@ const customerCard = { title: 'billingData', icon: 'vn:payment', }, - component: () => - import('src/pages/Customer/Card/CustomerBillingData.vue'), + component: () => import('src/pages/Customer/Card/CustomerBillingData.vue'), }, { path: 'address', @@ -85,9 +82,7 @@ const customerCard = { title: 'address-create', }, component: () => - import( - 'src/pages/Customer/components/CustomerAddressCreate.vue' - ), + import('src/pages/Customer/components/CustomerAddressCreate.vue'), }, { path: ':addressId', @@ -125,8 +120,7 @@ const customerCard = { title: 'credits', icon: 'vn:credit', }, - component: () => - import('src/pages/Customer/Card/CustomerCredits.vue'), + component: () => import('src/pages/Customer/Card/CustomerCredits.vue'), }, { path: 'greuges', @@ -135,8 +129,7 @@ const customerCard = { title: 'greuges', icon: 'vn:greuge', }, - component: () => - import('src/pages/Customer/Card/CustomerGreuges.vue'), + component: () => import('src/pages/Customer/Card/CustomerGreuges.vue'), }, { path: 'balance', @@ -145,8 +138,7 @@ const customerCard = { title: 'balance', icon: 'balance', }, - component: () => - import('src/pages/Customer/Card/CustomerBalance.vue'), + component: () => import('src/pages/Customer/Card/CustomerBalance.vue'), }, { path: 'recoveries', @@ -155,8 +147,7 @@ const customerCard = { title: 'recoveries', icon: 'vn:recovery', }, - component: () => - import('src/pages/Customer/Card/CustomerRecoveries.vue'), + component: () => import('src/pages/Customer/Card/CustomerRecoveries.vue'), }, { path: 'web-access', @@ -165,8 +156,7 @@ const customerCard = { title: 'webAccess', icon: 'vn:web', }, - component: () => - import('src/pages/Customer/Card/CustomerWebAccess.vue'), + component: () => import('src/pages/Customer/Card/CustomerWebAccess.vue'), }, { path: 'log', @@ -247,9 +237,7 @@ const customerCard = { title: 'creditOpinion', }, component: () => - import( - 'src/pages/Customer/Card/CustomerCreditOpinion.vue' - ), + import('src/pages/Customer/Card/CustomerCreditOpinion.vue'), }, ], }, @@ -319,9 +307,7 @@ const customerCard = { title: 'samples', }, component: () => - import( - 'src/pages/Customer/Card/CustomerSamples.vue' - ), + import('src/pages/Customer/Card/CustomerSamples.vue'), }, { path: 'create', @@ -376,9 +362,7 @@ const customerCard = { title: 'fileManagement', }, component: () => - import( - 'src/pages/Customer/Card/CustomerFileManagement.vue' - ), + import('src/pages/Customer/Card/CustomerFileManagement.vue'), }, { path: 'file-management', @@ -420,8 +404,7 @@ const customerCard = { meta: { title: 'unpaid', }, - component: () => - import('src/pages/Customer/Card/CustomerUnpaid.vue'), + component: () => import('src/pages/Customer/Card/CustomerUnpaid.vue'), }, ], }, @@ -429,7 +412,7 @@ const customerCard = { }; export default { - name: 'Customer', + name: 'Customer', path: '/customer', meta: { title: 'customers', @@ -469,15 +452,6 @@ export default { customerCard, ], }, - { - path: 'create', - name: 'CustomerCreate', - meta: { - title: 'customerCreate', - icon: 'add', - }, - component: () => import('src/pages/Customer/CustomerCreate.vue'), - }, { path: 'payments', name: 'CustomerPayments', diff --git a/src/router/modules/entry.js b/src/router/modules/entry.js index b5656dc5f..02eea8c6c 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -81,7 +81,7 @@ export default { keyBinding: 'e', menu: [ 'EntryList', - 'MyEntries', + 'EntrySupplier', 'EntryLatestBuys', 'EntryStockBought', 'EntryWasteRecalc', @@ -125,21 +125,12 @@ export default { }, { path: 'my', - name: 'MyEntries', + name: 'EntrySupplier', meta: { title: 'labeler', icon: 'sell', }, - component: () => import('src/pages/Entry/MyEntries.vue'), - }, - { - path: 'latest-buys', - name: 'EntryLatestBuys', - meta: { - title: 'latestBuys', - icon: 'contact_support', - }, - component: () => import('src/pages/Entry/EntryLatestBuys.vue'), + component: () => import('src/pages/Entry/EntrySupplier.vue'), }, { path: 'stock-Bought', diff --git a/src/router/modules/monitor.js b/src/router/modules/monitor.js index 89ba4078f..3f30ace72 100644 --- a/src/router/modules/monitor.js +++ b/src/router/modules/monitor.js @@ -8,13 +8,10 @@ export default { icon: 'grid_view', moduleName: 'Monitor', keyBinding: 'm', + menu: ['MonitorTickets', 'MonitorClientsActions'], }, component: RouterView, redirect: { name: 'MonitorMain' }, - menus: { - main: ['MonitorTickets', 'MonitorClientsActions'], - card: [], - }, children: [ { path: '', diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 835324d20..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, ], @@ -264,11 +274,11 @@ export default { path: 'roadmap', name: 'RouteRoadmap', redirect: { name: 'RoadmapList' }, + component: () => import('src/pages/Route/RouteRoadmap.vue'), meta: { title: 'RouteRoadmap', icon: 'vn:troncales', }, - component: () => import('src/pages/Route/RouteRoadmap.vue'), children: [ { name: 'RoadmapList', @@ -277,6 +287,7 @@ export default { title: 'list', icon: 'view_list', }, + component: () => import('src/pages/Route/RouteRoadmap.vue'), }, roadmapCard, ], @@ -294,11 +305,11 @@ export default { path: 'agency', name: 'RouteAgency', redirect: { name: 'AgencyList' }, + component: () => import('src/pages/Route/Agency/AgencyList.vue'), meta: { title: 'agency', icon: 'garage_home', }, - component: () => import('src/pages/Route/Agency/AgencyList.vue'), children: [ { name: 'AgencyList', @@ -307,6 +318,8 @@ export default { title: 'list', icon: 'view_list', }, + component: () => + import('src/pages/Route/Agency/AgencyList.vue'), }, agencyCard, ], @@ -315,11 +328,11 @@ export default { path: 'vehicle', name: 'RouteVehicle', redirect: { name: 'VehicleList' }, + component: () => import('src/pages/Route/Vehicle/VehicleList.vue'), meta: { title: 'vehicle', icon: 'directions_car', }, - component: () => import('src/pages/Route/Vehicle/VehicleList.vue'), children: [ { path: 'list', @@ -328,6 +341,8 @@ export default { title: 'vehicleList', icon: 'directions_car', }, + component: () => + import('src/pages/Route/Vehicle/VehicleList.vue'), }, vehicleCard, ], diff --git a/src/router/modules/wagon.js b/src/router/modules/wagon.js index 4a322d305..798c671eb 100644 --- a/src/router/modules/wagon.js +++ b/src/router/modules/wagon.js @@ -1,52 +1,60 @@ import { RouterView } from 'vue-router'; +const wagonCard = { + name: 'WagonCard', + path: ':id', + component: () => import('src/pages/Wagon/Card/WagonCard.vue'), + redirect: { name: 'WagonEdit' }, + meta: { + menu: ['WagonEdit'], + }, + children: [ + { + path: 'edit', + name: 'WagonEdit', + meta: { + title: 'wagonEdit', + icon: 'edit', + }, + component: () => import('src/pages/Wagon/WagonCreate.vue'), + }, + ], +}; + export default { - path: '/wagon', name: 'Wagon', + path: '/wagon', meta: { title: 'wagons', icon: 'vn:trolley', moduleName: 'Wagon', + menu: ['WagonList', 'WagonTypeList', 'WagonCounter'], }, component: RouterView, redirect: { name: 'WagonMain' }, - menus: { - main: ['WagonList', 'WagonTypeList', 'WagonCounter', 'WagonTray'], - card: [], - }, children: [ { - path: '/wagon', + path: '', name: 'WagonMain', component: () => import('src/components/common/VnModule.vue'), - redirect: { name: 'WagonList' }, + redirect: { name: 'WagonIndexMain' }, children: [ { - path: 'list', - name: 'WagonList', - meta: { - title: 'list', - icon: 'vn:trolley', - }, + path: '', + name: 'WagonIndexMain', + redirect: { name: 'WagonList' }, component: () => import('src/pages/Wagon/WagonList.vue'), - }, - { - path: 'create', - name: 'WagonCreate', - meta: { - title: 'wagonCreate', - icon: 'create', - }, - component: () => import('src/pages/Wagon/WagonCreate.vue'), - }, - { - path: ':id/edit', - name: 'WagonEdit', - meta: { - title: 'wagonEdit', - icon: 'edit', - }, - component: () => import('src/pages/Wagon/WagonCreate.vue'), + children: [ + { + name: 'WagonList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + wagonCard, + ], }, { path: 'counter', @@ -57,40 +65,32 @@ export default { }, component: () => import('src/pages/Wagon/WagonCounter.vue'), }, - ], - }, - { - path: '/wagon/type', - name: 'WagonTypeMain', - component: () => import('src/components/common/VnModule.vue'), - redirect: { name: 'WagonTypeList' }, - children: [ { - path: 'list', - name: 'WagonTypeList', - meta: { - title: 'typesList', - icon: 'view_list', - }, - component: () => import('src/pages/Wagon/Type/WagonTypeList.vue'), - }, - { - path: 'create', - name: 'WagonTypeCreate', - meta: { - title: 'typeCreate', - icon: 'create', - }, - component: () => import('src/pages/Wagon/Type/WagonTypeList.vue'), - }, - { - path: ':id/edit', - name: 'WagonTypeEdit', - meta: { - title: 'typeEdit', - icon: 'edit', - }, - component: () => import('src/pages/Wagon/Type/WagonTypeEdit.vue'), + path: 'type', + name: 'WagonTypeMain', + redirect: { name: 'WagonTypeList' }, + children: [ + { + path: 'list', + name: 'WagonTypeList', + meta: { + title: 'typesList', + icon: 'view_list', + }, + component: () => + import('src/pages/Wagon/Type/WagonTypeList.vue'), + }, + { + path: ':id/edit', + name: 'WagonTypeEdit', + meta: { + title: 'typeEdit', + icon: 'edit', + }, + component: () => + import('src/pages/Wagon/Type/WagonTypeEdit.vue'), + }, + ], }, ], }, diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index 3eb95a96e..ff3d483cf 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -271,12 +271,14 @@ export default { path: 'department', name: 'Department', redirect: { name: 'WorkerDepartment' }, - component: () => import('src/pages/Worker/WorkerDepartment.vue'), + meta: { title: 'department', icon: 'vn:greuge' }, children: [ { + component: () => + import('src/pages/Worker/WorkerDepartment.vue'), + meta: { title: 'department', icon: 'vn:greuge' }, name: 'WorkerDepartment', path: 'list', - meta: { title: 'department', icon: 'vn:greuge' }, }, departmentCard, ], diff --git a/src/router/modules/zone.js b/src/router/modules/zone.js index f400a708e..f48a715b9 100644 --- a/src/router/modules/zone.js +++ b/src/router/modules/zone.js @@ -1,24 +1,12 @@ import { RouterView } from 'vue-router'; -export default { - path: '/zone', - name: 'Zone', +const zoneCard = { + name: 'ZoneCard', + path: ':id', + component: () => import('src/pages/Zone/Card/ZoneCard.vue'), + redirect: { name: 'ZoneSummary' }, meta: { - title: 'zones', - icon: 'vn:zone', - moduleName: 'Zone', - keyBinding: 'z', - }, - component: RouterView, - redirect: { name: 'ZoneMain' }, - menus: { - main: [ - 'ZoneList', - 'ZoneDeliveryDays', - 'ZoneUpcomingList', - 'ZoneUpcomingDeliveries', - ], - card: [ + menu: [ 'ZoneBasicData', 'ZoneWarehouses', 'ZoneHistory', @@ -28,19 +16,102 @@ export default { }, children: [ { - path: '/zone', + name: 'ZoneSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Zone/Card/ZoneSummary.vue'), + }, + { + path: 'basic-data', + name: 'ZoneBasicData', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Zone/Card/ZoneBasicData.vue'), + }, + { + path: 'location', + name: 'ZoneLocations', + meta: { + title: 'locations', + icon: 'my_location', + }, + component: () => import('src/pages/Zone/Card/ZoneLocations.vue'), + }, + { + path: 'warehouses', + name: 'ZoneWarehouses', + meta: { + title: 'warehouses', + icon: 'home', + }, + component: () => import('src/pages/Zone/Card/ZoneWarehouses.vue'), + }, + { + path: 'log', + name: 'ZoneHistory', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Zone/Card/ZoneLog.vue'), + }, + { + path: 'events', + name: 'ZoneEvents', + meta: { + title: 'calendar', + icon: 'vn:calendar', + }, + component: () => import('src/pages/Zone/Card/ZoneEvents.vue'), + }, + ], +}; + +export default { + name: 'Zone', + path: '/zone', + meta: { + title: 'zones', + icon: 'vn:zone', + moduleName: 'Zone', + keyBinding: 'z', + menu: [ + 'ZoneList', + 'ZoneDeliveryDays', + 'ZoneUpcomingList', + 'ZoneUpcomingDeliveries', + ], + }, + component: RouterView, + redirect: { name: 'ZoneMain' }, + children: [ + { name: 'ZoneMain', + path: '', component: () => import('src/components/common/VnModule.vue'), - redirect: { name: 'ZoneList' }, + redirect: { name: 'ZoneIndexMain' }, children: [ { - path: 'list', - name: 'ZoneList', - meta: { - title: 'zonesList', - icon: 'view_list', - }, + path: '', + name: 'ZoneIndexMain', + redirect: { name: 'ZoneList' }, component: () => import('src/pages/Zone/ZoneList.vue'), + children: [ + { + name: 'ZoneList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + zoneCard, + ], }, { path: 'delivery-days', @@ -62,67 +133,5 @@ export default { }, ], }, - { - name: 'ZoneCard', - path: ':id', - component: () => import('src/pages/Zone/Card/ZoneCard.vue'), - redirect: { name: 'ZoneSummary' }, - children: [ - { - name: 'ZoneSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Zone/Card/ZoneSummary.vue'), - }, - { - name: 'ZoneBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => import('src/pages/Zone/Card/ZoneBasicData.vue'), - }, - { - name: 'ZoneLocations', - path: 'location', - meta: { - title: 'locations', - icon: 'my_location', - }, - component: () => import('src/pages/Zone/Card/ZoneLocations.vue'), - }, - { - name: 'ZoneWarehouses', - path: 'warehouses', - meta: { - title: 'warehouses', - icon: 'home', - }, - component: () => import('src/pages/Zone/Card/ZoneWarehouses.vue'), - }, - { - name: 'ZoneHistory', - path: 'log', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Zone/Card/ZoneLog.vue'), - }, - { - name: 'ZoneEvents', - path: 'events', - meta: { - title: 'calendar', - icon: 'vn:calendar', - }, - component: () => import('src/pages/Zone/Card/ZoneEvents.vue'), - }, - ], - }, ], }; diff --git a/src/stores/__tests__/useArrayDataStore.spec.js b/src/stores/__tests__/useArrayDataStore.spec.js new file mode 100644 index 000000000..79f17cf69 --- /dev/null +++ b/src/stores/__tests__/useArrayDataStore.spec.js @@ -0,0 +1,95 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { setActivePinia, createPinia } from 'pinia'; +import { useArrayDataStore } from '../useArrayDataStore'; + +describe('useArrayDataStore', () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + + it('should get undefined for non-existent key', () => { + const store = useArrayDataStore(); + expect(store.get('nonExistent')).toBeUndefined(); + }); + + it('should set default state for new key', () => { + const store = useArrayDataStore(); + store.set('test'); + const state = store.get('test'); + + expect(state).toMatchObject({ + filter: {}, + userFilter: {}, + userParams: {}, + url: '', + limit: 20, + skip: 0, + order: '', + isLoading: false, + userParamsChanged: false, + exprBuilder: null, + searchUrl: 'params', + navigate: null, + page: 1, + mapKey: 'id', + oneRecord: false, + }); + }); + + it('should clear state for specific key', () => { + const store = useArrayDataStore(); + store.set('test'); + store.clear('test'); + expect(store.get('test')).toBeUndefined(); + }); + + it('should reset all properties when no options provided', () => { + const store = useArrayDataStore(); + store.set('test'); + const state = store.get('test'); + state.limit = 50; + state.page = 3; + + store.reset('test'); + expect(store.get('test').limit).toBe(20); + expect(store.get('test').page).toBe(1); + }); + + it('should reset only specified properties', () => { + const store = useArrayDataStore(); + store.set('test'); + const state = store.get('test'); + state.limit = 50; + state.page = 3; + state.url = 'test-url'; + + store.reset('test', ['limit', 'page']); + expect(state.limit).toBe(20); + expect(state.page).toBe(1); + expect(state.url).toBe('test-url'); + }); + + it('should reset nested properties', () => { + const store = useArrayDataStore(); + store.set('test'); + const state = store.get('test'); + state.filter.skip = 10; + + store.reset('test', ['filter.skip']); + expect(state.filter.skip).toBe(0); + }); + + it('should reset pagination properties', () => { + const store = useArrayDataStore(); + store.set('test'); + const state = store.get('test'); + state.skip = 20; + state.filter.skip = 20; + state.page = 3; + + store.resetPagination('test'); + expect(state.skip).toBe(0); + expect(state.filter.skip).toBe(0); + expect(state.page).toBe(1); + }); +}); diff --git a/src/stores/__tests__/useDescriptorStore.spec.js b/src/stores/__tests__/useDescriptorStore.spec.js new file mode 100644 index 000000000..61aab8d14 --- /dev/null +++ b/src/stores/__tests__/useDescriptorStore.spec.js @@ -0,0 +1,28 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import 'app/test/vitest/helper'; + +import { useDescriptorStore } from 'src/stores/useDescriptorStore'; +import { useStateStore } from 'stores/useStateStore'; + +describe('useDescriptorStore', () => { + const { get, has } = useDescriptorStore(); + const stateStore = useStateStore(); + + beforeEach(() => { + stateStore.setDescriptors({}); + }); + + function getDescriptors() { + return stateStore.descriptors; + } + + it('should get descriptors in stateStore', async () => { + expect(Object.keys(getDescriptors()).length).toBe(0); + get(); + expect(Object.keys(getDescriptors()).length).toBeGreaterThan(0); + }); + + it('should find ticketDescriptor if search ticketFk', async () => { + expect(has('ticketFk')).toBeDefined(); + }); +}); diff --git a/src/stores/__tests__/useNavigationStore.spec.js b/src/stores/__tests__/useNavigationStore.spec.js index c5df6157e..120fa64cb 100644 --- a/src/stores/__tests__/useNavigationStore.spec.js +++ b/src/stores/__tests__/useNavigationStore.spec.js @@ -1,15 +1,17 @@ import { setActivePinia, createPinia } from 'pinia'; -import { describe, beforeEach, afterEach, it, expect, vi, beforeAll } from 'vitest'; +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { useNavigationStore } from '../useNavigationStore'; -import axios from 'axios'; +import { default as axios } from 'axios'; let store; -vi.mock('src/router/modules', () => [ - { name: 'Item', meta: {} }, - { name: 'Shelving', meta: {} }, - { name: 'Order', meta: {} }, -]); +vi.mock('src/router/modules', () => ({ + default: [ + { name: 'Item', meta: {} }, + { name: 'Shelving', meta: {} }, + { name: 'Order', meta: {} }, + ], +})); vi.mock('src/filters', () => ({ toLowerCamel: vi.fn((name) => name.toLowerCase()), diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index b3996d1e3..569ff1c7e 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -18,7 +18,6 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { navigate: null, page: 1, mapKey: 'id', - keepData: false, oneRecord: false, }; diff --git a/src/stores/useDescriptorStore.js b/src/stores/useDescriptorStore.js new file mode 100644 index 000000000..be342b016 --- /dev/null +++ b/src/stores/useDescriptorStore.js @@ -0,0 +1,33 @@ +import { defineAsyncComponent } from 'vue'; +import { defineStore } from 'pinia'; +import { useStateStore } from 'stores/useStateStore'; + +export const useDescriptorStore = defineStore('descriptorStore', () => { + const { descriptors, setDescriptors } = useStateStore(); + function get() { + if (Object.keys(descriptors).length) return descriptors; + + const currentDescriptors = {}; + const files = import.meta.glob(`/src/**/*DescriptorProxy.vue`); + const moduleParser = { + account: 'user', + 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(files[file]); + } + setDescriptors(currentDescriptors); + return currentDescriptors; + } + + function has(name) { + return get()[name]; + } + + return { + has, + get, + }; +}); diff --git a/src/stores/useStateStore.js b/src/stores/useStateStore.js index ca447bc11..44fa133d0 100644 --- a/src/stores/useStateStore.js +++ b/src/stores/useStateStore.js @@ -8,6 +8,7 @@ export const useStateStore = defineStore('stateStore', () => { const rightAdvancedDrawer = ref(false); const subToolbar = ref(false); const cardDescriptor = ref(null); + const descriptors = ref({}); function cardDescriptorChangeValue(descriptor) { cardDescriptor.value = descriptor; @@ -52,6 +53,10 @@ export const useStateStore = defineStore('stateStore', () => { return subToolbar.value; } + function setDescriptors(value) { + descriptors.value = value; + } + return { cardDescriptor, cardDescriptorChangeValue, @@ -68,5 +73,7 @@ export const useStateStore = defineStore('stateStore', () => { isSubToolbarShown, toggleSubToolbar, rightDrawerChangeValue, + descriptors, + setDescriptors, }; }); diff --git a/src/stores/useWeekdayStore.js b/src/stores/useWeekdayStore.js index 57a302dc1..bf6b2704d 100644 --- a/src/stores/useWeekdayStore.js +++ b/src/stores/useWeekdayStore.js @@ -77,14 +77,14 @@ export const useWeekdayStore = defineStore('weekdayStore', () => { const locales = {}; for (let code of localeOrder.es) { const weekDay = weekdaysMap[code]; - const locale = t(`weekdays.${weekdaysMap[code].code}`); + const locale = t(`weekdays.${weekDay?.code}`); const obj = { ...weekDay, locale, localeChar: locale.substr(0, 1), localeAbr: locale.substr(0, 3), }; - locales[weekDay.code] = obj; + locales[weekDay?.code] = obj; } return locales; }); diff --git a/test/cypress/cypressParallel.sh b/test/cypress/cypressParallel.sh deleted file mode 100644 index 8ef26bcde..000000000 --- a/test/cypress/cypressParallel.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -find 'test/cypress/integration' \ - -mindepth 1 \ - -maxdepth 1 \ - -type d | \ -xargs -P "$1" -I {} sh -c ' - echo "🔷 {}" && - xvfb-run -a cypress run \ - --headless \ - --spec "{}" \ - --quiet \ - > /dev/null -' -wait diff --git a/test/cypress/docker/cypressParallel.sh b/test/cypress/docker/cypressParallel.sh new file mode 100644 index 000000000..8e253f1e3 --- /dev/null +++ b/test/cypress/docker/cypressParallel.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +echo $2 +if [ -z "$2" ]; then + TEST_DIRS=$(find 'test/cypress/integration' -mindepth 1 -maxdepth 1 -type d) +else + TEST_DIRS=$2 +fi + +echo $TEST_DIRS x$1 + +echo "$TEST_DIRS" | xargs -P "$1" -I {} sh -c ' + echo "🔷 {}" && + xvfb-run -a cypress run \ + --headless \ + --spec "{}" \ + --quiet \ + > /dev/null +' +wait diff --git a/test/cypress/docker/find/find-imports.js b/test/cypress/docker/find/find-imports.js new file mode 100644 index 000000000..622049c9f --- /dev/null +++ b/test/cypress/docker/find/find-imports.js @@ -0,0 +1,52 @@ +import fs from 'fs'; +import { parse } from 'es-module-lexer'; +import { parse as vueParse } from '@vue/compiler-sfc'; +import glob from 'fast-glob'; +import { resolveImportPath, toRelative } from './resolve-import-path.js'; + +const ROUTER_MODULES = 'src/router/modules/'; +const files = await glob(['src/**/*.{vue,js,ts}'], { absolute: true }); +const vueFiles = new Map(); + +export async function findImports(targetFile, visited = new Set(), identation = '') { + if (visited.has(targetFile)) return []; // Avoid infinite loops + visited.add(targetFile); + + const usageFiles = files + .filter((file) => { + let content = fs.readFileSync(file, 'utf8'); + if (file.endsWith('.vue')) { + if (vueFiles.has(file)) { + content = vueFiles.get(file); + } else { + const { descriptor } = vueParse(content); + content = descriptor?.scriptSetup?.content ?? ''; + vueFiles.set(file, content); + } + } + if (!content.trim()) return false; + + return parse(content)[0].some((imp) => { + if (!imp?.n) return false; + return resolveImportPath(imp.n, targetFile) === targetFile; + }); + }) + .map((file) => toRelative(file)); + + let fullTree = [...usageFiles]; + for (const file of usageFiles) { + if (file.startsWith(ROUTER_MODULES)) { + continue; + } + fullTree = [ + ...fullTree, + ...(await findImports(file, visited, identation + ' ')), + ]; + } + + return getUniques([...fullTree, targetFile]); // Remove duplicates +} + +function getUniques(array) { + return Array.from(new Set(array)); +} diff --git a/test/cypress/docker/find/find.js b/test/cypress/docker/find/find.js new file mode 100644 index 000000000..4f8063c86 --- /dev/null +++ b/test/cypress/docker/find/find.js @@ -0,0 +1,53 @@ +import { execSync } from 'child_process'; +import { findImports } from './find-imports.js'; +import { getModules } from './get-modules.js'; +const E2E_PATH = 'test/cypress/integration'; +const FINDED_PATHS = ['src', E2E_PATH]; + +function getGitDiff(options) { + const TARGET_BRANCH = options[2] || 'dev'; + const diff = execSync(`git diff --name-only origin/${TARGET_BRANCH}`, { + encoding: 'utf-8', + }); + return diff.split('\n'); +} + +async function getChangedModules() { + let changedModules = new Set(); + const changes = getGitDiff(process.argv); + for (const change of changes) { + if (!change) continue; + if (!FINDED_PATHS.some((prefix) => change.startsWith(prefix))) return ''; + const changedArray = [ + ...changedModules, + ...new Set(getModules(await findImports(change))), + ]; + if (change.startsWith(E2E_PATH)) changedArray.push(change); + changedModules = new Set(changedArray); + } + return cleanSpecs(changedModules).join('\n'); +} + +getChangedModules() + .then((modules) => console.log(modules)) // is return + .catch((e) => { + console.error(e); + process.exit(1); + }); + +function cleanSpecs(changedModules) { + let specifics = []; + const modules = []; + for (const changed of changedModules) { + if (changed.endsWith('*.spec.js')) { + modules.push(changed); + continue; + } + specifics.push(changed); + } + specifics = specifics.filter( + (spec) => !modules.some((module) => spec.startsWith(module.split('**')[0])), + ); + + return [...modules, ...specifics]; +} diff --git a/test/cypress/docker/find/get-modules.js b/test/cypress/docker/find/get-modules.js new file mode 100644 index 000000000..4ac9ec8c4 --- /dev/null +++ b/test/cypress/docker/find/get-modules.js @@ -0,0 +1,13 @@ +export function getModules(files) { + const CYPRESS_PREFIX = 'test/cypress/integration/'; + const CYPRESS_SUFIX = '/**/*.spec.js'; + const modules = []; + for (const file of files) { + if (file.startsWith('src/page')) { + modules.push( + CYPRESS_PREFIX + file.split('/')[2].toLowerCase() + CYPRESS_SUFIX, + ); + } + } + return modules; +} diff --git a/test/cypress/docker/find/resolve-import-path.js b/test/cypress/docker/find/resolve-import-path.js new file mode 100644 index 000000000..38c225fd2 --- /dev/null +++ b/test/cypress/docker/find/resolve-import-path.js @@ -0,0 +1,34 @@ +import fs from 'fs'; +import path from 'path'; +const rootDir = process.cwd(); +const config = JSON.parse(fs.readFileSync('jsconfig.json', 'utf-8')); +const { paths, baseUrl } = config.compilerOptions; + +function resolveImportPath(importPath, fileBase) { + if (!importPath) return null; + importPath = jsConfigPaths(importPath); + const fileDir = path.dirname(fileBase); + if (importPath.startsWith('.') || importPath.startsWith('/')) { + return path.relative(rootDir, path.resolve(fileDir, importPath)); + } + + return importPath; +} +function toRelative(file) { + return path.relative(rootDir, file); +} + +function jsConfigPaths(importPath) { + for (const [aliasPattern, [target]] of Object.entries(paths)) { + const alias = aliasPattern.replace('/*', ''); + const targetBase = target.replace('/*', ''); + + if (importPath.startsWith(alias)) { + const rest = importPath.slice(alias.length); + return path.resolve(baseUrl, targetBase + rest); + } + } + return importPath; +} + +export { resolveImportPath, toRelative }; diff --git a/test/cypress/docker/run.sh b/test/cypress/docker/run.sh new file mode 100755 index 000000000..f62f57960 --- /dev/null +++ b/test/cypress/docker/run.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +salix_dir="${1:-$HOME/Projects/salix}" +salix_dir=$(eval echo "$salix_dir") + +echo "$salix_dir" + +current_dir=$(pwd) + +cleanup() { + docker-compose -p e2e --project-directory . -f test/cypress/docker-compose.yml down -v +} + +trap cleanup SIGINT + +# CLEAN +rm -rf test/cypress/screenshots +rm -f test/cypress/results/* +rm -f test/cypress/reports/* +rm -f junit/e2e-*.xml + +# RUN +export CI=true +export TZ=Europe/Madrid + +# IMAGES +docker build -t registry.verdnatura.es/salix-back:dev -f "$salix_dir/back/Dockerfile" "$salix_dir" +cd "$salix_dir" && npx myt run -t +docker exec vn-database sh -c "rm -rf /mysql-template" +docker exec vn-database sh -c "cp -a /var/lib/mysql /mysql-template" +docker commit vn-database registry.verdnatura.es/salix-db:dev +docker rm -f vn-database +cd "$current_dir" +docker build -f ./docs/Dockerfile.dev -t lilium-dev . +# END IMAGES + +docker-compose -p e2e --project-directory . -f test/cypress/docker-compose.yml up -d +files=$(node test/cypress/docker/find/find.js) +echo $files + +docker run -it --rm \ + -v "$(pwd)":/app \ + --network e2e_default \ + -e CI \ + -e TZ \ + lilium-dev \ + bash -c "sh test/cypress/docker/cypressParallel.sh 2 '$files'" + +cleanup diff --git a/test/cypress/summary.sh b/test/cypress/docker/summary.sh similarity index 100% rename from test/cypress/summary.sh rename to test/cypress/docker/summary.sh 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/claim/claimAction.spec.js b/test/cypress/integration/claim/claimAction.spec.js index b0a16a2ad..8f406ad2f 100644 --- a/test/cypress/integration/claim/claimAction.spec.js +++ b/test/cypress/integration/claim/claimAction.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('ClaimAction', () => { +describe.skip('ClaimAction', () => { const claimId = 1; const firstRow = 'tbody > :nth-child(1)'; @@ -15,12 +15,14 @@ describe('ClaimAction', () => { cy.get('[title="Import claim"]').click(); }); - it('should change destination', () => { + // https://redmine.verdnatura.es/issues/8756 + xit('should change destination', () => { const rowData = [true, null, null, 'Bueno']; cy.fillRow(firstRow, rowData); }); - it('should change destination from other button', () => { + // https://redmine.verdnatura.es/issues/8756 + xit('should change destination from other button', () => { const rowData = [true]; cy.fillRow(firstRow, rowData); @@ -33,7 +35,8 @@ describe('ClaimAction', () => { cy.get('[title="Regularize"]').click(); }); - it('should remove the line', () => { + // https://redmine.verdnatura.es/issues/8756 + xit('should remove the line', () => { cy.fillRow(firstRow, [true]); cy.removeCard(); cy.clickConfirm(); diff --git a/test/cypress/integration/claim/claimDevelopment.spec.js b/test/cypress/integration/claim/claimDevelopment.spec.js index 05ee7f0b8..097d870df 100755 --- a/test/cypress/integration/claim/claimDevelopment.spec.js +++ b/test/cypress/integration/claim/claimDevelopment.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('ClaimDevelopment', () => { +describe.skip('ClaimDevelopment', () => { const claimId = 1; const firstLineReason = 'tbody > :nth-child(1) > :nth-child(2)'; const thirdRow = 'tbody > :nth-child(3)'; @@ -19,11 +19,10 @@ describe('ClaimDevelopment', () => { cy.getValue(firstLineReason).should('equal', lastReason); }); - it('should edit line', () => { + it.skip('should edit line', () => { cy.selectOption(firstLineReason, newReason); cy.saveCard(); - cy.login('developer'); cy.visit(`/#/claim/${claimId}/development`); cy.getValue(firstLineReason).should('equal', newReason); @@ -49,12 +48,9 @@ describe('ClaimDevelopment', () => { cy.fillRow(thirdRow, rowData); cy.saveCard(); - cy.login('developer'); - cy.visit(`/#/claim/${claimId}/development`); - cy.validateRow(thirdRow, rowData); - cy.reload(); + cy.visit(`/#/claim/${claimId}/development`); cy.validateRow(thirdRow, rowData); //remove row @@ -63,7 +59,7 @@ describe('ClaimDevelopment', () => { cy.clickConfirm(); cy.get(thirdRow).should('not.exist'); - cy.reload(); + cy.visit(`/#/claim/${claimId}/development`); cy.get(thirdRow).should('not.exist'); }); }); diff --git a/test/cypress/integration/claim/claimPhoto.spec.js b/test/cypress/integration/claim/claimPhoto.spec.js index b84b4f958..ac0460029 100755 --- a/test/cypress/integration/claim/claimPhoto.spec.js +++ b/test/cypress/integration/claim/claimPhoto.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('ClaimPhoto', () => { +describe.skip('ClaimPhoto', () => { const carrouselClose = '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon'; beforeEach(() => { diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/customer/clientAddress.spec.js similarity index 100% rename from test/cypress/integration/client/clientAddress.spec.js rename to test/cypress/integration/customer/clientAddress.spec.js diff --git a/test/cypress/integration/customer/clientBalance.spec.js b/test/cypress/integration/customer/clientBalance.spec.js new file mode 100644 index 000000000..fff6a5e04 --- /dev/null +++ b/test/cypress/integration/customer/clientBalance.spec.js @@ -0,0 +1,13 @@ +/// <reference types="cypress" /> +describe('Client balance', () => { + beforeEach(() => { + cy.login('developer'); + cy.visit('#/customer/1101/balance'); + }); + it('Should create a mandate', () => { + cy.get('.q-page-sticky > div > .q-btn').click(); + cy.selectOption('[data-cy="paymentBank"]', 2); + cy.dataCy('paymentAmount_input').clear().type('100'); + cy.saveCard(); + }); +}); diff --git a/test/cypress/integration/client/clientBasicData.spec.js b/test/cypress/integration/customer/clientBasicData.spec.js similarity index 100% rename from test/cypress/integration/client/clientBasicData.spec.js rename to test/cypress/integration/customer/clientBasicData.spec.js diff --git a/test/cypress/integration/client/clientBillingData.spec.js b/test/cypress/integration/customer/clientBillingData.spec.js similarity index 100% rename from test/cypress/integration/client/clientBillingData.spec.js rename to test/cypress/integration/customer/clientBillingData.spec.js diff --git a/test/cypress/integration/client/clientCredits.spec.js b/test/cypress/integration/customer/clientCredits.spec.js similarity index 100% rename from test/cypress/integration/client/clientCredits.spec.js rename to test/cypress/integration/customer/clientCredits.spec.js diff --git a/test/cypress/integration/client/clientFiscalData.spec.js b/test/cypress/integration/customer/clientFiscalData.spec.js similarity index 100% rename from test/cypress/integration/client/clientFiscalData.spec.js rename to test/cypress/integration/customer/clientFiscalData.spec.js diff --git a/test/cypress/integration/client/clientGreuges.spec.js b/test/cypress/integration/customer/clientGreuges.spec.js similarity index 100% rename from test/cypress/integration/client/clientGreuges.spec.js rename to test/cypress/integration/customer/clientGreuges.spec.js diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/customer/clientList.spec.js similarity index 95% rename from test/cypress/integration/client/clientList.spec.js rename to test/cypress/integration/customer/clientList.spec.js index b29ad74af..caf94b8bd 100644 --- a/test/cypress/integration/client/clientList.spec.js +++ b/test/cypress/integration/customer/clientList.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('Client list', () => { +describe.skip('Client list', () => { beforeEach(() => { cy.login('developer'); cy.visit('/#/customer/list', { @@ -25,7 +25,7 @@ describe('Client list', () => { 'Web user': { val: `user_test_${randomInt}` }, Street: { val: `C/ STREET ${randomInt}` }, Email: { val: `user.test${randomInt}@cypress.com` }, - 'Sales person': { val: 'salesPerson', type: 'select' }, + Team: { val: 'Informatica', type: 'select' }, Location: { val: '46000', type: 'select' }, 'Business type': { val: 'others', type: 'select' }, }; @@ -53,7 +53,7 @@ describe('Client list', () => { it('Client founded create ticket', () => { const search = 'Jessica Jones'; cy.searchByLabel('Name', search); - cy.openActionDescriptor('Create ticket'); + cy.selectDescriptorOption(); cy.waitForElement('#formModel'); cy.waitForElement('.q-form'); cy.checkValueForm(1, search); diff --git a/test/cypress/integration/client/clientNotes.spec.js b/test/cypress/integration/customer/clientNotes.spec.js similarity index 100% rename from test/cypress/integration/client/clientNotes.spec.js rename to test/cypress/integration/customer/clientNotes.spec.js diff --git a/test/cypress/integration/client/clientRecoveries.spec.js b/test/cypress/integration/customer/clientRecoveries.spec.js similarity index 100% rename from test/cypress/integration/client/clientRecoveries.spec.js rename to test/cypress/integration/customer/clientRecoveries.spec.js diff --git a/test/cypress/integration/client/clientSms.spec.js b/test/cypress/integration/customer/clientSms.spec.js similarity index 100% rename from test/cypress/integration/client/clientSms.spec.js rename to test/cypress/integration/customer/clientSms.spec.js diff --git a/test/cypress/integration/client/clientWebAccess.spec.js b/test/cypress/integration/customer/clientWebAccess.spec.js similarity index 100% rename from test/cypress/integration/client/clientWebAccess.spec.js rename to test/cypress/integration/customer/clientWebAccess.spec.js diff --git a/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js b/test/cypress/integration/customer/credit-management/clientCreditContracts.spec.js similarity index 100% rename from test/cypress/integration/client/credit-management/clientCreditContracts.spec.js rename to test/cypress/integration/customer/credit-management/clientCreditContracts.spec.js diff --git a/test/cypress/integration/client/credit-management/clientCreditOpinion.spec.js b/test/cypress/integration/customer/credit-management/clientCreditOpinion.spec.js similarity index 100% rename from test/cypress/integration/client/credit-management/clientCreditOpinion.spec.js rename to test/cypress/integration/customer/credit-management/clientCreditOpinion.spec.js diff --git a/test/cypress/integration/client/others/clientConsumption.spec.js b/test/cypress/integration/customer/others/clientConsumption.spec.js similarity index 100% rename from test/cypress/integration/client/others/clientConsumption.spec.js rename to test/cypress/integration/customer/others/clientConsumption.spec.js diff --git a/test/cypress/integration/client/others/clientContacts.spec.js b/test/cypress/integration/customer/others/clientContacts.spec.js similarity index 100% rename from test/cypress/integration/client/others/clientContacts.spec.js rename to test/cypress/integration/customer/others/clientContacts.spec.js diff --git a/test/cypress/integration/client/others/clientMandates.spec.js b/test/cypress/integration/customer/others/clientMandates.spec.js similarity index 100% rename from test/cypress/integration/client/others/clientMandates.spec.js rename to test/cypress/integration/customer/others/clientMandates.spec.js diff --git a/test/cypress/integration/client/others/clientSamples.spec.js b/test/cypress/integration/customer/others/clientSamples.spec.js similarity index 100% rename from test/cypress/integration/client/others/clientSamples.spec.js rename to test/cypress/integration/customer/others/clientSamples.spec.js diff --git a/test/cypress/integration/client/others/clientUnpaid.spec.js b/test/cypress/integration/customer/others/clientUnpaid.spec.js similarity index 100% rename from test/cypress/integration/client/others/clientUnpaid.spec.js rename to test/cypress/integration/customer/others/clientUnpaid.spec.js diff --git a/test/cypress/integration/client/others/clientWebPayments.spec.js b/test/cypress/integration/customer/others/clientWebPayments.spec.js similarity index 100% rename from test/cypress/integration/client/others/clientWebPayments.spec.js rename to test/cypress/integration/customer/others/clientWebPayments.spec.js diff --git a/test/cypress/integration/entry/commands.js b/test/cypress/integration/entry/commands.js new file mode 100644 index 000000000..4d4a8f980 --- /dev/null +++ b/test/cypress/integration/entry/commands.js @@ -0,0 +1,20 @@ +Cypress.Commands.add('selectTravel', (warehouse = '1') => { + cy.get('i[data-cy="Travel_icon"]').click(); + 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(); +}); + +Cypress.Commands.add('deleteEntry', () => { + cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); + cy.waitForElement('div[data-cy="delete-entry"]').click(); +}); + +Cypress.Commands.add('createEntry', () => { + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + cy.selectTravel('one'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + cy.url().should('include', 'summary'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); +}); diff --git a/test/cypress/integration/entry/entryCard/entryBasicData.spec.js b/test/cypress/integration/entry/entryCard/entryBasicData.spec.js new file mode 100644 index 000000000..ba689b8c7 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryBasicData.spec.js @@ -0,0 +1,19 @@ +import '../commands.js'; + +describe('EntryBasicData', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Change Travel', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); + cy.selectTravel('two'); + cy.saveCard(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryBuys.spec.js b/test/cypress/integration/entry/entryCard/entryBuys.spec.js new file mode 100644 index 000000000..b5e185a8e --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryBuys.spec.js @@ -0,0 +1,101 @@ +import '../commands.js'; +describe('EntryBuys', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Edit buys and use toolbar actions', () => { + const COLORS = { + negative: 'rgb(251, 82, 82)', + positive: 'rgb(200, 228, 132)', + enabled: 'rgb(255, 255, 255)', + disable: 'rgb(168, 168, 168)', + }; + + const selectCell = (field, row = 0) => + cy.get(`td[data-col-field="${field}"][data-row-index="${row}"]`); + const selectSpan = (field, row = 0) => selectCell(field, row).find('div > span'); + const selectButton = (cySelector) => cy.get(`button[data-cy="${cySelector}"]`); + const clickAndType = (field, value, row = 0) => { + selectCell(field, row).click().type(`${value}{esc}`); + }; + const checkText = (field, expectedText, row = 0) => + selectCell(field, row).should('have.text', expectedText); + const checkColor = (field, expectedColor, row = 0) => + selectSpan(field, row).should('have.css', 'color', expectedColor); + + cy.createEntry(); + createBuy(); + + selectCell('isIgnored').click().click().type('{esc}'); + checkText('isIgnored', 'close'); + + clickAndType('stickers', '1'); + checkText('stickers', '0/01'); + checkText('quantity', '1'); + checkText('amount', '50.00'); + clickAndType('packing', '2'); + checkText('packing', '12'); + checkText('weight', '12.0'); + checkText('quantity', '12'); + checkText('amount', '600.00'); + checkColor('packing', COLORS.enabled); + + selectCell('groupingMode').click().click().click(); + checkColor('packing', COLORS.disable); + checkColor('grouping', COLORS.enabled); + + selectCell('buyingValue').click().clear().type('{backspace}{backspace}1'); + checkText('amount', '12.00'); + checkColor('minPrice', COLORS.disable); + + selectCell('hasMinPrice').click().click(); + checkColor('minPrice', COLORS.enabled); + selectCell('hasMinPrice').click(); + + cy.saveCard(); + cy.get('span[data-cy="footer-stickers"]').should('have.text', '1'); + cy.get('.q-notification__message').contains('Data saved'); + + selectButton('change-quantity-sign').should('be.disabled'); + selectButton('check-buy-amount').should('be.disabled'); + cy.get('tr.cursor-pointer > .q-table--col-auto-width > .q-checkbox').click(); + selectButton('change-quantity-sign').should('be.enabled'); + selectButton('check-buy-amount').should('be.enabled'); + + selectButton('change-quantity-sign').click(); + selectButton('set-negative-quantity').click(); + checkText('quantity', '-12'); + selectButton('set-positive-quantity').click(); + checkText('quantity', '12'); + checkColor('amount', COLORS.disable); + + selectButton('check-buy-amount').click(); + selectButton('uncheck-amount').click(); + checkColor('amount', COLORS.disable); + + selectButton('check-amount').click(); + checkColor('amount', COLORS.positive); + cy.saveCard(); + + cy.get('tbody > tr [tabindex="0"][role="checkbox"]').click(); + cy.dataCy('transferBuys').should('be.enabled').click(); + cy.dataCy('entryDestinyInput').should('be.visible').type('100'); + cy.dataCy('transferBuysBtn').click(); + + cy.deleteEntry(); + }); + + function createBuy() { + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get('a[data-cy="EntryBuys-menu-item"]').click(); + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + + cy.get('input[data-cy="itemFk-create-popup"]').type('1'); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + } +}); diff --git a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js new file mode 100644 index 000000000..8185866db --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js @@ -0,0 +1,40 @@ +import '../commands.js'; +describe('EntryDescriptor', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Clone entry and recalculate rates', () => { + cy.createEntry(); + + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.url().then((previousUrl) => { + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); + + cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); + + cy.url() + .should('not.eq', previousUrl) + .then(() => { + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="recalculate-rates"]').click(); + + cy.get('.q-notification__message') + .eq(2) + .should('have.text', 'Entry prices recalculated'); + cy.deleteEntry(); + + cy.visit(previousUrl); + + cy.waitForElement('[data-cy="entry-buys"]'); + cy.deleteEntry(); + }); + }); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryDms.spec.js b/test/cypress/integration/entry/entryCard/entryDms.spec.js new file mode 100644 index 000000000..f3f0ef20b --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryDms.spec.js @@ -0,0 +1,22 @@ +import '../commands.js'; +describe('EntryDms', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('should create edit and remove new dms', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.dataCy('EntryDms-menu-item').click(); + cy.dataCy('addButton').click(); + cy.dataCy('attachFile').click(); + cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { + force: true, + }); + cy.dataCy('FormModelPopup_save').click(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryLock.spec.js b/test/cypress/integration/entry/entryCard/entryLock.spec.js new file mode 100644 index 000000000..6ba4392ae --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryLock.spec.js @@ -0,0 +1,44 @@ +import '../commands.js'; +describe('EntryLock', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Should notify when entry is lock by another user', () => { + const checkLockMessage = () => { + cy.get('[role="dialog"]').should('be.visible'); + cy.get('[data-cy="VnConfirm_message"] > span').should( + 'contain.text', + 'This entry has been locked by buyerNick', + ); + }; + + cy.createEntry(); + goToEntryBuys(); + cy.get('.q-notification__message') + .eq(1) + .should('have.text', 'The entry has been locked successfully'); + + cy.login('logistic'); + cy.reload(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_cancel"]').click(); + cy.url().should('include', 'summary'); + + goToEntryBuys(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_confirm"]').click(); + cy.url().should('include', 'buys'); + + cy.deleteEntry(); + + function goToEntryBuys() { + const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; + cy.get(entryBuySelector).should('be.visible'); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get(entryBuySelector).click(); + } + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryNotes.spec.js b/test/cypress/integration/entry/entryCard/entryNotes.spec.js new file mode 100644 index 000000000..544ac23b0 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryNotes.spec.js @@ -0,0 +1,50 @@ +import '../commands.js'; + +describe('EntryNotes', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + const createObservation = (type, description) => { + cy.dataCy('vnTableCreateBtn').click(); + cy.dataCy('Observation type_select').eq(1).should('be.visible').type(type); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.dataCy('Description_input').should('be.visible').type(description); + cy.dataCy('FormModelPopup_save').should('be.enabled').click(); + }; + + const editObservation = (rowIndex, type, description) => { + cy.get(`td[data-col-field="description"][data-row-index="${rowIndex}"]`) + .click() + .clear() + .type(description); + cy.get(`td[data-col-field="observationTypeFk"][data-row-index="${rowIndex}"]`) + .click() + .clear() + .type(type); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.saveCard(); + }; + + it('Create, delete, and edit observations', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.dataCy('EntryNotes-menu-item').click(); + + createObservation('Packager', 'test'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + + editObservation(0, 'Administrative', 'test2'); + + createObservation('Administrative', 'test'); + cy.get('.q-notification__message') + .eq(2) + .should('have.text', "The observation type can't be repeated"); + cy.dataCy('FormModelPopup_cancel').click(); + + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryDms.spec.js b/test/cypress/integration/entry/entryDms.spec.js deleted file mode 100644 index 47dcdba9e..000000000 --- a/test/cypress/integration/entry/entryDms.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -describe('EntryDms', () => { - const entryId = 1; - - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('developer'); - cy.visit(`/#/entry/${entryId}/dms`); - }); - - it('should create edit and remove new dms', () => { - cy.addRow(); - cy.get('.icon-attach').click(); - cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { - force: true, - }); - - cy.get('tbody > tr').then((value) => { - const u = undefined; - - //Create and check if exist new row - let newFileTd = Cypress.$(value).length; - cy.get('.q-btn--standard > .q-btn__content > .block').click(); - expect(value).to.have.length(newFileTd++); - const newRowSelector = `tbody > :nth-child(${newFileTd})`; - cy.waitForElement(newRowSelector); - cy.validateRow(newRowSelector, [u, u, u, u, u, 'ENTRADA ID 1']); - - //Edit new dms - const newDescription = 'entry id 1 modified'; - const textAreaSelector = - '.q-textarea > .q-field__inner > .q-field__control > .q-field__control-container'; - cy.get( - `tbody :nth-child(${newFileTd}) > .text-right > .no-wrap > :nth-child(2) > .q-btn > .q-btn__content > .q-icon` - ).click(); - - cy.get(textAreaSelector).clear(); - cy.get(textAreaSelector).type(newDescription); - cy.saveCard(); - cy.reload(); - - cy.validateRow(newRowSelector, [u, u, u, u, u, newDescription]); - }); - }); -}); diff --git a/test/cypress/integration/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js index 4c4c4f004..bad47615f 100644 --- a/test/cypress/integration/entry/entryList.spec.js +++ b/test/cypress/integration/entry/entryList.spec.js @@ -1,223 +1,55 @@ -describe('Entry', () => { +import './commands'; + +describe('EntryList', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('buyer'); cy.visit(`/#/entry/list`); }); - it('Filter deleted entries and other fields', () => { - createEntry(); + it('View popup summary', () => { + cy.createEntry(); cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); cy.waitForElement('[data-cy="entry-buys"]'); - deleteEntry(); + cy.deleteEntry(); cy.typeSearchbar('{enter}'); - cy.get('span[title="Date"]').click().click(); - cy.typeSearchbar('{enter}'); - cy.url().should('include', 'order'); - cy.get('td[data-row-index="0"][data-col-field="landed"]').should( - 'have.text', - '-', - ); + cy.get('button[title="Summary"]').eq(1).should('be.visible').click(); + cy.dataCy('entry-summary').should('be.visible'); }); - it.skip('Create entry, modify travel and add buys', () => { - createEntryAndBuy(); - cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); - selectTravel('two'); - cy.saveCard(); - cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); - deleteEntry(); + it('Show supplierDescriptor on click on supplierDescriptor', () => { + cy.typeSearchbar('{enter}'); + cy.get('td[data-col-field="supplierFk"] > div > span').eq(0).click(); + cy.get('div[role="menu"] > div[class="descriptor"]').should('be.visible'); }); - it('Clone entry and recalculate rates', () => { - createEntry(); + it('Landed badge should display the right color', () => { + cy.typeSearchbar('{enter}'); - cy.waitForElement('[data-cy="entry-buys"]'); - - cy.url().then((previousUrl) => { - cy.get('[data-cy="descriptor-more-opts"]').click(); - cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); - - cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); - - cy.url() - .should('not.eq', previousUrl) - .then(() => { - cy.waitForElement('[data-cy="entry-buys"]'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); - cy.get('div[data-cy="recalculate-rates"]').click(); - - cy.get('.q-notification__message') - .eq(2) - .should('have.text', 'Entry prices recalculated'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); - deleteEntry(); - - cy.log(previousUrl); - - cy.visit(previousUrl); - - cy.waitForElement('[data-cy="entry-buys"]'); - deleteEntry(); + const checkBadgeDate = (selector, comparisonFn) => { + cy.get(selector) + .should('exist') + .each(($badge) => { + const badgeText = $badge.text().trim(); + const badgeDate = new Date(badgeText); + const compareDate = new Date('01/01/2001'); + comparisonFn(badgeDate, compareDate); }); - }); - }); - - it('Should notify when entry is lock by another user', () => { - const checkLockMessage = () => { - cy.get('[role="dialog"]').should('be.visible'); - cy.get('[data-cy="VnConfirm_message"] > span').should( - 'contain.text', - 'This entry has been locked by buyerNick', - ); }; - createEntry(); - goToEntryBuys(); - cy.get('.q-notification__message') - .eq(1) - .should('have.text', 'The entry has been locked successfully'); - - cy.login('logistic'); - cy.reload(); - checkLockMessage(); - cy.get('[data-cy="VnConfirm_cancel"]').click(); - cy.url().should('include', 'summary'); - - goToEntryBuys(); - checkLockMessage(); - cy.get('[data-cy="VnConfirm_confirm"]').click(); - cy.url().should('include', 'buys'); - - deleteEntry(); - }); - - it('Edit buys and use toolbar actions', () => { - const COLORS = { - negative: 'rgb(251, 82, 82)', - positive: 'rgb(200, 228, 132)', - enabled: 'rgb(255, 255, 255)', - disable: 'rgb(168, 168, 168)', - }; - - const selectCell = (field, row = 0) => - cy.get(`td[data-col-field="${field}"][data-row-index="${row}"]`); - const selectSpan = (field, row = 0) => selectCell(field, row).find('div > span'); - const selectButton = (cySelector) => cy.get(`button[data-cy="${cySelector}"]`); - const clickAndType = (field, value, row = 0) => { - selectCell(field, row).click().type(`${value}{esc}`); - }; - const checkText = (field, expectedText, row = 0) => - selectCell(field, row).should('have.text', expectedText); - const checkColor = (field, expectedColor, row = 0) => - selectSpan(field, row).should('have.css', 'color', expectedColor); - - createEntryAndBuy(); - - selectCell('isIgnored').click().click().type('{esc}'); - checkText('isIgnored', 'close'); - - clickAndType('stickers', '1'); - checkText('stickers', '0/01'); - checkText('quantity', '1'); - checkText('amount', '50.00'); - clickAndType('packing', '2'); - checkText('packing', '12'); - checkText('weight', '12.0'); - checkText('quantity', '12'); - checkText('amount', '600.00'); - checkColor('packing', COLORS.enabled); - - selectCell('groupingMode').click().click().click(); - checkColor('packing', COLORS.disable); - checkColor('grouping', COLORS.enabled); - - selectCell('buyingValue').click().clear().type('{backspace}{backspace}1'); - checkText('amount', '12.00'); - checkColor('minPrice', COLORS.disable); - - selectCell('hasMinPrice').click().click(); - checkColor('minPrice', COLORS.enabled); - selectCell('hasMinPrice').click(); - - cy.saveCard(); - cy.get('span[data-cy="footer-stickers"]').should('have.text', '1'); - cy.get('.q-notification__message').contains('Data saved'); - - selectButton('change-quantity-sign').should('be.disabled'); - selectButton('check-buy-amount').should('be.disabled'); - cy.get('tr.cursor-pointer > .q-table--col-auto-width > .q-checkbox').click(); - selectButton('change-quantity-sign').should('be.enabled'); - selectButton('check-buy-amount').should('be.enabled'); - - selectButton('change-quantity-sign').click(); - selectButton('set-negative-quantity').click(); - checkText('quantity', '-12'); - selectButton('set-positive-quantity').click(); - checkText('quantity', '12'); - checkColor('amount', COLORS.disable); - - selectButton('check-buy-amount').click(); - selectButton('uncheck-amount').click(); - checkColor('amount', COLORS.disable); - - selectButton('check-amount').click(); - checkColor('amount', COLORS.positive); - cy.saveCard(); - - cy.get('span[data-cy="footer-amount"]').should( - 'have.css', - 'color', - COLORS.positive, + checkBadgeDate( + 'td[data-col-field="landed"] > div .bg-warning', + (badgeDate, compareDate) => { + expect(badgeDate.getTime()).to.be.greaterThan(compareDate.getTime()); + }, ); - deleteEntry(); + // 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()); + }, + ); */ }); - - function goToEntryBuys() { - const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; - cy.get(entryBuySelector).should('be.visible'); - cy.waitForElement('[data-cy="entry-buys"]'); - cy.get(entryBuySelector).click(); - } - - function deleteEntry() { - cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); - cy.waitForElement('div[data-cy="delete-entry"]').click(); - cy.url().should('include', 'list'); - } - - function createEntryAndBuy() { - createEntry(); - createBuy(); - } - - function createEntry() { - cy.get('button[data-cy="vnTableCreateBtn"]').click(); - selectTravel('one'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); - cy.url().should('include', 'summary'); - cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); - } - - function selectTravel(warehouse) { - cy.get('i[data-cy="Travel_icon"]').click(); - cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); - cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); - cy.get('button[data-cy="save-filter-travel-form"]').click(); - cy.get('tr').eq(1).click(); - } - - function createBuy() { - cy.get('a[data-cy="EntryBuys-menu-item"]').click(); - cy.get('a[data-cy="EntryBuys-menu-item"]').click(); - cy.get('button[data-cy="vnTableCreateBtn"]').click(); - - cy.get('input[data-cy="itemFk-create-popup"]').type('1'); - cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); - cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); - } }); diff --git a/test/cypress/integration/entry/entryStockBought.spec.js b/test/cypress/integration/entry/entryStockBought.spec.js new file mode 100644 index 000000000..3fad44d91 --- /dev/null +++ b/test/cypress/integration/entry/entryStockBought.spec.js @@ -0,0 +1,23 @@ +describe('EntryStockBought', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/stock-Bought`); + }); + + it('Should edit the reserved space adjust the purchased spaces and check detail', () => { + cy.get('[data-cy="edit-travel"]').should('be.visible').click(); + cy.get('input[aria-label="m3"]').clear().type('60'); + cy.get('[data-cy="FormModelPopup_save"]').click(); + cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); + + cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); + cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); + cy.get('input[name="reserve"]').type('10{enter}'); + cy.get('button[title="Save"]').click(); + cy.checkNotification('Data saved'); + + cy.get('[data-cy="searchBtn"]').eq(0).click(); + cy.get('tBody > tr').eq(1).its('length').should('eq', 1); + }); +}); diff --git a/test/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/entrySupplier.spec.js similarity index 71% rename from test/cypress/integration/entry/myEntry.spec.js rename to test/cypress/integration/entry/entrySupplier.spec.js index ed469d9e2..83deecea5 100644 --- a/test/cypress/integration/entry/myEntry.spec.js +++ b/test/cypress/integration/entry/entrySupplier.spec.js @@ -1,4 +1,4 @@ -describe('EntryMy when is supplier', () => { +describe('EntrySupplier when is supplier', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('supplier'); @@ -13,5 +13,7 @@ describe('EntryMy when is supplier', () => { cy.dataCy('cardBtn').eq(2).click(); cy.dataCy('printLabelsBtn').click(); cy.window().its('open').should('be.called'); + cy.dataCy('seeLabelBtn').eq(0).should('be.visible').click(); + cy.window().its('open').should('be.called'); }); }); diff --git a/test/cypress/integration/entry/entryWasteRecalc.spec.js b/test/cypress/integration/entry/entryWasteRecalc.spec.js new file mode 100644 index 000000000..1b358676c --- /dev/null +++ b/test/cypress/integration/entry/entryWasteRecalc.spec.js @@ -0,0 +1,22 @@ +import './commands'; +describe('EntryDms', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyerBoss'); + cy.visit(`/#/entry/waste-recalc`); + }); + + it('should recalc waste for today', () => { + cy.waitForElement('[data-cy="wasteRecalc"]'); + cy.dataCy('recalc').should('be.disabled'); + + cy.dataCy('dateFrom').should('be.visible').click().type('01-01-2001'); + cy.dataCy('dateTo').should('be.visible').click().type('01-01-2001'); + + cy.dataCy('recalc').should('be.enabled').click(); + cy.get('.q-notification__message').should( + 'have.text', + 'The wastes were successfully recalculated', + ); + }); +}); diff --git a/test/cypress/integration/entry/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js deleted file mode 100644 index 91e0d507e..000000000 --- a/test/cypress/integration/entry/stockBought.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -describe('EntryStockBought', () => { - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('buyer'); - cy.visit(`/#/entry/stock-Bought`); - }); - it('Should edit the reserved space', () => { - cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); - cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); - cy.get('input[name="reserve"]').type('10{enter}'); - cy.get('button[title="Save"]').click(); - cy.checkNotification('Data saved'); - }); - it('Should add a new reserved space for buyerBoss', () => { - cy.addBtnClick(); - cy.get('input[aria-label="Reserve"]').type('1'); - cy.get('input[aria-label="Date"]').eq(1).clear(); - cy.get('input[aria-label="Date"]').eq(1).type('01-01'); - cy.get('input[aria-label="Buyer"]').type('itNick'); - cy.get('div[role="listbox"] > div > div[role="option"]') - .eq(1) - .should('be.visible') - .click(); - - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get('.q-notification__message').should('have.text', 'Data created'); - - cy.get('[data-col-field="reserve"][data-row-index="1"]').click().clear(); - cy.get('[data-cy="searchBtn"]').eq(1).click(); - cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata') - .should('have.text', 'warningNo data available') - .type('{esc}'); - cy.get('[data-col-field="reserve"][data-row-index="1"]') - .click() - .type('{backspace}{enter}'); - cy.get('[data-cy="crudModelDefaultSaveBtn"]').should('be.enabled').click(); - cy.get('.q-notification__message').eq(1).should('have.text', 'Data saved'); - }); - it('Should check detail for the buyer', () => { - cy.get('[data-cy="searchBtn"]').eq(0).click(); - cy.get('tBody > tr').eq(1).its('length').should('eq', 1); - }); - - it('Should edit travel m3 and refresh', () => { - cy.get('[data-cy="edit-travel"]').should('be.visible').click(); - cy.get('input[aria-label="m3"]').clear().type('60'); - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); - }); -}); diff --git a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js index 11ca1bb59..ee4d9fb74 100644 --- a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js @@ -1,26 +1,40 @@ /// <reference types="cypress" /> +import moment from 'moment'; describe('InvoiceInBasicData', () => { - const firstFormSelect = '.q-card > .vn-row:nth-child(1) > .q-select'; const dialogInputs = '.q-dialog input'; - const resetBtn = '.q-btn-group--push > .q-btn--flat'; const getDocumentBtns = (opt) => `[data-cy="dms-buttons"] > :nth-child(${opt})`; + const futureDate = moment().add(1, 'days').format('DD-MM-YYYY'); + const mock = { + invoiceInBasicDataSupplier: { val: 'Bros nick', type: 'select' }, + invoiceInBasicDataSupplierRef_input: 'mockInvoice41', + invoiceInBasicDataIssued: { val: futureDate, type: 'date' }, + invoiceInBasicDataOperated: { val: futureDate, type: 'date' }, + invoiceInBasicDatabookEntried: { val: futureDate, type: 'date' }, + invoiceInBasicDataBooked: { + val: moment().add(5, 'days').format('DD-MM-YYYY'), + type: 'date', + }, + invoiceInBasicDataDeductibleExpenseFk: { + val: '4751000000', + type: 'select', + }, + invoiceInBasicDataCurrencyFk: { val: 'USD', type: 'select' }, + invoiceInBasicDataCompanyFk: { val: 'CCs', type: 'select' }, + invoiceInBasicDataWithholdingSageFk: { + val: 'Arrendamiento y subarrendamiento', + type: 'select', + }, + }; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/basic-data`); }); - it('should edit the provideer and supplier ref', () => { - cy.dataCy('UnDeductibleVatSelect').type('4751000000'); - cy.get('.q-menu .q-item').contains('4751000000').click(); - cy.get(resetBtn).click(); - - cy.waitForElement('#formModel').within(() => { - cy.dataCy('vnSupplierSelect').type('Bros nick'); - }) - cy.get('.q-menu .q-item').contains('Bros nick').click(); + it('should edit every field', () => { + cy.fillInForm(mock, { attr: 'data-cy' }); cy.saveCard(); - cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Bros nick'); + cy.validateForm(mock, { attr: 'data-cy' }); }); it('should edit, remove and create the dms data', () => { @@ -44,7 +58,7 @@ describe('InvoiceInBasicData', () => { cy.checkNotification('Data saved'); //create - cy.get('[data-cy="dms-create"]').eq(0).click(); + cy.get('[data-cy="invoiceInBasicDataDmsAdd"]').eq(0).click(); cy.get('[data-cy="VnDms_inputFile"').selectFile( 'test/cypress/fixtures/image.jpg', { diff --git a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js index 731174040..275fa1358 100644 --- a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js @@ -1,22 +1,59 @@ -/// <reference types="cypress" /> -describe('InvoiceInCorrective', () => { - const saveDialog = '.q-card > .q-card__actions > .q-btn--standard '; +describe('invoiceInCorrective', () => { + beforeEach(() => cy.login('administrative')); - it('should create a correcting invoice', () => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit(`/#/invoice-in/1/summary`); + it('should modify the invoice', () => { + cy.visit('/#/invoice-in/1/summary'); cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + cy.intercept('POST', '/api/InvoiceInCorrections/crud').as('crud'); + cy.intercept('GET', /InvoiceInCorrections\?filter=.+/).as('getCorrective'); - cy.openActionsDescriptor(); + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); - cy.dataCy('createCorrectiveItem').click(); - cy.get(saveDialog).click(); - cy.wait('@corrective').then((interception) => { - const correctingId = interception.response.body; - cy.url().should('include', `/invoice-in/${correctingId}/summary`); - cy.visit(`/#/invoice-in/${correctingId}/corrective`); + cy.wait('@corrective').then(({ response }) => { + const correctingFk = response.body; + cy.url().should('include', `/invoice-in/${correctingFk}/summary`); + cy.visit(`/#/invoice-in/${correctingFk}/corrective`); + cy.selectOption('[data-cy="invoiceInCorrective_class"]', 'r4'); + cy.selectOption('[data-cy="invoiceInCorrective_type"]', 'sustitución'); + cy.selectOption('[data-cy="invoiceInCorrective_reason"]', 'vat'); + cy.dataCy('crudModelDefaultSaveBtn').click(); + + cy.wait('@crud'); + cy.reload(); + cy.wait('@getCorrective'); + cy.validateRow('tbody > :nth-of-type(1)', [ + , + 'S – Por sustitución', + 'R4', + 'Error in VAT calculation', + ]); }); - cy.get('tbody > tr:visible').should('have.length', 1); + }); + + it('should not be able to modify the invoice if the original invoice is booked', () => { + cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + cy.visit('/#/invoice-in/4/summary'); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); + + cy.wait('@corrective').then(({ response }) => { + const correctingFk = response.body; + cy.url().should('include', `/invoice-in/${correctingFk}/summary`); + cy.visit(`/#/invoice-in/${correctingFk}/corrective`); + + cy.dataCy('invoiceInCorrective_class').should('be.disabled'); + cy.dataCy('invoiceInCorrective_type').should('be.disabled'); + cy.dataCy('invoiceInCorrective_reason').should('be.disabled'); + }); + }); + + it('should show/hide the section if it is a corrective invoice', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.get('[data-cy="InvoiceInCorrective-menu-item"]').should('not.exist'); + cy.clicDescriptorAction(4); + cy.get('[data-cy="InvoiceInCorrective-menu-item"]').should('exist'); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js index 97a9fe976..9744486e0 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js @@ -1,21 +1,147 @@ describe('InvoiceInDescriptor', () => { - const book = '.summaryHeader > .no-wrap > .q-btn'; - const firstDescritorOpt = '.q-menu > .q-list > :nth-child(5) > .q-item__section'; - const checkbox = ':nth-child(5) > .q-checkbox'; + beforeEach(() => cy.login('administrative')); - it('should booking and unbooking the invoice properly', () => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit('/#/invoice-in/1/summary'); - cy.waitForElement('.q-page'); + describe('more options', () => { + it('should booking and unbooking the invoice properly', () => { + const checkbox = '[data-cy="vnLvIs booked"] > .q-checkbox'; + cy.visit('/#/invoice-in/2/summary'); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox, false); + }); - cy.get(book).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.get(checkbox).invoke('attr', 'aria-checked').should('eq', 'true'); + it('should delete the invoice properly', () => { + cy.visit('/#/invoice-in/2/summary'); + cy.selectDescriptorOption(2); + cy.clickConfirm(); + cy.checkNotification('invoice deleted'); + }); - cy.dataCy('descriptor-more-opts').first().click(); - cy.get(firstDescritorOpt).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.get(checkbox).invoke('attr', 'aria-checked').should('eq', 'false'); + it('should clone the invoice properly', () => { + cy.visit('/#/invoice-in/3/summary'); + cy.selectDescriptorOption(3); + cy.clickConfirm(); + cy.checkNotification('Invoice cloned'); + }); + + it('should show the agricultural PDF properly', () => { + cy.visit('/#/invoice-in/6/summary'); + cy.validatePdfDownload( + /api\/InvoiceIns\/6\/invoice-in-pdf\?access_token=.*/, + () => cy.selectDescriptorOption(4), + ); + }); + + it('should send the agricultural PDF properly', () => { + cy.intercept('POST', 'api/InvoiceIns/6/invoice-in-email').as('sendEmail'); + cy.visit('/#/invoice-in/6/summary'); + cy.selectDescriptorOption(5); + + cy.dataCy('SendEmailNotificationDialogInput_input').type( + '{selectall}jorgito@gmail.mx', + ); + cy.clickConfirm(); + cy.checkNotification('Notification sent'); + cy.wait('@sendEmail').then(({ request, response }) => { + expect(request.body).to.deep.equal({ + recipientId: 2, + recipient: 'jorgito@gmail.mx', + }); + expect(response.statusCode).to.equal(200); + }); + }); + // https://redmine.verdnatura.es/issues/8767 + it.skip('should download the file properly', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.validateDownload(() => cy.selectDescriptorOption(5)); + }); + }); + + describe('buttons', () => { + beforeEach(() => cy.visit('/#/invoice-in/1/summary')); + + it('should navigate to the supplier summary', () => { + cy.clicDescriptorAction(1); + cy.url().should('to.match', /supplier\/\d+\/summary/); + }); + + it('should navigate to the entry summary', () => { + cy.clicDescriptorAction(2); + cy.url().should('to.match', /entry\/\d+\/summary/); + }); + + it('should navigate to the invoiceIn list', () => { + cy.clicDescriptorAction(3); + cy.url().should('to.match', /invoice-in\/list\?table=\{.*supplierFk.+\}/); + }); + }); + + describe('corrective', () => { + const originalId = 4; + + beforeEach(() => cy.visit(`/#/invoice-in/${originalId}/summary`)); + + it('should create a correcting invoice and redirect to original invoice', () => { + createCorrective(); + redirect(originalId); + }); + + it('should create a correcting invoice and navigate to list filtered by corrective', () => { + createCorrective(); + redirect(originalId); + + cy.clicDescriptorAction(4); + cy.validateVnTableRows({ + cols: [ + { + name: 'supplierRef', + val: '1237', + operation: 'include', + }, + ], + }); + }); + }); + + describe('link', () => { + it('should open the supplier descriptor popup', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.intercept('GET', /Suppliers\/\d+/).as('getSupplier'); + + cy.dataCy('invoiceInDescriptor_supplier').then(($el) => { + const alias = $el.text().trim(); + $el.click(); + cy.wait('@getSupplier').then(() => + cy.validateDescriptor({ listbox: { 1: alias }, popup: true }), + ); + }); + }); }); }); + +function createCorrective() { + cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); + + cy.wait('@corrective').then(({ response }) => { + const correctingId = response.body; + cy.url().should('include', `/invoice-in/${correctingId}/summary`); + cy.visit(`/#/invoice-in/${correctingId}/corrective`); + cy.dataCy('invoiceInCorrective_class').should('contain.value', 'R2'); + cy.dataCy('invoiceInCorrective_type').should('contain.value', 'diferencias'); + cy.dataCy('invoiceInCorrective_reason').should('contain.value', 'sales details'); + }); +} + +function redirect(subtitle) { + const regex = new RegExp(`InvoiceIns/${subtitle}\\?filter=.*`); + cy.intercept('GET', regex).as('getOriginal'); + cy.clicDescriptorAction(4); + cy.wait('@getOriginal'); + cy.validateDescriptor({ subtitle }); +} diff --git a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js index 5a5becd22..2fc34a7ae 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js @@ -4,7 +4,7 @@ describe('InvoiceInDueDay', () => { const addBtn = '.q-page-sticky > div > .q-btn > .q-btn__content'; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/6/due-day`); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js index 4c2550548..6a1c18785 100644 --- a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js @@ -6,7 +6,7 @@ describe('InvoiceInIntrastat', () => { const firstRowAmount = `${firstRow} > :nth-child(3)`; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/intrastat`); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js index d9ab3f7e7..ef3e33000 100644 --- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -1,13 +1,21 @@ /// <reference types="cypress" /> + describe('InvoiceInList', () => { const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; const firstId = `${firstRow} > td:nth-child(2) span`; - const firstDetailBtn = `${firstRow} .q-btn:nth-child(1)`; - const summaryHeaders = '.summaryBody .header-link'; + const invoiceId = '6'; + 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' }, + 'Invoice nº_input': mockInvoiceRef, + Company_select: { val: 'orn', type: 'select' }, + 'Expedition date_inputDate': '16-11-2001', + }; beforeEach(() => { cy.viewport(1920, 1080); - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/list`); cy.get('#searchbar input').type('{enter}'); }); @@ -23,8 +31,28 @@ 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('[data-col-field="id"]').then(($cells) => { + const exactMatch = [...$cells].find( + (cell) => cell.textContent.trim() === invoiceId, + ); + expect(exactMatch).to.exist; + cy.wrap(exactMatch).closest('tr').find('.q-btn:nth-child(1)').click(); + }); + cy.get(summaryHeaders('max-width')).contains('Basic data'); + cy.get(summaryHeaders('vat')).contains('Vat'); + }); + + it('should create a new Invoice', () => { + cy.dataCy('vnTableCreateBtn').click(); + cy.fillInForm({ ...mock }, { attr: 'data-cy' }); + cy.dataCy('FormModelPopup_save').click(); + cy.intercept('GET', /\/api\/InvoiceIns\/\d+\/getTotals$/).as('invoice'); + cy.wait('@invoice').then(() => { + cy.validateDescriptor({ + title: mockInvoiceRef, + listBox: { 0: '11/16/2001', 3: 'The farmer' }, + }); + cy.dataCy('invoiceInBasicDataCompanyFk').should('have.value', 'ORN'); + }); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js new file mode 100644 index 000000000..3750f8f06 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js @@ -0,0 +1,25 @@ +describe('InvoiceInSerial', () => { + beforeEach(() => { + cy.login('administrative'); + cy.visit('#/invoice-in/serial'); + }); + + it('should filter by serial number', () => { + cy.dataCy('serial_input').type('R{enter}'); + cy.validateVnTableRows({ cols: [{ name: 'serial', val: 'r' }] }); + }); + + it('should filter by last days ', () => { + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((before) => { + cy.dataCy('Last days_input') + .type('{selectall}1{enter}') + .then(() => { + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((after) => expect(+after).to.be.lessThan(+before)); + }); + }); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js b/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js new file mode 100644 index 000000000..72dbdd9a8 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js @@ -0,0 +1,24 @@ +describe('InvoiceInSummary', () => { + beforeEach(() => { + cy.login('administrative'); + cy.visit('/#/invoice-in/3/summary'); + }); + + it('should booking and unbooking the invoice properly', () => { + const checkbox = '[data-cy="vnLvIs booked"] > .q-checkbox'; + cy.dataCy('invoiceInSummary_book').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox); + }); + + it('should open the supplier descriptor popup', () => { + cy.intercept('GET', /Suppliers\/\d+/).as('getSupplier'); + cy.dataCy('invoiceInSummary_supplier').then(($el) => { + const description = $el.text().trim(); + $el.click(); + cy.wait('@getSupplier').then(() => + cy.validateDescriptor({ description, popup: true }), + ); + }); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js index 3e9997a74..ff7d639e6 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -1,14 +1,14 @@ /// <reference types="cypress" /> describe('InvoiceInVat', () => { const thirdRow = 'tbody > :nth-child(3)'; - const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)'; + const firstLineVat = 'tbody > :nth-child(1) '; const vats = '[data-cy="vat-sageiva"]'; const dialogInputs = '.q-dialog label input'; const addBtn = 'tbody tr:nth-child(1) td:nth-child(2) .--add-icon'; const randomInt = Math.floor(Math.random() * 100); beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/vat`); }); @@ -18,6 +18,17 @@ describe('InvoiceInVat', () => { cy.get(vats).eq(0).should('have.value', '8: H.P. IVA 21% CEE'); }); + it('should mark the line as deductible VAT', () => { + cy.get(`${firstLineVat} [data-cy="isDeductible_checkbox"]`).click(); + + cy.saveCard(); + + cy.get(`${firstLineVat} [data-cy="isDeductible_checkbox"]`) + + .click(); + cy.saveCard(); + }); + it('should add a new row', () => { cy.addRow(); cy.fillRow(thirdRow, [true, 2000000001, 30, 'H.P. IVA 10']); diff --git a/test/cypress/integration/invoiceOut/invoiceOutList.spec.js b/test/cypress/integration/invoiceOut/invoiceOutList.spec.js index d3a84d226..b8b42fa4b 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutList.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutList.spec.js @@ -13,7 +13,6 @@ describe('InvoiceOut list', () => { ':nth-child(1) > .text-right > [data-cy="tableAction-0"] > .q-btn__content > .q-icon'; beforeEach(() => { - cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/invoice-out/list`); cy.typeSearchbar('{enter}'); @@ -41,7 +40,7 @@ describe('InvoiceOut list', () => { }); it('should filter the results by client ID, then check the first result is correct', () => { - cy.dataCy('Customer ID_input').type('1103'); + cy.dataCy('Client id_input').type('1103'); cy.get(filterBtn).click(); cy.get(firstRowDescriptor).click(); cy.get('.q-item > .q-item__label').should('include.text', '1103'); diff --git a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js index 0039f6240..9c6eef2ed 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js @@ -16,9 +16,9 @@ describe('InvoiceOut negative bases', () => { cy.get(getDescriptors('ticketFk')).click(); cy.get('.descriptor').should('be.visible'); cy.get('.q-item > .q-item__label').should('include.text', '23'); - cy.get(getDescriptors('workerName')).click(); + cy.get(getDescriptors('departmentFk')).click(); cy.get('.descriptor').should('be.visible'); - cy.get('.q-item > .q-item__label').should('include.text', '18'); + cy.get('.q-item > .q-item__label').should('include.text', '155'); }); it('should filter and download as CSV', () => { diff --git a/test/cypress/integration/invoiceOut/invoiceOutSummary.spec.js b/test/cypress/integration/invoiceOut/invoiceOutSummary.spec.js index 63e828f55..49eed32c7 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutSummary.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutSummary.spec.js @@ -37,7 +37,7 @@ describe('InvoiceOut summary', () => { }); }); - it('should transfer the invoice ', () => { + it.skip('should transfer the invoice ', () => { cy.typeSearchbar('T1111111{enter}'); cy.dataCy('descriptor-more-opts').click(); cy.get(selectMenuOption(1)).click(); @@ -50,7 +50,7 @@ describe('InvoiceOut summary', () => { cy.dataCy('descriptor-more-opts').click(); cy.get(selectMenuOption(3)).click(); cy.dataCy('InvoiceOutDescriptorMenuSendPdfOption').click(); - cy.dataCy('SendEmailNotifiactionDialogInput').should('be.visible'); + cy.dataCy('SendEmailNotificationDialogInput').should('be.visible'); cy.get(confirmSend).click(); cy.checkNotification('Notification sent'); }); @@ -59,7 +59,7 @@ describe('InvoiceOut summary', () => { cy.dataCy('descriptor-more-opts').click(); cy.get(selectMenuOption(3)).click(); cy.dataCy('InvoiceOutDescriptorMenuSendCsvOption').click(); - cy.dataCy('SendEmailNotifiactionDialogInput').should('be.visible'); + cy.dataCy('SendEmailNotificationDialogInput').should('be.visible'); cy.get(confirmSend).click(); cy.checkNotification('Notification sent'); }); diff --git a/test/cypress/integration/item/ItemFixedPrice.spec.js b/test/cypress/integration/item/ItemFixedPrice.spec.js index 404e8e365..41230f570 100644 --- a/test/cypress/integration/item/ItemFixedPrice.spec.js +++ b/test/cypress/integration/item/ItemFixedPrice.spec.js @@ -1,9 +1,14 @@ /// <reference types="cypress" /> -function goTo(n = 1) { - return `.q-virtual-scroll__content > :nth-child(${n})`; -} -const firstRow = goTo(); describe('Handle Items FixedPrice', () => { + const grouping = 'Grouping price'; + const saveEditBtn = '.q-mt-lg > .q-btn--standard'; + const createForm = { + 'Grouping price': { val: '5' }, + 'Packing price': { val: '5' }, + Started: { val: '01-01-2001', type: 'date' }, + Ended: { val: '15-01-2001', type: 'date' }, + }; + beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -13,50 +18,57 @@ describe('Handle Items FixedPrice', () => { '.q-header > .q-toolbar > :nth-child(1) > .q-btn__content > .q-icon', ).click(); }); - it.skip('filter', function () { + + it('filter by category', () => { cy.get('.category-filter > :nth-child(1) > .q-btn__content > .q-icon').click(); - cy.selectOption('.list > :nth-child(2)', 'Alstroemeria'); - cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); - - cy.addBtnClick(); - cy.selectOption(`${firstRow} > :nth-child(2)`, '#13'); - cy.get(`${firstRow} > :nth-child(4)`).find('input').type(1); - cy.get(`${firstRow} > :nth-child(5)`).find('input').type('2'); - cy.selectOption(`${firstRow} > :nth-child(9)`, 'Warehouse One'); - cy.get('.q-notification__message').should('have.text', 'Data saved'); - /* ==== End Cypress Studio ==== */ - }); - it.skip('Create and delete ', function () { - cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); - cy.addBtnClick(); - cy.selectOption(`${firstRow} > :nth-child(2)`, '#11'); - cy.get(`${firstRow} > :nth-child(4)`).type('1'); - cy.get(`${firstRow} > :nth-child(5)`).type('2'); - cy.selectOption(`${firstRow} > :nth-child(9)`, 'Warehouse One'); - cy.get('.q-notification__message').should('have.text', 'Data saved'); - cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); - cy.get(`${firstRow} > .text-right > .q-btn > .q-btn__content > .q-icon`).click(); - cy.get( - '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block', - ).click(); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + cy.get('.q-table__middle').should('be.visible').should('have.length', 1); }); - it.skip('Massive edit', function () { - cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click(); - cy.get('#subToolbar > .q-btn--standard').click(); - cy.selectOption("[data-cy='field-to-edit']", 'Min price'); - cy.dataCy('value-to-edit').find('input').type('1'); - cy.get('.countLines').should('have.text', ' 1 '); - cy.get('.q-mt-lg > .q-btn--standard').click(); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + it('should create a new fixed price, then delete it', () => { + cy.dataCy('vnTableCreateBtn').click(); + cy.dataCy('FixedPriceCreateNameSelect').type('Melee weapon combat fist 15cm'); + cy.get('.q-menu .q-item').contains('Melee weapon combat fist 15cm').click(); + cy.dataCy('FixedPriceCreateWarehouseSelect').type('Warehouse One'); + cy.get('.q-menu .q-item').contains('Warehouse One').click(); + cy.get('.q-menu').then(($menu) => { + if ($menu.is(':visible')) { + cy.dataCy('FixedPriceCreateWarehouseSelect').as('focusedElement').focus(); + cy.dataCy('FixedPriceCreateWarehouseSelect').blur(); + } + }); + cy.fillInForm(createForm); + cy.dataCy('FormModelPopup_save').click(); + cy.checkNotification('Data created'); + cy.get('[data-col-field="name"]').each(($el) => { + cy.wrap($el) + .invoke('text') + .then((text) => { + if (text.includes('Melee weapon combat fist 15cm')) { + cy.wrap($el).parent().find('.q-checkbox').click(); + cy.get('[data-cy="crudModelDefaultRemoveBtn"]').click(); + cy.dataCy('VnConfirm_confirm').click().click(); + cy.checkNotification('Data saved'); + } + }); + }); }); - it.skip('Massive remove', function () { - cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click(); - cy.get('#subToolbar > .q-btn--flat').click(); - cy.get( - '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block', - ).click(); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + + it('should edit all items', () => { + cy.get('.bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner').click(); + cy.dataCy('FixedPriceToolbarEditBtn').should('not.be.disabled'); + cy.dataCy('FixedPriceToolbarEditBtn').click(); + cy.dataCy('EditFixedPriceSelectOption').type(grouping); + cy.get('.q-menu .q-item').contains(grouping).click(); + cy.dataCy('EditFixedPriceValueOption').type('5'); + cy.get(saveEditBtn).click(); + cy.checkNotification('Data saved'); + }); + + it('should remove all items', () => { + cy.get('.bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner').click(); + cy.dataCy('crudModelDefaultRemoveBtn').should('not.be.disabled'); + cy.dataCy('crudModelDefaultRemoveBtn').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.checkNotification('Data saved'); }); }); diff --git a/test/cypress/integration/item/itemSummary.spec.js b/test/cypress/integration/item/itemSummary.spec.js index ad8267ecf..8d67c8e3c 100644 --- a/test/cypress/integration/item/itemSummary.spec.js +++ b/test/cypress/integration/item/itemSummary.spec.js @@ -19,6 +19,7 @@ describe('Item summary', () => { cy.get('.q-menu > .q-list > :nth-child(1) > .q-item__section').click(); cy.dataCy('regularizeStockInput').type('10'); cy.dataCy('Warehouse_select').type('Warehouse One{enter}'); + cy.dataCy('FormModelPopup_save').click(); cy.checkNotification('Data created'); }); }); diff --git a/test/cypress/integration/outLogin/login.spec.js b/test/cypress/integration/login/login.spec.js similarity index 75% rename from test/cypress/integration/outLogin/login.spec.js rename to test/cypress/integration/login/login.spec.js index 2bd5a8c3b..22e30dd8e 100755 --- a/test/cypress/integration/outLogin/login.spec.js +++ b/test/cypress/integration/login/login.spec.js @@ -12,7 +12,7 @@ describe('Login', () => { cy.get('button[type="submit"]').click(); cy.get('.q-notification__message').should( 'have.text', - 'Invalid username or password' + 'Invalid username or password', ); }); @@ -23,7 +23,7 @@ describe('Login', () => { cy.get('button[type="submit"]').click(); cy.get('.q-notification__message').should( 'have.text', - 'Invalid username or password' + 'Invalid username or password', ); }); @@ -33,7 +33,7 @@ describe('Login', () => { cy.get('button[type="submit"]').click(); cy.get('.q-notification__message').should( 'have.text', - 'You have successfully logged in' + 'You have successfully logged in', ); cy.url().should('contain', '/dashboard'); }); @@ -44,7 +44,7 @@ describe('Login', () => { cy.get('button[type="submit"]').click(); cy.get('.q-notification__message').should( 'have.text', - 'You have successfully logged in' + 'You have successfully logged in', ); cy.url().should('contain', '/dashboard'); cy.get('#user').click(); @@ -61,14 +61,4 @@ describe('Login', () => { cy.get('button[type="submit"]').click(); cy.url().should('contain', '/dashboard'); }); - - // ticket creation is not yet implemented, use this test once it is - // it(`should get redirected to ticket creation after login since salesPerson can do it`, () => { - // cy.visit('/#/ticket/create', { failOnStatusCode: false }); - // cy.url().should('contain', '/#/login?redirect=/ticket/create'); - // cy.get('input[aria-label="Username"]').type('salesPerson'); - // cy.get('input[aria-label="Password"]').type('nightmare'); - // cy.get('button[type="submit"]').click(); - // cy.url().should('contain', '/#/ticket/create'); - // }) }); diff --git a/test/cypress/integration/outLogin/logout.spec.js b/test/cypress/integration/login/logout.spec.js similarity index 80% rename from test/cypress/integration/outLogin/logout.spec.js rename to test/cypress/integration/login/logout.spec.js index 373f0cc93..9f022617d 100644 --- a/test/cypress/integration/outLogin/logout.spec.js +++ b/test/cypress/integration/login/logout.spec.js @@ -1,8 +1,8 @@ /// <reference types="cypress" /> -describe('Logout', () => { +describe.skip('Logout', () => { beforeEach(() => { cy.login('developer'); - cy.visit(`/#/dashboard`, false); + cy.visit(`/#/dashboard`); cy.waitForElement('.q-page', 6000); }); describe('by user', () => { @@ -24,11 +24,13 @@ describe('Logout', () => { }, }, statusMessage: 'AUTHORIZATION_REQUIRED', - }); + }).as('badRequest'); }); it('when token not exists', () => { - cy.get('.q-list').first().should('be.visible').click(); + cy.get('.q-list').should('be.visible').first().should('be.visible').click(); + cy.wait('@badRequest'); + cy.checkNotification('Authorization Required'); }); }); diff --git a/test/cypress/integration/outLogin/recoverPassword.spec.js b/test/cypress/integration/login/recoverPassword.spec.js similarity index 100% rename from test/cypress/integration/outLogin/recoverPassword.spec.js rename to test/cypress/integration/login/recoverPassword.spec.js diff --git a/test/cypress/integration/outLogin/twoFactor.spec.js b/test/cypress/integration/login/twoFactor.spec.js similarity index 100% rename from test/cypress/integration/outLogin/twoFactor.spec.js rename to test/cypress/integration/login/twoFactor.spec.js diff --git a/test/cypress/integration/Order/orderCatalog.spec.js b/test/cypress/integration/order/orderCatalog.spec.js similarity index 93% rename from test/cypress/integration/Order/orderCatalog.spec.js rename to test/cypress/integration/order/orderCatalog.spec.js index a106d0e8a..050dd396c 100644 --- a/test/cypress/integration/Order/orderCatalog.spec.js +++ b/test/cypress/integration/order/orderCatalog.spec.js @@ -34,7 +34,7 @@ describe('OrderCatalog', () => { searchByCustomTagInput('Silver'); }); - it('filters by custom value dialog', () => { + it.skip('filters by custom value dialog', () => { Cypress.on('uncaught:exception', (err) => { if (err.message.includes('canceled')) { return false; @@ -55,9 +55,9 @@ describe('OrderCatalog', () => { it('removes a secondary tag', () => { cy.get(':nth-child(1) > [data-cy="catalogFilterCategory"]').click(); cy.selectOption('[data-cy="catalogFilterType"]', 'Anthurium'); - cy.dataCy('vnFilterPanelChip').should('exist'); + cy.dataCy('vnFilterPanelChip_typeFk').should('exist'); cy.get('[data-cy="catalogFilterCustomTag"] > .q-chip__icon--remove').click(); - cy.dataCy('vnFilterPanelChip').should('not.exist'); + cy.dataCy('vnFilterPanelChip_typeFk').should('not.exist'); }); it('Removes category tag', () => { diff --git a/test/cypress/integration/order/orderList.spec.js b/test/cypress/integration/order/orderList.spec.js index 8b8852a02..ee011ea05 100644 --- a/test/cypress/integration/order/orderList.spec.js +++ b/test/cypress/integration/order/orderList.spec.js @@ -32,10 +32,12 @@ describe('OrderList', () => { 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.get(agencyCreateSelect).click(); - cy.get('.q-menu > div> .q-item:nth-child(1)').click(); + cy.selectOption(agencyCreateSelect, 1); + cy.intercept('GET', /\/api\/Orders\/\d/).as('orderSale'); cy.get('[data-cy="FormModelPopup_save"] > .q-btn__content > .block').click(); cy.wait('@orderSale'); @@ -60,8 +62,8 @@ describe('OrderList', () => { cy.get(clientCreateSelect).should('have.value', 'Bruce Wayne'); cy.get(addressCreateSelect).should('have.value', 'Bruce Wayne'); cy.dataCy('landedDate').find('input').type('06/01/2001'); - cy.get(agencyCreateSelect).click(); - cy.get('.q-menu > div> .q-item:nth-child(1)').click(); + cy.selectOption(agencyCreateSelect, 1); + cy.intercept('GET', /\/api\/Orders\/\d/).as('orderSale'); cy.get('[data-cy="FormModelPopup_save"] > .q-btn__content > .block').click(); cy.wait('@orderSale'); 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/agency/agencyWorkCenter.spec.js b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js index 5679ceba1..79dcd6f70 100644 --- a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js +++ b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js @@ -1,27 +1,34 @@ -describe.skip('AgencyWorkCenter', () => { +describe('AgencyWorkCenter', () => { + const selectors = { + workCenter: 'workCenter_select', + popupSave: 'FormModelPopup_save', + popupCancel: 'FormModelPopup_cancel', + remove: 'removeWorkCenterBtn', + }; + + const messages = { + dataCreated: 'Data created', + alreadyAssigned: 'This workCenter is already assigned to this agency', + removed: 'Work center removed successfully', + }; + beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/route/agency/11/workCenter`); }); - const createButton = '.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon'; - const workCenterCombobox = 'input[role="combobox"]'; - it('check workCenter crud', () => { - // create - cy.get(createButton).click(); - cy.get(workCenterCombobox).type('workCenterOne{enter}'); + it('Should add work center, check already assigned and remove work center', () => { + cy.addBtnClick(); + cy.selectOption('[data-cy="workCenter_select"]', 'workCenterOne'); + cy.dataCy(selectors.popupSave).click(); cy.checkNotification('Data created'); - - // expect error when duplicate - cy.get(createButton).click(); - cy.selectOption(workCenterCombobox, 'workCenterOne'); - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.checkNotification('This workCenter is already assigned to this agency'); - cy.get('[data-cy="FormModelPopup_cancel"]').click(); - - // delete - cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click(); - cy.checkNotification('WorkCenter removed successfully'); + cy.addBtnClick(); + cy.selectOption('[data-cy="workCenter_select"]', 'workCenterOne'); + cy.dataCy(selectors.popupSave).click(); + cy.checkNotification(messages.alreadyAssigned); + cy.dataCy(selectors.popupCancel).click(); + cy.dataCy(selectors.remove).click(); + cy.checkNotification(messages.removed); }); }); diff --git a/test/cypress/integration/route/cmr/cmrList.spec.js b/test/cypress/integration/route/cmr/cmrList.spec.js new file mode 100644 index 000000000..d33508e3a --- /dev/null +++ b/test/cypress/integration/route/cmr/cmrList.spec.js @@ -0,0 +1,91 @@ +describe('Cmr list', () => { + const getLinkSelector = (colField) => + `tr:first-child > [data-col-field="${colField}"] > .no-padding > .link`; + + const selectors = { + ticket: getLinkSelector('ticketFk'), + client: getLinkSelector('clientFk'), + lastRowSelectCheckBox: + '.q-virtual-scroll__content > tr:last-child > :nth-child(1) > .q-checkbox', + downloadBtn: '#subToolbar > .q-btn', + viewCmr: 'tableAction-0', + descriptorOpenSummaryBtn: '.descriptor [data-cy="openSummaryBtn"]', + summaryTitle: '.summaryHeader', + descriptorId: '.descriptor .subtitle', + descriptorTitle: '.descriptor .title', + summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]', + descriptorGoToSummaryBtn: '.descriptor [data-cy="goToSummaryBtn"]', + removeFilter: '.q-chip__icon--remove', + }; + + const data = { + ticket: '1', + client: 'Bruce Wayne', + }; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit('/#/route/cmr'); + cy.typeSearchbar('{enter}'); + cy.get(selectors.removeFilter).click(); + }); + + it('Should download selected cmr', () => { + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.get(selectors.lastRowSelectCheckBox).should('be.visible').click(); + cy.get(selectors.downloadBtn).should('be.visible').click(); + cy.wait(3000); + + const fileName = 'cmrs.zip'; + cy.readFile(`${downloadsFolder}/${fileName}`).should('exist'); + }); + + it('Should open selected cmr pdf', () => { + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen'); + }); + cy.dataCy(selectors.viewCmr).last().click(); + cy.get('@windowOpen').should('be.calledWithMatch', '\/api\/Cmrs\/3'); + }); + + describe('Ticket pop-ups', () => { + it('Should redirect to the ticket summary from the ticket descriptor pop-up', () => { + cy.get(selectors.ticket).should('be.visible').click(); + cy.containContent(selectors.descriptorId, data.ticket); + cy.get(selectors.descriptorGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/ticket/1/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + + it('Should redirect to the ticket summary from summary pop-up from the ticket descriptor pop-up', () => { + cy.get(selectors.ticket).should('be.visible').click(); + cy.containContent(selectors.descriptorId, data.ticket); + cy.get(selectors.descriptorOpenSummaryBtn).should('be.visible').click(); + cy.containContent(selectors.summaryTitle, data.client); + cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/ticket/1/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + }); + + describe('Client pop-ups', () => { + it('Should redirect to the client summary from the client descriptor pop-up', () => { + cy.get(selectors.client).should('be.visible').click(); + cy.containContent(selectors.descriptorTitle, data.client); + cy.get(selectors.descriptorGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/customer/1101/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + + it('Should redirect to the client summary from summary pop-up from the client descriptor pop-up', () => { + cy.get(selectors.client).should('be.visible').click(); + cy.containContent(selectors.descriptorTitle, data.client); + cy.get(selectors.descriptorOpenSummaryBtn).should('be.visible').click(); + cy.containContent(selectors.summaryTitle, data.client); + cy.get(selectors.summaryGoToSummaryBtn).should('be.visible').click(); + cy.url().should('include', '/customer/1101/summary'); + cy.containContent(selectors.summaryTitle, data.client); + }); + }); +}); diff --git a/test/cypress/integration/route/roadMap/roadmapList.spec.js b/test/cypress/integration/route/roadMap/roadmapList.spec.js index 6d46b2cf6..35c0c2b02 100644 --- a/test/cypress/integration/route/roadMap/roadmapList.spec.js +++ b/test/cypress/integration/route/roadMap/roadmapList.spec.js @@ -1,12 +1,74 @@ describe('RoadMap', () => { + const getSelector = (colField) => + `tr:last-child > [data-col-field="${colField}"] > .no-padding`; + + const selectors = { + roadmap: getSelector('name'), + id: getSelector('id'), + etd: getSelector('etd'), + summaryHeader: '.summaryHeader > :nth-child(2)', + summaryGoToSummaryBtn: '.summaryHeader > a > .q-icon', + summaryBtn: 'tableAction-0', + inputRoadmap: 'Roadmap_input', + checkbox: '.q-virtual-scroll__content tr:last-child .q-checkbox', + cloneFormBtn: '.q-card__actions > .q-btn--standard', + cloneBtn: '#subToolbar > :nth-child(3)', + deleteBtn: ':nth-child(4) > .q-btn__content', + confirmBtn: 'VnConfirm_confirm', + inputEtd: 'ETD_inputDate', + }; + + const data = { + roadmap: 'TEST-ROADMAP', + etd: '01/01/2025', + }; + + const dataCreated = 'Data created'; + const summaryUrl = '/summary'; + beforeEach(() => { + cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/route/roadmap`); + cy.typeSearchbar('{enter}'); }); + + it('Should list roadmaps', () => { + cy.get('.q-table') + .children() + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + it('Route list create roadmap and redirect', () => { cy.addBtnClick(); - cy.get('input[name="name"]').type('roadMapTestOne{enter}'); - cy.get('.q-notification__message').should('have.text', 'Data created'); - cy.url().should('include', '/summary'); + cy.dataCy(selectors.inputRoadmap).type(`${data.roadmap}{enter}`); + cy.checkNotification(dataCreated); + cy.url().should('include', summaryUrl); + }); + + it('open summary', () => { + cy.dataCy(selectors.summaryBtn).last().click(); + cy.get(selectors.summaryHeader).should('contain', data.roadmap); + cy.get(selectors.summaryGoToSummaryBtn).click(); + cy.get(selectors.summaryHeader).should('contain', data.roadmap); + }); + + it('Should clone selected roadmap with new ETD', () => { + cy.get(selectors.checkbox).click(); + cy.get(selectors.cloneBtn).click(); + cy.dataCy(selectors.inputEtd).click().type(`${data.etd}{enter}`); + cy.get(selectors.cloneFormBtn).click(); + cy.get(selectors.etd).should('contain', data.etd); + }); + + it('Should delete selected roadmap', () => { + cy.get(selectors.id).then(($el) => { + cy.get(selectors.checkbox).click(); + cy.get(selectors.deleteBtn).click(); + cy.dataCy(selectors.confirmBtn).click(); + cy.typeSearchbar('{enter}'); + cy.get(selectors.id).should('not.have.text', $el.text); + }); }); }); 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 e3505ad60..97735ca4b 100644 --- a/test/cypress/integration/route/routeExtendedList.spec.js +++ b/test/cypress/integration/route/routeExtendedList.spec.js @@ -8,6 +8,8 @@ describe.skip('Route extended list', () => { date: getSelector('dated'), description: getSelector('description'), served: getSelector('isOk'), + firstRowSelectCheckBox: + 'tbody > tr:first-child > :nth-child(1) .q-checkbox__inner', lastRowSelectCheckBox: 'tbody > tr:last-child > :nth-child(1) .q-checkbox__inner', removeBtn: '[title="Remove"]', resetBtn: '[title="Reset"]', @@ -19,7 +21,7 @@ describe.skip('Route extended list', () => { markServedBtn: '#st-actions > .q-btn-group > :nth-child(3)', searchbar: 'searchbar', firstTicketsRowSelectCheckBox: - '.q-card > :nth-child(2) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(1) > .q-table--col-auto-width > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg > .q-checkbox__svg', + '.q-card .q-table > tbody > :nth-child(1) .q-checkbox', }; const checkboxState = { @@ -32,18 +34,18 @@ describe.skip('Route extended list', () => { const originalFields = [ { selector: selectors.worker, type: 'select', value: 'logistic' }, - { selector: selectors.agency, type: 'select', value: 'Super-Man delivery' }, + { selector: selectors.agency, type: 'select', value: 'inhouse pickup' }, { selector: selectors.vehicle, type: 'select', value: '3333-IMK' }, - { selector: selectors.date, type: 'date', value: '01/02/2024' }, + { selector: selectors.date, type: 'date', value: '01/01/2001' }, { selector: selectors.description, type: 'input', value: 'Test route' }, { selector: selectors.served, type: 'checkbox', value: checkboxState.uncheck }, ]; const updateFields = [ { selector: selectors.worker, type: 'select', value: 'salesperson' }, - { selector: selectors.agency, type: 'select', value: 'inhouse pickup' }, + { selector: selectors.agency, type: 'select', value: 'Super-Man delivery' }, { selector: selectors.vehicle, type: 'select', value: '1111-IMK' }, - { selector: selectors.date, type: 'date', value: '01/01/2001' }, + { selector: selectors.date, type: 'date', value: '11/01/2001' }, { selector: selectors.description, type: 'input', value: 'Description updated' }, { selector: selectors.served, type: 'checkbox', value: checkboxState.check }, ]; @@ -51,17 +53,20 @@ describe.skip('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}{enter}`); + 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}{enter}`); + cy.get(selector) + .should('be.visible') + .click() + .type(`{selectall}{backspace}${value}`); break; case 'checkbox': cy.get(selector).should('be.visible').click().click(); @@ -76,15 +81,6 @@ describe.skip('Route extended list', () => { cy.typeSearchbar('{enter}'); }); - after(() => { - cy.visit(url); - cy.typeSearchbar('{enter}'); - cy.get(selectors.lastRowSelectCheckBox).click(); - - cy.get(selectors.removeBtn).click(); - cy.dataCy(selectors.confirmBtn).click(); - }); - it('Should list routes', () => { cy.get('.q-table') .children() @@ -97,9 +93,9 @@ describe.skip('Route extended list', () => { const data = { Worker: { val: 'logistic', type: 'select' }, - Agency: { val: 'Super-Man delivery', type: 'select' }, + Agency: { val: 'inhouse pickup', type: 'select' }, Vehicle: { val: '3333-IMK', type: 'select' }, - Date: { val: '02-01-2024', type: 'date' }, + Date: { val: '01-01-2001', type: 'date' }, From: { val: '01-01-2024', type: 'date' }, To: { val: '10-01-2024', type: 'date' }, 'Km start': { val: 1000 }, @@ -110,8 +106,8 @@ describe.skip('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', () => { @@ -126,26 +122,31 @@ describe.skip('Route extended list', () => { }); }); - it('Should clone selected route', () => { - cy.get(selectors.lastRowSelectCheckBox).click(); + it('Should clone selected route and add ticket', () => { + cy.get(selectors.firstRowSelectCheckBox).click(); cy.get(selectors.cloneBtn).click(); - cy.dataCy('route.Starting date_inputDate').type('10-05-2001{enter}'); + cy.dataCy('Starting date_inputDate').type('01-01-2001'); cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); - cy.validateContent(selectors.date, '05/10/2001'); + cy.validateContent(selectors.date, '01/01/2001'); + + cy.dataCy('tableAction-0').last().click(); + cy.get(selectors.firstTicketsRowSelectCheckBox).click(); + cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); + cy.checkNotification(dataSaved); + + cy.get(selectors.lastRowSelectCheckBox).click(); + cy.get(selectors.removeBtn).click(); + cy.dataCy(selectors.confirmBtn).click(); }); it('Should download selected route', () => { 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'); - - cy.task('deleteFile', `${downloadsFolder}/${fileName}`).then((deleted) => { - expect(deleted).to.be.true; - }); }); it('Should mark as served the selected route', () => { @@ -158,7 +159,6 @@ describe.skip('Route extended list', () => { it('Should delete the selected route', () => { cy.get(selectors.lastRowSelectCheckBox).click(); - cy.get(selectors.removeBtn).click(); cy.dataCy(selectors.confirmBtn).click(); @@ -175,18 +175,15 @@ describe.skip('Route extended list', () => { cy.typeSearchbar('{enter}'); - updateFields.forEach(({ selector, value }) => { - cy.validateContent(selector, value); + updateFields.forEach(({ selector, value, type }) => { + if (type === 'date') { + const [month, day, year] = value.split('/'); + value = `${day}/${month}/${year}`; + } + cy.get(selector).should('contain', value); }); }); - it('Should add ticket to route', () => { - cy.dataCy('tableAction-0').last().click(); - cy.get(selectors.firstTicketsRowSelectCheckBox).click(); - cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); - cy.checkNotification(dataSaved); - }); - it('Should open summary pop-up when click summuary icon', () => { cy.dataCy('tableAction-1').last().click(); cy.get('.summaryHeader > :nth-child(2').should('contain', updateFields[4].value); diff --git a/test/cypress/integration/route/routeList.spec.js b/test/cypress/integration/route/routeList.spec.js index 04278cfc5..f08c267a4 100644 --- a/test/cypress/integration/route/routeList.spec.js +++ b/test/cypress/integration/route/routeList.spec.js @@ -1,37 +1,205 @@ describe('Route', () => { + const getSelector = (colField) => + `tr:last-child > [data-col-field="${colField}"] > .no-padding > .link`; + + const selectors = { + lastRow: 'tr:last-child > [data-col-field="workerFk"]', + workerLink: getSelector('workerFk'), + agencyLink: getSelector('agencyModeFk'), + vehicleLink: getSelector('vehicleFk'), + assignedTicketsBtn: 'tableAction-0', + rowSummaryBtn: 'tableAction-1', + summaryTitle: '.summaryHeader', + descriptorTitle: '.descriptor .title', + descriptorOpenSummaryBtn: '.descriptor [data-cy="openSummaryBtn"]', + descriptorGoToSummaryBtn: '.descriptor [data-cy="goToSummaryBtn"]', + SummaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]', + }; + + const data = { + Worker: { val: 'logistic', type: 'select' }, + Agency: { val: 'Walking', type: 'select' }, + Vehicle: { val: '3333-BAT', type: 'select' }, + Description: { val: 'routeTest' }, + }; + + const summaryUrl = '/summary'; + beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit(`/#/route/extended-list`); + cy.visit(`/#/route/list`); + cy.typeSearchbar('{enter}'); }); - it('Route list create route', () => { + it('Should list routes', () => { + cy.get('.q-table') + .children() + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + + it('Should create new route', () => { cy.addBtnClick(); - cy.get('.q-card input[name="description"]').type('routeTestOne{enter}'); - cy.get('.q-notification__message').should('have.text', 'Data created'); - cy.url().should('include', '/summary'); + + cy.fillInForm(data); + + cy.dataCy('FormModelPopup_save').should('be.visible').click(); + + cy.checkNotification('Data created'); + cy.url().should('include', summaryUrl); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); + }); }); - it('Route list search and edit', () => { - cy.get('#searchbar input').type('{enter}'); - cy.get('[data-col-field="description"][data-row-index="0"]') - .click() - .type('routeTestOne{enter}'); - cy.get('.q-table tr') - .its('length') - .then((rowCount) => { - expect(rowCount).to.be.greaterThan(0); + it('Should open route summary by clicking a route', () => { + cy.get(selectors.lastRow).should('be.visible').click(); + cy.url().should('include', summaryUrl); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); }); - cy.get('[data-col-field="workerFk"][data-row-index="0"]') - .click() - .type('{downArrow}{enter}'); - cy.get('[data-col-field="agencyModeFk"][data-row-index="0"]') - .click() - .type('{downArrow}{enter}'); - cy.get('[data-col-field="vehicleFk"][data-row-index="0"]') - .click() - .type('{downArrow}{enter}'); - cy.get('button[title="Save"]').click(); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('Should redirect to the summary from the route pop-up summary', () => { + cy.dataCy(selectors.rowSummaryBtn).last().should('be.visible').click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Description.val); + }); + }); + + it('Should redirect to the route assigned tickets from the row assignedTicketsBtn', () => { + cy.dataCy(selectors.assignedTicketsBtn).first().should('be.visible').click(); + cy.url().should('include', '1/tickets'); + cy.get('.q-table') + .children() + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + + describe('Worker pop-ups', () => { + it('Should redirect to summary from the worker pop-up descriptor', () => { + cy.get(selectors.workerLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + cy.get(selectors.descriptorGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + }); + + it('Should redirect to the summary from the worker pop-up summary', () => { + cy.get(selectors.workerLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + cy.get(selectors.descriptorOpenSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Worker.val); + }); + }); + }); + + describe('Agency pop-ups', () => { + it('Should redirect to summary from the agency pop-up descriptor', () => { + cy.get(selectors.agencyLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + cy.get(selectors.descriptorGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + }); + + it('Should redirect to the summary from the agency pop-up summary', () => { + cy.get(selectors.agencyLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + cy.get(selectors.descriptorOpenSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Agency.val); + }); + }); + }); + + describe('Vehicle pop-ups', () => { + it('Should redirect to summary from the vehicle pop-up descriptor', () => { + cy.get(selectors.vehicleLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + cy.get(selectors.descriptorGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + }); + + it('Should redirect to the summary from the vehicle pop-up summary', () => { + cy.get(selectors.vehicleLink).click(); + cy.get(selectors.descriptorTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + cy.get(selectors.descriptorOpenSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + cy.get(selectors.SummaryGoToSummaryBtn).click(); + cy.get(selectors.summaryTitle) + .invoke('text') + .then((text) => { + expect(text).to.include(data.Vehicle.val); + }); + }); }); }); diff --git a/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js b/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js index 64b9ca0a0..3e9c816c4 100644 --- a/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js +++ b/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js @@ -2,11 +2,11 @@ describe('Vehicle', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('deliveryAssistant'); - cy.visit(`/#/route/vehicle/7`); + cy.visit(`/#/route/vehicle/7/summary`); }); it('should delete a vehicle', () => { - cy.openActionsDescriptor(); + cy.dataCy('descriptor-more-opts').click(); cy.get('[data-cy="delete"]').click(); cy.checkNotification('Vehicle removed'); }); diff --git a/test/cypress/integration/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/shelving/parking/parkingList.spec.js b/test/cypress/integration/shelving/parking/parkingList.spec.js index ca1877621..7372da164 100644 --- a/test/cypress/integration/shelving/parking/parkingList.spec.js +++ b/test/cypress/integration/shelving/parking/parkingList.spec.js @@ -2,7 +2,7 @@ describe('ParkingList', () => { const searchbar = '#searchbar input'; const firstCard = ':nth-child(1) > .q-card > .no-margin > .q-py-none'; - const summaryHeader = '.summaryBody .header'; + const summaryHeader = '.header-link'; beforeEach(() => { cy.viewport(1920, 1080); diff --git a/test/cypress/integration/shelving/shelvingList.spec.js b/test/cypress/integration/shelving/shelvingList.spec.js index 745dd1b78..20b72e419 100644 --- a/test/cypress/integration/shelving/shelvingList.spec.js +++ b/test/cypress/integration/shelving/shelvingList.spec.js @@ -16,8 +16,8 @@ describe('ShelvingList', () => { it('should redirect from preview to basic-data', () => { cy.typeSearchbar('{enter}'); cy.dataCy('cardBtn').eq(0).click(); - cy.get('.q-card > .header').click(); - cy.url().should('include', '/shelving/1/basic-data'); + cy.get('.summaryHeader > .header > .q-icon').click(); + cy.url().should('include', '/shelving/1/summary'); }); it('should filter and redirect if only one result', () => { diff --git a/test/cypress/integration/Supplier/SupplierBalance.spec.js b/test/cypress/integration/supplier/SupplierBalance.spec.js similarity index 100% rename from test/cypress/integration/Supplier/SupplierBalance.spec.js rename to test/cypress/integration/supplier/SupplierBalance.spec.js diff --git a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js index 19f4dc3b2..b4997fa69 100644 --- a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js +++ b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js @@ -138,7 +138,7 @@ describe.skip('Ticket Lack detail', () => { cy.get('[data-cy="itemProposal"]').click(); cy.wait('@getItemGetSimilar'); }); - describe('Replace item if', () => { + describe.skip('Replace item if', () => { it('Quantity is less than available', () => { cy.get(':nth-child(1) > .text-right > .q-btn').click(); }); diff --git a/test/cypress/integration/ticket/ticketBasicData.spec.js b/test/cypress/integration/ticket/ticketBasicData.spec.js new file mode 100644 index 000000000..443e9569b --- /dev/null +++ b/test/cypress/integration/ticket/ticketBasicData.spec.js @@ -0,0 +1,46 @@ +/// <reference types="cypress" /> +describe('TicketBasicData', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/basic-data'); + }); + + it('Should redirect to customer basic data', () => { + cy.get('.q-page').should('be.visible'); + cy.get(':nth-child(2) > div > .text-primary').click(); + cy.dataCy('Address_select').click(); + cy.get('.q-btn-group ').find('.q-btn__content > .q-icon').click(); + cy.get( + '[data-cy="CustomerBasicData-menu-item"] > .q-item__section--main', + ).click(); + cy.url().should('include', '/customer/1104/basic-data'); + }); + it.only('stepper', () => { + cy.get('.q-stepper__tab--active').should('have.class', 'q-stepper__tab--active'); + + cy.get('.q-stepper__nav > .q-btn--standard').click(); + cy.get('.q-stepper__tab--done').should('have.class', 'q-stepper__tab--done'); + cy.get('.q-stepper__tab--active').should('have.class', 'q-stepper__tab--active'); + cy.get('tr:nth-child(1)>:nth-child(1)>span').should('have.class', 'link').click(); + cy.dataCy('ItemDescriptor').should('exist'); + + cy.get('.q-drawer__content > :nth-child(1)').each(() => { + cy.get('span').should('contain.text', 'Price: €'); + cy.get('span').should('contain.text', 'New price: €'); + cy.get('span').should('contain.text', 'Difference: €'); + }); + cy.get( + ':nth-child(3) > .q-radio > .q-radio__inner > .q-radio__bg > .q-radio__check', + ).should('have.class', 'q-radio__check'); + cy.get( + '.q-stepper__step-inner > .q-drawer-container > .q-drawer > .q-drawer__content', + ).click(); + cy.get(':nth-child(2) > :nth-child(1) > .text-weight-bold').click(); + cy.get(':nth-child(3) > .q-radio > .q-radio__inner').should( + 'have.class', + 'q-radio__inner--truthy', + ); + cy.get('.q-drawer__content > :nth-child(2)').click(); + }); +}); diff --git a/test/cypress/integration/ticket/ticketComponents.spec.js b/test/cypress/integration/ticket/ticketComponents.spec.js new file mode 100644 index 000000000..23dbf8bcd --- /dev/null +++ b/test/cypress/integration/ticket/ticketComponents.spec.js @@ -0,0 +1,30 @@ +/// <reference types="cypress" /> + +describe('TicketComponents', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/1/components'); + }); + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.validateScrollContent([ + { row: 2, col: 2, text: 'Base to commission: €799.20' }, + { row: 2, col: 3, text: 'Total without VAT: €807.20' }, + { row: 3, col: 2, text: 'valor de compra: €425.000' }, + { row: 3, col: 4, text: 'maná auto: €7.998' }, + { row: 4, col: 2, text: 'Price: €5.00' }, + { row: 4, col: 3, text: 'Bonus: €1.00' }, + { row: 4, col: 5, text: 'Packages: 6' }, + { row: 4, col: 4, text: 'Zone: Zone pickup A ' }, + { row: 5, col: 2, text: 'Total price: €16.00' }, + ]); + cy.get(':nth-child(4) > .link').click(); + + cy.dataCy('ZoneDescriptor').should('exist'); + cy.getRowCol('total').should('have.text', '€250.000€247.000€4.970'); + cy.getRowCol('import').should('have.text', '€50.000€49.400€0.994'); + cy.getRowCol('components').should('have.text', 'valor de compramargenmaná auto'); + cy.getRowCol('serie').should('have.text', 'costeempresacartera_comercial'); + }); +}); 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/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 25ee05033..e18025319 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -1,7 +1,5 @@ /// <reference types="cypress" /> describe('TicketList', () => { - const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; - beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); @@ -11,7 +9,7 @@ describe('TicketList', () => { const searchResults = (search) => { if (search) cy.typeSearchbar().type(search); cy.dataCy('vn-searchbar').find('input').type('{enter}'); - cy.get(firstRow).should('exist'); + cy.getRow().should('exist'); }; it('should search results', () => { @@ -24,13 +22,13 @@ describe('TicketList', () => { cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); }); - cy.get(firstRow).should('be.visible').find('.q-btn:first').click(); + cy.getRow().should('be.visible').find('.q-btn:first').click(); cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/); }); it('should open ticket summary', () => { searchResults(); - cy.get(firstRow).find('.q-btn:last').click(); + cy.getRow().find('.q-btn:last').click(); cy.get('.summaryHeader').should('exist'); cy.get('.summaryBody').should('exist'); }); @@ -38,12 +36,14 @@ describe('TicketList', () => { it('filter client and create ticket', () => { cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); searchResults(); + cy.wait('@ticketSearchbar'); - cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); cy.dataCy('Customer ID_input').clear('1'); cy.dataCy('Customer ID_input').type('1101{enter}'); - cy.get('[data-cy="vnTableCreateBtn"] > .q-btn__content > .q-icon').click(); + cy.intercept('GET', /\/api\/Clients\?filter/).as('clientFilter'); + cy.vnTableCreateBtn(); + cy.wait('@clientFilter'); cy.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); cy.dataCy('Address_select').click(); @@ -51,8 +51,7 @@ describe('TicketList', () => { cy.dataCy('Address_select').should('have.value', 'Bruce Wayne'); }); it('Client list create new ticket', () => { - cy.dataCy('vnTableCreateBtn').should('exist'); - cy.dataCy('vnTableCreateBtn').click(); + cy.vnTableCreateBtn(); const data = { Customer: { val: 1, type: 'select' }, Warehouse: { val: 'Warehouse One', type: 'select' }, diff --git a/test/cypress/integration/ticket/ticketNotes.spec.js b/test/cypress/integration/ticket/ticketNotes.spec.js index 5b44f9e1f..df1ff9137 100644 --- a/test/cypress/integration/ticket/ticketNotes.spec.js +++ b/test/cypress/integration/ticket/ticketNotes.spec.js @@ -19,7 +19,7 @@ describe('TicketNotes', () => { cy.checkNotification('Data saved'); cy.dataCy('ticketNotesRemoveNoteBtn').should('exist'); cy.dataCy('ticketNotesRemoveNoteBtn').click(); - cy.dataCy('VnConfirm_confirm').click(); + cy.confirmVnConfirm(); cy.checkNotification('Data saved'); }); }); diff --git a/test/cypress/integration/ticket/ticketPackage.spec.js b/test/cypress/integration/ticket/ticketPackage.spec.js new file mode 100644 index 000000000..992efd53b --- /dev/null +++ b/test/cypress/integration/ticket/ticketPackage.spec.js @@ -0,0 +1,21 @@ +/// <reference types="cypress" /> +describe('TicketPackages', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/package'); + }); + + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.get('.vn-row > .q-btn > .q-btn__content > .q-icon').click(); + cy.dataCy('Package_select').click(); + cy.get('.q-menu :nth-child(1) >.q-item__section').click(); + cy.dataCy('Quantity_input').clear().type('5'); + cy.saveCrudModel(); + cy.checkNotification('Data saved'); + cy.get('.q-mb-md > .text-primary').click(); + cy.confirmVnConfirm(); + cy.checkNotification('Data saved'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketPictures.spec.js b/test/cypress/integration/ticket/ticketPictures.spec.js new file mode 100644 index 000000000..1165b54bf --- /dev/null +++ b/test/cypress/integration/ticket/ticketPictures.spec.js @@ -0,0 +1,18 @@ +/// <reference types="cypress" /> +describe('TicketPictures', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/picture'); + }); + it('Should load layout', () => { + cy.get(':nth-child(1) > .q-card > .content').should('be.visible'); + cy.get('.content > .link').should('be.visible').click(); + cy.dataCy('ItemDescriptor').should('exist'); + cy.dataCy('vnLvColor:'); + cy.dataCy('vnLvColor:'); + cy.dataCy('vnLvTallos:'); + cy.get('.q-mt-md').should('be.visible'); + cy.get(':nth-child(1) > .q-card > .img-wrapper').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketRequest.spec.js b/test/cypress/integration/ticket/ticketRequest.spec.js index b9dc509ef..3b237826e 100644 --- a/test/cypress/integration/ticket/ticketRequest.spec.js +++ b/test/cypress/integration/ticket/ticketRequest.spec.js @@ -7,8 +7,7 @@ describe('TicketRequest', () => { }); it('Creates a new request', () => { - cy.dataCy('vnTableCreateBtn').should('exist'); - cy.dataCy('vnTableCreateBtn').click(); + cy.vnTableCreateBtn(); const data = { Description: { val: 'Purchase description' }, Atender: { val: 'buyerNick', type: 'select' }, diff --git a/test/cypress/integration/ticket/ticketSale.spec.js b/test/cypress/integration/ticket/ticketSale.spec.js index 81ea761c4..f433f0d11 100644 --- a/test/cypress/integration/ticket/ticketSale.spec.js +++ b/test/cypress/integration/ticket/ticketSale.spec.js @@ -1,20 +1,112 @@ /// <reference types="cypress" /> +const firstRow = 'tbody > :nth-child(1)'; describe('TicketSale', () => { - describe.skip('Free ticket #31', () => { + describe('#23', () => { + beforeEach(() => { + cy.login('salesBoss'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/23/sale'); + }); + + it('update price', () => { + const price = Number((Math.random() * 99 + 1).toFixed(2)); + cy.waitForElement(firstRow); + cy.get('[data-col-field="price"]').find('.q-btn').click(); + cy.waitForElement('[data-cy="ticketEditManaProxy"]'); + cy.dataCy('ticketEditManaProxy').should('exist'); + cy.waitForElement('[data-cy="Price_input"]'); + cy.dataCy('Price_input').clear().type(price); + cy.intercept('POST', /\/api\/Sales\/\d+\/updatePrice/).as('updatePrice'); + + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + cy.wait('@updatePrice').its('response.statusCode').should('eq', 200); + + cy.get('[data-col-field="price"]') + .find('.q-btn > .q-btn__content') + .should('contain.text', `€${price}`); + }); + it('update discount', () => { + const discount = Math.floor(Math.random() * 100) + 1; + selectFirstRow(); + cy.get('[data-col-field="discount"]').find('.q-btn').click(); + cy.waitForElement('[data-cy="ticketEditManaProxy"]'); + cy.dataCy('ticketEditManaProxy').should('exist'); + cy.waitForElement('[data-cy="Disc_input"]'); + cy.dataCy('Disc_input').clear().type(discount); + cy.intercept('POST', /\/api\/Tickets\/\d+\/updateDiscount/).as( + 'updateDiscount', + ); + + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204); + + cy.get('[data-col-field="discount"]') + .find('.q-btn > .q-btn__content') + .should('have.text', `${discount}.00%`); + }); + + it('change concept', () => { + const concept = Math.floor(Math.random() * 100) + 1; + cy.waitForElement(firstRow); + cy.get('[data-col-field="item"]').click(); + cy.intercept('POST', '**/api').as('postRequest'); + + cy.get('.q-menu') + .find('[data-cy="undefined_input"]') + .type(concept) + .type('{enter}'); + handleVnConfirm(); + + cy.get('[data-col-field="item"]').should('contain.text', `${concept}`); + }); + it('change quantity ', () => { + const quantity = Math.floor(Math.random() * 100) + 1; + cy.waitForElement(firstRow); + cy.dataCy('ticketSaleQuantityInput').find('input').clear(); + cy.intercept('POST', '**/api').as('postRequest'); + + cy.dataCy('ticketSaleQuantityInput') + .find('input') + .type(quantity) + .trigger('tab'); + cy.get('.q-page > :nth-child(6)').click(); + + handleVnConfirm(); + + cy.get('[data-cy="ticketSaleQuantityInput"]') + .find('input') + .should('have.value', `${quantity}`); + }); + }); + describe('#24 add claim', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/24/sale'); + }); + + it('adds claim', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('createClaimItem').click(); + cy.confirmVnConfirm(); + cy.url().should('contain', 'claim/'); + // Delete created claim to avoid cluttering the database + cy.dataCy('descriptor-more-opts').click(); + cy.dataCy('deleteClaim').click(); + cy.confirmVnConfirm(); + }); + }); + describe('#31 free ticket', () => { beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); cy.visit('/#/ticket/31/sale'); }); - const firstRow = 'tbody > :nth-child(1)'; - - const selectFirstRow = () => { - cy.waitForElement(firstRow); - cy.get(firstRow).find('.q-checkbox__inner').click(); - }; - it('it should add item to basket', () => { cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); @@ -44,9 +136,12 @@ describe('TicketSale', () => { cy.dataCy('recalculatePriceItem').click(); cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200); cy.checkNotification('Data saved'); + cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); }); - it('should update discount when "Update discount" is clicked', () => { + it.only('should update discount when "Update discount" is clicked', () => { + const discount = Number((Math.random() * 99 + 1).toFixed(2)); + selectFirstRow(); cy.dataCy('ticketSaleMoreActionsDropdown').click(); cy.waitForElement('[data-cy="updateDiscountItem"]'); @@ -54,39 +149,23 @@ describe('TicketSale', () => { cy.dataCy('updateDiscountItem').click(); cy.waitForElement('[data-cy="ticketSaleDiscountInput"]'); cy.dataCy('ticketSaleDiscountInput').find('input').focus(); - cy.dataCy('ticketSaleDiscountInput').find('input').type('10'); + cy.intercept('POST', /\/api\/Tickets\/\d+\/updateDiscount/).as( + 'updateDiscount', + ); + cy.dataCy('ticketSaleDiscountInput').find('input').type(discount); + cy.dataCy('saveManaBtn').click(); - cy.waitForElement('.q-notification__message'); + cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204); cy.checkNotification('Data saved'); + cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); }); it('adds claim', () => { selectFirstRow(); cy.dataCy('ticketSaleMoreActionsDropdown').click(); cy.dataCy('createClaimItem').click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.url().should('contain', 'claim/'); - // Delete created claim to avoid cluttering the database - cy.dataCy('descriptor-more-opts').click(); - cy.dataCy('deleteClaim').click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.checkNotification('Data deleted'); - }); - - it('marks row as reserved', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="markAsReservedItem"]'); - cy.dataCy('markAsReservedItem').click(); - cy.dataCy('ticketSaleReservedIcon').should('exist'); - }); - - it('unmarks row as reserved', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="unmarkAsReservedItem"]'); - cy.dataCy('unmarkAsReservedItem').click(); - cy.dataCy('ticketSaleReservedIcon').should('not.exist'); + cy.confirmVnConfirm(); + cy.checkNotification('Future ticket date not allowed'); }); it('refunds row with warehouse', () => { @@ -105,8 +184,18 @@ describe('TicketSale', () => { cy.checkNotification('The following refund ticket have been created'); }); - it('transfer sale to a new ticket', () => { + it('should redirect to ticket logs', () => { + cy.get(firstRow).find('.q-btn:last').click(); + cy.url().should('match', /\/ticket\/31\/log/); + }); + }); + describe('#32 transfer', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); cy.visit('/#/ticket/32/sale'); + }); + it('transfer sale to a new ticket', () => { cy.get('.q-item > .q-item__label').should('have.text', ' #32'); selectFirstRow(); cy.dataCy('ticketSaleTransferBtn').click(); @@ -114,94 +203,14 @@ describe('TicketSale', () => { cy.dataCy('ticketTransferNewTicketBtn').click(); cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); }); - - it('should redirect to ticket logs', () => { - cy.get(firstRow).find('.q-btn:last').click(); - cy.url().should('match', /\/ticket\/31\/log/); - }); - }); - describe.skip('Ticket prepared #23', () => { - beforeEach(() => { - cy.login('developer'); - cy.viewport(1920, 1080); - cy.visit('/#/ticket/23/sale'); - }); - - const firstRow = 'tbody > :nth-child(1)'; - - const selectFirstRow = () => { - cy.waitForElement(firstRow); - cy.get(firstRow).find('.q-checkbox__inner').click(); - }; - - it('update price', () => { - const price = Number((Math.random() * 99 + 1).toFixed(2)); - cy.waitForElement(firstRow); - cy.get('[data-col-field="price"]').find('.q-btn').click(); - cy.waitForElement('[data-cy="ticketEditManaProxy"]'); - cy.dataCy('ticketEditManaProxy').should('exist'); - cy.waitForElement('[data-cy="Price_input"]'); - cy.dataCy('Price_input').clear(); - cy.dataCy('Price_input').type(price); - cy.dataCy('saveManaBtn').click(); - handleVnConfirm(); - - cy.get('[data-col-field="price"]') - .find('.q-btn > .q-btn__content') - .should('have.text', `€${price}`); - }); - it('update discount', () => { - const discount = Math.floor(Math.random() * 100) + 1; - selectFirstRow(); - cy.get('[data-col-field="discount"]').find('.q-btn').click(); - cy.waitForElement('[data-cy="ticketEditManaProxy"]'); - cy.dataCy('ticketEditManaProxy').should('exist'); - cy.waitForElement('[data-cy="Disc_input"]'); - cy.dataCy('Disc_input').clear(); - cy.dataCy('Disc_input').type(discount); - cy.dataCy('saveManaBtn').click(); - handleVnConfirm(); - - cy.get('[data-col-field="discount"]') - .find('.q-btn > .q-btn__content') - .should('have.text', `${discount}.00%`); - }); - - it('change concept', () => { - const concept = Math.floor(Math.random() * 100) + 1; - cy.waitForElement(firstRow); - cy.get('[data-col-field="item"]').click(); - cy.get('.q-menu') - .find('[data-cy="undefined_input"]') - .type(concept) - .type('{enter}'); - handleVnConfirm(); - - cy.get('[data-col-field="item"]').should('contain.text', `${concept}`); - }); - it('change quantity ', () => { - const quantity = Math.floor(Math.random() * 100) + 1; - cy.waitForElement(firstRow); - cy.dataCy('ticketSaleQuantityInput').find('input').clear(); - cy.dataCy('ticketSaleQuantityInput') - .find('input') - .type(quantity) - .trigger('tab'); - cy.get('.q-page > :nth-child(6)').click(); - - handleVnConfirm(); - - cy.get('[data-cy="ticketSaleQuantityInput"]') - .find('input') - .should('have.value', `${quantity}`); - }); }); }); - +function selectFirstRow() { + cy.waitForElement(firstRow); + cy.get(firstRow).find('.q-checkbox__inner').click(); +} function handleVnConfirm() { - cy.get('[data-cy="VnConfirm_confirm"]').click(); - cy.waitForElement('.q-notification__message'); + cy.confirmVnConfirm(); - cy.get('.q-notification__message').should('be.visible'); cy.checkNotification('Data saved'); } diff --git a/test/cypress/integration/ticket/ticketSaleTracking.spec.js b/test/cypress/integration/ticket/ticketSaleTracking.spec.js new file mode 100644 index 000000000..9ee9f8824 --- /dev/null +++ b/test/cypress/integration/ticket/ticketSaleTracking.spec.js @@ -0,0 +1,53 @@ +/// <reference types="cypress" /> +function uncheckedSVG(className, state) { + cy.get(`${className} .q-checkbox__svg`).should( + state === 'checked' ? 'not.have.attr' : 'have.attr', + 'fill', + 'none', + ); +} +function checkedSVG(className, state) { + cy.get(`${className} .q-checkbox__svg> .q-checkbox__truthy`).should( + state === 'checked' ? 'not.have.attr' : 'have.attr', + 'fill', + 'none', + ); +} + +function clickIconAndCloseDialog(n) { + cy.get( + `:nth-child(1) > :nth-child(6) > :nth-child(${n}) > .q-btn__content > .q-icon`, + ).click(); +} + +describe('TicketSaleTracking', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/1/sale-tracking'); + }); + + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + // Check checkbox states + uncheckedSVG('.pink', 'checked'); + uncheckedSVG('.cyan', 'checked'); + uncheckedSVG('.warning', 'checked'); + uncheckedSVG('.info', 'checked'); + checkedSVG('.yellow', 'unchecked'); + + cy.get('.q-page').click(); + cy.get( + ':nth-child(1) > :nth-child(6) > :nth-child(2) > .q-btn__content > .q-icon', + ).click(); + cy.get('body').type('{esc}'); + cy.get( + ':nth-child(1) > :nth-child(6) > :nth-child(1) > .q-btn__content > .q-icon', + ).click(); + cy.get( + '.q-dialog__inner > .q-table__container :nth-child(1) > :nth-child(2) .link.q-btn', + ).click(); + + cy.dataCy('WorkerDescriptor').should('exist'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketService.spec.js b/test/cypress/integration/ticket/ticketService.spec.js new file mode 100644 index 000000000..5bf8e2aab --- /dev/null +++ b/test/cypress/integration/ticket/ticketService.spec.js @@ -0,0 +1,23 @@ +/// <reference types="cypress" /> +describe('TicketService', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/service'); + }); + + it('Add and remove service', () => { + cy.get('.q-page').should('be.visible'); + cy.addBtnClick(); + cy.dataCy('Description_icon').click(); + cy.dataCy('Description_input').clear().type('test'); + cy.saveFormModel(); + cy.selectOption('[data-cy="Description_select"]', 'test'); + + cy.dataCy('Quantity_input').clear().type('1'); + cy.dataCy('Price_input').clear().type('2'); + cy.saveCrudModel(); + cy.checkNotification('Data saved'); + cy.get(':nth-child(5) > .q-icon').click(); + }); +}); diff --git a/test/cypress/integration/ticket/ticketSms.spec.js b/test/cypress/integration/ticket/ticketSms.spec.js new file mode 100644 index 000000000..feafb2157 --- /dev/null +++ b/test/cypress/integration/ticket/ticketSms.spec.js @@ -0,0 +1,22 @@ +/// <reference types="cypress" /> +describe('TicketSms', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/32/sms'); + }); + + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.get('.q-infinite-scroll > :nth-child(1)').should( + 'contain.text', + '0004 444444444Lorem ipsum dolor sit amet, consectetur adipiscing elit.2001-01-01 00:00:00OK', + ); + cy.get( + ':nth-child(1) > .q-item > .q-item__section--top > .column > .q-avatar', + ).should('be.visible'); + cy.get( + ':nth-child(1) > .q-item > .q-item__section--side.justify-center > .center > .q-chip > .q-chip__content', + ).should('have.class', 'q-chip__content'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketTracking.spec.js b/test/cypress/integration/ticket/ticketTracking.spec.js new file mode 100644 index 000000000..f351ee0a1 --- /dev/null +++ b/test/cypress/integration/ticket/ticketTracking.spec.js @@ -0,0 +1,25 @@ +/// <reference types="cypress" /> +describe('Ticket tracking', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/tracking'); + }); + + it('Add new tracking', () => { + cy.get('.q-page').should('be.visible'); + + cy.getRowCol('worker').find('span').should('have.class', 'link').click(); + cy.dataCy('WorkerDescriptor').should('exist'); + cy.vnTableCreateBtn(); + cy.selectOption('.q-field--float [data-cy="State_select"]', 'OK').click(); + cy.saveFormModel(); + cy.get( + ':last-child > [data-col-field="state"] > [data-cy="vnTableCell_state"]', + ).should('have.text', 'OK'); + cy.get(':last-child > [data-col-field="worker"]').should( + 'have.text', + 'developer ', + ); + }); +}); diff --git a/test/cypress/integration/ticket/ticketVolume.spec.js b/test/cypress/integration/ticket/ticketVolume.spec.js new file mode 100644 index 000000000..59ff6dcb2 --- /dev/null +++ b/test/cypress/integration/ticket/ticketVolume.spec.js @@ -0,0 +1,27 @@ +/// <reference types="cypress" /> +function checkRightLabel(index, value, tag = 'Volume: ') { + cy.get(`.q-scrollarea__content > :nth-child(${index}) > :nth-child(2) > span`) + .should('be.visible') + .should('have.text', `${tag}${value}`); +} +describe('TicketVolume', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/1/volume'); + }); + + it('Check right panel info', () => { + cy.get('.q-page').should('be.visible'); + checkRightLabel(2, '0.028'); + checkRightLabel(3, '0.014'); + checkRightLabel(4, '1.526'); + }); + it('Descriptors', () => { + cy.get(':nth-child(1) > [data-col-field="itemFk"]') + .find('span') + .should('have.class', 'link') + .click(); + cy.dataCy('ItemDescriptor').should('exist'); + }); +}); 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 80b9d07df..57faeac85 100644 --- a/test/cypress/integration/vnComponent/VnLog.spec.js +++ b/test/cypress/integration/vnComponent/VnLog.spec.js @@ -1,25 +1,28 @@ /// <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', () => { + cy.dataCy('iconLaunch-claimFk').first().click(); + cy.dataCy('vnDescriptor_subtitle').contains('1'); + cy.dataCy('iconLaunch-claimFk').first().click(); }); }); diff --git a/test/cypress/integration/vnComponent/VnShortcut.spec.js b/test/cypress/integration/vnComponent/VnShortcut.spec.js index e08c44635..cc5cacbe4 100644 --- a/test/cypress/integration/vnComponent/VnShortcut.spec.js +++ b/test/cypress/integration/vnComponent/VnShortcut.spec.js @@ -1,6 +1,6 @@ /// <reference types="cypress" /> - -describe('VnShortcuts', () => { +// https://redmine.verdnatura.es/issues/8848 +describe.skip('VnShortcuts', () => { const modules = { item: 'a', customer: 'c', @@ -27,12 +27,15 @@ describe('VnShortcuts', () => { code: `Key${shortcut.toUpperCase()}`, }); + cy.waitSpinner(); cy.url().should('include', module); if (['monitor', 'claim'].includes(module)) { return; } cy.waitForElement('.q-page').should('exist'); cy.dataCy('vnTableCreateBtn').should('exist'); + cy.waitSpinner(); + cy.get('.q-page').trigger('keydown', { ctrlKey: true, altKey: true, diff --git a/test/cypress/integration/vnComponent/crudModel.commands.js b/test/cypress/integration/vnComponent/crudModel.commands.js new file mode 100644 index 000000000..9d08f064a --- /dev/null +++ b/test/cypress/integration/vnComponent/crudModel.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('saveCrudModel', () => + cy.dataCy('crudModelDefaultSaveBtn').should('exist').click(), +); diff --git a/test/cypress/integration/vnComponent/formModel.commands.js b/test/cypress/integration/vnComponent/formModel.commands.js new file mode 100644 index 000000000..2814b0091 --- /dev/null +++ b/test/cypress/integration/vnComponent/formModel.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('saveFormModel', () => + cy.dataCy('FormModelPopup_save').should('exist').click(), +); diff --git a/test/cypress/integration/vnComponent/vnConfirm.commands.js b/test/cypress/integration/vnComponent/vnConfirm.commands.js new file mode 100644 index 000000000..9f93967d6 --- /dev/null +++ b/test/cypress/integration/vnComponent/vnConfirm.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('confirmVnConfirm', () => + cy.dataCy('VnConfirm_confirm').should('exist').click(), +); diff --git a/test/cypress/integration/vnComponent/vnSelect.commands.js b/test/cypress/integration/vnComponent/vnSelect.commands.js new file mode 100644 index 000000000..017b6e7ea --- /dev/null +++ b/test/cypress/integration/vnComponent/vnSelect.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('clickOption', (index = 1) => + cy.get(`.q-menu :nth-child(${index}) >.q-item__section`).click(), +); diff --git a/test/cypress/integration/vnComponent/vnTable.commands.js b/test/cypress/integration/vnComponent/vnTable.commands.js new file mode 100644 index 000000000..6c7e71e13 --- /dev/null +++ b/test/cypress/integration/vnComponent/vnTable.commands.js @@ -0,0 +1,16 @@ +Cypress.Commands.add('getRow', (index = 1) => + cy.get(`.vnTable .q-virtual-scroll__content tr:nth-child(${index})`), +); +Cypress.Commands.add('getRowCol', (field, index = 1) => + cy.get( + `.vnTable .q-virtual-scroll__content > :nth-child(${index}) > [data-col-field="${field}"]`, + ), +); + +Cypress.Commands.add('vnTableCreateBtn', () => + cy.dataCy('vnTableCreateBtn').should('exist').click(), +); + +Cypress.Commands.add('waitTableScrollLoad', () => + cy.waitForElement('[data-q-vs-anchor]'), +); diff --git a/test/cypress/integration/wagon/wagonCreate.spec.js b/test/cypress/integration/wagon/wagonCreate.spec.js index 6d185ea69..88855fdf9 100644 --- a/test/cypress/integration/wagon/wagonCreate.spec.js +++ b/test/cypress/integration/wagon/wagonCreate.spec.js @@ -1,4 +1,4 @@ -describe.skip('WagonCreate', () => { +describe('WagonCreate', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -17,7 +17,7 @@ describe.skip('WagonCreate', () => { '.grid-create > [label="Volume"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Volume_input"]', ).type('100'); cy.selectOption('[data-cy="Type_select"]', '1'); - + cy.dataCy('FormModelPopup_save').click(); cy.get('[title="Remove"] > .q-btn__content > .q-icon').first().click(); }); }); diff --git a/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js index 49d7d9f01..915927a6d 100644 --- a/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js +++ b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js @@ -2,7 +2,7 @@ describe('WagonTypeCreate', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit('/#/wagon/type/create'); + cy.visit('/#/wagon/type/list'); cy.waitForElement('.q-page', 6000); }); diff --git a/test/cypress/integration/worker/workerBasicData.spec.js b/test/cypress/integration/worker/workerBasicData.spec.js new file mode 100644 index 000000000..cf452a044 --- /dev/null +++ b/test/cypress/integration/worker/workerBasicData.spec.js @@ -0,0 +1,15 @@ +describe('WorkerBasicData', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('/#/worker/1107/basic-data'); + }); + + it('should modify worker summary', () => { + cy.dataCy('MaritalStatus').type('Married'); + cy.dataCy('fi').type('42572374H'); + cy.dataCy('country').type('Alemania'); + cy.saveCard(); + cy.checkNotification('Data saved'); + }); +}); diff --git a/test/cypress/integration/worker/workerBusiness.spec.js b/test/cypress/integration/worker/workerBusiness.spec.js new file mode 100644 index 000000000..46da28cd6 --- /dev/null +++ b/test/cypress/integration/worker/workerBusiness.spec.js @@ -0,0 +1,34 @@ +describe.skip('WorkerBusiness', () => { + const saveBtn = '.q-mt-lg > .q-btn--standard'; + const contributionCode = `Representantes de comercio`; + const contractType = `INDEFINIDO A TIEMPO COMPLETO`; + + const Business = { + 'Start Date': { val: '26-12-2002', type: 'date' }, + Company: { val: `VNL`, type: 'select' }, + Department: { val: `RECICLAJE`, type: 'select' }, + 'Professional Category': { val: `employee`, type: 'select' }, + 'Work Calendar': { val: `General schedule`, type: 'select' }, + 'Work Center': { val: `Silla`, type: 'select' }, + 'Contract Category': { val: `INFORMATICA`, type: 'select' }, + 'Contribution Code': { val: contributionCode, type: 'select' }, + Rate: { val: `5` }, + 'Contract Type': { val: contractType, type: 'select' }, + 'Transport Workers Salary': { val: `1000` }, + }; + + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('hr'); + cy.visit('/#/worker/1107/business'); + cy.addCard(); + }); + + it('should create a business', () => { + cy.fillInForm({ + ...Business, + }); + cy.get(saveBtn).click(); + cy.checkNotification('Data created'); + }); +}); diff --git a/test/cypress/integration/worker/workerList.spec.js b/test/cypress/integration/worker/workerList.spec.js index 0a45441c1..d964c3dc8 100644 --- a/test/cypress/integration/worker/workerList.spec.js +++ b/test/cypress/integration/worker/workerList.spec.js @@ -1,4 +1,5 @@ -describe('WorkerList', () => { +// https://redmine.verdnatura.es/issues/8848 +describe.skip('WorkerList', () => { const inputName = '.q-drawer .q-form input[aria-label="First Name"]'; const searchBtn = '.q-drawer button:nth-child(3)'; const descriptorTitle = '.descriptor .title span'; @@ -13,7 +14,7 @@ describe('WorkerList', () => { cy.intercept('GET', /\/api\/Workers\/summary+/).as('worker'); cy.get(searchBtn).click(); cy.wait('@worker').then(() => - cy.get(descriptorTitle).should('include.text', 'Jessica') + cy.get(descriptorTitle).should('include.text', 'Jessica'), ); }); }); diff --git a/test/cypress/integration/worker/workerMutual.spec.js b/test/cypress/integration/worker/workerMutual.spec.js new file mode 100644 index 000000000..a6d2c5f4f --- /dev/null +++ b/test/cypress/integration/worker/workerMutual.spec.js @@ -0,0 +1,23 @@ +/// <reference types="cypress" /> +describe('WorkerMutual', () => { + const userId = 1106; + const saveBtn = '.q-mt-lg > .q-btn--standard'; + const medicalReview = { + Date: { val: '01-01-2001', type: 'date' }, + 'Formation Center': { val: '1', type: 'select' }, + Invoice: { val: '24532' }, + Amount: { val: '540' }, + }; + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit(`/#/worker/${userId}/medical`); + cy.addCard(); + }); + + it('should create a medical Review', () => { + cy.fillInForm(medicalReview); + cy.get(saveBtn).click(); + cy.checkNotification('Data created'); + }); +}); diff --git a/test/cypress/integration/client/clientBalance.spec.js b/test/cypress/integration/worker/workerNotes.spec.js similarity index 53% rename from test/cypress/integration/client/clientBalance.spec.js rename to test/cypress/integration/worker/workerNotes.spec.js index abfa74cec..661314ac9 100644 --- a/test/cypress/integration/client/clientBalance.spec.js +++ b/test/cypress/integration/worker/workerNotes.spec.js @@ -1,11 +1,13 @@ /// <reference types="cypress" /> -describe('Client balance', () => { +describe('WorkerNotes', () => { + const userId = 1106; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1101/balance'); + cy.visit(`/#/worker/${userId}/notes`); }); + it('Should load layout', () => { - cy.get('.q-page').should('be.visible'); + cy.get('.q-card').should('be.visible'); }); }); diff --git a/test/cypress/integration/worker/workerOperator.spec.js b/test/cypress/integration/worker/workerOperator.spec.js new file mode 100644 index 000000000..95839aeba --- /dev/null +++ b/test/cypress/integration/worker/workerOperator.spec.js @@ -0,0 +1,19 @@ +/// <reference types="cypress" /> +describe('WorkerOperator', () => { + const userId = 1106; + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('hr'); + cy.visit(`/#/worker/${userId}/operator`); + }); + + it('should fill the operator form', () => { + cy.dataCy('numberOfWagons').type('4'); + cy.dataCy('linesLimit').type('6'); + cy.dataCy('volumeLimit').type('3'); + cy.dataCy('sizeLimit').type('3'); + cy.saveCard(); + + cy.checkNotification('Data saved'); + }); +}); diff --git a/test/cypress/integration/worker/workerPda.spec.js b/test/cypress/integration/worker/workerPda.spec.js index 31ec19eda..2623e81cf 100644 --- a/test/cypress/integration/worker/workerPda.spec.js +++ b/test/cypress/integration/worker/workerPda.spec.js @@ -1,23 +1,80 @@ describe('WorkerPda', () => { - const select = '[data-cy="pda-dialog-select"]'; + const deviceId = 4; beforeEach(() => { - cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/worker/1110/pda`); }); - it('assign pda', () => { - cy.addBtnClick(); - cy.get(select).click(); - cy.get(select).type('{downArrow}{enter}'); - cy.get('.q-notification__message').should('have.text', 'Data created'); + it('assign and delete pda', () => { + creatNewPDA(); + cy.checkNotification('Data created'); + cy.visit(`/#/worker/1110/pda`); + removeNewPDA(); + cy.checkNotification('PDA deallocated'); }); - it('delete pda', () => { - cy.get('.btn-delete').click(); - cy.get( - '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block' - ).click(); - cy.get('.q-notification__message').should('have.text', 'PDA deallocated'); + it('send and download pdf to docuware', () => { + //Send + cy.intercept('POST', '/api/Docuwares/upload-pda-pdf', (req) => { + req.reply({ + statusCode: 200, + body: {}, + }); + }); + + creatNewPDA(); + + cy.dataCy('workerPda-send').click(); + cy.clickConfirm(); + cy.checkNotification('PDF sended to signed'); + + //Download + cy.intercept('POST', /\/api\/Docuwares\/Jones%20Jessica\/checkFile/, (req) => { + req.reply({ + statusCode: 200, + body: { + id: deviceId, + state: 'Firmado', + }, + }); + }); + cy.get('#st-actions').contains('refresh').click(); + cy.intercept('GET', '/api/Docuwares/download-pda-pdf**', (req) => { + req.reply({ + statusCode: 200, + body: {}, + }); + }); + + cy.dataCy('workerPda-download').click(); + removeNewPDA(); }); + + it('send 2 pdfs to docuware', () => { + cy.intercept('POST', '/api/Docuwares/upload-pda-pdf', (req) => { + req.reply({ + statusCode: 200, + body: {}, + }); + }); + + creatNewPDA(); + creatNewPDA(2); + cy.selectRows([1, 2]); + cy.get('#st-actions').contains('Send').click(); + cy.checkNotification('PDF sended to signed'); + + removeNewPDA(); + }); + + function creatNewPDA(id = deviceId) { + cy.addBtnClick(); + cy.selectOption('[data-cy="pda-dialog-select"]', id); + cy.saveCard(); + } + + function removeNewPDA() { + cy.dataCy('workerPda-remove').first().click(); + cy.clickConfirm(); + } }); diff --git a/test/cypress/integration/worker/workerPit.spec.js b/test/cypress/integration/worker/workerPit.spec.js index 19cbebc20..04f232648 100644 --- a/test/cypress/integration/worker/workerPit.spec.js +++ b/test/cypress/integration/worker/workerPit.spec.js @@ -1,19 +1,5 @@ describe('WorkerPit', () => { - const familySituationInput = '[data-cy="Family Situation_input"]'; - const familySituation = '1'; - const childPensionInput = '[data-cy="Child Pension_input"]'; - const childPension = '120'; - const spouseNifInput = '[data-cy="Spouse Pension_input"]'; - const spouseNif = '65117125P'; - const spousePensionInput = '[data-cy="Spouse Pension_input"]'; - const spousePension = '120'; const addRelative = '[data-cy="addRelative"]'; - const isDescendantSelect = '[data-cy="Descendant/Ascendant"]'; - const Descendant = 'Descendiente'; - const birthedInput = '[data-cy="Birth Year_input"]'; - const birthed = '2002'; - const adoptionYearInput = '[data-cy="Adoption Year_input"]'; - const adoptionYear = '2004'; const saveRelative = '[data-cy="workerPitRelativeSaveBtn"]'; const savePIT = '#st-actions > .q-btn-group > .q-btn--standard'; @@ -24,15 +10,15 @@ describe('WorkerPit', () => { }); it('complete PIT', () => { - cy.get(familySituationInput).type(familySituation); - cy.get(childPensionInput).type(childPension); - cy.get(spouseNifInput).type(spouseNif); - cy.get(spousePensionInput).type(spousePension); + cy.dataCy('familySituation').type('1'); + cy.dataCy('childPension').type('120'); + cy.dataCy('spouseNif').type('65117125P'); + cy.dataCy('spousePension').type('120'); cy.get(savePIT).click(); cy.get(addRelative).click(); - cy.get(isDescendantSelect).type(Descendant); - cy.get(birthedInput).type(birthed); - cy.get(adoptionYearInput).type(adoptionYear); + cy.dataCy('Descendant/Ascendant').type('Descendiente'); + cy.dataCy('birthed').type('2002'); + cy.dataCy('adoptionYear').type('2004'); cy.get(saveRelative).click(); }); }); diff --git a/test/cypress/integration/worker/workerSummary.spec.js b/test/cypress/integration/worker/workerSummary.spec.js index 3d70fdf96..6071c4cdf 100644 --- a/test/cypress/integration/worker/workerSummary.spec.js +++ b/test/cypress/integration/worker/workerSummary.spec.js @@ -1,16 +1,26 @@ describe('WorkerSummary', () => { + 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(department).click(); + cy.get('.descriptor').should('be.visible'); + cy.get('.q-item > .q-item__label').should('include.text', '43'); + 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'); }); }); diff --git a/test/cypress/integration/zone/zoneCalendar.spec.js b/test/cypress/integration/zone/zoneCalendar.spec.js index d71c29142..68b85d1d2 100644 --- a/test/cypress/integration/zone/zoneCalendar.spec.js +++ b/test/cypress/integration/zone/zoneCalendar.spec.js @@ -1,22 +1,19 @@ describe('ZoneCalendar', () => { const addEventBtn = '.q-page-sticky > div > .q-btn'; const submitBtn = '.q-mt-lg > .q-btn--standard'; - const deleteBtn = '.q-item__section--side > .q-btn'; - const from = '.q-field__control-container > [data-cy="ZoneEventsFromDate"]'; - const to = '.q-field__control-container > [data-cy="ZoneEventsToDate"]'; + const deleteBtn = 'ZoneEventsPanelDeleteBtn'; beforeEach(() => { cy.login('developer'); - cy.viewport(1920, 1080); - cy.visit(`/#/zone/11/events`); + cy.visit(`/#/zone/13/events`); }); it('should include a one day event, then delete it', () => { cy.get(addEventBtn).click(); cy.dataCy('ZoneEventInclusionDayRadio').click(); - cy.get('.q-card > :nth-child(5)').type('02/04/2001'); + cy.get('.q-card > :nth-child(5)').type('01/01/2001'); cy.get(submitBtn).click(); - cy.get(deleteBtn).click(); + cy.dataCy(deleteBtn).click(); cy.dataCy('VnConfirm_confirm').click(); }); @@ -25,7 +22,7 @@ describe('ZoneCalendar', () => { cy.get('.flex > .q-gutter-x-sm > :nth-child(1)').click(); cy.get('.flex > .q-gutter-x-sm > :nth-child(2)').click(); cy.get(submitBtn).click(); - cy.get(deleteBtn).click(); + cy.dataCy(deleteBtn).click(); cy.dataCy('VnConfirm_confirm').click(); }); @@ -36,7 +33,7 @@ describe('ZoneCalendar', () => { cy.dataCy('From_inputDate').type('01/01/2001'); cy.dataCy('To_inputDate').type('31/01/2001'); cy.get(submitBtn).click(); - cy.get(deleteBtn).click(); + cy.dataCy(deleteBtn).click(); cy.dataCy('VnConfirm_confirm').click(); }); @@ -47,7 +44,7 @@ describe('ZoneCalendar', () => { cy.get( '.q-current-day > .q-calendar-month__day--content > [data-cy="ZoneCalendarDay"]', ).click(); - cy.get('.q-mt-lg > :nth-child(2)').click(); + cy.dataCy('ZoneEventExclusionDeleteBtn').click(); cy.dataCy('VnConfirm_confirm').click(); }); }); diff --git a/test/cypress/integration/zone/zoneCreate.spec.js b/test/cypress/integration/zone/zoneCreate.spec.js index 9ef1945bf..fadf5b07f 100644 --- a/test/cypress/integration/zone/zoneCreate.spec.js +++ b/test/cypress/integration/zone/zoneCreate.spec.js @@ -1,4 +1,4 @@ -describe.skip('ZoneCreate', () => { +describe('ZoneCreate', () => { const data = { Name: { val: 'Zone pickup D' }, Price: { val: '3' }, diff --git a/test/cypress/integration/zone/zoneDeliveryDays.spec.js b/test/cypress/integration/zone/zoneDeliveryDays.spec.js index 291c20ce3..a89def12d 100644 --- a/test/cypress/integration/zone/zoneDeliveryDays.spec.js +++ b/test/cypress/integration/zone/zoneDeliveryDays.spec.js @@ -37,7 +37,6 @@ describe('ZoneDeliveryDays', () => { cy.get('@focusedElement').blur(); } }); - cy.get('.q-menu').should('not.exist'); cy.dataCy('ZoneDeliveryDaysAgencySelect').type(agency); cy.get('.q-menu .q-item').contains(agency).click(); @@ -49,7 +48,6 @@ describe('ZoneDeliveryDays', () => { cy.get('@focusedElement').blur(); } }); - cy.get('.q-menu').should('not.exist'); cy.get(submitForm).click(); cy.wait('@events').then((interception) => { diff --git a/test/cypress/integration/zone/zoneList.spec.js b/test/cypress/integration/zone/zoneList.spec.js index b1b0db3fc..c84b1b017 100644 --- a/test/cypress/integration/zone/zoneList.spec.js +++ b/test/cypress/integration/zone/zoneList.spec.js @@ -1,26 +1,18 @@ describe('ZoneList', () => { const agency = 'inhouse pickup'; + const firstSummaryIcon = + ':nth-child(1) > .q-table--col-auto-width > [data-cy="tableAction-0"]'; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); cy.visit('/#/zone/list'); - }); - - it('should filter by agency', () => { - cy.dataCy('zoneFilterPanelAgencySelect').type(agency); - cy.get('.q-menu .q-item').contains(agency).click(); - cy.get(':nth-child(1) > [data-col-field="agencyModeFk"]').should( - 'include.text', - agency, - ); + cy.typeSearchbar('{enter}'); }); it('should open the zone summary', () => { - cy.dataCy('zoneFilterPanelAgencySelect').type(agency); - cy.get('.q-menu .q-item').contains(agency).click(); - cy.dataCy('tableAction-0').eq(1).click(); + cy.get(firstSummaryIcon).click(); cy.get('.header > .q-icon').click(); - cy.url().should('include', 'zone/2/summary'); + cy.url().should('include', 'zone/1/summary'); }); it('should clone the zone', () => { @@ -29,7 +21,5 @@ describe('ZoneList', () => { cy.dataCy('VnConfirm_confirm').click(); cy.url().should('not.include', 'zone/2/'); cy.url().should('match', /zone\/\d+\/basic-data/); - cy.get('.list-box > :nth-child(1)').should('include.text', agency); - cy.get('.title > span').should('include.text', 'Zone pickup B'); }); }); diff --git a/test/cypress/integration/zone/zoneLocations.spec.js b/test/cypress/integration/zone/zoneLocations.spec.js index 04b7f1991..dabd3eb2b 100644 --- a/test/cypress/integration/zone/zoneLocations.spec.js +++ b/test/cypress/integration/zone/zoneLocations.spec.js @@ -1,23 +1,59 @@ describe('ZoneLocations', () => { - const data = { - Warehouse: { val: 'Warehouse One', type: 'select' }, - }; - - const postalCode = '[style=""] > :nth-child(1) > :nth-child(1) > :nth-child(2) > :nth-child(1) > :nth-child(1) > :nth-child(2) > :nth-child(1) > .q-tree__node--parent > .q-tree__node-collapsible > .q-tree__children' - + const cp = 46680; + const searchIcon = '.router-link-active > .q-icon'; beforeEach(() => { - cy.viewport(1280, 720); cy.login('developer'); cy.visit(`/#/zone/2/location`); }); - it('should show all locations on entry', () => { - cy.get('.q-tree > :nth-child(1) > :nth-child(2) > :nth-child(1)').children().should('have.length', 9); + it('should be able to search by postal code', () => { + cy.get('.q-tree > :nth-child(1) > :nth-child(2) > :nth-child(1)') + .should('exist') + .should('be.visible'); + + cy.intercept('GET', '**/api/Zones/2/getLeaves*', (req) => { + req.headers['cache-control'] = 'no-cache'; + req.headers['pragma'] = 'no-cache'; + req.headers['expires'] = '0'; + + req.on('response', (res) => { + delete res.headers['if-none-match']; + delete res.headers['if-modified-since']; + }); + }).as('location'); + cy.get('#searchbarForm').type(cp); + cy.get(searchIcon).click(); + cy.wait('@location').then((interception) => { + const data = interception.response.body; + expect(data).to.include(cp); + }); }); - it('should be able to search by postal code', () => { - cy.get('#searchbarForm').type('46680'); - cy.get('.router-link-active > .q-icon').click(); - cy.get(postalCode).should('include.text', '46680') + it('should check, uncheck, and set a location to mixed state', () => { + cy.get('#searchbarForm').type(cp); + cy.get(searchIcon).click(); + + cy.get('.q-tree > :nth-child(1) > :nth-child(2) > :nth-child(1)') + .as('tree') + .within(() => { + cy.get('[data-cy="ZoneLocationTreeCheckbox"] > .q-checkbox__inner') + .last() + .as('lastCheckbox'); + + const verifyCheckboxState = (state) => { + cy.get('@lastCheckbox') + .parents('.q-checkbox') + .should('have.attr', 'aria-checked', state); + }; + + cy.get('@lastCheckbox').click(); + verifyCheckboxState('true'); + + cy.get('@lastCheckbox').click(); + verifyCheckboxState('false'); + + cy.get('@lastCheckbox').click(); + verifyCheckboxState('mixed'); + }); }); }); diff --git a/test/cypress/integration/zone/zoneWarehouse.spec.js b/test/cypress/integration/zone/zoneWarehouse.spec.js index b2c1c1ed2..d7a9854bb 100644 --- a/test/cypress/integration/zone/zoneWarehouse.spec.js +++ b/test/cypress/integration/zone/zoneWarehouse.spec.js @@ -1,4 +1,4 @@ -describe.skip('ZoneWarehouse', () => { +describe('ZoneWarehouse', () => { const data = { Warehouse: { val: 'Warehouse Two', type: 'select' }, }; @@ -18,7 +18,7 @@ describe.skip('ZoneWarehouse', () => { cy.checkNotification(dataError); }); - it('should create & remove a warehouse', () => { + it.skip('should create & remove a warehouse', () => { cy.addBtnClick(); cy.fillInForm(data); cy.get(saveBtn).click(); diff --git a/test/cypress/run.sh b/test/cypress/run.sh deleted file mode 100755 index 1f506aa57..000000000 --- a/test/cypress/run.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -cleanup() { - if [[ -z "$ended" ]]; then - ended=true - docker-compose -p e2e --project-directory . -f test/cypress/docker-compose.yml down -v - fi -} - -trap cleanup SIGINT - -#CLEAN -rm -rf test/cypress/screenshots -rm -f test/cypress/results/* -rm -f test/cypress/reports/* -rm -f junit/e2e-*.xml - -#RUN -export CI=true -export TZ=Europe/Madrid - -docker-compose -p e2e --project-directory . -f test/cypress/docker-compose.yml up -d - -docker run -it --rm \ - -v "$(pwd)":/app \ - --network e2e_default \ - -e CI \ - -e TZ \ - lilium-dev \ - bash -c 'sh test/cypress/cypressParallel.sh 2' - -cleanup diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 8437112e0..41f91e855 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -27,8 +27,14 @@ // DO NOT REMOVE // Imports Quasar Cypress AE predefined commands // import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress'; - +import moment from 'moment'; import waitUntil from './waitUntil'; +// Importar dinámicamente todos los archivos con el sufijo .commands.js dentro de la carpeta src/test/cypress/integration +const requireCommands = require.context('../integration', true, /\.commands\.js$/); +// Iterar sobre cada archivo y requerirlo +requireCommands.keys().forEach(requireCommands); + +// Common comma Cypress.Commands.add('waitUntil', { prevSubject: 'optional' }, waitUntil); Cypress.Commands.add('resetDB', () => { @@ -62,12 +68,7 @@ Cypress.Commands.overwrite('visit', (originalFn, url, options, waitRequest = tru originalFn(url, options); cy.waitUntil(() => cy.document().then((doc) => doc.readyState === 'complete')); cy.waitUntil(() => cy.get('main').should('exist')); - if (waitRequest) - cy.get('body').then(($body) => { - if ($body.find('[data-cy="loading-spinner"]').length) { - cy.get('[data-cy="loading-spinner"]').should('not.be.visible'); - } - }); + if (waitRequest) cy.waitSpinner(); }); Cypress.Commands.add('waitForElement', (element) => { @@ -99,6 +100,14 @@ Cypress.Commands.add('getValue', (selector) => { }); }); +Cypress.Commands.add('waitSpinner', () => { + cy.get('body').then(($body) => { + if ($body.find('[data-cy="loading-spinner"]').length) { + cy.get('[data-cy="loading-spinner"]').should('not.be.visible'); + } + }); +}); + // Fill Inputs Cypress.Commands.add('selectOption', (selector, option, timeout = 2500) => { cy.waitForElement(selector, timeout); @@ -116,11 +125,13 @@ Cypress.Commands.add('selectOption', (selector, option, timeout = 2500) => { function selectItem(selector, option, ariaControl, hasWrite = true) { if (!hasWrite) cy.wait(100); + cy.waitSpinner(); getItems(ariaControl).then((items) => { - const matchingItem = items - .toArray() - .find((item) => item.innerText.includes(option)); + const matchingItem = items.toArray().find((item) => { + const val = typeof option == 'string' ? option.toLowerCase() : option; + return item.innerText.toLowerCase().includes(val); + }); if (matchingItem) return cy.wrap(matchingItem).click(); if (hasWrite) cy.get(selector).clear().type(option); @@ -135,6 +146,7 @@ function getItems(ariaControl, startTime = Cypress._.now(), timeout = 2500) { .should('exist') .find('.q-item') .should('exist') + .should('be.visible') .then(($items) => { if (!$items?.length || $items.first().text().trim() === '') { if (Cypress._.now() - startTime > timeout) { @@ -155,14 +167,20 @@ Cypress.Commands.add('countSelectOptions', (selector, option) => { cy.get('.q-menu .q-item').should('have.length', option); }); -Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { +Cypress.Commands.add('fillInForm', (obj, opts = {}) => { + cy.waitSpinner(); + const { form = '.q-form > .q-card', attr = 'aria-label' } = opts; cy.waitForElement(form); cy.get(`${form} input`).each(([el]) => { cy.wrap(el) - .invoke('attr', 'aria-label') - .then((ariaLabel) => { - const field = obj[ariaLabel]; + .invoke('attr', attr) + .then((key) => { + const field = obj[key]; if (!field) return; + if (typeof field == 'string') + return cy + .wrap(el) + .type(`{selectall}{backspace}${field}`, { delay: 0 }); const { type, val } = field; switch (type) { @@ -170,7 +188,9 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { cy.selectOption(el, val); break; case 'date': - cy.get(el).type(val.split('-').join('')); + cy.get(el).type( + `{selectall}{backspace}${val.split('-').join('')}`, + ); break; case 'time': cy.get(el).click(); @@ -179,13 +199,47 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { cy.get('.q-time .q-time__link').contains(val.x).click(); break; default: - cy.wrap(el).type(val); + cy.wrap(el).type(`{selectall}${val}`, { delay: 0 }); break; } }); }); }); +Cypress.Commands.add('validateForm', (obj, opts = {}) => { + const { form = '.q-form > .q-card', attr = 'data-cy' } = opts; + cy.waitForElement(form); + cy.get(`${form} input`).each(([el]) => { + cy.wrap(el) + .invoke('attr', attr) + .then((key) => { + const field = obj[key]; + if (!field) return; + + const { type, val } = field; + cy.get(el) + .invoke('val') + .then((elVal) => { + if (typeof field == 'string') + expect(elVal.toLowerCase()).to.equal(field.toLowerCase()); + else + switch (type) { + case 'date': + const elDate = moment(elVal, 'DD-MM-YYYY'); + const mockDate = moment(val, 'DD-MM-YYYY'); + expect(elDate.isSame(mockDate, 'day')).to.be.true; + break; + default: + expect(elVal.toLowerCase()).to.equal( + val.toLowerCase(), + ); + break; + } + }); + }); + }); +}); + Cypress.Commands.add('checkOption', (selector) => { cy.get(selector).find('.q-checkbox__inner').click(); }); @@ -202,14 +256,17 @@ Cypress.Commands.add('saveCard', () => { Cypress.Commands.add('resetCard', () => { cy.get('[title="Reset"]').click(); }); + Cypress.Commands.add('removeCard', () => { cy.get('[title="Remove"]').click(); }); + Cypress.Commands.add('addCard', () => { cy.waitForElement('tbody'); cy.waitForElement('.q-page-sticky > div > .q-btn'); cy.get('.q-page-sticky > div > .q-btn').click(); }); + Cypress.Commands.add('clickConfirm', () => { cy.waitForElement('.q-dialog__inner > .q-card'); cy.get('.q-card__actions > .q-btn--unelevated > .q-btn__content > .block').click(); @@ -290,6 +347,7 @@ Cypress.Commands.add('removeRow', (rowIndex) => { }); }); }); + Cypress.Commands.add('openListSummary', (row) => { cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click(); }); @@ -317,6 +375,15 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => { cy.get(selector).should('have.text', expectedValue); }); +Cypress.Commands.add('containContent', (selector, expectedValue) => { + cy.get(selector) + .should('be.visible') + .invoke('text') + .then((text) => { + expect(text).to.include(expectedValue); + }); +}); + Cypress.Commands.add('openActionDescriptor', (opt) => { cy.openActionsDescriptor(); const listItem = '[role="menu"] .q-list .q-item'; @@ -324,13 +391,7 @@ Cypress.Commands.add('openActionDescriptor', (opt) => { }); Cypress.Commands.add('openActionsDescriptor', () => { - cy.get('[data-cy="descriptor-more-opts"]').click(); -}); - -Cypress.Commands.add('clickButtonDescriptor', (id) => { - cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`) - .invoke('removeAttr', 'target') - .click(); + cy.get('[data-cy="vnDescriptor"] [data-cy="descriptor-more-opts"]').click(); }); Cypress.Commands.add('openUserPanel', () => { @@ -390,6 +451,7 @@ Cypress.Commands.add('clickButtonWith', (type, value) => { break; } }); + Cypress.Commands.add('clickButtonWithIcon', (iconClass) => { cy.waitForElement('[data-cy="descriptor_actions"]'); cy.get('[data-cy="loading-spinner"]', { timeout: 10000 }).should('not.be.visible'); @@ -418,3 +480,142 @@ Cypress.Commands.add('searchBtnFilterPanel', () => { Cypress.Commands.add('waitRequest', (alias, cb) => { cy.wait(alias).then(cb); }); + +Cypress.Commands.add('validateDescriptor', (toCheck = {}) => { + const { title, description, subtitle, listbox = {}, popup = false } = toCheck; + + const popupSelector = popup ? '[role="menu"] ' : ''; + + if (title) cy.get(`${popupSelector}[data-cy="vnDescriptor_title"]`).contains(title); + if (description) + cy.get(`${popupSelector}[data-cy="vnDescriptor_description"]`).contains( + description, + ); + if (subtitle) + cy.get(`${popupSelector}[data-cy="vnDescriptor_subtitle"]`).contains(subtitle); + + for (const index in listbox) + cy.get(`${popupSelector}[data-cy="vnDescriptor_listbox"] > *`) + .eq(index) + .should('contain.text', listbox[index]); +}); + +Cypress.Commands.add('validateVnTableRows', (opts = {}) => { + let { cols = [], rows = [] } = opts; + if (!Array.isArray(cols)) cols = [cols]; + const rowSelector = rows.length + ? rows.map((row) => `> :nth-child(${row})`).join(', ') + : '> *'; + cy.get(`[data-cy="vnTable"] .q-virtual-scroll__content`).within(() => { + cy.get(`${rowSelector}`).each(($el) => { + for (const { name, type = 'string', val, operation = 'equal' } of cols) { + cy.wrap($el) + .find(`[data-cy="vnTableCell_${name}"]`) + .invoke('text') + .then((text) => { + if (type === 'string') + expect(text.trim().toLowerCase()).to[operation]( + val.toLowerCase(), + ); + if (type === 'number') cy.checkNumber(text, val, operation); + if (type === 'date') cy.checkDate(text, val, operation); + }); + } + }); + }); +}); + +Cypress.Commands.add('checkDate', (rawDate, expectedVal, operation) => { + const date = moment(rawDate.trim(), 'MM/DD/YYYY'); + const compareDate = moment(expectedVal, 'DD/MM/YYYY'); + + switch (operation) { + case 'equal': + expect(text.trim()).to.equal(compareDate); + break; + case 'before': + expect(date.isBefore(compareDate)).to.be.true; + break; + case 'after': + expect(date.isAfter(compareDate)).to.be.true; + } +}); + +Cypress.Commands.add('selectDescriptorOption', (opt = 1) => { + const listItem = '[data-cy="descriptor-more-opts_list"]'; + cy.get('body').then(($body) => { + if (!$body.find(listItem).length) cy.openActionsDescriptor(); + }); + + cy.waitForElement(listItem); + cy.get(`${listItem} > :not(template):nth-of-type(${opt})`).click(); +}); + +Cypress.Commands.add('validateCheckbox', (selector, expectedVal = 'true') => { + cy.get(selector).should('have.attr', 'aria-checked', expectedVal.toString()); +}); + +Cypress.Commands.add('validateDownload', (trigger, opts = {}) => { + const { + url = /api\/dms\/\d+\/downloadFile\?access_token=.+/, + types = ['text/plain', 'image/jpeg'], + alias = 'download', + } = opts; + cy.intercept('GET', url).as(alias); + trigger().then(() => { + cy.wait(`@${alias}`).then(({ response }) => { + expect(response.statusCode).to.equal(200); + const isValidType = types.some((type) => + response.headers['content-type'].includes(type), + ); + expect(isValidType).to.be.true; + }); + }); +}); + +Cypress.Commands.add('validatePdfDownload', (match, trigger) => { + cy.window().then((win) => { + cy.stub(win, 'open') + .callsFake(() => null) + .as('pdf'); + }); + trigger(); + cy.get('@pdf') + .should('be.calledOnce') + .then((stub) => { + const [url] = stub.getCall(0).args; + expect(url).to.match(match); + cy.request(url).then((response) => + expect(response.headers['content-type']).to.include('application/pdf'), + ); + }); +}); + +Cypress.Commands.add('clicDescriptorAction', (index = 1) => { + cy.get(`[data-cy="descriptor_actions"] .q-btn:nth-of-type(${index})`).click(); +}); + +Cypress.Commands.add('checkQueryParams', (expectedParams = {}) => { + cy.url().then((url) => { + const urlParams = new URLSearchParams(url.split('?')[1]); + + for (const key in expectedParams) { + const expected = expectedParams[key]; + const param = JSON.parse(decodeURIComponent(urlParams.get(key))); + + if (typeof expected === 'object') { + const { subkey, val } = expected; + expect(param[subkey]).to.equal(val); + } else expect(param).to.equal(expected); + } + }); +}); + +Cypress.Commands.add('validateScrollContent', (validations) => { + validations.forEach(({ row, col, text }) => { + cy.get(`.q-scrollarea__content > :nth-child(${row}) > :nth-child(${col})`).should( + 'have.text', + text, + ); + }); +}); diff --git a/test/cypress/support/index.js b/test/cypress/support/index.js index 87e869b6d..b0f0fb3b1 100644 --- a/test/cypress/support/index.js +++ b/test/cypress/support/index.js @@ -40,6 +40,11 @@ style.innerHTML = ` `; document.head.appendChild(style); +// FIXME: https://redmine.verdnatura.es/issues/8771 +Cypress.on('uncaught:exception', (err) => { + if (err.code === 'ERR_CANCELED') return false; +}); + const waitForApiReady = (url, maxRetries = 20, delay = 1000) => { let retries = 0; diff --git a/test/vitest/helper.js b/test/vitest/helper.js index 5e283c1d4..30324a1f9 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -4,8 +4,6 @@ import { createTestingPinia } from '@pinia/testing'; import { vi } from 'vitest'; import { i18n } from 'src/boot/i18n'; import { Notify, Dialog } from 'quasar'; -import axios from 'axios'; -import keyShortcut from 'src/boot/keyShortcut'; import * as useValidator from 'src/composables/useValidator'; installQuasarPlugin({ @@ -54,8 +52,6 @@ vi.mock('vue-router', () => ({ onBeforeRouteLeave: () => {}, })); -vi.mock('axios'); - vi.spyOn(useValidator, 'useValidator').mockImplementation(() => { return { validate: vi.fn(), @@ -125,5 +121,4 @@ export function createWrapper(component, options) { return { vm, wrapper }; } - -export { axios, flushPromises }; +export { flushPromises }; diff --git a/test/vitest/setup-file.js b/test/vitest/setup-file.js index 288f80beb..0ba9e53c2 100644 --- a/test/vitest/setup-file.js +++ b/test/vitest/setup-file.js @@ -1 +1,27 @@ // This file will be run before each test file, don't delete or vitest will not work. +import { vi } from 'vitest'; + +vi.mock('axios'); +vi.mock('vue-router', () => ({ + useRouter: () => ({ + push: vi.fn(), + replace: vi.fn(), + currentRoute: { + value: { + params: { + id: 1, + }, + meta: { moduleName: 'mockName' }, + matched: [{ path: 'mockName/list' }], + }, + }, + }), + useRoute: () => ({ + matched: [], + query: {}, + params: {}, + meta: { moduleName: 'mockName' }, + path: 'mockSection/list', + }), + onBeforeRouteLeave: () => {}, +})); diff --git a/vitest.config.js b/vitest.config.js index f856a1dc9..c2c3661a9 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,12 +5,11 @@ import jsconfigPaths from 'vite-jsconfig-paths'; import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; import path from 'path'; -let reporters, - outputFile; +let reporters, outputFile; if (process.env.CI) { reporters = ['junit', 'default']; - outputFile = {junit: './junit/vitest.xml'}; + outputFile = { junit: './junit/vitest.xml' }; } else { reporters = 'default'; } @@ -18,6 +17,7 @@ if (process.env.CI) { // https://vitejs.dev/config/ export default defineConfig({ test: { + globals: true, reporters, outputFile, environment: 'happy-dom', @@ -28,6 +28,9 @@ export default defineConfig({ 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', ], }, + server: { + hmr: { overlay: false }, + }, plugins: [ vue({ template: { @@ -39,8 +42,11 @@ export default defineConfig({ sassVariables: 'src/quasar-variables.scss', }), VueI18nPlugin({ + strictMessage: false, + + runtimeOnly: false, include: [ - path.resolve(__dirname, 'src/i18n/**'), + path.resolve(__dirname, 'src/i18n/locale/**'), path.resolve(__dirname, 'src/pages/**/locale/**'), ], }),