diff --git a/.gitignore b/.gitignore index 8c2586de6..2f91bb7dd 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ yarn-error.log* # Cypress directories and files /test/cypress/videos /test/cypress/screenshots +/junit # VitePress directories and files /docs/.vitepress/cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b68b7fa..dd75a00a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,225 @@ +# 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 🆕 + +- feat: add order for table (origin/8681_ticketAdvance_updates) by:Javier Segarra +- feat: detect when is descriptor proxy by:Javier Segarra +- feat: refs #7356 update CrudModel by:Javier Segarra +- feat: refs #8242 remove teleport by:Javier Segarra +- feat: refs #8242 use stateStore by:Javier Segarra +- fix: fixed negative bases style by:Jon +- fix: fixed style when clicking on icons by:Jon +- refactor: refs #6897 remove debug logs and unused style (origin/6897-fixSomeCaus) by:pablone +- style: refs #7356 eslint format by:Javier Segarra + +### Changed 📦 + +- perf: refs #7356 minor changes (origin/7356_ticketService) by:Javier Segarra +- refactor: refs #6897 remove debug logs and unused style (origin/6897-fixSomeCaus) by:pablone +- refactor: refs #6897 update component props and attributes for consistency and improved functionality (origin/6897-fixMinorIssues) by:pablone +- refactor: refs #6897 update component props and improve UI handling in Entry pages by:pablone +- refactor: refs #6897 update VnTable components for improved value handling and UI adjustments (origin/6897-minorFixes) by:pablone +- refactor: refs #8697 simplify date handling in ItemDiary component by:pablone + +### Fixed 🛠️ + +- fix: add datakey by:Javier Segarra +- fix: fixed account descriptor menu and created e2e by:Jon +- fix: fixed negative bases style by:Jon +- fix: fixed style when clicking on icons by:Jon +- fix: refs #6553 workerBusiness (origin/6553-fixWorkerBusinessV2) by:carlossa +- fix: refs #6553 workerBusiness v3 by:carlossa +- fix: refs #6897 prevent default event behavior in autocompleteExpense function by:pablone +- fix: refs #7356 chaining params by:Javier Segarra +- fix: refs #7356 ticketService by:Javier Segarra +- fix: refs #8242 workerDepartmentTree bug (origin/8242_leftMenu_responsive) by:Javier Segarra +- fix: workerBasicData by:carlossa +- Revert "revert 1015acefb7e400be2d8b5958dba69b4d98276b34" (origin/fix_revert_revert, fix_revert_revert) by:alexm + # Version 25.06 - 2025-02-18 ### Added 🆕 diff --git a/Jenkinsfile b/Jenkinsfile index ea3f1b439..7f4144a54 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,6 +26,7 @@ node { // https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables echo "NODE_NAME: ${env.NODE_NAME}" echo "WORKSPACE: ${env.WORKSPACE}" + echo "CHANGE_TARGET: ${env.CHANGE_TARGET}" configFileProvider([ configFile(fileId: 'salix-front.properties', @@ -107,24 +108,32 @@ pipeline { } stage('E2E') { environment { - CREDENTIALS = credentials('docker-registry') + CREDS = credentials('docker-registry') COMPOSE_PROJECT = "${PROJECT_NAME}-${env.BUILD_ID}" COMPOSE_PARAMS = "-p ${env.COMPOSE_PROJECT} -f test/cypress/docker-compose.yml --project-directory ." } steps { script { - sh 'rm junit/e2e-*.xml || true' + 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" - image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ") { - sh 'cypress run --browser chromium || true' + + image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") { + sh 'sh test/cypress/cypressParallel.sh 2' } } } post { always { sh "docker-compose ${env.COMPOSE_PARAMS} down -v" + archiveArtifacts artifacts: 'test/cypress/screenshots/**/*', allowEmptyArchive: true junit( testResults: 'junit/e2e-*.xml', allowEmptyResults: true diff --git a/README.md b/README.md index 262e12e58..d280e29ce 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,18 @@ 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 diff --git a/cypress.config.js b/cypress.config.js index 5cf075e2a..d9cdbe728 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,13 +1,18 @@ import { defineConfig } from 'cypress'; -let urlHost, reporter, reporterOptions; +let urlHost, reporter, reporterOptions, timeouts; if (process.env.CI) { urlHost = 'front'; reporter = 'junit'; reporterOptions = { mochaFile: 'junit/e2e-[hash].xml', - toConsole: false, + }; + timeouts = { + defaultCommandTimeout: 30000, + requestTimeout: 30000, + responseTimeout: 60000, + pageLoadTimeout: 60000, }; } else { urlHost = 'localhost'; @@ -20,17 +25,19 @@ if (process.env.CI) { reportDir: 'test/cypress/reports', inlineAssets: true, }; + timeouts = { + defaultCommandTimeout: 10000, + requestTimeout: 10000, + responseTimeout: 30000, + pageLoadTimeout: 60000, + }; } export default defineConfig({ e2e: { baseUrl: `http://${urlHost}:9000`, experimentalStudio: false, - defaultCommandTimeout: 10000, trashAssetsBeforeRuns: false, - requestTimeout: 10000, - responseTimeout: 30000, - pageLoadTimeout: 60000, defaultBrowser: 'chromium', fixturesFolder: 'test/cypress/fixtures', screenshotsFolder: 'test/cypress/screenshots', @@ -50,8 +57,8 @@ export default defineConfig({ }, viewportWidth: 1280, viewportHeight: 720, + ...timeouts, + includeShadowDom: true, + waitForAnimations: true, }, - experimentalMemoryManagement: true, - defaultCommandTimeout: 10000, - numTestsKeptInMemory: 2, }); diff --git a/docs/Dockerfile.dev b/docs/Dockerfile.dev index 29b194ffa..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 \ @@ -39,7 +41,7 @@ ENV PNPM_HOME="/home/app/.local/share/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN pnpm setup \ - && pnpm install --global cypress@13.6.6 \ + && pnpm install --global cypress@14.1.0 \ && cypress install WORKDIR /app diff --git a/package.json b/package.json index 1361d1fd8..017412ef2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "25.10.0", + "version": "25.14.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -13,6 +13,8 @@ "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/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:front:ci": "vitest run", @@ -47,18 +49,21 @@ "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", "@vue/test-utils": "^2.4.4", "autoprefixer": "^10.4.14", - "cypress": "^13.6.6", + "cypress": "^14.1.0", "cypress-mochawesome-reporter": "^3.8.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-cypress": "^4.1.0", "eslint-plugin-vue": "^9.32.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": "^0.34.0", + "xunit-viewer": "^10.6.1" }, "engines": { "node": "^20 || ^18 || ^16", @@ -71,4 +76,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 31a01e69c..51fc75469 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ dependencies: version: 2.4.1 '@quasar/extras': specifier: ^1.16.16 - version: 1.16.16 + version: 1.16.17 axios: specifier: ^1.4.0 version: 1.7.9 @@ -45,10 +45,10 @@ dependencies: devDependencies: '@commitlint/cli': specifier: ^19.2.1 - version: 19.6.1(@types/node@22.10.7)(typescript@5.7.3) + version: 19.7.1(@types/node@22.13.5)(typescript@5.7.3) '@commitlint/config-conventional': specifier: ^19.1.0 - version: 19.6.0 + version: 19.7.1 '@intlify/unplugin-vue-i18n': specifier: ^0.8.2 version: 0.8.2(vue-i18n@9.14.2) @@ -57,216 +57,225 @@ devDependencies: version: 0.1.7(pinia@2.3.1)(vue@3.5.13) '@quasar/app-vite': specifier: ^2.0.8 - version: 2.0.8(@types/node@22.10.7)(eslint@9.18.0)(pinia@2.3.1)(quasar@2.17.7)(sass@1.83.4)(typescript@5.7.3)(vue-router@4.5.0)(vue@3.5.13) + 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) '@quasar/quasar-app-extension-qcalendar': specifier: ^4.0.2 - version: 4.0.3 + 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.0.11)(vitest@0.34.6)(vue@3.5.13) + 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) '@vue/test-utils': specifier: ^2.4.4 version: 2.4.6 autoprefixer: specifier: ^10.4.14 - version: 10.4.20(postcss@8.5.1) + version: 10.4.20(postcss@8.5.3) cypress: - specifier: ^13.6.6 - version: 13.17.0 + specifier: ^14.1.0 + version: 14.1.0 cypress-mochawesome-reporter: specifier: ^3.8.2 - version: 3.8.2(cypress@13.17.0)(mocha@11.0.1) + version: 3.8.2(cypress@14.1.0)(mocha@11.1.0) eslint: specifier: ^9.18.0 - version: 9.18.0 + version: 9.20.1 eslint-config-prettier: specifier: ^10.0.1 - version: 10.0.1(eslint@9.18.0) + version: 10.0.1(eslint@9.20.1) eslint-plugin-cypress: specifier: ^4.1.0 - version: 4.1.0(eslint@9.18.0) + version: 4.1.0(eslint@9.20.1) eslint-plugin-vue: specifier: ^9.32.0 - version: 9.32.0(eslint@9.18.0) + version: 9.32.0(eslint@9.20.1) 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 postcss: specifier: ^8.4.23 - version: 8.5.1 + version: 8.5.3 prettier: specifier: ^3.4.2 - version: 3.4.2 + version: 3.5.1 sass: specifier: ^1.83.4 - version: 1.83.4 + version: 1.85.0 vitepress: specifier: ^1.6.3 - version: 1.6.3(@algolia/client-search@5.20.0)(@types/node@22.10.7)(axios@1.7.9)(postcss@8.5.1)(sass@1.83.4)(search-insights@2.17.3)(typescript@5.7.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) vitest: specifier: ^0.34.0 - version: 0.34.6(sass@1.83.4) + version: 0.34.6(sass@1.85.0) + 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) packages: - /@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.20.0)(algoliasearch@5.20.0)(search-insights@2.17.3): + /@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.0)(algoliasearch@5.20.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.20.0)(algoliasearch@5.20.0) + '@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.0)(algoliasearch@5.20.0)(search-insights@2.17.3): + /@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.0)(algoliasearch@5.20.0) + '@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.0)(algoliasearch@5.20.0): + /@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.0)(algoliasearch@5.20.0) - '@algolia/client-search': 5.20.0 - algoliasearch: 5.20.0 + '@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.0)(algoliasearch@5.20.0): + /@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.0 - algoliasearch: 5.20.0 + '@algolia/client-search': 5.20.3 + algoliasearch: 5.20.3 dev: true - /@algolia/client-abtesting@5.20.0: - resolution: {integrity: sha512-YaEoNc1Xf2Yk6oCfXXkZ4+dIPLulCx8Ivqj0OsdkHWnsI3aOJChY5qsfyHhDBNSOhqn2ilgHWxSfyZrjxBcAww==} + /@algolia/client-abtesting@5.20.3: + resolution: {integrity: sha512-wPOzHYSsW+H97JkBLmnlOdJSpbb9mIiuNPycUCV5DgzSkJFaI/OFxXfZXAh1gqxK+hf0miKue1C9bltjWljrNA==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-CIT9ni0+5sYwqehw+t5cesjho3ugKQjPVy/iPiJvtJX4g8Cdb6je6SPt2uX72cf2ISiXCAX9U3cY0nN0efnRDw==} + /@algolia/client-analytics@5.20.3: + resolution: {integrity: sha512-XE3iduH9lA7iTQacDGofBQyIyIgaX8qbTRRdj1bOCmfzc9b98CoiMwhNwdTifmmMewmN0EhVF3hP8KjKWwX7Yw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-iSTFT3IU8KNpbAHcBUJw2HUrPnMXeXLyGajmCL7gIzWOsYM4GabZDHXOFx93WGiXMti1dymz8k8R+bfHv1YZmA==} + /@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.0: - resolution: {integrity: sha512-w9RIojD45z1csvW1vZmAko82fqE/Dm+Ovsy2ElTsjFDB0HMAiLh2FO86hMHbEXDPz6GhHKgGNmBRiRP8dDPgJg==} + /@algolia/client-insights@5.20.3: + resolution: {integrity: sha512-QGc/bmDUBgzB71rDL6kihI2e1Mx6G6PxYO5Ks84iL3tDcIel1aFuxtRF14P8saGgdIe1B6I6QkpkeIddZ6vWQw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-p/hftHhrbiHaEcxubYOzqVV4gUqYWLpTwK+nl2xN3eTrSW9SNuFlAvUBFqPXSVBqc6J5XL9dNKn3y8OA1KElSQ==} + /@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.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-m4aAuis5vZi7P4gTfiEs6YPrk/9hNTESj3gEmGFgfJw3hO2ubdS4jSId1URd6dGdt0ax2QuapXufcrN58hPUcw==} + /@algolia/client-query-suggestions@5.20.3: + resolution: {integrity: sha512-Nn872PuOI8qzi1bxMMhJ0t2AzVBqN01jbymBQOkypvZHrrjZPso3iTpuuLLo9gi3yc/08vaaWTAwJfPhxPwJUw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-KL1zWTzrlN4MSiaK1ea560iCA/UewMbS4ZsLQRPoDTWyrbDKVbztkPwwv764LAqgXk0fvkNZvJ3IelcK7DqhjQ==} + /@algolia/client-search@5.20.3: + resolution: {integrity: sha512-9+Fm1ahV8/2goSIPIqZnVitV5yHW5E5xTdKy33xnqGd45A9yVv5tTkudWzEXsbfBB47j9Xb3uYPZjAvV5RHbKA==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-shj2lTdzl9un4XJblrgqg54DoK6JeKFO8K8qInMu4XhE2JuB8De6PUuXAQwiRigZupbI0xq8aM0LKdc9+qiLQA==} + /@algolia/ingestion@1.20.3: + resolution: {integrity: sha512-5GHNTiZ3saLjTNyr6WkP5hzDg2eFFAYWomvPcm9eHWskjzXt8R0IOiW9kkTS6I6hXBwN5H9Zna5mZDSqqJdg+g==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-aF9blPwOhKtWvkjyyXh9P5peqmhCA1XxLBRgItT+K6pbT0q4hBDQrCid+pQZJYy4HFUKjB/NDDwyzFhj/rwKhw==} + /@algolia/monitoring@1.20.3: + resolution: {integrity: sha512-KUWQbTPoRjP37ivXSQ1+lWMfaifCCMzTnEcEnXwAmherS5Tp7us6BAqQDMGOD4E7xyaS2I8pto6WlOzxH+CxmA==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-T6B/WPdZR3b89/F9Vvk6QCbt/wrLAtrGoL8z4qPXDFApQ8MuTFWbleN/4rHn6APWO3ps+BUePIEbue2rY5MlRw==} + /@algolia/recommend@5.20.3: + resolution: {integrity: sha512-oo/gG77xTTTclkrdFem0Kmx5+iSRFiwuRRdxZETDjwzCI7svutdbwBgV/Vy4D4QpYaX4nhY/P43k84uEowCE4Q==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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.0: - resolution: {integrity: sha512-t6//lXsq8E85JMenHrI6mhViipUT5riNhEfCcvtRsTV+KIBpC6Od18eK864dmBhoc5MubM0f+sGpKOqJIlBSCg==} + /@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.0 + '@algolia/client-common': 5.20.3 dev: true - /@algolia/requester-fetch@5.20.0: - resolution: {integrity: sha512-FHxYGqRY+6bgjKsK4aUsTAg6xMs2S21elPe4Y50GB0Y041ihvw41Vlwy2QS6K9ldoftX4JvXodbKTcmuQxywdQ==} + /@algolia/requester-fetch@5.20.3: + resolution: {integrity: sha512-eAVlXz7UNzTsA1EDr+p0nlIH7WFxo7k3NMxYe8p38DH8YVWLgm2MgOVFUMNg9HCi6ZNOi/A2w/id2ZZ4sKgUOw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.20.0 + '@algolia/client-common': 5.20.3 dev: true - /@algolia/requester-node-http@5.20.0: - resolution: {integrity: sha512-kmtQClq/w3vtPteDSPvaW9SPZL/xrIgMrxZyAgsFwrJk0vJxqyC5/hwHmrCraDnStnGSADnLpBf4SpZnwnkwWw==} + /@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.0 + '@algolia/client-common': 5.20.3 dev: true /@babel/code-frame@7.26.2: @@ -286,15 +295,22 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - /@babel/parser@7.26.5: - resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + /@babel/parser@7.26.9: + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 - /@babel/types@7.26.5: - resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} + /@babel/runtime@7.26.9: + resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} + 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==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.25.9 @@ -304,6 +320,74 @@ packages: resolution: {integrity: sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==} dev: true + /@codemirror/autocomplete@6.18.6: + resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + '@lezer/common': 1.2.3 + dev: true + + /@codemirror/commands@6.8.0: + resolution: {integrity: sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==} + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + '@lezer/common': 1.2.3 + dev: true + + /@codemirror/language@6.10.8: + resolution: {integrity: sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==} + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + '@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==} + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + crelt: 1.0.6 + dev: true + + /@codemirror/search@6.5.10: + resolution: {integrity: sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==} + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + crelt: 1.0.6 + dev: true + + /@codemirror/state@6.5.2: + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + dependencies: + '@marijn/find-cluster-break': 1.0.2 + dev: true + + /@codemirror/theme-one-dark@6.1.2: + resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + '@lezer/highlight': 1.2.1 + dev: true + + /@codemirror/view@6.36.3: + resolution: {integrity: sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==} + dependencies: + '@codemirror/state': 6.5.2 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + dev: true + /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -311,14 +395,14 @@ packages: dev: true optional: true - /@commitlint/cli@19.6.1(@types/node@22.10.7)(typescript@5.7.3): - resolution: {integrity: sha512-8hcyA6ZoHwWXC76BoC8qVOSr8xHy00LZhZpauiD0iO0VYbVhMnED0da85lTfIULxl7Lj4c6vZgF0Wu/ed1+jlQ==} + /@commitlint/cli@19.7.1(@types/node@22.13.5)(typescript@5.7.3): + resolution: {integrity: sha512-iObGjR1tE/PfDtDTEfd+tnRkB3/HJzpQqRTyofS2MPPkDn1mp3DBC8SoPDayokfAy+xKhF8+bwRCJO25Nea0YQ==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 19.5.0 - '@commitlint/lint': 19.6.0 - '@commitlint/load': 19.6.1(@types/node@22.10.7)(typescript@5.7.3) + '@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 tinyexec: 0.3.2 @@ -328,8 +412,8 @@ packages: - typescript dev: true - /@commitlint/config-conventional@19.6.0: - resolution: {integrity: sha512-DJT40iMnTYtBtUfw9ApbsLZFke1zKh6llITVJ+x9mtpHD08gsNXaIRqHTmwTZL3dNX5+WoyK7pCN/5zswvkBCQ==} + /@commitlint/config-conventional@19.7.1: + resolution: {integrity: sha512-fsEIF8zgiI/FIWSnykdQNj/0JE4av08MudLTyYHm4FlLWemKoQvPNUYU2M/3tktWcCEyq7aOkDDgtjrmgWFbvg==} engines: {node: '>=v18'} dependencies: '@commitlint/types': 19.5.0 @@ -369,25 +453,25 @@ packages: chalk: 5.4.1 dev: true - /@commitlint/is-ignored@19.6.0: - resolution: {integrity: sha512-Ov6iBgxJQFR9koOupDPHvcHU9keFupDgtB3lObdEZDroiG4jj1rzky60fbQozFKVYRTUdrBGICHG0YVmRuAJmw==} + /@commitlint/is-ignored@19.7.1: + resolution: {integrity: sha512-3IaOc6HVg2hAoGleRK3r9vL9zZ3XY0rf1RsUf6jdQLuaD46ZHnXBiOPTyQ004C4IvYjSWqJwlh0/u2P73aIE3g==} engines: {node: '>=v18'} dependencies: '@commitlint/types': 19.5.0 - semver: 7.6.3 + semver: 7.7.1 dev: true - /@commitlint/lint@19.6.0: - resolution: {integrity: sha512-LRo7zDkXtcIrpco9RnfhOKeg8PAnE3oDDoalnrVU/EVaKHYBWYL1DlRR7+3AWn0JiBqD8yKOfetVxJGdEtZ0tg==} + /@commitlint/lint@19.7.1: + resolution: {integrity: sha512-LhcPfVjcOcOZA7LEuBBeO00o3MeZa+tWrX9Xyl1r9PMd5FWsEoZI9IgnGqTKZ0lZt5pO3ZlstgnRyY1CJJc9Xg==} engines: {node: '>=v18'} dependencies: - '@commitlint/is-ignored': 19.6.0 + '@commitlint/is-ignored': 19.7.1 '@commitlint/parse': 19.5.0 '@commitlint/rules': 19.6.0 '@commitlint/types': 19.5.0 dev: true - /@commitlint/load@19.6.1(@types/node@22.10.7)(typescript@5.7.3): + /@commitlint/load@19.6.1(@types/node@22.13.5)(typescript@5.7.3): resolution: {integrity: sha512-kE4mRKWWNju2QpsCWt428XBvUH55OET2N4QKQ0bF85qS/XbsRGG1MiTByDNlEVpEPceMkDr46LNH95DtRwcsfA==} engines: {node: '>=v18'} dependencies: @@ -397,7 +481,7 @@ packages: '@commitlint/types': 19.5.0 chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.7.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.7)(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) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -487,7 +571,7 @@ packages: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 4.0.1 + form-data: 4.0.2 http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 @@ -496,7 +580,7 @@ packages: performance-now: 2.1.0 qs: 6.13.1 safe-buffer: 5.2.1 - tough-cookie: 5.1.0 + tough-cookie: 5.1.1 tunnel-agent: 0.6.0 uuid: 8.3.2 dev: true @@ -514,11 +598,11 @@ packages: resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} dev: true - /@docsearch/js@3.8.2(@algolia/client-search@5.20.0)(search-insights@2.17.3): + /@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.0)(search-insights@2.17.3) - preact: 10.25.4 + '@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' @@ -527,7 +611,7 @@ packages: - search-insights dev: true - /@docsearch/react@3.8.2(@algolia/client-search@5.20.0)(search-insights@2.17.3): + /@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' @@ -544,10 +628,12 @@ packages: search-insights: optional: true dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.20.0)(algoliasearch@5.20.0)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.20.0)(algoliasearch@5.20.0) + '@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.0 + 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' @@ -571,6 +657,15 @@ 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'} @@ -589,6 +684,15 @@ 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'} @@ -607,6 +711,15 @@ 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'} @@ -625,6 +738,15 @@ 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'} @@ -643,6 +765,15 @@ 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'} @@ -661,6 +792,15 @@ 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'} @@ -679,6 +819,15 @@ 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'} @@ -697,6 +846,15 @@ 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'} @@ -715,6 +873,15 @@ 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'} @@ -733,6 +900,15 @@ 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'} @@ -751,6 +927,15 @@ 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'} @@ -769,6 +954,15 @@ 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'} @@ -787,6 +981,15 @@ 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'} @@ -805,6 +1008,15 @@ 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'} @@ -823,6 +1035,15 @@ 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'} @@ -841,6 +1062,15 @@ 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'} @@ -859,6 +1089,15 @@ 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==} engines: {node: '>=18'} @@ -868,6 +1107,15 @@ 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'} @@ -886,6 +1134,15 @@ 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==} engines: {node: '>=18'} @@ -895,6 +1152,15 @@ 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'} @@ -913,6 +1179,15 @@ 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'} @@ -931,6 +1206,15 @@ 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'} @@ -949,6 +1233,15 @@ 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'} @@ -967,6 +1260,15 @@ 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'} @@ -985,13 +1287,22 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.1(eslint@9.18.0): + /@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==} 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.18.0 + eslint: 9.20.1 eslint-visitor-keys: 3.4.3 dev: true @@ -1000,19 +1311,19 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/config-array@0.19.1: - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + /@eslint/config-array@0.19.2: + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@eslint/object-schema': 2.1.5 + '@eslint/object-schema': 2.1.6 debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color dev: true - /@eslint/core@0.10.0: - resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} + /@eslint/core@0.11.0: + resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@types/json-schema': 7.0.15 @@ -1027,7 +1338,7 @@ packages: espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 @@ -1035,21 +1346,21 @@ packages: - supports-color dev: true - /@eslint/js@9.18.0: - resolution: {integrity: sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==} + /@eslint/js@9.20.0: + resolution: {integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@eslint/object-schema@2.1.5: - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + /@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.5: - resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} + /@eslint/plugin-kit@0.2.6: + resolution: {integrity: sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@eslint/core': 0.10.0 + '@eslint/core': 0.11.0 levn: 0.4.1 dev: true @@ -1076,13 +1387,13 @@ packages: engines: {node: '>=18.18'} dev: true - /@humanwhocodes/retry@0.4.1: - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + /@humanwhocodes/retry@0.4.2: + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} dev: true - /@iconify-json/simple-icons@1.2.21: - resolution: {integrity: sha512-aqbIuVshMZ2fNEhm25//9DoKudboXF3CpoEQJJlHl9gVSVNOTr4cgaCIZvgSEYmys2HHEfmhcpoZIhoEFZS8SQ==} + /@iconify-json/simple-icons@1.2.25: + resolution: {integrity: sha512-2E1/gOCO97rF6usfhhiXxwzCb+UhdEsxW3lW1Sew+xZY0COY6dp82Z/r1rUt2fWKneWjuoGcNeJHHXQyG8mIuw==} dependencies: '@iconify/types': 2.0.0 dev: true @@ -1091,8 +1402,8 @@ packages: resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} dev: true - /@inquirer/figures@1.0.9: - resolution: {integrity: sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==} + /@inquirer/figures@1.0.10: + resolution: {integrity: sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==} engines: {node: '>=18'} dev: true @@ -1234,6 +1545,26 @@ packages: '@jridgewell/sourcemap-codec': 1.5.0 dev: true + /@lezer/common@1.2.3: + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + dev: true + + /@lezer/highlight@1.2.1: + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + dependencies: + '@lezer/common': 1.2.3 + dev: true + + /@lezer/lr@1.4.2: + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + dependencies: + '@lezer/common': 1.2.3 + dev: true + + /@marijn/find-cluster-break@1.0.2: + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + dev: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1252,15 +1583,15 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.19.0 dev: true /@one-ini/wasm@0.1.1: resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} dev: true - /@parcel/watcher-android-arm64@2.5.0: - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} + /@parcel/watcher-android-arm64@2.5.1: + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] @@ -1268,8 +1599,8 @@ packages: dev: true optional: true - /@parcel/watcher-darwin-arm64@2.5.0: - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} + /@parcel/watcher-darwin-arm64@2.5.1: + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] @@ -1277,8 +1608,8 @@ packages: dev: true optional: true - /@parcel/watcher-darwin-x64@2.5.0: - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} + /@parcel/watcher-darwin-x64@2.5.1: + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] @@ -1286,8 +1617,8 @@ packages: dev: true optional: true - /@parcel/watcher-freebsd-x64@2.5.0: - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} + /@parcel/watcher-freebsd-x64@2.5.1: + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] @@ -1295,8 +1626,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm-glibc@2.5.0: - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} + /@parcel/watcher-linux-arm-glibc@2.5.1: + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] @@ -1304,8 +1635,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm-musl@2.5.0: - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} + /@parcel/watcher-linux-arm-musl@2.5.1: + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] @@ -1313,8 +1644,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm64-glibc@2.5.0: - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} + /@parcel/watcher-linux-arm64-glibc@2.5.1: + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] @@ -1322,8 +1653,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm64-musl@2.5.0: - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} + /@parcel/watcher-linux-arm64-musl@2.5.1: + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] @@ -1331,8 +1662,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-x64-glibc@2.5.0: - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} + /@parcel/watcher-linux-x64-glibc@2.5.1: + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] @@ -1340,8 +1671,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-x64-musl@2.5.0: - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} + /@parcel/watcher-linux-x64-musl@2.5.1: + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] @@ -1349,8 +1680,8 @@ packages: dev: true optional: true - /@parcel/watcher-win32-arm64@2.5.0: - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} + /@parcel/watcher-win32-arm64@2.5.1: + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] @@ -1358,8 +1689,8 @@ packages: dev: true optional: true - /@parcel/watcher-win32-ia32@2.5.0: - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} + /@parcel/watcher-win32-ia32@2.5.1: + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] @@ -1367,8 +1698,8 @@ packages: dev: true optional: true - /@parcel/watcher-win32-x64@2.5.0: - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} + /@parcel/watcher-win32-x64@2.5.1: + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] @@ -1376,8 +1707,8 @@ packages: dev: true optional: true - /@parcel/watcher@2.5.0: - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} + /@parcel/watcher@2.5.1: + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} engines: {node: '>= 10.0.0'} requiresBuild: true dependencies: @@ -1386,19 +1717,19 @@ packages: micromatch: 4.0.8 node-addon-api: 7.1.1 optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.0 - '@parcel/watcher-darwin-arm64': 2.5.0 - '@parcel/watcher-darwin-x64': 2.5.0 - '@parcel/watcher-freebsd-x64': 2.5.0 - '@parcel/watcher-linux-arm-glibc': 2.5.0 - '@parcel/watcher-linux-arm-musl': 2.5.0 - '@parcel/watcher-linux-arm64-glibc': 2.5.0 - '@parcel/watcher-linux-arm64-musl': 2.5.0 - '@parcel/watcher-linux-x64-glibc': 2.5.0 - '@parcel/watcher-linux-x64-musl': 2.5.0 - '@parcel/watcher-win32-arm64': 2.5.0 - '@parcel/watcher-win32-ia32': 2.5.0 - '@parcel/watcher-win32-x64': 2.5.0 + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 dev: true optional: true @@ -1442,15 +1773,15 @@ packages: config-chain: 1.1.13 dev: false - /@quasar/app-vite@2.0.8(@types/node@22.10.7)(eslint@9.18.0)(pinia@2.3.1)(quasar@2.17.7)(sass@1.83.4)(typescript@5.7.3)(vue-router@4.5.0)(vue@3.5.13): - resolution: {integrity: sha512-E2l5vV0Fi955U2Uz+iSAeVaJzsA0x5GY9ZMU6irIJWep39O/zpFGcyGz9uXjBEBkOX002id1P5HoGnh4Tm4alQ==} + /@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==} engines: {node: ^30 || ^28 || ^26 || ^24 || ^22 || ^20 || ^18, npm: '>= 6.14.12', yarn: '>= 1.17.3'} hasBin: true peerDependencies: '@electron/packager': '>= 18' electron-builder: '>= 22' eslint: '*' - pinia: ^2.0.0 + pinia: ^2.0.0 || ^3.0.0 quasar: ^2.16.0 typescript: '>= 5.4' vue: ^3.2.29 @@ -1472,16 +1803,16 @@ 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.0.11)(vue@3.5.13) + '@quasar/vite-plugin': 1.9.0(@vitejs/plugin-vue@5.2.1)(quasar@2.17.7)(vite@6.1.1)(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.0.11)(vue@3.5.13) + '@vitejs/plugin-vue': 5.2.1(vite@6.2.0)(vue@3.5.13) archiver: 7.0.1 chokidar: 3.6.0 ci-info: 4.1.0 - compression: 1.7.5 + compression: 1.8.0 confbox: 0.1.8 cross-spawn: 7.0.6 dot-prop: 9.0.0 @@ -1489,7 +1820,7 @@ packages: dotenv-expand: 11.0.7 elementtree: 0.1.7 esbuild: 0.24.2 - eslint: 9.18.0 + eslint: 9.20.1 express: 4.21.2 fs-extra: 11.3.0 html-minifier-terser: 7.2.0 @@ -1502,13 +1833,13 @@ packages: pinia: 2.3.1(typescript@5.7.3)(vue@3.5.13) quasar: 2.17.7 rollup-plugin-visualizer: 5.14.0 - sass-embedded: 1.83.4 - semver: 7.6.3 + sass-embedded: 1.85.0 + semver: 7.7.1 serialize-javascript: 6.0.2 - tinyglobby: 0.2.10 + tinyglobby: 0.2.12 ts-essentials: 9.4.2(typescript@5.7.3) typescript: 5.7.3 - vite: 6.0.11(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.83.4) + 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) vue-router: 4.5.0(vue@3.5.13) webpack-merge: 6.0.1 @@ -1535,7 +1866,7 @@ packages: dependencies: '@quasar/ssl-certificate': 1.0.0 ci-info: 4.1.0 - compression: 1.7.5 + compression: 1.8.0 connect-history-api-fallback: 2.0.0 cors: 2.8.5 cross-spawn: 7.0.6 @@ -1553,18 +1884,18 @@ packages: - supports-color dev: false - /@quasar/extras@1.16.16: - resolution: {integrity: sha512-aswGUbEyLvt45KB1u6hBD3s82KnOdkqTn6YVu3xX5aGgwQkCWPyqb3FMTEHG+4+gGTMp4pIcnng96RlqswQctQ==} + /@quasar/extras@1.16.17: + resolution: {integrity: sha512-4aX9XU/oj1+8O2C7LQCgywmoIw7suyUEZMPFFLWI61f21mF55VOsMdLCBhjeFgL5U4EWy079mfOR6/J8thi/ag==} dev: false - /@quasar/quasar-app-extension-qcalendar@4.0.3: - resolution: {integrity: sha512-cmPsNKj/UdQYMouh1jc4pj1dsBCp8N1FiIWZPfnqUslo9cFNan5gUs5ENZ2PhMpoT+8XgZDhE0staeUdHglb+g==} - engines: {node: '>= 10.0.0', npm: '>= 5.6.0', yarn: '>= 1.6.0'} + /@quasar/quasar-app-extension-qcalendar@4.1.2: + resolution: {integrity: sha512-uhZ0k8znOQg8pGl+vc9VW+np72znuzaIMGsdGgI1pY/0/pSZ1rzsBT8xALX5T0oQXJkOT9OHwSrsw7WJxFGD9A==} + engines: {node: ^28 || ^26 || ^24 || ^22 || ^20 || ^18, npm: '>= 6.13.4', yarn: '>= 1.21.1'} dependencies: - '@quasar/quasar-ui-qcalendar': 4.0.3 + '@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.0.11)(vitest@0.34.6)(vue@3.5.13): + /@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): resolution: {integrity: sha512-eyzdUdmZiCueNS+5nedjMmzdbpCetSrtdGIwW6KplW1dTzRbLiNvYUjpBOxQGmJCgEhWy9zuswJ7MZ/bTql24Q==} engines: {node: '>= 12.22.1', npm: '>= 6.14.12', yarn: '>= 1.17.3'} peerDependencies: @@ -1581,9 +1912,9 @@ packages: happy-dom: 11.2.0 lodash-es: 4.17.21 quasar: 2.17.7 - vite-jsconfig-paths: 2.0.1(vite@6.0.11) - vite-tsconfig-paths: 4.3.2(typescript@5.7.3)(vite@6.0.11) - vitest: 0.34.6(sass@1.83.4) + 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) transitivePeerDependencies: - supports-color @@ -1591,8 +1922,8 @@ packages: - vite dev: true - /@quasar/quasar-ui-qcalendar@4.0.3: - resolution: {integrity: sha512-/+TQSWnWjOu9VDgV7qpOcJlYqpMm3nXVk2VfJfIYoMwKvjWAJmY6HDxdupx+0aTg2lMftXnOkZDLG9rnxpQ98g==} + /@quasar/quasar-ui-qcalendar@4.1.2: + resolution: {integrity: sha512-z4ZesDZbHvA0w6CvB8Sm5rsUhyUNO+7F9fO32wYssjX3m4oBi0OzRxWZRkOD/s7wtx0WxUZEllHP2UEx/whaBg==} dev: true /@quasar/render-ssr-error@1.0.3: @@ -1609,7 +1940,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.0.11)(vue@3.5.13): + /@quasar/vite-plugin@1.9.0(@vitejs/plugin-vue@5.2.1)(quasar@2.17.7)(vite@6.1.1)(vue@3.5.13): resolution: {integrity: sha512-r1MFtI2QZJ2g20pe75Zuv4aoi0uoK8oP0yEdzLWRoOLCbhtf2+StJpUza9TydYi3KcvCl9+4HUf3OAWVKoxDmQ==} engines: {node: '>=18'} peerDependencies: @@ -1618,9 +1949,9 @@ 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.0.11)(vue@3.5.13) + '@vitejs/plugin-vue': 5.2.1(vite@6.2.0)(vue@3.5.13) quasar: 2.17.7 - vite: 6.0.11(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.83.4) + 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) dev: true @@ -1632,212 +1963,212 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/rollup-android-arm-eabi@4.31.0: - resolution: {integrity: sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==} + /@rollup/rollup-android-arm-eabi@4.34.8: + resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.31.0: - resolution: {integrity: sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==} + /@rollup/rollup-android-arm64@4.34.8: + resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.31.0: - resolution: {integrity: sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==} + /@rollup/rollup-darwin-arm64@4.34.8: + resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.31.0: - resolution: {integrity: sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==} + /@rollup/rollup-darwin-x64@4.34.8: + resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-freebsd-arm64@4.31.0: - resolution: {integrity: sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==} + /@rollup/rollup-freebsd-arm64@4.34.8: + resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} cpu: [arm64] os: [freebsd] requiresBuild: true dev: true optional: true - /@rollup/rollup-freebsd-x64@4.31.0: - resolution: {integrity: sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==} + /@rollup/rollup-freebsd-x64@4.34.8: + resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} cpu: [x64] os: [freebsd] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.31.0: - resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==} + /@rollup/rollup-linux-arm-gnueabihf@4.34.8: + resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf@4.31.0: - resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==} + /@rollup/rollup-linux-arm-musleabihf@4.34.8: + resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.31.0: - resolution: {integrity: sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==} + /@rollup/rollup-linux-arm64-gnu@4.34.8: + resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.31.0: - resolution: {integrity: sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==} + /@rollup/rollup-linux-arm64-musl@4.34.8: + resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-loongarch64-gnu@4.31.0: - resolution: {integrity: sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==} + /@rollup/rollup-linux-loongarch64-gnu@4.34.8: + resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} cpu: [loong64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.31.0: - resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} + /@rollup/rollup-linux-powerpc64le-gnu@4.34.8: + resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.31.0: - resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==} + /@rollup/rollup-linux-riscv64-gnu@4.34.8: + resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.31.0: - resolution: {integrity: sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==} + /@rollup/rollup-linux-s390x-gnu@4.34.8: + resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.31.0: - resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==} + /@rollup/rollup-linux-x64-gnu@4.34.8: + resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.31.0: - resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==} + /@rollup/rollup-linux-x64-musl@4.34.8: + resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.31.0: - resolution: {integrity: sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==} + /@rollup/rollup-win32-arm64-msvc@4.34.8: + resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.31.0: - resolution: {integrity: sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==} + /@rollup/rollup-win32-ia32-msvc@4.34.8: + resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.31.0: - resolution: {integrity: sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==} + /@rollup/rollup-win32-x64-msvc@4.34.8: + resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@shikijs/core@2.1.0: - resolution: {integrity: sha512-v795KDmvs+4oV0XD05YLzfDMe9ISBgNjtFxP4PAEv5DqyeghO1/TwDqs9ca5/E6fuO95IcAcWqR6cCX9TnqLZA==} + /@shikijs/core@2.5.0: + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} dependencies: - '@shikijs/engine-javascript': 2.1.0 - '@shikijs/engine-oniguruma': 2.1.0 - '@shikijs/types': 2.1.0 - '@shikijs/vscode-textmate': 10.0.1 + '@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.4 + hast-util-to-html: 9.0.5 dev: true - /@shikijs/engine-javascript@2.1.0: - resolution: {integrity: sha512-cgIUdAliOsoaa0rJz/z+jvhrpRd+fVAoixVFEVxUq5FA+tHgBZAIfVJSgJNVRj2hs/wZ1+4hMe82eKAThVh0nQ==} + /@shikijs/engine-javascript@2.5.0: + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} dependencies: - '@shikijs/types': 2.1.0 - '@shikijs/vscode-textmate': 10.0.1 - oniguruma-to-es: 2.3.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 dev: true - /@shikijs/engine-oniguruma@2.1.0: - resolution: {integrity: sha512-Ujik33wEDqgqY2WpjRDUBECGcKPv3eGGkoXPujIXvokLaRmGky8NisSk8lHUGeSFxo/Cz5sgFej9sJmA9yeepg==} + /@shikijs/engine-oniguruma@2.5.0: + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} dependencies: - '@shikijs/types': 2.1.0 - '@shikijs/vscode-textmate': 10.0.1 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 dev: true - /@shikijs/langs@2.1.0: - resolution: {integrity: sha512-Jn0gS4rPgerMDPj1ydjgFzZr5fAIoMYz4k7ZT3LJxWWBWA6lokK0pumUwVtb+MzXtlpjxOaQejLprmLbvMZyww==} + /@shikijs/langs@2.5.0: + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} dependencies: - '@shikijs/types': 2.1.0 + '@shikijs/types': 2.5.0 dev: true - /@shikijs/themes@2.1.0: - resolution: {integrity: sha512-oS2mU6+bz+8TKutsjBxBA7Z3vrQk21RCmADLpnu8cy3tZD6Rw0FKqDyXNtwX52BuIDKHxZNmRlTdG3vtcYv3NQ==} + /@shikijs/themes@2.5.0: + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} dependencies: - '@shikijs/types': 2.1.0 + '@shikijs/types': 2.5.0 dev: true - /@shikijs/transformers@2.1.0: - resolution: {integrity: sha512-3sfvh6OKUVkT5wZFU1xxiq1qqNIuCwUY3yOb9ZGm19y80UZ/eoroLE2orGNzfivyTxR93GfXXZC/ghPR0/SBow==} + /@shikijs/transformers@2.5.0: + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} dependencies: - '@shikijs/core': 2.1.0 - '@shikijs/types': 2.1.0 + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 dev: true - /@shikijs/types@2.1.0: - resolution: {integrity: sha512-OFOdHA6VEVbiQvepJ8yqicC6VmBrKxFFhM2EsHHrZESqLVAXOSeRDiuSYV185lIgp15TVic5vYBYNhTsk1xHLg==} + /@shikijs/types@2.5.0: + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} dependencies: - '@shikijs/vscode-textmate': 10.0.1 + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 dev: true - /@shikijs/vscode-textmate@10.0.1: - resolution: {integrity: sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==} + /@shikijs/vscode-textmate@10.0.2: + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} dev: true /@sinclair/typebox@0.27.8: @@ -1854,6 +2185,10 @@ packages: engines: {node: '>=14.16'} dev: false + /@socket.io/component-emitter@3.1.2: + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + dev: true + /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -1872,7 +2207,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: true /@types/cacheable-request@6.0.3: @@ -1880,7 +2215,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 22.10.7 + '@types/node': 22.13.4 '@types/responselike': 1.0.3 dev: false @@ -1910,19 +2245,25 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: true /@types/conventional-commits-parser@5.0.1: resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: true /@types/cordova@11.0.3: resolution: {integrity: sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==} dev: true + /@types/cors@2.8.17: + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + dependencies: + '@types/node': 22.13.5 + dev: true + /@types/estree@1.0.6: resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} dev: true @@ -1930,7 +2271,7 @@ packages: /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -1973,10 +2314,10 @@ packages: resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} dev: true - /@types/http-proxy@1.17.15: - resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==} + /@types/http-proxy@1.17.16: + resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: false /@types/json-schema@7.0.15: @@ -1990,7 +2331,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: false /@types/linkify-it@5.0.0: @@ -2021,13 +2362,19 @@ packages: /@types/node-forge@1.3.11: resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 - /@types/node@22.10.7: - resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + /@types/node@22.13.4: + resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==} dependencies: undici-types: 6.20.0 + /@types/node@22.13.5: + resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==} + dependencies: + undici-types: 6.20.0 + dev: true + /@types/qs@6.9.18: resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} dev: true @@ -2039,21 +2386,21 @@ packages: /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: false /@types/send@0.17.4: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 22.10.7 + '@types/node': 22.13.4 dev: true /@types/serve-static@1.15.7: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.10.7 + '@types/node': 22.13.4 '@types/send': 0.17.4 dev: true @@ -2077,10 +2424,57 @@ packages: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.5 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==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@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/search': 6.5.10 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + 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==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@babel/runtime': 7.26.9 + '@codemirror/commands': 6.8.0 + '@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: 6.0.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + dev: true + /@ungap/structured-clone@1.3.0: resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} dev: true @@ -2092,18 +2486,18 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 dependencies: - vite: 5.4.14(@types/node@22.10.7)(sass@1.83.4) + vite: 5.4.14(@types/node@22.13.5)(sass@1.85.0) vue: 3.5.13(typescript@5.7.3) dev: true - /@vitejs/plugin-vue@5.2.1(vite@6.0.11)(vue@3.5.13): + /@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} peerDependencies: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 dependencies: - vite: 6.0.11(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.83.4) + vite: 6.2.0(@types/node@22.13.5)(sass@1.85.0) vue: 3.5.13(typescript@5.7.3) dev: true @@ -2148,7 +2542,7 @@ packages: /@vue/compiler-core@3.5.13: resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} dependencies: - '@babel/parser': 7.26.5 + '@babel/parser': 7.26.9 '@vue/shared': 3.5.13 entities: 4.5.0 estree-walker: 2.0.2 @@ -2163,14 +2557,14 @@ packages: /@vue/compiler-sfc@3.5.13: resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} dependencies: - '@babel/parser': 7.26.5 + '@babel/parser': 7.26.9 '@vue/compiler-core': 3.5.13 '@vue/compiler-dom': 3.5.13 '@vue/compiler-ssr': 3.5.13 '@vue/shared': 3.5.13 estree-walker: 2.0.2 magic-string: 0.30.17 - postcss: 8.5.1 + postcss: 8.5.3 source-map-js: 1.2.1 /@vue/compiler-ssr@3.5.13: @@ -2182,16 +2576,16 @@ packages: /@vue/devtools-api@6.6.4: resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - /@vue/devtools-api@7.7.1: - resolution: {integrity: sha512-Cexc8GimowoDkJ6eNelOPdYIzsu2mgNyp0scOQ3tiaYSb9iok6LOESSsJvHaI+ib3joRfqRJNLkHFjhNuWA5dg==} + /@vue/devtools-api@7.7.2: + resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==} dependencies: - '@vue/devtools-kit': 7.7.1 + '@vue/devtools-kit': 7.7.2 dev: true - /@vue/devtools-kit@7.7.1: - resolution: {integrity: sha512-yhZ4NPnK/tmxGtLNQxmll90jIIXdb2jAhPF76anvn5M/UkZCiLJy28bYgPIACKZ7FCosyKoaope89/RsFJll1w==} + /@vue/devtools-kit@7.7.2: + resolution: {integrity: sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==} dependencies: - '@vue/devtools-shared': 7.7.1 + '@vue/devtools-shared': 7.7.2 birpc: 0.2.19 hookable: 5.5.3 mitt: 3.0.1 @@ -2200,8 +2594,8 @@ packages: superjson: 2.2.2 dev: true - /@vue/devtools-shared@7.7.1: - resolution: {integrity: sha512-BtgF7kHq4BHG23Lezc/3W2UhK2ga7a8ohAIAGJMBr4BkxUFzhqntQtCiuL1ijo2ztWnmusymkirgqUrXoQKumA==} + /@vue/devtools-shared@7.7.2: + resolution: {integrity: sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==} dependencies: rfdc: 1.4.1 dev: true @@ -2240,23 +2634,23 @@ packages: /@vue/test-utils@2.4.6: resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} dependencies: - js-beautify: 1.15.1 - vue-component-type-helpers: 2.2.0 + js-beautify: 1.15.3 + vue-component-type-helpers: 2.2.2 dev: true - /@vueuse/core@12.5.0(typescript@5.7.3): - resolution: {integrity: sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==} + /@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.5.0 - '@vueuse/shared': 12.5.0(typescript@5.7.3) + '@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.5.0(axios@1.7.9)(focus-trap@7.6.4)(typescript@5.7.3): - resolution: {integrity: sha512-HYLt8M6mjUfcoUOzyBcX2RjpfapIwHPBmQJtTmXOQW845Y/Osu9VuTJ5kPvnmWJ6IUa05WpblfOwZ+P0G4iZsQ==} + /@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 @@ -2296,8 +2690,8 @@ packages: universal-cookie: optional: true dependencies: - '@vueuse/core': 12.5.0(typescript@5.7.3) - '@vueuse/shared': 12.5.0(typescript@5.7.3) + '@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) @@ -2305,12 +2699,12 @@ packages: - typescript dev: true - /@vueuse/metadata@12.5.0: - resolution: {integrity: sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==} + /@vueuse/metadata@12.7.0: + resolution: {integrity: sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==} dev: true - /@vueuse/shared@12.5.0(typescript@5.7.3): - resolution: {integrity: sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==} + /@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: @@ -2325,9 +2719,9 @@ packages: through: 2.3.8 dev: true - /abbrev@2.0.0: - resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + /abbrev@3.0.0: + resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==} + engines: {node: ^18.17.0 || >=20.5.0} dev: true /abort-controller@3.0.0: @@ -2405,23 +2799,23 @@ packages: require-from-string: 2.0.2 dev: true - /algoliasearch@5.20.0: - resolution: {integrity: sha512-groO71Fvi5SWpxjI9Ia+chy0QBwT61mg6yxJV27f5YFf+Mw+STT75K6SHySpP8Co5LsCrtsbCH5dJZSRtkSKaQ==} + /algoliasearch@5.20.3: + resolution: {integrity: sha512-iNC6BGvipaalFfDfDnXUje8GUlW5asj0cTMsZJwO/0rhsyLx1L7GZFAY8wW+eQ6AM4Yge2p5GSE5hrBlfSD90Q==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-abtesting': 5.20.0 - '@algolia/client-analytics': 5.20.0 - '@algolia/client-common': 5.20.0 - '@algolia/client-insights': 5.20.0 - '@algolia/client-personalization': 5.20.0 - '@algolia/client-query-suggestions': 5.20.0 - '@algolia/client-search': 5.20.0 - '@algolia/ingestion': 1.20.0 - '@algolia/monitoring': 1.20.0 - '@algolia/recommend': 5.20.0 - '@algolia/requester-browser-xhr': 5.20.0 - '@algolia/requester-fetch': 5.20.0 - '@algolia/requester-node-http': 5.20.0 + '@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: @@ -2551,7 +2945,7 @@ packages: engines: {node: '>= 4.0.0'} dev: true - /autoprefixer@10.4.20(postcss@8.5.1): + /autoprefixer@10.4.20(postcss@8.5.3): resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} hasBin: true @@ -2559,11 +2953,11 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.4 - caniuse-lite: 1.0.30001695 + caniuse-lite: 1.0.30001700 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.1 + postcss: 8.5.3 postcss-value-parser: 4.2.0 dev: true @@ -2579,7 +2973,7 @@ packages: resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} dependencies: follow-redirects: 1.15.9 - form-data: 4.0.1 + form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -2601,6 +2995,11 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true + /base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + dev: true + /bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} dependencies: @@ -2708,8 +3107,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001695 - electron-to-chromium: 1.5.84 + caniuse-lite: 1.0.30001700 + electron-to-chromium: 1.5.102 node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) dev: true @@ -2806,8 +3205,8 @@ packages: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} - /call-bind-apply-helpers@1.0.1: - resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + /call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 @@ -2817,7 +3216,7 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.2.7 /callsites@3.1.0: @@ -2847,8 +3246,8 @@ packages: engines: {node: '>=14.16'} dev: false - /caniuse-lite@1.0.30001695: - resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} + /caniuse-lite@1.0.30001700: + resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} dev: true /caseless@0.12.0: @@ -2926,7 +3325,7 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} dependencies: - readdirp: 4.1.1 + readdirp: 4.1.2 dev: true /chromium@3.0.3: @@ -3014,14 +3413,6 @@ packages: wrap-ansi: 6.2.0 dev: true - /cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: true - /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -3051,6 +3442,18 @@ packages: engines: {node: '>=0.8'} dev: true + /codemirror@6.0.1: + 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/search': 6.5.10 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.3 + dev: true + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -3128,8 +3531,8 @@ packages: dependencies: mime-db: 1.53.0 - /compression@1.7.5: - resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==} + /compression@1.8.0: + resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==} engines: {node: '>= 0.8.0'} dependencies: bytes: 3.1.2 @@ -3181,6 +3584,11 @@ packages: engines: {node: '>=0.8'} dev: false + /console-clear@1.1.1: + resolution: {integrity: sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==} + engines: {node: '>=4'} + dev: true + /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -3223,6 +3631,11 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + /cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + dev: true + /copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -3243,9 +3656,8 @@ packages: dependencies: object-assign: 4.1.1 vary: 1.1.2 - dev: false - /cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.7)(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): resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} engines: {node: '>=v18'} peerDependencies: @@ -3253,7 +3665,7 @@ packages: cosmiconfig: '>=9' typescript: '>=5' dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.5 cosmiconfig: 9.0.0(typescript@5.7.3) jiti: 2.4.2 typescript: 5.7.3 @@ -3269,7 +3681,7 @@ packages: optional: true dependencies: env-paths: 2.2.1 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 typescript: 5.7.3 @@ -3289,6 +3701,10 @@ packages: readable-stream: 4.7.0 dev: true + /crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + dev: true + /croppie@2.6.5: resolution: {integrity: sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ==} dev: false @@ -3321,7 +3737,7 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - /cypress-mochawesome-reporter@3.8.2(cypress@13.17.0)(mocha@11.0.1): + /cypress-mochawesome-reporter@3.8.2(cypress@14.1.0)(mocha@11.1.0): resolution: {integrity: sha512-oJZkNzhNmN9ZD+LmZyFuPb8aWaIijyHyqYh52YOBvR6B6ckfJNCHP3A98a+/nG0H4t46CKTNwo+wNpMa4d2kjA==} engines: {node: '>=14'} hasBin: true @@ -3329,18 +3745,18 @@ packages: cypress: '>=6.2.0' dependencies: commander: 10.0.1 - cypress: 13.17.0 + cypress: 14.1.0 fs-extra: 10.1.0 - mochawesome: 7.1.3(mocha@11.0.1) - mochawesome-merge: 4.3.0 + mochawesome: 7.1.3(mocha@11.1.0) + mochawesome-merge: 4.4.1 mochawesome-report-generator: 6.2.0 transitivePeerDependencies: - mocha dev: true - /cypress@13.17.0: - resolution: {integrity: sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==} - engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} + /cypress@14.1.0: + resolution: {integrity: sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true requiresBuild: true dependencies: @@ -3381,7 +3797,7 @@ packages: process: 0.11.10 proxy-from-env: 1.0.0 request-progress: 3.0.0 - semver: 7.6.3 + semver: 7.7.1 supports-color: 8.1.1 tmp: 0.2.3 tree-kill: 1.2.2 @@ -3409,6 +3825,10 @@ packages: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} dev: true + /debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + dev: true + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -3442,6 +3862,18 @@ packages: supports-color: 8.1.1 dev: true + /debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + /debug@4.4.0(supports-color@8.1.1): resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -3555,6 +3987,11 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + /detect-file-encoding-and-language@2.4.0: + resolution: {integrity: sha512-moFSAumrGlLCNU5jnaHyCzRUJJu0BCZunfL08iMbnDAgvNnxZad7+WZ26U2dsrIbGChlDPLKmEyEb2tEPUJFkw==} + hasBin: true + dev: true + /detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -3604,7 +4041,7 @@ packages: resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} engines: {node: '>=18'} dependencies: - type-fest: 4.33.0 + type-fest: 4.35.0 dev: true /dotenv-expand@11.0.7: @@ -3623,7 +4060,7 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 @@ -3645,14 +4082,14 @@ packages: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.6.3 + semver: 7.7.1 dev: true /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - /electron-to-chromium@1.5.84: - resolution: {integrity: sha512-I+DQ8xgafao9Ha6y0qjHHvpZ9OfyA1qKlkHkjywxzniORU2awxyz7f/iVJcULmrF2yrM3nHQf+iDjJtbbexd/g==} + /electron-to-chromium@1.5.102: + resolution: {integrity: sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==} dev: true /elementtree@0.1.7: @@ -3685,6 +4122,30 @@ packages: dependencies: once: 1.4.0 + /engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + dev: true + + /engine.io@6.6.4: + resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} + engines: {node: '>=10.2.0'} + dependencies: + '@types/cors': 2.8.17 + '@types/node': 22.13.5 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.5 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -3722,6 +4183,15 @@ packages: dependencies: es-errors: 1.3.0 + /es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + 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'} @@ -3786,6 +4256,39 @@ packages: '@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 + dev: true + /escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -3809,38 +4312,38 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier@10.0.1(eslint@9.18.0): + /eslint-config-prettier@10.0.1(eslint@9.20.1): resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.18.0 + eslint: 9.20.1 dev: true - /eslint-plugin-cypress@4.1.0(eslint@9.18.0): + /eslint-plugin-cypress@4.1.0(eslint@9.20.1): resolution: {integrity: sha512-JhqkMY02mw74USwK9OFhectx3YSj6Co1NgWBxlGdKvlqiAp9vdEuQqt33DKGQFvvGS/NWtduuhWXWNnU29xDSg==} peerDependencies: eslint: '>=9' dependencies: - eslint: 9.18.0 - globals: 15.14.0 + eslint: 9.20.1 + globals: 15.15.0 dev: true - /eslint-plugin-vue@9.32.0(eslint@9.18.0): + /eslint-plugin-vue@9.32.0(eslint@9.20.1): resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==} 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.18.0) - eslint: 9.18.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) + eslint: 9.20.1 globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 - semver: 7.6.3 - vue-eslint-parser: 9.4.3(eslint@9.18.0) + semver: 7.7.1 + vue-eslint-parser: 9.4.3(eslint@9.20.1) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -3884,8 +4387,8 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /eslint@9.18.0: - resolution: {integrity: sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==} + /eslint@9.20.1: + resolution: {integrity: sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3894,16 +4397,16 @@ packages: jiti: optional: true dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.18.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.10.0 + '@eslint/config-array': 0.19.2 + '@eslint/core': 0.11.0 '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.18.0 - '@eslint/plugin-kit': 0.2.5 + '@eslint/js': 9.20.0 + '@eslint/plugin-kit': 0.2.6 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 + '@humanwhocodes/retry': 0.4.2 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 ajv: 6.12.6 @@ -4173,8 +4676,8 @@ packages: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} dev: true - /fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + /fastq@1.19.0: + resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} dependencies: reusify: 1.0.4 dev: true @@ -4258,7 +4761,7 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 dev: true @@ -4267,8 +4770,8 @@ packages: hasBin: true dev: true - /flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + /flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} dev: true /focus-trap@7.6.4: @@ -4303,12 +4806,13 @@ packages: engines: {node: '>= 14.17'} dev: false - /form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + /form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 mime-types: 2.1.35 /forwarded@0.2.0: @@ -4359,6 +4863,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==} @@ -4390,7 +4898,7 @@ packages: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 @@ -4401,6 +4909,11 @@ packages: hasown: 2.0.2 math-intrinsics: 1.1.0 + /get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + dev: true + /get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -4507,8 +5020,8 @@ packages: engines: {node: '>=18'} dev: true - /globals@15.14.0: - resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + /globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} dev: true @@ -4561,6 +5074,19 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + dev: true + /happy-dom@11.2.0: resolution: {integrity: sha512-z4PshcYIIH6SkymSNRcDFwYUJOENe+FOQDx5BbHgg/wQUgxF5p9I9/BN45Jff34bbhXV8yJgkC5N99eyOzXK3w==} dependencies: @@ -4580,6 +5106,12 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.1.0 + /has-yarn@3.0.0: resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4591,8 +5123,8 @@ packages: dependencies: function-bind: 1.1.2 - /hast-util-to-html@9.0.4: - resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==} + /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 @@ -4601,7 +5133,7 @@ packages: hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.0 - property-information: 6.5.0 + property-information: 7.0.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 zwitch: 2.0.4 @@ -4633,7 +5165,7 @@ packages: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.37.0 + terser: 5.39.0 dev: true /html-void-elements@3.0.0: @@ -4663,7 +5195,7 @@ packages: '@types/express': optional: true dependencies: - '@types/http-proxy': 1.17.15 + '@types/http-proxy': 1.17.16 http-proxy: 1.18.1 is-glob: 4.0.3 is-plain-obj: 3.0.0 @@ -4755,8 +5287,8 @@ packages: resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} dev: true - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + /import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} dependencies: parent-module: 1.0.1 @@ -4807,7 +5339,7 @@ packages: resolution: {integrity: sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==} engines: {node: '>=18'} dependencies: - '@inquirer/figures': 1.0.9 + '@inquirer/figures': 1.0.10 ansi-escapes: 4.3.2 cli-width: 4.1.0 external-editor: 3.1.0 @@ -4821,6 +5353,10 @@ packages: yoctocolors-cjs: 2.1.2 dev: true + /ip@1.1.9: + resolution: {integrity: sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==} + dev: true + /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -5000,8 +5536,8 @@ packages: hasBin: true dev: true - /js-beautify@1.15.1: - resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} + /js-beautify@1.15.3: + resolution: {integrity: sha512-rKKGuyTxGNlyN4EQKWzNndzXpi0bOl8Gl8YQAW1as/oMz0XhD6sHJO1hTvoBDOSzKuJb9WkwoAb34FfdkKMv2A==} engines: {node: '>=14'} hasBin: true dependencies: @@ -5009,7 +5545,7 @@ packages: editorconfig: 1.0.4 glob: 10.4.5 js-cookie: 3.0.5 - nopt: 7.2.1 + nopt: 8.1.0 dev: true /js-cookie@3.0.5: @@ -5111,6 +5647,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: @@ -5323,6 +5869,12 @@ packages: yallist: 2.1.2 dev: false + /lzutf8@0.6.3: + resolution: {integrity: sha512-CAkF9HKrM+XpB0f3DepQ2to2iUEo0zrbh+XgBqgNBc1+k8HMM3u/YSfHI3Dr4GmoTIez2Pr/If1XFl3rU26AwA==} + dependencies: + readable-stream: 4.7.0 + dev: true + /magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} dependencies: @@ -5370,6 +5922,10 @@ packages: engines: {node: '>= 8'} dev: true + /merge@2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + dev: true + /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -5485,8 +6041,8 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true - /minisearch@7.1.1: - resolution: {integrity: sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==} + /minisearch@7.1.2: + resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} dev: true /mitt@3.0.1: @@ -5498,19 +6054,18 @@ packages: 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 - pathe: 2.0.2 + pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.5.4 dev: true - /mocha@11.0.1: - resolution: {integrity: sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==} + /mocha@11.1.0: + resolution: {integrity: sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true dependencies: @@ -5531,13 +6086,13 @@ packages: strip-json-comments: 3.1.1 supports-color: 8.1.1 workerpool: 6.5.1 - yargs: 16.2.0 - yargs-parser: 20.2.9 + yargs: 17.7.2 + yargs-parser: 21.1.1 yargs-unparser: 2.0.0 dev: true - /mochawesome-merge@4.3.0: - resolution: {integrity: sha512-1roR6g+VUlfdaRmL8dCiVpKiaUhbPVm1ZQYUM6zHX46mWk+tpsKVZR6ba98k2zc8nlPvYd71yn5gyH970pKBSw==} + /mochawesome-merge@4.4.1: + resolution: {integrity: sha512-QCzsXrfH5ewf4coUGvrAOZSpRSl9Vg39eqL2SpKKGkUw390f18hx9C90BNWTA4f/teD2nA0Inb1yxYPpok2gvg==} engines: {node: '>=10.0.0'} hasBin: true dependencies: @@ -5564,7 +6119,7 @@ packages: yargs: 17.7.2 dev: true - /mochawesome@7.1.3(mocha@11.0.1): + /mochawesome@7.1.3(mocha@11.1.0): resolution: {integrity: sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==} peerDependencies: mocha: '>=7' @@ -5576,7 +6131,7 @@ packages: lodash.isfunction: 3.0.9 lodash.isobject: 3.0.2 lodash.isstring: 4.0.1 - mocha: 11.0.1 + mocha: 11.1.0 mochawesome-report-generator: 6.2.0 strip-ansi: 6.0.1 uuid: 8.3.2 @@ -5622,6 +6177,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: true + /no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: @@ -5643,12 +6202,12 @@ packages: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} dev: true - /nopt@7.2.1: - resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + /nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} hasBin: true dependencies: - abbrev: 2.0.0 + abbrev: 3.0.0 dev: true /normalize-path@3.0.0: @@ -5694,8 +6253,8 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - /object-inspect@1.13.3: - resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + /object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} /on-finished@2.4.1: @@ -5726,12 +6285,12 @@ packages: mimic-fn: 4.0.0 dev: false - /oniguruma-to-es@2.3.0: - resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==} + /oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} dependencies: emoji-regex-xs: 1.0.0 - regex: 5.1.1 - regex-recursion: 5.1.1 + regex: 6.0.1 + regex-recursion: 6.0.2 dev: true /open@10.1.0: @@ -5876,9 +6435,9 @@ packages: engines: {node: '>=14.16'} dependencies: got: 12.6.1 - registry-auth-token: 5.0.3 + registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.6.3 + semver: 7.7.1 dev: false /param-case@3.0.4: @@ -5954,8 +6513,8 @@ packages: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} dev: true - /pathe@2.0.2: - resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==} + /pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} dev: true /pathval@1.1.1: @@ -6016,7 +6575,7 @@ packages: dependencies: confbox: 0.1.8 mlly: 1.7.4 - pathe: 2.0.2 + pathe: 2.0.3 dev: true /postcss-selector-parser@6.1.2: @@ -6031,16 +6590,16 @@ packages: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - /postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + /postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 - /preact@10.25.4: - resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==} + /preact@10.26.2: + resolution: {integrity: sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==} dev: true /prelude-ls@1.2.1: @@ -6048,8 +6607,8 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + /prettier@3.5.1: + resolution: {integrity: sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==} engines: {node: '>=14'} hasBin: true dev: true @@ -6089,8 +6648,8 @@ packages: react-is: 16.13.1 dev: true - /property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + /property-information@7.0.0: + resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} dev: true /proto-list@1.2.4: @@ -6153,10 +6712,6 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - dev: true - /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -6191,6 +6746,15 @@ packages: strip-json-comments: 2.0.1 dev: false + /react-dom@19.0.0(react@19.0.0): + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + dev: true + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true @@ -6199,6 +6763,11 @@ packages: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: true + /react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + dev: true + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: @@ -6243,8 +6812,8 @@ packages: picomatch: 2.3.1 dev: true - /readdirp@4.1.1: - resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + /readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} dev: true @@ -6258,10 +6827,13 @@ packages: tslib: 1.14.1 dev: true - /regex-recursion@5.1.1: - resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: true + + /regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} dependencies: - regex: 5.1.1 regex-utilities: 2.3.0 dev: true @@ -6269,14 +6841,14 @@ packages: resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} dev: true - /regex@5.1.1: - resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + /regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} dependencies: regex-utilities: 2.3.0 dev: true - /registry-auth-token@5.0.3: - resolution: {integrity: sha512-1bpc9IyC+e+CNFRaWyn77tk4xGG4PPUyfakSmA6F6cvUDjrm58dfyJ3II+9yb10EDkHoy1LaPSmHaWLOH3m6HA==} + /registry-auth-token@5.1.0: + resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} engines: {node: '>=14'} dependencies: '@pnpm/npm-conf': 2.3.1 @@ -6389,32 +6961,32 @@ packages: yargs: 17.7.2 dev: true - /rollup@4.31.0: - resolution: {integrity: sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==} + /rollup@4.34.8: + resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.31.0 - '@rollup/rollup-android-arm64': 4.31.0 - '@rollup/rollup-darwin-arm64': 4.31.0 - '@rollup/rollup-darwin-x64': 4.31.0 - '@rollup/rollup-freebsd-arm64': 4.31.0 - '@rollup/rollup-freebsd-x64': 4.31.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.31.0 - '@rollup/rollup-linux-arm-musleabihf': 4.31.0 - '@rollup/rollup-linux-arm64-gnu': 4.31.0 - '@rollup/rollup-linux-arm64-musl': 4.31.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.31.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.31.0 - '@rollup/rollup-linux-riscv64-gnu': 4.31.0 - '@rollup/rollup-linux-s390x-gnu': 4.31.0 - '@rollup/rollup-linux-x64-gnu': 4.31.0 - '@rollup/rollup-linux-x64-musl': 4.31.0 - '@rollup/rollup-win32-arm64-msvc': 4.31.0 - '@rollup/rollup-win32-ia32-msvc': 4.31.0 - '@rollup/rollup-win32-x64-msvc': 4.31.0 + '@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 fsevents: 2.3.3 dev: true @@ -6465,8 +7037,8 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sass-embedded-android-arm64@1.83.4: - resolution: {integrity: sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==} + /sass-embedded-android-arm64@1.85.0: + resolution: {integrity: sha512-4itDzRwezwrW8+YzMLIwHtMeH+qrBNdBsRn9lTVI15K+cNLC8z5JWJi6UCZ8TNNZr9LDBfsh5jUdjSub0yF7jg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [android] @@ -6474,8 +7046,8 @@ packages: dev: true optional: true - /sass-embedded-android-arm@1.83.4: - resolution: {integrity: sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==} + /sass-embedded-android-arm@1.85.0: + resolution: {integrity: sha512-pPBT7Ad6G8Mlao8ypVNXW2ya7I/Bhcny+RYZ/EmrunEXfhzCNp4PWV2VAweitPO9RnPIJwvUTkLc8Fu6K3nVmw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [android] @@ -6483,8 +7055,8 @@ packages: dev: true optional: true - /sass-embedded-android-ia32@1.83.4: - resolution: {integrity: sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==} + /sass-embedded-android-ia32@1.85.0: + resolution: {integrity: sha512-bwqKq95hzbGbMTeXCMQhH7yEdc2xJVwIXj7rGdD3McvyFWbED6362XRFFPI5YyjfD2wRJd9yWLh/hn+6VyjcYA==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [android] @@ -6492,8 +7064,8 @@ packages: dev: true optional: true - /sass-embedded-android-riscv64@1.83.4: - resolution: {integrity: sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==} + /sass-embedded-android-riscv64@1.85.0: + resolution: {integrity: sha512-Fgkgay+5EePJXZFHR5Vlkutnsmox2V6nX4U3mfGbSN1xjLRm8F5ST72V2s5Z0mnIFpGvEu/v7hfptgViqMvaxg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [android] @@ -6501,8 +7073,8 @@ packages: dev: true optional: true - /sass-embedded-android-x64@1.83.4: - resolution: {integrity: sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==} + /sass-embedded-android-x64@1.85.0: + resolution: {integrity: sha512-/bG3JgTn3eoIDHCiJNVkLeJgUesat4ghxqYmKMZUJx++4e6iKCDj8XwQTJAgm+QDrsPKXHBacHEANJ9LEAuTqg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [android] @@ -6510,8 +7082,8 @@ packages: dev: true optional: true - /sass-embedded-darwin-arm64@1.83.4: - resolution: {integrity: sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==} + /sass-embedded-darwin-arm64@1.85.0: + resolution: {integrity: sha512-plp8TyMz97YFBCB3ndftEvoW29vyfsSBJILM5U84cGzr06SvLh/Npjj8psfUeRw+upEk1zkFtw5u61sRCdgwIw==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [darwin] @@ -6519,8 +7091,8 @@ packages: dev: true optional: true - /sass-embedded-darwin-x64@1.83.4: - resolution: {integrity: sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==} + /sass-embedded-darwin-x64@1.85.0: + resolution: {integrity: sha512-LP8Zv8DG57Gn6PmSwWzC0gEZUsGdg36Ps3m0i1fVTOelql7N3HZIrlPYRjJvidL8ZlB3ISxNANebTREUHn/wkQ==} engines: {node: '>=14.0.0'} cpu: [x64] os: [darwin] @@ -6528,8 +7100,8 @@ packages: dev: true optional: true - /sass-embedded-linux-arm64@1.83.4: - resolution: {integrity: sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==} + /sass-embedded-linux-arm64@1.85.0: + resolution: {integrity: sha512-JRIRKVOY5Y8M1zlUOv9AQGju4P6lj8i5vLJZsVYVN/uY8Cd2dDJZPC8EOhjntp+IpF8AOGIHqCeCkHBceIyIjA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] @@ -6537,8 +7109,8 @@ packages: dev: true optional: true - /sass-embedded-linux-arm@1.83.4: - resolution: {integrity: sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==} + /sass-embedded-linux-arm@1.85.0: + resolution: {integrity: sha512-18xOAEfazJt1MMVS2TRHV94n81VyMnywOoJ7/S7I79qno/zx26OoqqP4XvH107xu8+mZ9Gg54LrUH6ZcgHk08g==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] @@ -6546,8 +7118,8 @@ packages: dev: true optional: true - /sass-embedded-linux-ia32@1.83.4: - resolution: {integrity: sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==} + /sass-embedded-linux-ia32@1.85.0: + resolution: {integrity: sha512-4JH+h+gLt9So22nNPQtsKojEsLzjld9ol3zWcOtMGclv+HojZGbCuhJUrLUcK72F8adXYsULmWhJPKROLIwYMA==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [linux] @@ -6555,8 +7127,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-arm64@1.83.4: - resolution: {integrity: sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==} + /sass-embedded-linux-musl-arm64@1.85.0: + resolution: {integrity: sha512-aoQjUjK28bvdw9XKTjQeayn8oWQ2QqvoTD11myklGd3IHH7Jj0nwXUstI4NxDueCKt3wghuZoIQkjOheReQxlg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] @@ -6564,8 +7136,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-arm@1.83.4: - resolution: {integrity: sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==} + /sass-embedded-linux-musl-arm@1.85.0: + resolution: {integrity: sha512-Z1j4ageDVFihqNUBnm89fxY46pY0zD/Clp1D3ZdI7S+D280+AEpbm5vMoH8LLhBQfQLf2w7H++SZGpQwrisudQ==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] @@ -6573,8 +7145,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-ia32@1.83.4: - resolution: {integrity: sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==} + /sass-embedded-linux-musl-ia32@1.85.0: + resolution: {integrity: sha512-/cJCSXOfXmQFH8deE+3U9x+BSz8i0d1Tt9gKV/Gat1Xm43Oumw8pmZgno+cDuGjYQInr9ryW5121pTMlj/PBXQ==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [linux] @@ -6582,8 +7154,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-riscv64@1.83.4: - resolution: {integrity: sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==} + /sass-embedded-linux-musl-riscv64@1.85.0: + resolution: {integrity: sha512-l+FJxMXkmg42RZq5RFKXg4InX0IA7yEiPHe4kVSdrczP7z3NLxk+W9wVkPnoRKYIMe1qZPPQ25y0TgI4HNWouA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] @@ -6591,8 +7163,8 @@ packages: dev: true optional: true - /sass-embedded-linux-musl-x64@1.83.4: - resolution: {integrity: sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==} + /sass-embedded-linux-musl-x64@1.85.0: + resolution: {integrity: sha512-M9ffjcYfFcRvkFA6V3DpOS955AyvmpvPAhL/xNK45d/ma1n1ehTWpd24tVeKiNK5CZkNjjMEfyw2fHa6MpqmEA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] @@ -6600,8 +7172,8 @@ packages: dev: true optional: true - /sass-embedded-linux-riscv64@1.83.4: - resolution: {integrity: sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==} + /sass-embedded-linux-riscv64@1.85.0: + resolution: {integrity: sha512-yqPXQWfM+qiIPkfn++48GOlbmSvUZIyL9nwFstBk0k4x40UhbhilfknqeTUpxoHfQzylTGVhrm5JE7MjM+LNZA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] @@ -6609,8 +7181,8 @@ packages: dev: true optional: true - /sass-embedded-linux-x64@1.83.4: - resolution: {integrity: sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==} + /sass-embedded-linux-x64@1.85.0: + resolution: {integrity: sha512-NTDeQFZcuVR7COoaRy8pZD6/+QznwBR8kVFsj7NpmvX9aJ7TX/q+OQZHX7Bfb3tsfKXhf1YZozegPuYxRnMKAQ==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] @@ -6618,8 +7190,8 @@ packages: dev: true optional: true - /sass-embedded-win32-arm64@1.83.4: - resolution: {integrity: sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==} + /sass-embedded-win32-arm64@1.85.0: + resolution: {integrity: sha512-gO0VAuxC4AdV+uZYJESRWVVHQWCGzNs0C3OKCAdH4r1vGRugooMi7J/5wbwUdXDA1MV9ICfhlKsph2n3GiPdqA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [win32] @@ -6627,8 +7199,8 @@ packages: dev: true optional: true - /sass-embedded-win32-ia32@1.83.4: - resolution: {integrity: sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==} + /sass-embedded-win32-ia32@1.85.0: + resolution: {integrity: sha512-PCyn6xeFIBUgBceNypuf73/5DWF2VWPlPqPuBprPsTvpZOMUJeBtP+Lf4mnu3dNy1z76mYVnpaCnQmzZ0zHZaA==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [win32] @@ -6636,8 +7208,8 @@ packages: dev: true optional: true - /sass-embedded-win32-x64@1.83.4: - resolution: {integrity: sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==} + /sass-embedded-win32-x64@1.85.0: + resolution: {integrity: sha512-AknE2jLp6OBwrR5hQ8pDsG94KhJCeSheFJ2xgbnk8RUjZX909JiNbgh2sNt9LG+RXf4xZa55dDL537gZoCx/iw==} engines: {node: '>=14.0.0'} cpu: [x64] os: [win32] @@ -6645,8 +7217,8 @@ packages: dev: true optional: true - /sass-embedded@1.83.4: - resolution: {integrity: sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==} + /sass-embedded@1.85.0: + resolution: {integrity: sha512-x3Vv54g0jv1aPSW8OTA/0GzQCs/HMQOjIkLtZJ3Xsn/I4vnyjKbVTQmFTax9bQjldqLEEkdbvy6ES/cOOnYNwA==} engines: {node: '>=16.0.0'} hasBin: true dependencies: @@ -6659,30 +7231,30 @@ packages: sync-child-process: 1.0.2 varint: 6.0.0 optionalDependencies: - sass-embedded-android-arm: 1.83.4 - sass-embedded-android-arm64: 1.83.4 - sass-embedded-android-ia32: 1.83.4 - sass-embedded-android-riscv64: 1.83.4 - sass-embedded-android-x64: 1.83.4 - sass-embedded-darwin-arm64: 1.83.4 - sass-embedded-darwin-x64: 1.83.4 - sass-embedded-linux-arm: 1.83.4 - sass-embedded-linux-arm64: 1.83.4 - sass-embedded-linux-ia32: 1.83.4 - sass-embedded-linux-musl-arm: 1.83.4 - sass-embedded-linux-musl-arm64: 1.83.4 - sass-embedded-linux-musl-ia32: 1.83.4 - sass-embedded-linux-musl-riscv64: 1.83.4 - sass-embedded-linux-musl-x64: 1.83.4 - sass-embedded-linux-riscv64: 1.83.4 - sass-embedded-linux-x64: 1.83.4 - sass-embedded-win32-arm64: 1.83.4 - sass-embedded-win32-ia32: 1.83.4 - sass-embedded-win32-x64: 1.83.4 + 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 dev: true - /sass@1.83.4: - resolution: {integrity: sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==} + /sass@1.85.0: + resolution: {integrity: sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==} engines: {node: '>=14.0.0'} hasBin: true dependencies: @@ -6690,13 +7262,21 @@ packages: immutable: 5.0.3 source-map-js: 1.2.1 optionalDependencies: - '@parcel/watcher': 2.5.0 + '@parcel/watcher': 2.5.1 dev: true /sax@1.1.4: resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} dev: true + /sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + dev: true + + /scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + dev: true + /search-insights@2.17.3: resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} dev: true @@ -6712,7 +7292,7 @@ packages: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} engines: {node: '>=12'} dependencies: - semver: 7.6.3 + semver: 7.7.1 dev: false /semver@6.3.1: @@ -6720,8 +7300,8 @@ packages: hasBin: true dev: true - /semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + /semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true @@ -6786,16 +7366,16 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - /shiki@2.1.0: - resolution: {integrity: sha512-yvKPdNGLXZv7WC4bl7JBbU3CEcUxnBanvMez8MG3gZXKpClGL4bHqFyLhTx+2zUvbjClUANs/S22HXb7aeOgmA==} + /shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} dependencies: - '@shikijs/core': 2.1.0 - '@shikijs/engine-javascript': 2.1.0 - '@shikijs/engine-oniguruma': 2.1.0 - '@shikijs/langs': 2.1.0 - '@shikijs/themes': 2.1.0 - '@shikijs/types': 2.1.0 - '@shikijs/vscode-textmate': 10.0.1 + '@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 @@ -6804,7 +7384,7 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 /side-channel-map@1.0.1: resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} @@ -6813,7 +7393,7 @@ packages: call-bound: 1.0.3 es-errors: 1.3.0 get-intrinsic: 1.2.7 - object-inspect: 1.13.3 + object-inspect: 1.13.4 /side-channel-weakmap@1.0.2: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} @@ -6822,7 +7402,7 @@ packages: call-bound: 1.0.3 es-errors: 1.3.0 get-intrinsic: 1.2.7 - object-inspect: 1.13.3 + object-inspect: 1.13.4 side-channel-map: 1.0.1 /side-channel@1.1.0: @@ -6830,7 +7410,7 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -6870,6 +7450,44 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true + /socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + dependencies: + debug: 4.3.7 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + dev: true + + /socket.io@4.8.1: + resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + engines: {node: '>=10.2.0'} + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.7 + engine.io: 6.6.4 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -6938,11 +7556,10 @@ packages: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} dev: true - /streamx@2.21.1: - resolution: {integrity: sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==} + /streamx@2.22.0: + resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} dependencies: fast-fifo: 1.3.2 - queue-tick: 1.0.1 text-decoder: 1.2.3 optionalDependencies: bare-events: 2.5.4 @@ -7024,6 +7641,10 @@ packages: acorn: 8.14.0 dev: true + /style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + dev: true + /sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -7079,7 +7700,7 @@ packages: dependencies: b4a: 1.6.7 fast-fifo: 1.3.2 - streamx: 2.21.1 + streamx: 2.22.0 dev: true /tcomb-validation@3.4.1: @@ -7092,8 +7713,8 @@ packages: resolution: {integrity: sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==} dev: true - /terser@5.37.0: - resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} + /terser@5.39.0: + resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} engines: {node: '>=10'} hasBin: true dependencies: @@ -7143,8 +7764,8 @@ packages: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} dev: true - /tinyglobby@0.2.10: - resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} + /tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} dependencies: fdir: 6.4.3(picomatch@4.0.2) @@ -7166,15 +7787,15 @@ packages: engines: {node: '>=12'} dev: false - /tldts-core@6.1.73: - resolution: {integrity: sha512-k1g5eX87vxu3g//6XMn62y4qjayu4cYby/PF7Ksnh4F4uUK1Z1ze/mJ4a+y5OjdJ+cXRp+YTInZhH+FGdUWy1w==} + /tldts-core@6.1.78: + resolution: {integrity: sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==} dev: true - /tldts@6.1.73: - resolution: {integrity: sha512-/h4bVmuEMm57c2uCiAf1Q9mlQk7cA22m+1Bu0K92vUUtTVT9D4mOFWD9r4WQuTULcG9eeZtNKhLl0Il1LdKGog==} + /tldts@6.1.78: + resolution: {integrity: sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ==} hasBin: true dependencies: - tldts-core: 6.1.73 + tldts-core: 6.1.78 dev: true /tmp@0.0.33: @@ -7198,11 +7819,11 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - /tough-cookie@5.1.0: - resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==} + /tough-cookie@5.1.1: + resolution: {integrity: sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==} engines: {node: '>=16'} dependencies: - tldts: 6.1.73 + tldts: 6.1.78 dev: true /tree-kill@1.2.2: @@ -7229,8 +7850,8 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /tsconfck@3.1.4(typescript@5.7.3): - resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} + /tsconfck@3.1.5(typescript@5.7.3): + resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} engines: {node: ^18 || >=20} hasBin: true peerDependencies: @@ -7306,8 +7927,8 @@ packages: engines: {node: '>=12.20'} dev: false - /type-fest@4.33.0: - resolution: {integrity: sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==} + /type-fest@4.35.0: + resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==} engines: {node: '>=16'} dev: true @@ -7337,6 +7958,14 @@ packages: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} dev: true + /uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + /undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} @@ -7436,7 +8065,7 @@ packages: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.1.0 - semver: 7.6.3 + semver: 7.7.1 semver-diff: 4.0.0 xdg-basedir: 5.1.0 dev: false @@ -7494,7 +8123,7 @@ packages: vfile-message: 4.0.2 dev: true - /vite-jsconfig-paths@2.0.1(vite@6.0.11): + /vite-jsconfig-paths@2.0.1(vite@6.2.0): resolution: {integrity: sha512-rabcTTfKs0MdAsQWcZjbIMo5fcp6jthZce7uFEPgVPgpSY+RNOwjzIJOPES6cB/GJZLSoLGfHM9kt5HNmJvp7A==} peerDependencies: vite: '>2.0.0-0' @@ -7503,12 +8132,12 @@ packages: globrex: 0.1.2 recrawl-sync: 2.2.3 tsconfig-paths: 3.15.0 - vite: 6.0.11(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.83.4) + vite: 6.2.0(@types/node@22.13.5)(sass@1.85.0) transitivePeerDependencies: - supports-color dev: true - /vite-node@0.34.6(@types/node@22.10.7)(sass@1.83.4): + /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'} hasBin: true @@ -7518,7 +8147,7 @@ packages: mlly: 1.7.4 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.14(@types/node@22.10.7)(sass@1.83.4) + vite: 5.4.14(@types/node@22.13.4)(sass@1.85.0) transitivePeerDependencies: - '@types/node' - less @@ -7531,7 +8160,7 @@ packages: - terser dev: true - /vite-tsconfig-paths@4.3.2(typescript@5.7.3)(vite@6.0.11): + /vite-tsconfig-paths@4.3.2(typescript@5.7.3)(vite@6.2.0): resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: vite: '*' @@ -7541,14 +8170,14 @@ packages: dependencies: debug: 4.4.0(supports-color@8.1.1) globrex: 0.1.2 - tsconfck: 3.1.4(typescript@5.7.3) - vite: 6.0.11(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.83.4) + tsconfck: 3.1.5(typescript@5.7.3) + vite: 6.2.0(@types/node@22.13.5)(sass@1.85.0) transitivePeerDependencies: - supports-color - typescript dev: true - /vite@5.4.14(@types/node@22.10.7)(sass@1.83.4): + /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 @@ -7579,17 +8208,57 @@ packages: terser: optional: true dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.4 esbuild: 0.21.5 - postcss: 8.5.1 - rollup: 4.31.0 - sass: 1.83.4 + postcss: 8.5.3 + rollup: 4.34.8 + sass: 1.85.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vite@6.0.11(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.83.4): - resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} + /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==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -7628,17 +8297,66 @@ packages: yaml: optional: true dependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.5 esbuild: 0.24.2 - postcss: 8.5.1 - rollup: 4.31.0 - sass: 1.83.4 - sass-embedded: 1.83.4 + postcss: 8.5.3 + rollup: 4.34.8 + sass: 1.85.0 + sass-embedded: 1.85.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vitepress@1.6.3(@algolia/client-search@5.20.0)(@types/node@22.10.7)(axios@1.7.9)(postcss@8.5.1)(sass@1.83.4)(search-insights@2.17.3)(typescript@5.7.3): + /vite@6.2.0(@types/node@22.13.5)(sass@1.85.0): + resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} + 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: @@ -7651,23 +8369,23 @@ packages: optional: true dependencies: '@docsearch/css': 3.8.2 - '@docsearch/js': 3.8.2(@algolia/client-search@5.20.0)(search-insights@2.17.3) - '@iconify-json/simple-icons': 1.2.21 - '@shikijs/core': 2.1.0 - '@shikijs/transformers': 2.1.0 - '@shikijs/types': 2.1.0 + '@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.1 + '@vue/devtools-api': 7.7.2 '@vue/shared': 3.5.13 - '@vueuse/core': 12.5.0(typescript@5.7.3) - '@vueuse/integrations': 12.5.0(axios@1.7.9)(focus-trap@7.6.4)(typescript@5.7.3) + '@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.1 - postcss: 8.5.1 - shiki: 2.1.0 - vite: 5.4.14(@types/node@22.10.7)(sass@1.83.4) + 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' @@ -7697,7 +8415,7 @@ packages: - universal-cookie dev: true - /vitest@0.34.6(sass@1.83.4): + /vitest@0.34.6(sass@1.85.0): resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} hasBin: true @@ -7730,7 +8448,7 @@ packages: dependencies: '@types/chai': 4.3.20 '@types/chai-subset': 1.3.5 - '@types/node': 22.10.7 + '@types/node': 22.13.4 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -7749,8 +8467,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.9.0 tinypool: 0.7.0 - vite: 5.4.14(@types/node@22.10.7)(sass@1.83.4) - vite-node: 0.34.6(@types/node@22.10.7)(sass@1.83.4) + 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) why-is-node-running: 2.3.0 transitivePeerDependencies: - less @@ -7763,8 +8481,8 @@ packages: - terser dev: true - /vue-component-type-helpers@2.2.0: - resolution: {integrity: sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==} + /vue-component-type-helpers@2.2.2: + resolution: {integrity: sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==} dev: true /vue-demi@0.14.10(vue@3.5.13): @@ -7781,20 +8499,20 @@ packages: dependencies: vue: 3.5.13(typescript@5.7.3) - /vue-eslint-parser@9.4.3(eslint@9.18.0): + /vue-eslint-parser@9.4.3(eslint@9.20.1): 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.18.0 + eslint: 9.20.1 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 esquery: 1.6.0 lodash: 4.17.21 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color dev: true @@ -7833,6 +8551,10 @@ packages: '@vue/shared': 3.5.13 typescript: 5.7.3 + /w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + dev: true + /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: @@ -7905,6 +8627,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /workerpool@6.5.1: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} dev: true @@ -7947,6 +8673,19 @@ packages: typedarray-to-buffer: 3.1.5 dev: false + /ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} @@ -7957,6 +8696,61 @@ packages: engines: {node: '>=12'} dev: true + /xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + dependencies: + sax: 1.1.4 + xmlbuilder: 11.0.1 + dev: true + + /xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + dev: true + + /xmldoc@1.3.0: + resolution: {integrity: sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==} + dependencies: + sax: 1.4.1 + 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): + 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) + chalk: 5.4.1 + chokidar: 3.6.0 + console-clear: 1.1.1 + debounce: 1.2.1 + detect-file-encoding-and-language: 2.4.0 + express: 4.21.2 + get-port: 7.1.0 + handlebars: 4.7.8 + ip: 1.1.9 + lzutf8: 0.6.3 + merge: 2.1.1 + socket.io: 4.8.1 + xml2js: 0.6.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@babel/runtime' + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + - '@codemirror/state' + - '@codemirror/theme-one-dark' + - '@codemirror/view' + - bufferutil + - codemirror + - react + - react-dom + - supports-color + - utf-8-validate + dev: true + /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: true @@ -7991,11 +8785,6 @@ packages: decamelize: 1.2.0 dev: true - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true - /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -8028,19 +8817,6 @@ packages: yargs-parser: 18.1.3 dev: true - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - dependencies: - cliui: 7.0.4 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - dev: true - /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 93a2ac96a..6303f48ae 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -181,17 +181,19 @@ 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; + } + await axios.post($props.saveUrl || $props.url + '/crud', changes); } finally { isLoading.value = false; } originalData.value = JSON.parse(JSON.stringify(formData.value)); - if (changes.creates?.length) await vnPaginateRef.value.fetch(); + if (changes?.creates?.length) await vnPaginateRef.value.fetch(); hasChanges.value = false; emit('saveChanges', data); diff --git a/src/components/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 765d97763..4aad327b2 100644 --- a/src/components/FilterTravelForm.vue +++ b/src/components/FilterTravelForm.vue @@ -124,7 +124,7 @@ const selectTravel = ({ id }) => { <FetchData url="AgencyModes" @on-fetch="(data) => (agenciesOptions = data)" - :filter="{ fields: ['id', 'name'], order: 'name ASC' }" + :filter="{ fields: ['id', 'name'], order: ['name ASC'] }" auto-load /> <FetchData @@ -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/LeftMenu.vue b/src/components/LeftMenu.vue index 9a9949499..8e83bf579 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 3e92c93a9..dbb6f1fe6 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -57,7 +57,7 @@ const refresh = () => window.location.reload(); :class="{ 'no-visible': !stateQuery.isLoading().value, }" - size="xs" + size="sm" data-cy="loading-spinner" /> <QSpace /> diff --git a/src/components/TicketProblems.vue b/src/components/TicketProblems.vue index 783f2556f..59be95035 100644 --- a/src/components/TicketProblems.vue +++ b/src/components/TicketProblems.vue @@ -12,20 +12,42 @@ defineProps({ row: { type: Object, required: true } }); > <QIcon name="vn:claims" size="xs"> <QTooltip> - {{ t('ticketSale.claim') }}: + {{ $t('ticketSale.claim') }}: {{ row.claim?.claimFk }} </QTooltip> </QIcon> </router-link> <QIcon - v-if="row?.risk" + v-if="row?.reserved" + color="primary" + name="vn:reserva" + size="xs" + data-cy="ticketSaleReservedIcon" + > + <QTooltip> + {{ t('ticketSale.reserved') }} + </QTooltip> + </QIcon> + <QIcon + v-if="row?.isDeleted" + color="primary" + name="vn:deletedTicket" + size="xs" + data-cy="ticketDeletedIcon" + > + <QTooltip> + {{ t('Ticket deleted') }} + </QTooltip> + </QIcon> + <QIcon + v-if="row?.hasRisk" name="vn:risk" :color="row.hasHighRisk ? 'negative' : 'primary'" size="xs" > <QTooltip> {{ $t('salesTicketsTable.risk') }}: - {{ toCurrency(row.risk - row.credit) }} + {{ toCurrency(row.risk - (row.credit ?? 0)) }} </QTooltip> </QIcon> <QIcon @@ -67,12 +89,7 @@ defineProps({ row: { type: Object, required: true } }); > <QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip> </QIcon> - <QIcon - v-if="row?.isTaxDataChecked !== 0" - name="vn:no036" - color="primary" - size="xs" - > + <QIcon v-if="row?.isTaxDataChecked" name="vn:no036" color="primary" size="xs"> <QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip> </QIcon> <QIcon v-if="row?.isFreezed" name="vn:frozen" color="primary" size="xs"> 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/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/VnTable.vue b/src/components/VnTable/VnTable.vue index 7e9f7aae0..c64217198 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -55,6 +55,10 @@ const $props = defineProps({ type: [Function, Boolean], default: null, }, + rowCtrlClick: { + type: [Function, Boolean], + default: null, + }, redirect: { type: String, default: null, @@ -136,7 +140,7 @@ const $props = defineProps({ }, dataCy: { type: String, - default: 'vn-table', + default: 'vnTable', }, }); @@ -591,18 +595,17 @@ function cardClick(_, row) { function removeTextValue(data, getChanges) { let changes = data.updates; - if (!changes) return data; - - for (const change of changes) { - for (const key in change.data) { - if (key.endsWith('VnTableTextValue')) { - delete change.data[key]; + if (changes) { + for (const change of changes) { + for (const key in change.data) { + if (key.endsWith('VnTableTextValue')) { + delete change.data[key]; + } } } + + data.updates = changes.filter((change) => Object.keys(change.data).length > 0); } - - data.updates = changes.filter((change) => Object.keys(change.data).length > 0); - if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges); return data; @@ -681,7 +684,7 @@ const rowCtrlClickFunction = computed(() => { @update:selected="emit('update:selected', $event)" @selection="(details) => handleSelection(details, rows)" :hide-selected-banner="true" - :data-cy="$props.dataCy ?? 'vnTable'" + :data-cy > <template #top-left v-if="!$props.withoutHeader"> <slot name="top-left"> </slot> @@ -772,12 +775,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}`" @@ -916,12 +920,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> @@ -962,6 +978,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}`" @@ -1024,38 +1042,43 @@ const rowCtrlClickFunction = computed(() => { @on-data-saved="(_, res) => createForm.onDataSaved(res)" > <template #form-inputs="{ data }"> - <div :style="createComplement?.containerStyle"> - <div - :style="createComplement?.previousStyle" - v-if="!quasar.screen.xs" - > - <slot name="previous-create-dialog" :data="data" /> - </div> - <div class="grid-create" :style="createComplement?.columnGridStyle"> - <slot - v-for="column of splittedColumns.create" - :key="column.name" - :name="`column-create-${column.name}`" - :data="data" - :column-name="column.name" - :label="column.label" + <slot name="alter-create" :data="data"> + <div :style="createComplement?.containerStyle"> + <div + :style="createComplement?.previousStyle" + v-if="!quasar.screen.xs" > - <VnColumn - :column="{ - ...column, - ...{ disable: column?.createDisable ?? false }, - }" - :row="{}" - default="input" - v-model="data[column.name]" - :show-label="true" - component-prop="columnCreate" - :data-cy="`${column.name}-create-popup`" - /> - </slot> - <slot name="more-create-dialog" :data="data" /> + <slot name="previous-create-dialog" :data="data" /> + </div> + <div + class="grid-create" + :style="createComplement?.columnGridStyle" + > + <slot + v-for="column of splittedColumns.create" + :key="column.name" + :name="`column-create-${column.name}`" + :data="data" + :column-name="column.name" + :label="column.label" + > + <VnColumn + :column="{ + ...column, + ...column?.createAttrs, + }" + :row="{}" + default="input" + v-model="data[column.name]" + :show-label="true" + component-prop="columnCreate" + :data-cy="`${column.name}-create-popup`" + /> + </slot> + <slot name="more-create-dialog" :data="data" /> + </div> </div> - </div> + </slot> </template> </FormModelPopup> </QDialog> @@ -1132,9 +1155,13 @@ es: .grid-create { display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); + grid-template-columns: 1fr 1fr; + max-width: 100%; grid-gap: 20px; margin: 0 auto; + .col-span-2 { + grid-column: span 2; + } } .flex-one { diff --git a/src/components/__tests__/CrudModel.spec.js b/src/components/__tests__/CrudModel.spec.js index e0afd30ad..f6c93e0d5 100644 --- a/src/components/__tests__/CrudModel.spec.js +++ b/src/components/__tests__/CrudModel.spec.js @@ -30,8 +30,8 @@ describe('CrudModel', () => { saveFn: '', }, }); - wrapper=wrapper.wrapper; - vm=wrapper.vm; + wrapper = wrapper.wrapper; + vm = wrapper.vm; }); beforeEach(() => { @@ -143,14 +143,14 @@ describe('CrudModel', () => { }); it('should return true if object is empty', async () => { - dummyObj ={}; - result = vm.isEmpty(dummyObj); + dummyObj = {}; + result = vm.isEmpty(dummyObj); expect(result).toBe(true); }); it('should return false if object is not empty', async () => { - dummyObj = {a:1, b:2, c:3}; + dummyObj = { a: 1, b: 2, c: 3 }; result = vm.isEmpty(dummyObj); expect(result).toBe(false); @@ -158,29 +158,31 @@ describe('CrudModel', () => { it('should return true if array is empty', async () => { dummyArray = []; - result = vm.isEmpty(dummyArray); + result = vm.isEmpty(dummyArray); expect(result).toBe(true); }); - + it('should return false if array is not empty', async () => { - dummyArray = [1,2,3]; + dummyArray = [1, 2, 3]; result = vm.isEmpty(dummyArray); expect(result).toBe(false); - }) + }); }); describe('resetData()', () => { it('should add $index to elements in data[] and sets originalData and formData with data', async () => { - data = [{ - name: 'Tony', - lastName: 'Stark', - age: 42, - }]; + data = [ + { + name: 'Tony', + lastName: 'Stark', + age: 42, + }, + ]; vm.resetData(data); - + expect(vm.originalData).toEqual(data); expect(vm.originalData[0].$index).toEqual(0); expect(vm.formData).toEqual(data); @@ -200,7 +202,7 @@ describe('CrudModel', () => { lastName: 'Stark', age: 42, }; - + vm.resetData(data); expect(vm.originalData).toEqual(data); @@ -210,17 +212,19 @@ describe('CrudModel', () => { }); describe('saveChanges()', () => { - data = [{ - name: 'Tony', - lastName: 'Stark', - age: 42, - }]; + data = [ + { + name: 'Tony', + lastName: 'Stark', + age: 42, + }, + ]; it('should call saveFn if exists', async () => { await wrapper.setProps({ saveFn: vi.fn() }); vm.saveChanges(data); - + expect(vm.saveFn).toHaveBeenCalledOnce(); expect(vm.isLoading).toBe(false); expect(vm.hasChanges).toBe(false); @@ -229,13 +233,15 @@ describe('CrudModel', () => { }); it("should use default url if there's not saveFn", async () => { - const postMock =vi.spyOn(axios, 'post'); - - vm.formData = [{ - name: 'Bruce', - lastName: 'Wayne', - age: 45, - }] + const postMock = vi.spyOn(axios, 'post'); + + vm.formData = [ + { + name: 'Bruce', + lastName: 'Wayne', + age: 45, + }, + ]; await vm.saveChanges(data); diff --git a/src/components/__tests__/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js index 4ab8b527f..0bcc587ac 100644 --- a/src/components/__tests__/Leftmenu.spec.js +++ b/src/components/__tests__/Leftmenu.spec.js @@ -15,10 +15,7 @@ vi.mock('src/router/modules', () => ({ meta: { title: 'customers', icon: 'vn:client', - }, - menus: { - main: ['CustomerList', 'CustomerCreate'], - card: ['CustomerBasicData'], + menu: ['CustomerList', 'CustomerCreate'], }, children: [ { @@ -50,14 +47,6 @@ vi.mock('src/router/modules', () => ({ ], }, }, - { - path: 'create', - name: 'CustomerCreate', - meta: { - title: 'createCustomer', - icon: 'vn:addperson', - }, - }, ], }, ], @@ -98,7 +87,7 @@ vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ icon: 'vn:client', moduleName: 'Customer', keyBinding: 'c', - menu: 'customer', + menu: ['customer'], }, }, ], @@ -260,15 +249,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.props.source = 'main'; vm.getRoutes(); @@ -351,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 d73133921..254eb9cf9 100644 --- a/src/components/common/SendEmailDialog.vue +++ b/src/components/common/SendEmailDialog.vue @@ -56,7 +56,12 @@ async function confirm() { {{ t('The notification will be sent to the following address') }} </QCardSection> <QCardSection class="q-pt-none"> - <VnInput v-model="address" is-outlined autofocus /> + <VnInput + v-model="address" + is-outlined + autofocus + data-cy="SendEmailNotifiactionDialogInput" + /> </QCardSection> <QCardActions align="right"> <QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup /> diff --git a/src/components/common/VnAccountNumber.vue b/src/components/common/VnAccountNumber.vue index c4fa78674..56add7329 100644 --- a/src/components/common/VnAccountNumber.vue +++ b/src/components/common/VnAccountNumber.vue @@ -1,12 +1,9 @@ <script setup> -import { nextTick, ref, watch } from 'vue'; -import { QInput } from 'quasar'; +import { nextTick, ref } from 'vue'; +import VnInput from './VnInput.vue'; +import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; const $props = defineProps({ - modelValue: { - type: String, - default: '', - }, insertable: { type: Boolean, default: false, @@ -14,70 +11,25 @@ const $props = defineProps({ }); const emit = defineEmits(['update:modelValue', 'accountShortToStandard']); +const model = defineModel({ prop: 'modelValue' }); +const inputRef = ref(false); -let internalValue = ref($props.modelValue); - -watch( - () => $props.modelValue, - (newVal) => { - internalValue.value = newVal; - } -); - -watch( - () => internalValue.value, - (newVal) => { - emit('update:modelValue', newVal); - accountShortToStandard(); - } -); - -const handleKeydown = (e) => { - if (e.key === 'Backspace') return; - if (e.key === '.') { - accountShortToStandard(); - // TODO: Fix this setTimeout, with nextTick doesn't work - setTimeout(() => { - setCursorPosition(0, e.target); - }, 1); - return; - } - - if ($props.insertable && e.key.match(/[0-9]/)) { - handleInsertMode(e); - } -}; -function setCursorPosition(pos, el = vnInputRef.value) { - el.focus(); - el.setSelectionRange(pos, pos); +function setCursorPosition(pos) { + const input = inputRef.value.vnInputRef.$el.querySelector('input'); + input.focus(); + input.setSelectionRange(pos, pos); } -const vnInputRef = ref(false); -const handleInsertMode = (e) => { - e.preventDefault(); - const input = e.target; - const cursorPos = input.selectionStart; - const { maxlength } = vnInputRef.value; - let currentValue = internalValue.value; - if (!currentValue) currentValue = e.key; - const newValue = e.key; - if (newValue && !isNaN(newValue) && cursorPos < maxlength) { - internalValue.value = - currentValue.substring(0, cursorPos) + - newValue + - currentValue.substring(cursorPos + 1); - } - nextTick(() => { - input.setSelectionRange(cursorPos + 1, cursorPos + 1); - }); -}; -function accountShortToStandard() { - internalValue.value = internalValue.value?.replace( - '.', - '0'.repeat(11 - internalValue.value.length) - ); + +async function handleUpdateModel(val) { + model.value = val?.at(-1) === '.' ? useAccountShortToStandard(val) : val; + await nextTick(() => setCursorPosition(0)); } </script> - <template> - <QInput @keydown="handleKeydown" ref="vnInputRef" v-model="internalValue" /> + <VnInput + v-model="model" + ref="inputRef" + :insertable + @update:model-value="handleUpdateModel" + /> </template> diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 44002c22a..620dc2ad2 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -1,50 +1,56 @@ <script setup> -import { onBeforeMount, computed } from 'vue'; -import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; +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'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; -import LeftMenu from 'components/LeftMenu.vue'; -import RightMenu from 'components/common/RightMenu.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 }, - idInWhere: { type: Boolean, default: false }, searchDataKey: { type: String, default: undefined }, searchbarProps: { type: Object, default: undefined }, redirectOnError: { type: Boolean, default: false }, }); const stateStore = useStateStore(); -const route = useRoute(); const router = useRouter(); -const searchRightDataKey = computed(() => { - if (!props.searchDataKey) return route.name; - return props.searchDataKey; -}); - 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 } = router.currentRoute.value; + 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); }); @@ -56,34 +62,13 @@ async function fetch(id, append = false) { 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> - <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> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="$route.path" /> + </div> </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/VnDms.vue b/src/components/common/VnDms.vue index 35308c2c4..bee300f4e 100644 --- a/src/components/common/VnDms.vue +++ b/src/components/common/VnDms.vue @@ -177,6 +177,7 @@ function addDefaultData(data) { name="vn:attach" class="cursor-pointer" @click="inputFileRef.pickFiles()" + data-cy="attachFile" > <QTooltip>{{ t('globals.selectFile') }}</QTooltip> </QIcon> diff --git a/src/components/common/VnDmsList.vue b/src/components/common/VnDmsList.vue index 424781a26..aafa9f4ba 100644 --- a/src/components/common/VnDmsList.vue +++ b/src/components/common/VnDmsList.vue @@ -389,10 +389,7 @@ defineExpose({ </div> </template> </QTable> - <div - v-else - class="info-row q-pa-md text-center" - > + <div v-else class="info-row q-pa-md text-center"> <h5> {{ t('No data to display') }} </h5> @@ -416,6 +413,7 @@ defineExpose({ v-shortcut @click="showFormDialog()" class="fill-icon" + data-cy="addButton" > <QTooltip> {{ t('Upload file') }} diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index aeb4a31fd..9821992cb 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -83,7 +83,7 @@ const mixinRules = [ requiredFieldRule, ...($attrs.rules ?? []), (val) => { - const { maxlength } = vnInputRef.value; + const maxlength = $props.maxlength; if (maxlength && +val.length > maxlength) return t(`maxLength`, { value: maxlength }); const { min, max } = vnInputRef.value.$attrs; @@ -108,7 +108,7 @@ const handleInsertMode = (e) => { e.preventDefault(); const input = e.target; const cursorPos = input.selectionStart; - const { maxlength } = vnInputRef.value; + const maxlength = $props.maxlength; let currentValue = value.value; if (!currentValue) currentValue = e.key; const newValue = e.key; @@ -143,7 +143,7 @@ const handleUppercase = () => { :rules="mixinRules" :lazy-rules="true" hide-bottom-space - :data-cy="$attrs.dataCy ?? $attrs.label + '_input'" + :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_input'" > <template #prepend v-if="$slots.prepend"> <slot name="prepend" /> diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 1f4705faa..343130f1d 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -107,7 +107,7 @@ const manageDate = (date) => { @click="isPopupOpen = !isPopupOpen" @keydown="isPopupOpen = false" hide-bottom-space - :data-cy="$attrs.dataCy ?? $attrs.label + '_inputDate'" + :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'" > <template #append> <QIcon diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 8f106a9f1..136dbf2a4 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -10,7 +10,7 @@ import { useColor } from 'src/composables/useColor'; import { useCapitalize } from 'src/composables/useCapitalize'; import { useValidator } from 'src/composables/useValidator'; import VnAvatar from '../ui/VnAvatar.vue'; -import VnJsonValue from '../common/VnJsonValue.vue'; +import VnLogValue from './VnLogValue.vue'; import FetchData from '../FetchData.vue'; import VnSelect from './VnSelect.vue'; import VnUserLink from '../ui/VnUserLink.vue'; @@ -560,10 +560,11 @@ watch( value.nameI18n }}: </span> - <VnJsonValue + <VnLogValue :value=" value.val.val " + :name="value.name" /> </QItem> </QCardSection> @@ -614,7 +615,11 @@ watch( > {{ prop.nameI18n }}: </span> - <VnJsonValue :value="prop.val.val" /> + <VnLogValue + :value="prop.val.val" + :name="prop.name" + /> + <VnIconLink /> <span v-if=" propIndex < @@ -642,8 +647,9 @@ watch( {{ prop.nameI18n }}: </span> <span v-if="log.action == 'update'"> - <VnJsonValue + <VnLogValue :value="prop.old.val" + :name="prop.name" /> <span v-if="prop.old.id" @@ -652,8 +658,9 @@ watch( #{{ prop.old.id }} </span> → - <VnJsonValue + <VnLogValue :value="prop.val.val" + :name="prop.name" /> <span v-if="prop.val.id" @@ -663,8 +670,9 @@ watch( </span> </span> <span v-else="prop.old.val"> - <VnJsonValue + <VnLogValue :value="prop.val.val" + :name="prop.name" /> <span v-if="prop.old.id" diff --git a/src/components/common/VnLogValue.vue b/src/components/common/VnLogValue.vue new file mode 100644 index 000000000..df0be4011 --- /dev/null +++ b/src/components/common/VnLogValue.vue @@ -0,0 +1,22 @@ +<script setup> +import { useDescriptorStore } from 'src/stores/useDescriptorStore'; +import VnJsonValue from './VnJsonValue.vue'; +import { computed } from 'vue'; +const descriptorStore = useDescriptorStore(); + +const $props = defineProps({ + name: { type: [String], default: undefined }, +}); + +const descriptor = computed(() => descriptorStore.has($props.name)); +</script> +<template> + <VnJsonValue v-bind="$attrs" /> + <QIcon + name="launch" + class="link" + v-if="$attrs.value && descriptor" + :data-cy="'iconLaunch-' + $props.name" + /> + <component :is="descriptor" :id="$attrs.value" v-if="$attrs.value && descriptor" /> +</template> diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index d111780bd..339f90e0e 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -302,6 +302,8 @@ defineExpose({ opts: myOptions, vnSelectRef }); function handleKeyDown(event) { if (event.key === 'Tab' && !event.shiftKey) { + event.preventDefault(); + const inputValue = vnSelectRef.value?.inputValue; if (inputValue) { diff --git a/src/components/common/__tests__/VnLogValue.spec.js b/src/components/common/__tests__/VnLogValue.spec.js new file mode 100644 index 000000000..c23743a02 --- /dev/null +++ b/src/components/common/__tests__/VnLogValue.spec.js @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import VnLogValue from 'src/components/common/VnLogValue.vue'; +import { createWrapper } from 'app/test/vitest/helper'; + +const buildComponent = (props) => { + return createWrapper(VnLogValue, { + props, + global: {}, + }).wrapper; +}; + +describe('VnLogValue', () => { + const id = 1; + it('renders without descriptor', async () => { + expect(getIcon('inventFk').exists()).toBe(false); + }); + + it('renders with descriptor', async () => { + expect(getIcon('claimFk').text()).toBe('launch'); + }); + + function getIcon(name) { + const wrapper = buildComponent({ value: { val: id }, name }); + return wrapper.find('.q-icon'); + } +}); diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index a29d1d429..cf74e4485 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -5,7 +5,7 @@ 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 { useRoute, useRouter } from 'vue-router'; import { useClipboard } from 'src/composables/useClipboard'; import VnMoreOptions from './VnMoreOptions.vue'; @@ -42,6 +42,7 @@ const $props = defineProps({ const state = useState(); const route = useRoute(); +const router = useRouter(); const { t } = useI18n(); const { copyText } = useClipboard(); const { viewSummary } = useSummaryDialog(); @@ -50,6 +51,9 @@ let store; let entity; const isLoading = ref(false); const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); +const DESCRIPTOR_PROXY = 'DescriptorProxy'; +const moduleName = ref(); +const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value; defineExpose({ getData }); onBeforeMount(async () => { @@ -76,15 +80,18 @@ onBeforeMount(async () => { ); }); -const routeName = computed(() => { - const DESCRIPTOR_PROXY = 'DescriptorProxy'; - +function getName() { let name = $props.dataKey; if ($props.dataKey.includes(DESCRIPTOR_PROXY)) { name = name.split(DESCRIPTOR_PROXY)[0]; } - return `${name}Summary`; + return name; +} +const routeName = computed(() => { + let routeName = getName(); + return `${routeName}Summary`; }); + async function getData() { store.url = $props.url; store.filter = $props.filter ?? {}; @@ -120,20 +127,35 @@ function copyIdText(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 iconModule = computed(() => { + moduleName.value = getName(); + if (isSameModuleName) { + return router.options.routes[1].children.find((r) => r.name === moduleName.value) + ?.meta?.icon; + } else { + return route.matched[1].meta.icon; + } +}); + +const toModule = computed(() => { + moduleName.value = getName(); + if (isSameModuleName) { + return router.options.routes[1].children.find((r) => r.name === moduleName.value) + ?.children[0]?.redirect; + } else { + return route.matched[1].path.split('/').length > 2 + ? route.matched[1].redirect + : route.matched[1].children[0].redirect; + } +}); </script> <template> - <div class="descriptor"> + <div class="descriptor" data-cy="cardDescriptor"> <template v-if="entity && !isLoading"> <div class="header bg-primary q-pa-sm justify-between"> - <slot name="header-extra-action" - ><QBtn + <slot name="header-extra-action"> + <QBtn round flat dense @@ -141,13 +163,13 @@ const toModule = computed(() => :icon="iconModule" color="white" class="link" - :to="$attrs['to-module'] ?? toModule" + :to="toModule" > <QTooltip> {{ t('globals.goToModuleIndex') }} </QTooltip> - </QBtn></slot - > + </QBtn> + </slot> <QBtn @click.stop="viewSummary(entity.id, $props.summary, $props.width)" round @@ -158,6 +180,7 @@ const toModule = computed(() => color="white" class="link" v-if="summary" + data-cy="openSummaryBtn" > <QTooltip> {{ t('components.smartCard.openSummary') }} @@ -172,6 +195,7 @@ const toModule = computed(() => icon="launch" round size="md" + data-cy="goToSummaryBtn" > <QTooltip> {{ t('components.cardDescriptor.summary') }} @@ -189,18 +213,27 @@ const toModule = computed(() => <QList dense> <QItemLabel header class="ellipsis text-h5" :lines="1"> <div class="title"> - <span v-if="$props.title" :title="getValueFromPath(title)"> + <span + v-if="$props.title" + :title="getValueFromPath(title)" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_title`" + > {{ getValueFromPath(title) ?? $props.title }} </span> <slot v-else name="description" :entity="entity"> - <span :title="entity.name"> - {{ entity.name }} - </span> + <span + :title="entity.name" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_description`" + v-text="entity.name" + /> </slot> </div> </QItemLabel> <QItem> - <QItemLabel class="subtitle"> + <QItemLabel + class="subtitle" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_subtitle`" + > #{{ getValueFromPath(subtitle) ?? entity.id }} </QItemLabel> <QBtn @@ -218,7 +251,10 @@ const toModule = computed(() => </QBtn> </QItem> </QList> - <div class="list-box q-mt-xs"> + <div + class="list-box q-mt-xs" + :data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_listbox`" + > <slot name="body" :entity="entity" /> </div> </div> @@ -230,7 +266,6 @@ const toModule = computed(() => </div> <slot name="after" /> </template> - <!-- Skeleton --> <SkeletonDescriptor v-if="!entity || isLoading" /> </div> <QInnerLoading diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 6a61994c1..05bfed998 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -81,6 +81,7 @@ async function fetch() { name: `${moduleName ?? route.meta.moduleName}Summary`, params: { id: entityId || entity.id }, }" + data-cy="goToSummaryBtn" > <QIcon name="open_in_new" color="white" size="sm" /> </router-link> diff --git a/src/components/ui/CatalogItem.vue b/src/components/ui/CatalogItem.vue index 7806562b2..0ae890e37 100644 --- a/src/components/ui/CatalogItem.vue +++ b/src/components/ui/CatalogItem.vue @@ -132,7 +132,8 @@ const card = toRef(props, 'item'); display: flex; flex-direction: column; gap: 4px; - + white-space: nowrap; + width: 192px; p { margin-bottom: 0; } diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index d6b525dc8..85cc8cde2 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -54,7 +54,7 @@ const $props = defineProps({ default: 'table', }, redirect: { - type: Boolean, + type: [String, Boolean], default: true, }, arrayData: { @@ -249,7 +249,7 @@ const getLocale = (label) => { :key="chip.label" :removable="!unremovableParams?.includes(chip.label)" @remove="remove(chip.label)" - data-cy="vnFilterPanelChip" + :data-cy="`vnFilterPanelChip_${chip.label}`" > <slot name="tags" diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index a198c9c05..50da8a143 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -28,7 +28,7 @@ function copyValueText() { const val = computed(() => $props.value); </script> <template> - <div class="vn-label-value"> + <div class="vn-label-value" :data-cy="`${$attrs['data-cy'] ?? 'vnLv'}${label ?? ''}`"> <QCheckbox v-if="typeof value === 'boolean'" v-model="val" diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue index 8a1c7a0f2..984e2b64f 100644 --- a/src/components/ui/VnMoreOptions.vue +++ b/src/components/ui/VnMoreOptions.vue @@ -12,7 +12,7 @@ {{ $t('components.cardDescriptor.moreOptions') }} </QTooltip> <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> - <QList> + <QList data-cy="descriptor-more-opts_list"> <slot name="menu" :menu-ref="$refs.menuRef" /> </QList> </QMenu> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index ec6289a67..6ce28254d 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -18,20 +18,16 @@ import VnInput from 'components/common/VnInput.vue'; const emit = defineEmits(['onFetch']); -const originalAttrs = useAttrs(); - -const $attrs = computed(() => { - const { style, ...rest } = originalAttrs; - return rest; -}); +const $attrs = useAttrs(); const isRequired = computed(() => { - return Object.keys($attrs).includes('required') + return Object.keys($attrs).includes('required'); }); const $props = defineProps({ url: { type: String, default: null }, - saveUrl: {type: String, default: null}, + saveUrl: { type: String, default: null }, + userFilter: { type: Object, default: () => {} }, filter: { type: Object, default: () => {} }, body: { type: Object, default: () => {} }, addNote: { type: Boolean, default: false }, @@ -65,7 +61,7 @@ async function insert() { } function confirmAndUpdate() { - if(!newNote.text && originalText) + if (!newNote.text && originalText) quasar .dialog({ component: VnConfirm, @@ -88,11 +84,17 @@ async function update() { ...body, ...{ notes: newNote.text }, }; - await axios.patch(`${$props.saveUrl ?? `${$props.url}/${$props.body.workerFk}`}`, newBody); + await axios.patch( + `${$props.saveUrl ?? `${$props.url}/${$props.body.workerFk}`}`, + newBody, + ); } onBeforeRouteLeave((to, from, next) => { - if ((newNote.text && !$props.justInput) || (newNote.text !== originalText) && $props.justInput) + if ( + (newNote.text && !$props.justInput) || + (newNote.text !== originalText && $props.justInput) + ) quasar.dialog({ component: VnConfirm, componentProps: { @@ -104,12 +106,11 @@ onBeforeRouteLeave((to, from, next) => { else next(); }); -function fetchData([ data ]) { +function fetchData([data]) { newNote.text = data?.notes; originalText = data?.notes; emit('onFetch', data); } - </script> <template> <FetchData @@ -126,8 +127,8 @@ function fetchData([ data ]) { @on-fetch="fetchData" auto-load /> - <QCard - class="q-pa-xs q-mb-lg full-width" + <QCard + class="q-pa-xs q-mb-lg full-width" :class="{ 'just-input': $props.justInput }" v-if="$props.addNote || $props.justInput" > @@ -179,12 +180,13 @@ function fetchData([ data ]) { :url="$props.url" order="created DESC" :limit="0" - :user-filter="$props.filter" + :user-filter="userFilter" + :filter="filter" auto-load ref="vnPaginateRef" class="show" v-bind="$attrs" - search-url="notes" + :search-url="false" @on-fetch=" newNote.text = ''; newNote.observationTypeFk = null; @@ -218,7 +220,7 @@ function fetchData([ data ]) { > {{ observationTypes.find( - (ot) => ot.id === note.observationTypeFk + (ot) => ot.id === note.observationTypeFk, )?.description }} </QBadge> diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 8607d9694..7b82aece8 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/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/useArrayData.js b/src/composables/useArrayData.js index fcc61972a..d1c1b01b8 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -148,8 +148,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); @@ -245,7 +244,7 @@ export function useArrayData(key, userOptions) { async function loadMore() { if (!store.hasMoreData) return; - store.skip = store.limit * store.page; + store.skip = (store?.filter?.limit ?? store.limit) * store.page; store.page += 1; await fetch({ append: true }); diff --git a/src/css/app.scss b/src/css/app.scss index 994ae7ff1..5befd150b 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; @@ -337,5 +339,5 @@ input::-webkit-inner-spin-button { } .containerShrinked { - width: 80%; + width: 70%; } 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 5b667555e..c1286267c 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 @@ -369,6 +369,7 @@ globals: countryFk: Country countryCodeFk: Country companyFk: Company + nickname: Alias model: Model fuel: Fuel active: Active @@ -530,6 +531,7 @@ ticket: customerCard: Customer card ticketList: Ticket List newOrder: New Order + ticketClaimed: Claimed ticket boxing: expedition: Expedition created: Created @@ -602,7 +604,6 @@ worker: balance: Balance medical: Medical list: - department: Department schedule: Schedule newWorker: New worker summary: @@ -861,7 +862,6 @@ components: mine: For me hasMinPrice: Minimum price # LatestBuysFilter - salesPersonFk: Buyer supplierFk: Supplier from: From to: To diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 3f004485d..681781d11 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 @@ -370,6 +370,7 @@ globals: countryFk: País countryCodeFk: País companyFk: Empresa + nickname: Alias errors: statusUnauthorized: Acceso denegado statusInternalServerError: Ha ocurrido un error interno del servidor @@ -530,13 +531,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 @@ -621,8 +622,6 @@ invoiceOut: errors: downloadCsvFailed: Error al descargar CSV order: - field: - salesPersonFk: Comercial form: clientFk: Cliente addressFk: Dirección @@ -690,7 +689,6 @@ worker: formation: Formación medical: Mutua list: - department: Departamento schedule: Horario newWorker: Nuevo trabajador summary: @@ -948,7 +946,6 @@ components: hasMinPrice: Precio mínimo wareHouseFk: Almacén # LatestBuysFilter - salesPersonFk: Comprador supplierFk: Proveedor visible: Visible active: Activo 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/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/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/AccountDescriptorProxy.vue b/src/pages/Account/Card/AccountDescriptorProxy.vue new file mode 100644 index 000000000..de3220fea --- /dev/null +++ b/src/pages/Account/Card/AccountDescriptorProxy.vue @@ -0,0 +1,14 @@ +<script setup> +import AccountDescriptor from './AccountDescriptor.vue'; +import AccountSummary from './AccountSummary.vue'; +</script> +<template> + <QPopupProxy style="max-width: 10px"> + <AccountDescriptor + v-if="$attrs.id" + v-bind="$attrs.id" + :summary="AccountSummary" + :proxy-render="true" + /> + </QPopupProxy> +</template> diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index f7a16e8c3..2172fec9a 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 } }); @@ -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/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/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/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..d789b63d3 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -5,6 +5,7 @@ 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 DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; @@ -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 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/ClaimNotes.vue b/src/pages/Claim/Card/ClaimNotes.vue index cc6e33779..68cb220ee 100644 --- a/src/pages/Claim/Card/ClaimNotes.vue +++ b/src/pages/Claim/Card/ClaimNotes.vue @@ -1,5 +1,5 @@ <script setup> -import { computed, useAttrs } from 'vue'; +import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useState } from 'src/composables/useState'; import VnNotes from 'src/components/ui/VnNotes.vue'; @@ -7,7 +7,6 @@ import VnNotes from 'src/components/ui/VnNotes.vue'; const route = useRoute(); const state = useState(); const user = state.getUser(); -const $attrs = useAttrs(); const $props = defineProps({ id: { type: [Number, String], default: null }, @@ -15,24 +14,21 @@ const $props = defineProps({ }); const claimId = computed(() => $props.id || route.params.id); -const claimFilter = computed(() => { - return { - where: { claimFk: claimId.value }, - fields: ['id', 'created', 'workerFk', 'text'], - include: { - relation: 'worker', - scope: { - fields: ['id', 'firstName', 'lastName'], - include: { - relation: 'user', - scope: { - fields: ['id', 'nickname', 'name'], - }, +const claimFilter = { + fields: ['id', 'created', 'workerFk', 'text'], + include: { + relation: 'worker', + scope: { + fields: ['id', 'firstName', 'lastName'], + include: { + relation: 'user', + scope: { + fields: ['id', 'nickname', 'name'], }, }, }, - }; -}); + }, +}; const body = { claimFk: claimId.value, @@ -43,7 +39,8 @@ const body = { <VnNotes url="claimObservations" :add-note="$props.addNote" - :filter="claimFilter" + :user-filter="claimFilter" + :filter="{ where: { claimFk: claimId } }" :body="body" v-bind="$attrs" style="overflow-y: auto" diff --git a/src/pages/Claim/Card/ClaimPhoto.vue b/src/pages/Claim/Card/ClaimPhoto.vue index d4acc9bbe..4ced7e862 100644 --- a/src/pages/Claim/Card/ClaimPhoto.vue +++ b/src/pages/Claim/Card/ClaimPhoto.vue @@ -210,6 +210,7 @@ function onDrag() { class="all-pointer-events absolute delete-button zindex" @click.stop="viewDeleteDms(index)" round + :data-cy="`delete-button-${index+1}`" /> <QIcon name="play_circle" @@ -227,6 +228,7 @@ function onDrag() { class="rounded-borders cursor-pointer fit" @click="openDialog(media.dmsFk)" v-if="!media.isVideo" + :data-cy="`file-${index+1}`" > </QImg> <video @@ -235,6 +237,7 @@ function onDrag() { muted="muted" v-if="media.isVideo" @click="openDialog(media.dmsFk)" + :data-cy="`file-${index+1}`" /> </QCard> </div> diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 210b0c982..5d06d5627 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -19,6 +19,7 @@ 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'; const route = useRoute(); @@ -252,13 +253,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 +274,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..577ac2a65 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" diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index 0fe7fc588..37146865c 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -44,15 +44,14 @@ const props = defineProps({ is-outlined /> <VnSelect - :label="t('Salesperson')" - v-model="params.salesPersonFk" - url="Workers/activeWithInheritedRole" - :filter="{ where: { role: 'salesPerson' } }" - :use-like="false" - option-filter="firstName" - dense outlined + dense rounded + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> <VnSelect :label="t('claim.attendedBy')" @@ -126,7 +125,6 @@ en: search: Contains clientFk: Customer clientName: Customer - salesPersonFk: Salesperson attenderFk: Attender claimResponsibleFk: Responsible claimStateFk: State @@ -139,7 +137,6 @@ es: search: Contiene clientFk: Cliente clientName: Cliente - salesPersonFk: Comercial attenderFk: Asistente claimResponsibleFk: Responsable claimStateFk: Estado @@ -148,6 +145,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..06996c2c1 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'), @@ -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/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 89f9d9449..8978c00f1 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 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(); @@ -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 @@ -118,14 +114,6 @@ const debtWarning = computed(() => { > <QTooltip>{{ t('Allowed substitution') }}</QTooltip> </QIcon> - <QIcon - v-if="customer?.isFreezed" - name="vn:frozen" - size="xs" - color="primary" - > - <QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip> - </QIcon> <QIcon v-if="!entity.account?.active" color="primary" @@ -150,6 +138,14 @@ const debtWarning = computed(() => { > <QTooltip>{{ t('customer.card.notChecked') }}</QTooltip> </QIcon> + <QIcon + v-if="entity?.isFreezed" + name="vn:frozen" + size="xs" + color="primary" + > + <QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip> + </QIcon> <QBtn v-if="entity.unpaid" flat @@ -163,13 +159,13 @@ const debtWarning = computed(() => { <br /> {{ t('unpaidDated', { - dated: toDate(customer.unpaid?.dated), + dated: toDate(entity.unpaid?.dated), }) }} <br /> {{ t('unpaidAmount', { - amount: toCurrency(customer.unpaid?.amount), + amount: toCurrency(entity.unpaid?.amount), }) }} </QTooltip> 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/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/CustomerNotes.vue b/src/pages/Customer/Card/CustomerNotes.vue index 189b59904..5a078b0cb 100644 --- a/src/pages/Customer/Card/CustomerNotes.vue +++ b/src/pages/Customer/Card/CustomerNotes.vue @@ -1,28 +1,15 @@ <script setup> -import { computed } from 'vue'; -import { useRoute } from 'vue-router'; import VnNotes from 'src/components/ui/VnNotes.vue'; - -const route = useRoute(); - -const noteFilter = computed(() => { - return { - order: 'created DESC', - where: { - clientFk: `${route.params.id}`, - }, - }; -}); </script> - <template> <VnNotes url="clientObservations" :add-note="true" - :filter="noteFilter" - :body="{ clientFk: route.params.id }" + :filter="{ where: { clientFk: $route.params.id } }" + :body="{ clientFk: $route.params.id }" style="overflow-y: auto" :select-type="true" required + order="created DESC" /> </template> diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index c98bf1ffb..7d5d691a3 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'; @@ -106,16 +107,12 @@ const sumRisk = ({ clientRisks }) => { {{ t('globals.params.email') }} <VnLinkMail email="entity.email"></VnLinkMail> </template ></VnLv> - <VnLv - :label="t('customer.summary.salesPerson')" - :value="entity?.salesPersonUser?.name" - > + <VnLv :label="t('globals.department')"> <template #value> - <VnUserLink - :name="entity.salesPersonUser?.name" - :worker-id="entity.salesPersonFk" - /> </template - ></VnLv> + <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" 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..2ace6dd02 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({ @@ -65,22 +64,15 @@ const exprBuilder = (param, value) => { </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 - dense + <VnSelect outlined + dense rounded - :input-debounce="0" + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> </QItemSection> </QItem> @@ -164,7 +156,6 @@ en: params: search: Contains fi: FI - salesPersonFk: Salesperson provinceFk: Province isActive: Is active city: City @@ -191,7 +182,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 +191,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..0eab7b7c5 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)" @@ -62,29 +55,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 @@ -219,7 +189,6 @@ const departments = ref(); en: params: clientFk: Client - salesPersonFk: Salesperson countryFk: Country paymentMethod: P. Method balance: Balance D. @@ -230,7 +199,6 @@ en: es: params: clientFk: Cliente - salesPersonFk: Comercial countryFk: País paymentMethod: F. Pago balance: Saldo V. @@ -239,7 +207,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..b30ed6f76 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,7 +126,6 @@ const columns = computed(() => [ es: Identifier: Identificador Social name: Razón social - Salesperson: Comercial Phone: Teléfono City: Población Email: Email diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index 6ecccc544..ac80fdaa4 100644 --- a/src/pages/Customer/components/CustomerNewPayment.vue +++ b/src/pages/Customer/components/CustomerNewPayment.vue @@ -5,7 +5,7 @@ import { useRoute } from 'vue-router'; import axios from 'axios'; import { getClientRisk } from '../composables/getClientRisk'; import { useDialogPluginComponent } from 'quasar'; - +import FormModelPopup from 'components/FormModelPopup.vue'; import { usePrintService } from 'composables/usePrintService'; import useNotify from 'src/composables/useNotify.js'; import FetchData from 'components/FetchData.vue'; @@ -74,26 +74,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) descriptions.push(data.description); + data.description = descriptions.join(', '); } const calculateFromAmount = (event) => { @@ -113,7 +111,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; } @@ -181,42 +178,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 +199,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,8 +220,28 @@ async function getAmountPaid() { @update:model-value="calculateFromAmount($event)" clearable v-model.number="data.amountPaid" + data-cy="paymentAmount" /> </VnRow> + <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="data.bankFk?.accountingType?.code == 'compensation'"> <div class="text-h6"> {{ t('Compensation') }} @@ -287,27 +282,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/composables/__tests__/getAddresses.spec.js b/src/pages/Customer/composables/__tests__/getAddresses.spec.js index 9e04a83cc..76825377d 100644 --- a/src/pages/Customer/composables/__tests__/getAddresses.spec.js +++ b/src/pages/Customer/composables/__tests__/getAddresses.spec.js @@ -17,9 +17,23 @@ describe('getAddresses', () => { expect(axios.get).toHaveBeenCalledWith(`Clients/${clientId}/addresses`, { params: { filter: JSON.stringify({ - fields: ['nickname', 'street', 'city', 'id'], + include: [ + { + relation: 'client', + scope: { + fields: ['defaultAddressFk'], + include: { + relation: 'defaultAddress', + scope: { + fields: ['id', 'agencyModeFk'], + }, + }, + }, + }, + ], + fields: ['nickname', 'street', 'city', 'id', 'isActive', 'clientFk'], where: { isActive: true }, - order: 'nickname ASC', + order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], }), }, }); @@ -30,4 +44,4 @@ describe('getAddresses', () => { expect(axios.get).not.toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/src/pages/Customer/composables/getAddresses.js b/src/pages/Customer/composables/getAddresses.js index e65e64455..568b7b571 100644 --- a/src/pages/Customer/composables/getAddresses.js +++ b/src/pages/Customer/composables/getAddresses.js @@ -1,15 +1,29 @@ import axios from 'axios'; -export async function getAddresses(clientId, _filter = {}) { +export async function getAddresses(clientId, _filter = {}) { if (!clientId) return; const filter = { ..._filter, - fields: ['nickname', 'street', 'city', 'id'], + include: [ + { + relation: 'client', + scope: { + fields: ['defaultAddressFk'], + include: { + relation: 'defaultAddress', + scope: { + fields: ['id', 'agencyModeFk'], + }, + }, + }, + }, + ], + fields: ['nickname', 'street', 'city', 'id', 'isActive', 'clientFk'], where: { isActive: true }, - order: 'nickname ASC', + order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], }; const params = { filter: JSON.stringify(filter) }; return await axios.get(`Clients/${clientId}/addresses`, { params, }); -}; \ No newline at end of file +} 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..34e4a0f9c 100644 --- a/src/pages/Entry/Card/EntryBasicData.vue +++ b/src/pages/Entry/Card/EntryBasicData.vue @@ -13,6 +13,7 @@ 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'; const route = useRoute(); const { t } = useI18n(); @@ -53,7 +54,7 @@ onMounted(() => { :clear-store-on-unmount="false" > <template #form="{ data }"> - <VnRow> + <VnRow class="q-py-sm"> <VnSelectTravelExtended :data="data" v-model="data.travelFk" @@ -65,7 +66,7 @@ onMounted(() => { :required="true" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInput v-model="data.reference" :label="t('globals.reference')" /> <VnInputNumber v-model="data.invoiceAmount" @@ -73,7 +74,7 @@ onMounted(() => { :positive="false" /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <VnInput v-model="data.invoiceNumber" :label="t('entry.summary.invoiceNumber')" @@ -84,12 +85,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,9 +104,10 @@ 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" @@ -121,8 +124,16 @@ onMounted(() => { :decimal-places="2" :positive="false" /> + <VnSelect + v-model="data.typeFk" + url="entryTypes" + :fields="['code', 'description']" + option-value="code" + optionLabel="description" + sortBy="description" + /> </VnRow> - <VnRow> + <VnRow class="q-py-sm"> <QInput :label="t('entry.basicData.observation')" type="textarea" @@ -132,14 +143,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..3990fde19 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,8 @@ 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'; const $props = defineProps({ id: { @@ -57,31 +59,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 +88,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 +183,6 @@ const columns = [ }, }, { - align: 'center', labelAbbreviation: 'GM', label: t('Grouping selector'), toolTip: t('Grouping selector'), @@ -240,7 +210,6 @@ const columns = [ }, }, { - align: 'center', labelAbbreviation: 'G', label: 'Grouping', toolTip: 'Grouping', @@ -294,7 +263,7 @@ const columns = [ align: 'center', label: t('Amount'), name: 'amount', - width: '45px', + width: '75px', component: 'number', attrs: { positive: false, @@ -310,7 +279,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 +291,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 +313,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 +322,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 +384,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) @@ -610,6 +586,7 @@ onMounted(() => { :url="`Entries/${entityId}/getBuyList`" search-url="EntryBuys" save-url="Buys/crud" + :filter="filter" :disable-option="{ card: true }" v-model:selected="selectedRows" @on-fetch="() => footerFetchDataRef.fetch()" @@ -655,7 +632,7 @@ onMounted(() => { :is-editable="editableMode" :without-header="!editableMode" :with-filters="editableMode" - :right-search="editableMode" + :right-search="false" :row-click="false" :columns="columns" :beforeSaveFn="beforeSave" @@ -666,6 +643,46 @@ 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" + /> + <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 +713,7 @@ onMounted(() => { </div> </template> <template #column-footer-weight> - {{ footer?.weight }} + <span class="q-pr-xs">{{ footer?.weight }}</span> </template> <template #column-footer-quantity> <span :style="getQuantityStyle(footer)" data-cy="footer-quantity"> @@ -704,9 +721,8 @@ onMounted(() => { </span> </template> <template #column-footer-amount> - <span :style="getAmountStyle(footer)" data-cy="footer-amount"> - {{ footer?.amount }} - </span> + <span data-cy="footer-amount">{{ footer?.amount }} / </span> + <span style="color: var(--q-positive)">{{ footer?.checkedAmount }}</span> </template> <template #column-create-itemFk="{ data }"> <VnSelect @@ -767,6 +783,8 @@ onMounted(() => { </template> <i18n> es: + Buyer: Comprador + Family: Familia Article: Artículo Siz.: Med. Size: Medida diff --git a/src/pages/Entry/Card/EntryCard.vue b/src/pages/Entry/Card/EntryCard.vue index be82289f4..50f8b8e55 100644 --- a/src/pages/Entry/Card/EntryCard.vue +++ b/src/pages/Entry/Card/EntryCard.vue @@ -1,10 +1,10 @@ <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" diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 69b300cb2..313ed3d72 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -146,9 +146,8 @@ async function deleteEntry() { <template> <CardDescriptor - ref="entryDescriptorRef" :url="`Entries/${entityId}`" - :userFilter="entryFilter" + :filter="entryFilter" title="supplier.nickname" data-key="Entry" width="lg-width" 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..53967e66f 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -92,13 +92,13 @@ onMounted(async () => { </div> <div class="card-content"> <VnCheckbox - :label="t('entry.summary.ordered')" + :label="t('entry.list.tableVisibleColumns.isOrdered')" v-model="entry.isOrdered" :disable="true" size="xs" /> <VnCheckbox - :label="t('globals.confirmed')" + :label="t('entry.list.tableVisibleColumns.isConfirmed')" v-model="entry.isConfirmed" :disable="true" size="xs" @@ -110,7 +110,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" diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index 6bce6aa04..82bcb1a79 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 > @@ -102,6 +102,7 @@ const entryFilterPanel = ref(); v-model="params.landed" @update:model-value="searchFn()" is-outlined + data-cy="landed" /> </QItemSection> </QItem> @@ -121,13 +122,6 @@ const entryFilterPanel = ref(); rounded /> </QItemSection> - <QItemSection> - <VnInput - v-model="params.invoiceNumber" - :label="t('params.invoiceNumber')" - is-outlined - /> - </QItemSection> </QItem> <QItem> <QItemSection> @@ -145,6 +139,7 @@ const entryFilterPanel = ref(); v-model="params.agencyModeId" @update:model-value="searchFn()" url="AgencyModes" + sort-by="name ASC" :fields="['id', 'name']" hide-selected dense @@ -170,6 +165,7 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" url="Warehouses" :fields="['id', 'name']" + sort-by="name ASC" hide-selected dense outlined @@ -185,6 +181,7 @@ const entryFilterPanel = ref(); @update:model-value="searchFn()" url="Warehouses" :fields="['id', 'name']" + sort-by="name ASC" hide-selected dense outlined @@ -232,15 +229,6 @@ const entryFilterPanel = ref(); /> </QItemSection> </QItem> - <QItem> - <QItemSection> - <VnInput - v-model="params.evaNotes" - :label="t('params.evaNotes')" - is-outlined - /> - </QItemSection> - </QItem> </template> </VnFilterPanel> </template> @@ -266,7 +254,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..5ebad3144 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -107,9 +107,8 @@ const columns = computed(() => [ attrs: { url: 'suppliers', fields: ['id', 'name'], - where: { order: 'name DESC' }, + sortBy: 'name ASC', }, - format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), width: '110px', }, { @@ -145,6 +144,7 @@ const columns = computed(() => [ attrs: { url: 'agencyModes', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -158,7 +158,6 @@ const columns = computed(() => [ component: 'input', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.warehouseOutFk'), name: 'warehouseOutFk', cardVisible: true, @@ -166,6 +165,7 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -174,7 +174,6 @@ const columns = computed(() => [ width: '65px', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.warehouseInFk'), name: 'warehouseInFk', cardVisible: true, @@ -182,6 +181,7 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], + sortBy: 'name ASC', }, columnField: { component: null, @@ -190,7 +190,6 @@ const columns = computed(() => [ width: '65px', }, { - align: 'left', labelAbbreviation: t('Type'), label: t('entry.list.tableVisibleColumns.entryTypeDescription'), toolTip: t('entry.list.tableVisibleColumns.entryTypeDescription'), @@ -201,6 +200,7 @@ const columns = computed(() => [ fields: ['code', 'description'], optionValue: 'code', optionLabel: 'description', + sortBy: 'description', }, width: '65px', format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription), @@ -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..5da51d5a6 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -1,24 +1,23 @@ <script setup> import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useState } from 'src/composables/useState'; -import { useQuasar } from 'quasar'; +import { useQuasar, date } from 'quasar'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import FetchData from 'components/FetchData.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnRow from 'components/ui/VnRow.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import EntryStockBoughtFilter from './EntryStockBoughtFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import EntryStockBoughtDetail from 'src/pages/Entry/EntryStockBoughtDetail.vue'; +import TravelDescriptorProxy from '../Travel/Card/TravelDescriptorProxy.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; +import axios from 'axios'; const { t } = useI18n(); const quasar = useQuasar(); -const state = useState(); -const user = state.getUser(); +const filterDate = ref(useFilterParams('StockBoughts').params); const footer = ref({ bought: 0, reserve: 0 }); const columns = computed(() => [ { @@ -46,7 +45,7 @@ const columns = computed(() => [ optionValue: 'id', }, columnFilter: false, - width: '50px', + width: '60%', }, { align: 'center', @@ -56,20 +55,20 @@ const columns = computed(() => [ create: true, component: 'number', summation: true, - width: '50px', format: ({ reserve }, dashIfEmpty) => dashIfEmpty(round(reserve)), + width: '20%', }, { - align: 'center', + align: 'right', label: t('entryStockBought.bought'), name: 'bought', summation: true, cardVisible: true, style: ({ reserve, bought }) => boughtStyle(bought, reserve), columnFilter: false, + width: '20%', }, { - align: 'left', label: t('entryStockBought.date'), name: 'dated', component: 'date', @@ -77,7 +76,7 @@ const columns = computed(() => [ create: true, }, { - align: 'left', + align: 'center', name: 'tableActions', actions: [ { @@ -90,7 +89,7 @@ const columns = computed(() => [ component: EntryStockBoughtDetail, componentProps: { workerFk: row.workerFk, - dated: userParams.value.dated, + dated: filterDate.value.dated, }, }); }, @@ -98,39 +97,29 @@ const columns = computed(() => [ ], }, ]); - const fetchDataRef = ref(); const travelDialogRef = ref(false); const tableRef = ref(); const travel = ref(null); -const userParams = ref({ - dated: Date.vnNew().toJSON(), -}); - -const filter = ref({ - fields: ['id', 'm3', 'warehouseInFk'], +const filter = computed(() => ({ + fields: ['id', 'm3', 'ref', 'warehouseInFk'], include: [ { relation: 'warehouseIn', scope: { - fields: ['code'], + fields: ['code', 'name'], }, }, ], where: { - shipped: (userParams.value.dated - ? new Date(userParams.value.dated) - : Date.vnNew() - ).setHours(0, 0, 0, 0), + shipped: date.adjustDate(filterDate.value.dated, { + hour: 0, + minute: 0, + second: 0, + }), m3: { neq: null }, }, -}); - -const setUserParams = async ({ dated }) => { - const shipped = (dated ? new Date(dated) : Date.vnNew()).setHours(0, 0, 0, 0); - filter.value.where.shipped = shipped; - fetchDataRef.value?.fetch(); -}; +})); function openDialog() { travelDialogRef.value = true; @@ -151,6 +140,31 @@ function round(value) { function boughtStyle(bought, reserve) { return reserve < bought ? { color: 'var(--q-negative)' } : ''; } + +async function beforeSave(data, getChanges) { + const changes = data.creates; + if (!changes) return data; + const patchPromises = []; + + for (const change of changes) { + if (change?.isReal === false && change?.reserve > 0) { + const postData = { + workerFk: change.workerFk, + reserve: change.reserve, + dated: filterDate.value.dated, + }; + const promise = axios.post('StockBoughts', postData).catch((error) => { + console.error('Error processing change: ', change, error); + }); + + patchPromises.push(promise); + } + } + + await Promise.all(patchPromises); + const filteredChanges = changes.filter((change) => change?.isReal !== false); + data.creates = filteredChanges; +} </script> <template> <VnSubToolbar> @@ -158,18 +172,17 @@ function boughtStyle(bought, reserve) { <FetchData ref="fetchDataRef" url="Travels" - auto-load :filter="filter" @on-fetch=" (data) => { travel = data.find( - (data) => data.warehouseIn?.code.toLowerCase() === 'vnh', + (data) => data.warehouseIn?.code?.toLowerCase() === 'vnh', ); } " /> <VnRow class="travel"> - <div v-if="travel"> + <div v-show="travel"> <span style="color: var(--vn-label-color)"> {{ t('entryStockBought.purchaseSpaces') }}: </span> @@ -180,7 +193,7 @@ function boughtStyle(bought, reserve) { v-if="travel?.m3" style="max-width: 20%" flat - icon="edit" + icon="search" @click="openDialog()" :title="t('entryStockBought.editTravel')" color="primary" @@ -195,57 +208,42 @@ function boughtStyle(bought, reserve) { :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> @@ -278,9 +276,6 @@ function boughtStyle(bought, reserve) { .column { min-width: 35%; margin-top: 5%; - display: flex; - flex-direction: column; - align-items: center; } .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..10d863ea2 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 @@ -33,7 +33,7 @@ entry: invoiceAmount: Importe ordered: Pedida booked: Contabilizada - excludedFromAvailable: Inventario + excludedFromAvailable: Excluido travelReference: Referencia travelAgency: Agencia travelShipped: F. envio @@ -56,7 +56,7 @@ entry: observation: Observación commission: Comisión booked: Contabilizada - excludedFromAvailable: Inventario + excludedFromAvailable: Excluido initialTemperature: Ini °C finalTemperature: Fin °C buys: @@ -66,30 +66,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,9 +113,13 @@ 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: Excluido isOrdered: Pedida isConfirmed: Confirmado isReceived: Recibida @@ -149,7 +135,7 @@ entryFilter: warehouseInFk: Destino entryTypeCode: Tipo de entrada hasToShowDeletedEntries: Mostrar entradas eliminadas -myEntries: +entrySupplier: id: ID landed: F. llegada shipped: F. salida @@ -164,10 +150,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..dc963a91b 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -121,25 +121,40 @@ function deleteFile(dmsFk) { hide-selected :is-clearable="false" :required="true" + data-cy="invoiceInBasicDataSupplier" /> <VnInput clearable clear-icon="close" :label="t('invoiceIn.supplierRef')" v-model="data.supplierRef" + data-cy="invoiceInBasicDataSupplierRef" /> </VnRow> <VnRow> - <VnInputDate :label="t('Expedition date')" v-model="data.issued" /> + <VnInputDate + :label="t('Expedition date')" + v-model="data.issued" + data-cy="invoiceInBasicDataIssued" + /> <VnInputDate :label="t('Operation date')" v-model="data.operated" autofocus + data-cy="invoiceInBasicDataOperated" /> </VnRow> <VnRow> - <VnInputDate :label="t('Entry date')" v-model="data.bookEntried" /> - <VnInputDate :label="t('Accounted date')" v-model="data.booked" /> + <VnInputDate + :label="t('Entry date')" + v-model="data.bookEntried" + data-cy="invoiceInBasicDatabookEntried" + /> + <VnInputDate + :label="t('Accounted date')" + v-model="data.booked" + data-cy="invoiceInBasicDataBooked" + /> </VnRow> <VnRow> <VnSelect @@ -149,7 +164,7 @@ function deleteFile(dmsFk) { option-value="id" option-label="id" :filter-options="['id', 'name']" - data-cy="UnDeductibleVatSelect" + data-cy="invoiceInBasicDataDeductibleExpenseFk" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -182,6 +197,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="downloadFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDownload" /> <QBtn :class="{ @@ -197,6 +213,7 @@ function deleteFile(dmsFk) { documentDialogRef.dms = data.dms; } " + data-cy="invoiceInBasicDataDmsEdit" > <QTooltip>{{ t('Edit document') }}</QTooltip> </QBtn> @@ -210,6 +227,7 @@ function deleteFile(dmsFk) { padding="xs" round @click="deleteFile(data.dmsFk)" + data-cy="invoiceInBasicDataDmsDelete" /> </div> <QBtn @@ -224,7 +242,7 @@ function deleteFile(dmsFk) { delete documentDialogRef.dms; } " - data-cy="dms-create" + data-cy="invoiceInBasicDataDmsAdd" > <QTooltip>{{ t('Create document') }}</QTooltip> </QBtn> @@ -237,9 +255,9 @@ function deleteFile(dmsFk) { :label="t('Currency')" v-model="data.currencyFk" :options="currencies" - option-value="id" option-label="code" sort-by="id" + data-cy="invoiceInBasicDataCurrencyFk" /> <VnSelect @@ -249,8 +267,8 @@ function deleteFile(dmsFk) { :label="t('Company')" v-model="data.companyFk" :options="companies" - option-value="id" option-label="code" + data-cy="invoiceInBasicDataCompanyFk" /> </VnRow> <VnRow> @@ -260,6 +278,7 @@ function deleteFile(dmsFk) { :options="sageWithholdings" option-value="id" option-label="withholding" + data-cy="invoiceInBasicDataWithholdingSageFk" /> </VnRow> </template> diff --git a/src/pages/InvoiceIn/Card/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..eb673c546 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -17,10 +17,6 @@ const { t } = useI18n(); const cardDescriptorRef = ref(); const entityId = computed(() => $props.id || +currentRoute.value.params.id); const totalAmount = ref(); -const config = ref(); -const cplusRectificationTypes = ref([]); -const siiTypeInvoiceIns = ref([]); -const invoiceCorrectionTypes = ref([]); const invoiceInCorrection = reactive({ correcting: [], corrected: null }); const routes = reactive({ getSupplier: (id) => { @@ -30,7 +26,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ supplierFk: id }), + table: JSON.stringify({ supplierFk: id }), }, }; }, @@ -39,7 +35,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ correctedFk: entityId.value }), + table: JSON.stringify({ correctedFk: entityId.value }), }, }; } @@ -108,7 +104,7 @@ async function setInvoiceCorrection(id) { <VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" /> <VnLv :label="t('invoiceIn.list.supplier')"> <template #value> - <span class="link"> + <span class="link" data-cy="invoiceInDescriptor_supplier"> {{ entity?.supplier?.nickname }} <SupplierDescriptorProxy :id="entity?.supplierFk" /> </span> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue index 8b039ec27..058f17d31 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, toRefs, reactive } from 'vue'; +import { ref, computed, toRefs, reactive, onBeforeMount } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -111,10 +111,9 @@ async function cloneInvoice() { } const isAgricultural = () => { - if (!config.value) return false; return ( - invoiceIn.value?.supplier?.sageFarmerWithholdingFk === - config?.value[0]?.sageWithholdingFk + invoiceIn.value?.supplier?.sageWithholdingFk == + config.value?.sageFarmerWithholdingFk ); }; function showPdfInvoice() { @@ -153,162 +152,183 @@ const createInvoiceInCorrection = async () => { ); push({ path: `/invoice-in/${correctingId}/summary` }); }; + +onBeforeMount(async () => { + config.value = ( + await axios.get('invoiceinConfigs/findOne', { + params: { fields: ['sageFarmerWithholdingFk'] }, + }) + ).data; +}); </script> - <template> - <FetchData - url="InvoiceCorrectionTypes" - @on-fetch="(data) => (invoiceCorrectionTypes = data)" - auto-load - /> - <FetchData - url="CplusRectificationTypes" - @on-fetch="(data) => (cplusRectificationTypes = data)" - auto-load - /> - <FetchData - url="SiiTypeInvoiceIns" - :where="{ code: { like: 'R%' } }" - @on-fetch="(data) => (siiTypeInvoiceIns = data)" - auto-load - /> - <FetchData - url="InvoiceInConfigs" - :where="{ fields: ['sageWithholdingFk'] }" - auto-load - @on-fetch="(data) => (config = data)" - /> - <InvoiceInToBook> - <template #content="{ book }"> - <QItem - v-if="!invoice?.isBooked && canEditProp('toBook')" - v-ripple - clickable - @click="book(entityId)" + <template v-if="config"> + <FetchData + url="InvoiceCorrectionTypes" + @on-fetch="(data) => (invoiceCorrectionTypes = data)" + auto-load + /> + <FetchData + url="CplusRectificationTypes" + @on-fetch="(data) => (cplusRectificationTypes = data)" + auto-load + /> + <FetchData + url="SiiTypeInvoiceIns" + :where="{ code: { like: 'R%' } }" + @on-fetch="(data) => (siiTypeInvoiceIns = data)" + auto-load + /> + <InvoiceInToBook> + <template #content="{ book }"> + <QItem + v-if="!invoice?.isBooked && canEditProp('toBook')" + v-ripple + clickable + @click="book(entityId)" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> + </QItem> + </template> + </InvoiceInToBook> + <QItem + v-if="invoice?.isBooked && canEditProp('toUnbook')" + v-ripple + clickable + @click="triggerMenu('unbook')" + > + <QItemSection> + {{ t('invoiceIn.descriptorMenu.unbook') }} + </QItemSection> + </QItem> + <QItem + v-if="canEditProp('deleteById')" + v-ripple + clickable + @click="triggerMenu('delete')" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection> + </QItem> + <QItem + v-if="canEditProp('clone')" + v-ripple + clickable + @click="triggerMenu('clone')" + > + <QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> + <QItemSection>{{ + t('invoiceIn.descriptorMenu.showAgriculturalPdf') + }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> + <QItemSection + >{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection > - <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> - </QItem> - </template> - </InvoiceInToBook> - <QItem - v-if="invoice?.isBooked && canEditProp('toUnbook')" - v-ripple - clickable - @click="triggerMenu('unbook')" - > - <QItemSection> - {{ t('invoiceIn.descriptorMenu.unbook') }} - </QItemSection> - </QItem> - <QItem - v-if="canEditProp('deleteById')" - v-ripple - clickable - @click="triggerMenu('delete')" - > - <QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection> - </QItem> - <QItem v-if="canEditProp('clone')" v-ripple clickable @click="triggerMenu('clone')"> - <QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection> - </QItem> - <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> - <QItemSection>{{ - t('invoiceIn.descriptorMenu.showAgriculturalPdf') - }}</QItemSection> - </QItem> - <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> - <QItemSection - >{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection + </QItem> + <QItem + v-if="!invoiceInCorrection.corrected" + v-ripple + clickable + @click="triggerMenu('correct')" + data-cy="createCorrectiveItem" > - </QItem> - <QItem - v-if="!invoiceInCorrection.corrected" - v-ripple - clickable - @click="triggerMenu('correct')" - data-cy="createCorrectiveItem" - > - <QItemSection - >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection + <QItemSection + >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection + > + </QItem> + <QItem + v-if="invoice.dmsFk" + v-ripple + clickable + @click="downloadFile(invoice.dmsFk)" > - </QItem> - <QItem v-if="invoice.dmsFk" v-ripple clickable @click="downloadFile(invoice.dmsFk)"> - <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> - </QItem> - <QDialog ref="correctionDialogRef"> - <QCard> - <QCardSection> - <QItem class="q-px-none"> - <span class="text-primary text-h6 full-width"> - {{ t('Create rectificative invoice') }} - </span> - <QBtn icon="close" flat round dense v-close-popup /> - </QItem> - </QCardSection> - <QCardSection> - <QItem> - <QItemSection> - <QInput - :label="t('Original invoice')" - v-model="entityId" - readonly - /> - <VnSelect - :label="`${useCapitalize(t('globals.class'))}`" - v-model="correctionFormData.invoiceClass" - :options="siiTypeInvoiceIns" - option-value="id" - option-label="code" - :required="true" - /> - </QItemSection> - <QItemSection> - <VnSelect - :label="`${useCapitalize(t('globals.type'))}`" - v-model="correctionFormData.invoiceType" - :options="cplusRectificationTypes" - option-value="id" - option-label="description" - :required="true" - > - <template #option="{ itemProps, opt }"> - <QItem v-bind="itemProps"> - <QItemSection> - <QItemLabel - >{{ opt.id }} - - {{ opt.description }}</QItemLabel - > - </QItemSection> - </QItem> - <div></div> - </template> - </VnSelect> + <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> + </QItem> + <QDialog ref="correctionDialogRef"> + <QCard data-cy="correctiveInvoiceDialog"> + <QCardSection> + <QItem class="q-px-none"> + <span class="text-primary text-h6 full-width"> + {{ t('Create rectificative invoice') }} + </span> + <QBtn icon="close" flat round dense v-close-popup /> + </QItem> + </QCardSection> + <QCardSection> + <QItem> + <QItemSection> + <QInput + :label="t('Original invoice')" + v-model="entityId" + readonly + /> + <VnSelect + :label="`${useCapitalize(t('globals.class'))}`" + v-model="correctionFormData.invoiceClass" + :options="siiTypeInvoiceIns" + option-value="id" + option-label="code" + :required="true" + data-cy="invoiceInDescriptorMenu_class" + /> + </QItemSection> + <QItemSection> + <VnSelect + :label="`${useCapitalize(t('globals.type'))}`" + v-model="correctionFormData.invoiceType" + :options="cplusRectificationTypes" + option-value="id" + option-label="description" + :required="true" + data-cy="invoiceInDescriptorMenu_type" + > + <template #option="{ itemProps, opt }"> + <QItem v-bind="itemProps"> + <QItemSection> + <QItemLabel + >{{ opt.id }} - + {{ opt.description }}</QItemLabel + > + </QItemSection> + </QItem> + <div></div> + </template> + </VnSelect> - <VnSelect - :label="`${useCapitalize(t('globals.reason'))}`" - v-model="correctionFormData.invoiceReason" - :options="invoiceCorrectionTypes" - option-value="id" - option-label="description" - :required="true" - /> - </QItemSection> - </QItem> - </QCardSection> - <QCardActions class="justify-end q-mr-sm"> - <QBtn flat :label="t('globals.close')" color="primary" v-close-popup /> - <QBtn - :label="t('globals.save')" - color="primary" - v-close-popup - @click="createInvoiceInCorrection" - :disable="isNotFilled" - /> - </QCardActions> - </QCard> - </QDialog> + <VnSelect + :label="`${useCapitalize(t('globals.reason'))}`" + v-model="correctionFormData.invoiceReason" + :options="invoiceCorrectionTypes" + option-value="id" + option-label="description" + :required="true" + data-cy="invoiceInDescriptorMenu_reason" + /> + </QItemSection> + </QItem> + </QCardSection> + <QCardActions class="justify-end q-mr-sm"> + <QBtn + flat + :label="t('globals.close')" + color="primary" + v-close-popup + /> + <QBtn + :label="t('globals.save')" + color="primary" + v-close-popup + @click="createInvoiceInCorrection" + :disable="isNotFilled" + data-cy="saveCorrectiveInvoice" + /> + </QCardActions> + </QCard> + </QDialog> + </template> </template> - <i18n> en: isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 18602f043..f6beecd3d 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -198,6 +198,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; color="orange-11" text-color="black" @click="book(entityId)" + data-cy="invoiceInSummary_book" /> </template> </InvoiceIntoBook> @@ -219,7 +220,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :value="entity.supplier?.name" > <template #value> - <span class="link"> + <span class="link" data-cy="invoiceInSummary_supplier"> {{ entity.supplier?.name }} <SupplierDescriptorProxy :id="entity.supplierFk" /> </span> diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index eae255120..e37cf5b7e 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -202,6 +202,9 @@ function setCursor(ref) { :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" + :acls="[ + { model: 'Expense', props: '*', accessType: 'WRITE' }, + ]" @keydown.tab.prevent=" autocompleteExpense( $event, diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index e010a1edb..a4fb0d653 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -7,6 +7,7 @@ import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import { dateRange } from 'src/filters'; import { date } from 'quasar'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; defineProps({ dataKey: { type: String, required: true } }); const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; @@ -147,13 +148,13 @@ function handleDaysAgo(params, daysAgo) { </QItem> <QItem> <QItemSection> - <QCheckbox + <VnCheckbox :label="$t('invoiceIn.isBooked')" v-model="params.isBooked" @update:model-value="searchFn()" toggle-indeterminate /> - <QCheckbox + <VnCheckbox :label="getLocale('params.correctingFk')" v-model="params.correctingFk" @update:model-value="searchFn()" diff --git a/src/pages/InvoiceIn/InvoiceInToBook.vue b/src/pages/InvoiceIn/InvoiceInToBook.vue index 5bdbe197b..23175f2e7 100644 --- a/src/pages/InvoiceIn/InvoiceInToBook.vue +++ b/src/pages/InvoiceIn/InvoiceInToBook.vue @@ -4,7 +4,7 @@ import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import { useArrayData } from 'src/composables/useArrayData'; -import qs from 'qs'; + const { notify, dialog } = useQuasar(); const { t } = useI18n(); @@ -61,17 +61,15 @@ async function checkToBook(id) { } async function toBook(id) { - let type = 'positive'; - let message = t('globals.dataSaved'); - + let err = false; try { await axios.post(`InvoiceIns/${id}/toBook`); store.data.isBooked = true; } catch (e) { - type = 'negative'; - message = t('It was not able to book the invoice'); + err = true; + throw e; } finally { - notify({ type, message }); + if (!err) notify({ type: 'positive', message: t('globals.dataSaved') }); } } </script> diff --git a/src/pages/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 dfaf6c109..2402c0bf6 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -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')"> @@ -70,6 +75,7 @@ function ticketFilter(invoice) { icon="vn:client" color="primary" :to="{ name: 'CustomerCard', params: { id: entity.client.id } }" + data-cy="invoiceOutDescriptorCustomerCard" > <QTooltip>{{ t('invoiceOut.card.customerCard') }}</QTooltip> </QBtn> @@ -81,6 +87,7 @@ function ticketFilter(invoice) { name: 'TicketList', query: { table: ticketFilter(entity) }, }" + data-cy="invoiceOutDescriptorTicketList" > <QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip> </QBtn> diff --git a/src/pages/InvoiceOut/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index 648b8e4e6..99524e0d6 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,7 +31,7 @@ const states = ref(); <QItem> <QItemSection> <VnInput - :label="t('Customer ID')" + :label="t('globals.params.clientFk')" v-model="params.clientFk" is-outlined /> @@ -38,13 +39,17 @@ const states = ref(); </QItem> <QItem> <QItemSection> - <VnInput v-model="params.fi" :label="t('FI')" is-outlined /> + <VnInput + v-model="params.fi" + :label="t('globals.params.fi')" + is-outlined + /> </QItemSection> </QItem> <QItem> <QItemSection> <VnInputNumber - :label="t('Amount')" + :label="t('globals.amount')" v-model="params.amount" is-outlined data-cy="InvoiceOutFilterAmountBtn" @@ -54,7 +59,7 @@ const states = ref(); <QItem> <QItemSection> <QInput - :label="t('Min')" + :label="t('invoiceOut.params.min')" dense lazy-rules outlined @@ -65,7 +70,7 @@ const states = ref(); </QItemSection> <QItemSection> <QInput - :label="t('Max')" + :label="t('invoiceOut.params.max')" dense lazy-rules outlined @@ -78,7 +83,7 @@ const states = ref(); <QItem> <QItemSection> <QCheckbox - :label="t('Has PDF')" + :label="t('invoiceOut.params.hasPdf')" toggle-indeterminate v-model="params.hasPdf" /> @@ -88,14 +93,31 @@ const states = ref(); <QItemSection> <VnInputDate v-model="params.created" - :label="t('Created')" + :label="t('invoiceOut.params.created')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.dued" :label="t('Dued')" is-outlined /> + <VnInputDate + v-model="params.dued" + :label="t('invoiceOut.params.dued')" + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + outlined + rounded + :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/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index a6ec9923e..3390ef33a 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', @@ -86,6 +95,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', @@ -185,7 +208,7 @@ watchEffect(selectedRows); prefix="invoiceOut" :array-data-props="{ url: 'InvoiceOuts/filter', - order: ['id DESC'], + order: 'id DESC', }" > <template #advanced-menu> @@ -229,8 +252,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 +425,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..b24c8b247 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 }"> @@ -129,12 +129,15 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnSelectWorker - :label="t('invoiceOut.negativeBases.comercial')" - v-model="params.workerName" - option-value="name" - is-outlined - @update:model-value="searchFn()" + <VnSelect + outlined + dense + rounded + :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/ItemBarcode.vue b/src/pages/Item/Card/ItemBarcode.vue index 590b524cd..53b4514b7 100644 --- a/src/pages/Item/Card/ItemBarcode.vue +++ b/src/pages/Item/Card/ItemBarcode.vue @@ -94,6 +94,7 @@ const submit = async (rows) => { icon="add_circle" v-shortcut="'+'" flat + data-cy="addBarcode_input" > <QTooltip> {{ t('Add barcode') }} 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/ItemDiary.vue b/src/pages/Item/Card/ItemDiary.vue index 83cd562a0..f839c1f71 100644 --- a/src/pages/Item/Card/ItemDiary.vue +++ b/src/pages/Item/Card/ItemDiary.vue @@ -158,15 +158,10 @@ const getBadgeAttrs = (_date) => { const scrollToToday = async () => { await nextTick(); - const todayCell = document.querySelector(`td[data-date="${today.toISOString()}"]`); - if (todayCell) { - todayCell.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } -}; - -const formatDateForAttribute = (dateValue) => { - if (dateValue instanceof Date) return date.formatDate(dateValue, 'YYYY-MM-DD'); - return dateValue; + const todayCell = document.querySelector( + `td[data-date="${date.formatDate(today, 'YYYY-MM-DD')}"]`, + ); + if (todayCell) todayCell.scrollIntoView({ behavior: 'smooth', block: 'center' }); }; async function updateWarehouse(warehouseFk) { @@ -242,7 +237,7 @@ async function updateWarehouse(warehouseFk) { </QTd> </template> <template #body-cell-date="{ row }"> - <QTd @click.stop :data-date="formatDateForAttribute(row.shipped)"> + <QTd @click.stop :data-date="row?.shipped.substring(0, 10)"> <QBadge v-bind="getBadgeAttrs(row.shipped)" class="q-ma-none" diff --git a/src/pages/Item/ItemRequest.vue b/src/pages/Item/ItemRequest.vue index 76e4b8083..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', @@ -226,7 +242,6 @@ const onDenyAccept = (_, responseData) => { order="shipped ASC, isOk ASC" :columns="columns" :user-params="userParams" - :is-editable="true" :right-search="false" auto-load :disable-option="{ card: true }" @@ -263,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..a29203df3 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -221,7 +221,7 @@ en: attenderFk: Atender clientFk: Client id warehouseFk: Warehouse - requesterFk: Salesperson + requesterFk: Requester from: From to: To mine: For me @@ -239,7 +239,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/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/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 9d27fc96e..ff8df26d4 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 diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 935f5160b..7b768d0cb 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 diff --git a/src/pages/Monitor/MonitorClients.vue b/src/pages/Monitor/MonitorClients.vue index c1958cdcb..278b0b26f 100644 --- a/src/pages/Monitor/MonitorClients.vue +++ b/src/pages/Monitor/MonitorClients.vue @@ -31,7 +31,7 @@ function exprBuilder(param, value) { switch (param) { case 'clientFk': return { [`c.id`]: value }; - case 'salesPersonFk': + case 'departmentFk': return { [`c.${param}`]: value }; } } @@ -62,25 +62,17 @@ const columns = computed(() => [ columnFilter: false, }, { - label: t('salesClientsTable.salesPerson'), - name: 'salesPersonFk', - field: 'salesPerson', align: 'left', + name: 'departmentFk', + label: t('customer.summary.team'), + component: 'select', + attrs: { + url: 'Departments', + }, columnField: { component: null, }, - optionFilter: 'firstName', - columnFilter: { - component: 'select', - attrs: { - url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], - sortBy: 'nickname ASC', - where: { role: 'salesPerson' }, - useLike: false, - }, - }, - columnClass: 'no-padding', + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { label: t('salesClientsTable.client'), @@ -128,9 +120,9 @@ 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 class="link" :title="row.department" v-text="row.department" /> + <WorkerDescriptorProxy :id="row.departmentFk" dense /> </template> <template #column-clientFk="{ row }"> <span class="link" :title="row.clientName" v-text="row.clientName" /> diff --git a/src/pages/Monitor/MonitorOrders.vue b/src/pages/Monitor/MonitorOrders.vue index 873f8abb4..2679f7224 100644 --- a/src/pages/Monitor/MonitorOrders.vue +++ b/src/pages/Monitor/MonitorOrders.vue @@ -1,9 +1,9 @@ <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'; @@ -20,8 +20,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 +63,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'), @@ -184,11 +182,10 @@ const openTab = (id) => <CustomerDescriptorProxy :id="row.clientFk" /> </QTd> </template> - - <template #column-salesPersonFk="{ row }"> + <template #column-departmentFk="{ row }"> <QTd @click.stop> - <span class="link" v-text="row.salesPerson" /> - <WorkerDescriptorProxy :id="row.salesPersonFk" dense /> + <span class="link" v-text="row.departmentName" /> + <DepartmentDescriptorProxy :id="row.departmentFk" dense /> </QTd> </template> </VnTable> diff --git a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue index 48710d696..447dd35b8 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(); @@ -113,16 +112,16 @@ const getLocale = (label) => { </QItem> <QItem> <QItemSection> - <VnSelectWorker + <VnSelect outlined dense rounded - :label="t('globals.params.salesPersonFk')" - v-model="params.salesPersonFk" - :params="{ departmentCodes: ['VT'] }" - :no-one="true" - > - </VnSelectWorker> + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" + /> </QItemSection> </QItem> <QItem> diff --git a/src/pages/Monitor/Ticket/MonitorTickets.vue b/src/pages/Monitor/Ticket/MonitorTickets.vue index 2ec862df0..03d751595 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'; @@ -17,6 +17,7 @@ import MonitorTicketFilter from './MonitorTicketFilter.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; import VnDateBadge from 'src/components/common/VnDateBadge.vue'; import { useStateStore } from 'src/stores/useStateStore'; +import useOpenURL from 'src/composables/useOpenURL'; const DEFAULT_AUTO_REFRESH = 2 * 60 * 1000; const { t } = useI18n(); @@ -48,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': @@ -107,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'), @@ -321,8 +321,7 @@ const totalPriceColor = (ticket) => { if (total > 0 && total < 50) return 'warning'; }; -const openTab = (id) => - window.open(`#/ticket/${id}/sale`, '_blank', 'noopener, noreferrer'); +const openTab = (id) => useOpenURL(`#/ticket/${id}/sale`); </script> <template> <FetchData @@ -397,6 +396,7 @@ const openTab = (id) => default-mode="table" auto-load :row-click="({ id }) => openTab(id)" + :row-ctrl-click="(_, { id }) => openTab(id)" :disable-option="{ card: true }" :user-params="{ from, to, scopeDays: 0 }" > @@ -436,10 +436,10 @@ const openTab = (id) => <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 }"> diff --git a/src/pages/Monitor/locale/en.yml b/src/pages/Monitor/locale/en.yml index 496c8761a..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 @@ -22,7 +21,7 @@ salesTicketsTable: notVisible: Not visible purchaseRequest: Purchase request clientFrozen: Client frozen - risk: Risk + risk: Excess risk componentLack: Component lack tooLittle: Ticket too little identifier: Identifier diff --git a/src/pages/Monitor/locale/es.yml b/src/pages/Monitor/locale/es.yml index f6a29879f..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 @@ -22,7 +21,7 @@ salesTicketsTable: notVisible: No visible purchaseRequest: Petición de compra clientFrozen: Cliente congelado - risk: Riesgo + risk: Exceso de riesgo componentLack: Faltan componentes tooLittle: Ticket demasiado pequeño identifier: Identificador 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..7dab307a0 100644 --- a/src/pages/Order/Card/OrderCard.vue +++ b/src/pages/Order/Card/OrderCard.vue @@ -1,11 +1,11 @@ <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 + <VnCard data-key="Order" url="Orders" :filter="filter" diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue index 0d18864dc..f34549c1e 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -8,7 +8,7 @@ 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 DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; const DEFAULT_ITEMS = 0; @@ -66,11 +66,11 @@ const total = ref(0); :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/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..42578423f 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({ @@ -62,15 +61,15 @@ const sourceList = ref([]); outlined rounded /> - <VnSelectWorker - :label="t('globals.salesPerson')" - v-model="params.workerFk" - :params="{ - departmentCodes: ['VT'], - }" - dense + <VnSelect outlined + dense rounded + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> <VnInputDate v-model="params.from" @@ -125,7 +124,6 @@ en: search: Includes clientFk: Client agencyModeFk: Agency - salesPersonFk: Sales Person from: From to: To orderFk: Order @@ -136,7 +134,6 @@ en: showEmpty: Show Empty customerId: Customer ID agency: Agency - salesPerson: Sales Person fromLanded: From Landed toLanded: To Landed orderId: Order ID @@ -149,7 +146,6 @@ es: search: Búsqueda clientFk: Cliente agencyModeFk: Agencia - salesPersonFk: Comercial from: Desde to: Hasta orderFk: Cesta @@ -160,7 +156,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/OrderList.vue b/src/pages/Order/OrderList.vue index 40990f329..d75390d96 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -1,6 +1,6 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { computed, ref, onMounted } from 'vue'; +import { computed, ref, onMounted, watch } from 'vue'; import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; import { toDateTimeFormat } from 'src/filters/date'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; @@ -10,12 +10,13 @@ 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(); const { viewSummary } = useSummaryDialog(); @@ -24,6 +25,11 @@ const agencyList = ref([]); const route = useRoute(); const addressOptions = ref([]); const dataKey = 'OrderList'; +const formInitialData = ref({ + active: true, + addressId: null, + clientFk: null, +}); const columns = computed(() => [ { @@ -53,22 +59,17 @@ 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, + create: true, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), }, { align: 'center', @@ -147,27 +148,61 @@ const columns = computed(() => [ ], }, ]); -onMounted(() => { - if (!route.query.createForm) return; - const clientId = route.query.createForm; - const id = JSON.parse(clientId); - fetchClientAddress(id.clientFk); +onMounted(async () => { + if (!route.query) return; + if (route.query?.createForm) { + await onClientSelected(JSON.parse(route.query?.createForm)); + } else if (route.query?.table) { + const query = JSON.parse(route.query?.table); + const clientFk = query?.clientFk; + if (clientFk) await onClientSelected({ clientFk }); + } + if (tableRef.value) tableRef.value.create.formInitialData = formInitialData.value; }); -async function fetchClientAddress(id, formData = {}) { - const { data } = await axios.get( - `Clients/${id}/addresses?filter[order]=isActive DESC` - ); +watch( + () => route.query.table, + async (newValue) => { + if (newValue) { + const clientFk = +JSON.parse(newValue)?.clientFk; + if (clientFk) await onClientSelected({ clientFk }); + if (tableRef.value) + tableRef.value.create.formInitialData = formInitialData.value; + } + }, +); + +async function onClientSelected({ clientFk }, formData = {}) { + if (!clientFk) { + addressOptions.value = []; + formData.defaultAddressFk = null; + formData.addressId = null; + return; + } + const { data } = await getAddresses(clientFk); addressOptions.value = data; - formData.addressId = data.defaultAddressFk; - fetchAgencies(formData); + formData.defaultAddressFk = data[0].client.defaultAddressFk; + formData.addressId = formData.defaultAddressFk; + 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: { addressFk: addressId, landed }, + params: { + filter: JSON.stringify({ + order: ['name ASC', 'agencyMode DESC', 'agencyModeFk ASC'], + }), + addressFk: addressId, + landed, + }, }); agencyList.value = data; } @@ -181,6 +216,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> @@ -206,11 +246,7 @@ const getDateColor = (date) => { onDataSaved: (url) => { tableRef.redirect(`${url}/catalog`); }, - formInitialData: { - active: true, - addressId: null, - clientFk: null, - }, + formInitialData, }" :user-params="{ showEmpty: false }" :columns="columns" @@ -223,10 +259,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 }"> @@ -242,7 +278,9 @@ const getDateColor = (date) => { :include="{ relation: 'addresses' }" v-model="data.clientFk" :label="t('module.customer')" - @update:model-value="(id) => fetchClientAddress(id, data)" + @update:model-value=" + (id) => onClientSelected({ clientFk: id }, data) + " > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -258,6 +296,7 @@ const getDateColor = (date) => { </template> </VnSelect> <VnSelect + :disable="!data.clientFk" v-model="data.addressId" :options="addressOptions" :label="t('module.address')" @@ -266,7 +305,19 @@ const getDateColor = (date) => { @update:model-value="() => fetchAgencies(data)" > <template #option="scope"> - <QItem v-bind="scope.itemProps"> + <QItem + v-bind="scope.itemProps" + :class="{ disabled: !scope.opt.isActive }" + > + <QItemSection style="min-width: min-content" avatar> + <QIcon + v-if="isDefaultAddress(scope.opt, data)" + size="sm" + color="grey" + name="star" + class="fill-icon" + /> + </QItemSection> <QItemSection> <QItemLabel :class="{ @@ -284,6 +335,9 @@ const getDateColor = (date) => { {{ scope.opt?.street }}, {{ scope.opt?.city }} </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> </QItemSection> </QItem> </template> @@ -291,6 +345,7 @@ const getDateColor = (date) => { <VnInputDate v-model="data.landed" :label="t('module.landed')" + data-cy="landedDate" @update:model-value="() => fetchAgencies(data)" /> <VnSelect 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..c21298470 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" /> </template> diff --git a/src/pages/Route/Agency/Card/AgencyDescriptor.vue b/src/pages/Route/Agency/Card/AgencyDescriptor.vue index a0472c6c3..09aa5ad91 100644 --- a/src/pages/Route/Agency/Card/AgencyDescriptor.vue +++ b/src/pages/Route/Agency/Card/AgencyDescriptor.vue @@ -17,7 +17,7 @@ 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> 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/composables/__tests__/getAgencies.spec.js b/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js index ccf7872cb..99966569c 100644 --- a/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js +++ b/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js @@ -27,14 +27,17 @@ describe('getAgencies', () => { landed: 'true', }; const filter = { - fields: ['nickname', 'street', 'city', 'id'], + fields: ['name', 'street', 'city', 'id'], where: { isActive: true }, - order: 'nickname ASC', + order: ['name ASC'], }; await getAgencies(formData, null, filter); - expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData, filter)); + expect(axios.get).toHaveBeenCalledWith( + 'Agencies/getAgenciesWithWarehouse', + generateParams(formData, filter), + ); }); it('should not call API when formData is missing required landed field', async () => { @@ -64,19 +67,19 @@ describe('getAgencies', () => { it('should return options and agency when default agency is found', async () => { const formData = { warehouseId: '123', addressId: '456', landed: 'true' }; const client = { defaultAddress: { agencyModeFk: 'Agency1' } }; - + const { options, agency } = await getAgencies(formData, client); - + expect(options).toEqual(response.data); expect(agency).toEqual(response.data[0]); - }); + }); - it('should return options and agency when client is not provided', async () => { + it('should return options and agency when client is not provided', async () => { const formData = { warehouseId: '123', addressId: '456', landed: 'true' }; - + const { options, agency } = await getAgencies(formData); - + expect(options).toEqual(response.data); expect(agency).toBeNull(); - }); + }); }); diff --git a/src/pages/Route/Agency/composables/getAgencies.js b/src/pages/Route/Agency/composables/getAgencies.js index 850f87456..180ac943e 100644 --- a/src/pages/Route/Agency/composables/getAgencies.js +++ b/src/pages/Route/Agency/composables/getAgencies.js @@ -1,14 +1,14 @@ import axios from 'axios'; -import agency from 'src/router/modules/agency'; export async function getAgencies(formData, client, _filter = {}) { if (!formData.warehouseId || !formData.addressId || !formData.landed) return; - + const filter = { - ..._filter + ..._filter, + order: ['name ASC'], }; - let defaultAgency = null; + let agency = null; let params = { filter: JSON.stringify(filter), warehouseFk: formData.warehouseId, @@ -16,11 +16,15 @@ export async function getAgencies(formData, client, _filter = {}) { landed: formData.landed, }; - const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params }); + const { data: options } = await axios.get('Agencies/getAgenciesWithWarehouse', { + params, + }); - if(data && client) { - defaultAgency = data.find((agency) => agency.agencyModeFk === client.defaultAddress.agencyModeFk ); - }; - - return {options: data, agency: defaultAgency} + if (options && client) { + agency = options.find( + ({ agencyModeFk }) => agencyModeFk === client.defaultAddress.agencyModeFk, + ); + } + + return { options, agency }; } 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 3be409ec9..f70f60e1c 100644 --- a/src/pages/Route/Card/RouteAutonomousFilter.vue +++ b/src/pages/Route/Card/RouteAutonomousFilter.vue @@ -44,8 +44,7 @@ const exprBuilder = (param, value) => { <template> <FetchData url="AgencyModes" - :filter="{ fields: ['id', 'name'] }" - sort-by="name ASC" + :filter="{ fields: ['id', 'name'], order: ['name ASC'] }" @on-fetch="(data) => (agencyList = data)" auto-load /> 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..c57e51473 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -2,11 +2,11 @@ import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import CardDescriptor from 'components/ui/CardDescriptor.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,12 +27,16 @@ 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; }; 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..cb5158517 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,6 +33,7 @@ const emit = defineEmits(['search']); <QItem class="q-my-sm"> <QItemSection> <VnSelectWorker + :label="t('globals.worker')" v-model="params.workerFk" dense outlined @@ -44,7 +45,7 @@ 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" @@ -61,7 +62,7 @@ const emit = defineEmits(['search']); <QItemSection> <VnInputDate v-model="params.from" - :label="t('From')" + :label="t('globals.from')" is-outlined :disable="Boolean(params.scopeDays)" @update:model-value="params.scopeDays = null" @@ -72,7 +73,7 @@ const emit = defineEmits(['search']); <QItemSection> <VnInputDate v-model="params.to" - :label="t('To')" + :label="t('globals.to')" is-outlined :disable="Boolean(params.scopeDays)" @update:model-value="params.scopeDays = null" @@ -84,7 +85,7 @@ const emit = defineEmits(['search']); <VnInput v-model="params.scopeDays" type="number" - :label="t('Days Onward')" + :label="t('globals.daysOnward')" is-outlined clearable :disable="Boolean(params.from || params.to)" @@ -98,7 +99,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" @@ -120,7 +121,7 @@ const emit = defineEmits(['search']); <QItem class="q-my-sm"> <QItemSection> <VnSelect - :label="t('Warehouse')" + :label="t('globals.warehouse')" v-model="params.warehouseFk" url="Warehouses" option-value="id" @@ -136,7 +137,7 @@ const emit = defineEmits(['search']); <QItemSection> <VnInput v-model="params.description" - :label="t('Description')" + :label="t('globals.description')" is-outlined clearable /> @@ -146,7 +147,7 @@ const emit = defineEmits(['search']); <QItemSection> <QCheckbox v-model="params.isOk" - :label="t('Served')" + :label="t('route.filter.Served')" toggle-indeterminate /> </QItemSection> @@ -154,38 +155,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..f68628095 100644 --- a/src/pages/Route/Card/RouteSummary.vue +++ b/src/pages/Route/Card/RouteSummary.vue @@ -135,7 +135,7 @@ 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> @@ -168,7 +168,7 @@ const ticketColumns = ref([ <VnLv :label="t('route.summary.volume')" :value="`${dashIfEmpty(entity?.route?.m3)} / ${dashIfEmpty( - entity?.route?.vehicle?.m3 + entity?.route?.vehicle?.m3, )} m³`" /> <VnLv @@ -221,7 +221,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 +230,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 +238,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..d0683e481 100644 --- a/src/pages/Route/Cmr/CmrList.vue +++ b/src/pages/Route/Cmr/CmrList.vue @@ -2,28 +2,38 @@ 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 +42,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 +111,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 +120,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 +152,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 +166,16 @@ 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" + :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..198bcf8c7 100644 --- a/src/pages/Route/Roadmap/RoadmapDescriptor.vue +++ b/src/pages/Route/Roadmap/RoadmapDescriptor.vue @@ -15,6 +15,10 @@ const $props = defineProps({ required: false, default: null, }, + summary: { + type: Object, + default: null, + }, }); const route = useRoute(); @@ -26,7 +30,12 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor :url="`Roadmaps/${entityId}`" :filter="filter" data-key="Roadmap"> + <CardDescriptor + :url="`Roadmaps/${entityId}`" + :filter="filter" + data-key="Roadmap" + :summary="summary" + > <template #body="{ entity }"> <VnLv :label="t('Roadmap')" :value="entity?.name" /> <VnLv :label="t('ETD')" :value="toDateHourMin(entity?.etd)" /> 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..b905cfde8 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: { @@ -46,11 +46,11 @@ const columns = computed(() => [ }, isId: true, columnFilter: false, + width: '25px', }, { - align: 'center', name: 'workerFk', - label: t('route.Worker'), + label: t('globals.worker'), create: true, component: 'select', attrs: { @@ -71,9 +71,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 +89,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 +109,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 +119,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 +128,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,30 +137,31 @@ const columns = computed(() => [ format: ({ date }) => toDate(date), }, { - align: 'center', + align: 'right', name: 'm3', label: 'm3', cardVisible: true, columnClass: 'shrink', + width: '50px', }, { - align: 'center', name: 'started', label: t('route.hourStarted'), component: 'time', columnFilter: false, format: ({ started }) => toHour(started), + width: '50px', }, { - align: 'center', name: 'finished', label: t('route.hourFinished'), component: 'time', columnFilter: false, format: ({ finished }) => toHour(finished), + width: '50px', }, { - align: 'center', + align: 'right', name: 'kmStart', label: t('route.KmStart'), columnClass: 'shrink', @@ -173,7 +169,7 @@ const columns = computed(() => [ visible: false, }, { - align: 'center', + align: 'right', name: 'kmEnd', label: t('route.KmEnd'), columnClass: 'shrink', @@ -181,16 +177,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 +197,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 +209,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 +271,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 +285,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 +332,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..64e3abcd5 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', @@ -34,25 +46,21 @@ const columns = computed(() => [ condition: () => true, }, columnFilter: false, + width: '25px', }, { align: 'left', name: 'workerFk', - label: t('route.Worker'), + label: t('globals.worker'), component: markRaw(VnSelectWorker), create: true, - cardVisible: true, format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), columnFilter: false, - }, - { - align: 'left', - name: 'agencyName', - label: t('route.Agency'), cardVisible: true, + width: '100px', }, { - label: t('route.Agency'), + label: t('globals.agency'), name: 'agencyModeFk', component: 'select', attrs: { @@ -64,19 +72,13 @@ const columns = computed(() => [ }, }, create: true, - columnFilter: false, - visible: false, - }, - { - align: 'left', - name: 'vehiclePlateNumber', - label: t('route.Vehicle'), + columnFilter: true, cardVisible: true, + visible: true, }, { name: 'vehicleFk', - label: t('route.Vehicle'), - cardVisible: true, + label: t('globals.vehicle'), component: 'select', attrs: { url: 'vehicles', @@ -89,29 +91,32 @@ const columns = computed(() => [ }, }, create: true, - columnFilter: false, - visible: false, + columnFilter: true, + cardVisible: true, + visible: true, }, { - align: 'left', + align: 'center', name: 'started', label: t('route.hourStarted'), cardVisible: true, columnFilter: false, - format: (row) => toHour(row.started), + format: ({ started }) => toHour(started), + width: '50px', }, { - align: 'left', + align: 'center', name: 'finished', label: t('route.hourFinished'), cardVisible: true, columnFilter: false, - format: (row) => toHour(row.started), + format: ({ finished }) => toHour(finished), + width: '50px', }, { align: 'left', name: 'description', - label: t('route.Description'), + label: t('globals.description'), cardVisible: true, isTitle: true, create: true, @@ -119,7 +124,6 @@ const columns = computed(() => [ columnFilter: false, }, { - align: 'left', name: 'isOk', label: t('route.Served'), component: 'checkbox', @@ -130,6 +134,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 +165,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 +185,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..c981b86a5 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> @@ -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..3b3ec94fc 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', }, @@ -319,7 +319,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 +341,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 +349,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..ad2ae61e4 100644 --- a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue +++ b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue @@ -1,17 +1,29 @@ <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 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}`" + :url="`Vehicles/${entityId}`" data-key="Vehicle" title="numberPlate" - :to-module="{ name: 'VehicleList' }" > <template #menu="{ entity }"> <QItem 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/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..283b61855 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/ShelvingFilter.vue b/src/pages/Shelving/Card/ShelvingFilter.vue index 56cf4f58c..88d716046 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({ @@ -46,19 +47,7 @@ const emit = defineEmits(['search']); </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" outlined rounded /> </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/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/ShelvingList.vue b/src/pages/Shelving/ShelvingList.vue index 4e0c21100..651121de8 100644 --- a/src/pages/Shelving/ShelvingList.vue +++ b/src/pages/Shelving/ShelvingList.vue @@ -1,14 +1,17 @@ <script setup> -import VnPaginate from 'components/ui/VnPaginate.vue'; -import CardList from 'components/ui/CardList.vue'; -import VnLv from 'components/ui/VnLv.vue'; +import { computed } from 'vue'; import { useRouter } from 'vue-router'; -import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue'; -import ShelvingSummary from 'pages/Shelving/Card/ShelvingSummary.vue'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import { useI18n } from 'vue-i18n'; +import VnTable from 'components/VnTable/VnTable.vue'; import VnSection from 'src/components/common/VnSection.vue'; +import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue'; +import ShelvingSummary from './Card/ShelvingSummary.vue'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import exprBuilder from './ShelvingExprBuilder.js'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; +const { t } = useI18n(); const router = useRouter(); const { viewSummary } = useSummaryDialog(); const dataKey = 'ShelvingList'; @@ -17,9 +20,56 @@ const filter = { include: [{ relation: 'parking' }], }; -function navigate(id) { - router.push({ path: `/shelving/${id}` }); -} +const columns = computed(() => [ + { + align: 'left', + name: 'code', + label: t('globals.code'), + isId: true, + isTitle: true, + columnFilter: false, + create: true, + }, + { + align: 'left', + name: 'parking', + label: t('shelving.list.parking'), + sortable: true, + format: (val) => val?.code ?? '', + cardVisible: true, + }, + { + align: 'left', + name: 'priority', + label: t('shelving.list.priority'), + sortable: true, + cardVisible: true, + create: true, + }, + { + align: 'left', + name: 'isRecyclable', + label: t('shelving.summary.recyclable'), + sortable: true, + }, + { + align: 'right', + label: '', + name: 'tableActions', + actions: [ + { + title: t('components.smartCard.viewSummary'), + icon: 'preview', + action: (row) => viewSummary(row.id, ShelvingSummary), + isPrimary: true, + }, + ], + }, +]); + +const onDataSaved = ({ id }) => { + router.push({ name: 'ShelvingBasicData', params: { id } }); +}; </script> <template> @@ -37,48 +87,75 @@ function navigate(id) { <ShelvingFilter data-key="ShelvingList" /> </template> <template #body> - <QPage class="column items-center q-pa-md"> - <div class="vn-card-list"> - <VnPaginate :data-key="dataKey"> - <template #body="{ rows }"> - <CardList - v-for="row of rows" - :key="row.id" - :id="row.id" - :title="row.code" - @click="navigate(row.id)" - > - <template #list-items> - <VnLv - :label="$t('shelving.list.parking')" - :title-label="$t('shelving.list.parking')" - :value="row.parking?.code" - /> - <VnLv - :label="$t('shelving.list.priority')" - :value="row?.priority" - /> - </template> - <template #actions> - <QBtn - :label="$t('components.smartCard.openSummary')" - @click.stop="viewSummary(row.id, ShelvingSummary)" - color="primary" - /> - </template> - </CardList> - </template> - </VnPaginate> - </div> - <QPageSticky :offset="[20, 20]"> - <RouterLink :to="{ name: 'ShelvingCreate' }"> - <QBtn fab icon="add" color="primary" v-shortcut="'+'" /> - <QTooltip> - {{ $t('shelving.list.newShelving') }} - </QTooltip> - </RouterLink> - </QPageSticky> - </QPage> + <VnTable + :data-key="dataKey" + :columns="columns" + is-editable="false" + :right-search="false" + :use-model="true" + :disable-option="{ table: true }" + redirect="shelving" + default-mode="card" + :create="{ + urlCreate: 'Shelvings', + title: t('globals.pageTitles.shelvingCreate'), + onDataSaved, + formInitialData: { + parkingFk: null, + priority: null, + code: '', + isRecyclable: false, + }, + }" + > + <template #more-create-dialog="{ data }"> + <VnSelect + v-model="data.parkingFk" + url="Parkings" + option-value="id" + option-label="code" + :label="t('shelving.list.parking')" + :filter-options="['id', 'code']" + :fields="['id', 'code']" + /> + <VnCheckbox + v-model="data.isRecyclable" + :label="t('shelving.summary.recyclable')" + /> + </template> + </VnTable> </template> </VnSection> </template> + +<style lang="scss" scoped> +.list { + display: flex; + flex-direction: column; + align-items: center; + width: 55%; +} +.list-container { + display: flex; + justify-content: center; +} +</style> + +<i18n> + es: + shelving: + list: + parking: Estacionamiento + priority: Prioridad + + summary: + recyclable: Reciclable + en: + shelving: + list: + parking: Parking + priority: Priority + + summary: + recyclable: Recyclable +</i18n> 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/SupplierFilter.js b/src/pages/Supplier/Card/SupplierFilter.js index 3ce5c3de2..3aabe2c6d 100644 --- a/src/pages/Supplier/Card/SupplierFilter.js +++ b/src/pages/Supplier/Card/SupplierFilter.js @@ -11,6 +11,11 @@ export default { 'isSerious', 'isTrucker', 'account', + 'workerFk', + 'note', + 'isReal', + 'isPayMethodChecked', + 'companySize', ], include: [ { diff --git a/src/pages/Supplier/Card/SupplierFiscalData.vue b/src/pages/Supplier/Card/SupplierFiscalData.vue index ecee5b76b..4293bd41a 100644 --- a/src/pages/Supplier/Card/SupplierFiscalData.vue +++ b/src/pages/Supplier/Card/SupplierFiscalData.vue @@ -108,7 +108,6 @@ function handleLocation(data, location) { <VnAccountNumber v-model="data.account" :label="t('supplier.fiscalData.account')" - clearable data-cy="supplierFiscalDataAccount" insertable :maxlength="10" @@ -185,8 +184,8 @@ function handleLocation(data, location) { /> <VnCheckbox v-model="data.isVies" - :label="t('globals.isVies')" - :info="t('whenActivatingIt')" + :label="t('globals.isVies')" + :info="t('whenActivatingIt')" /> </div> </VnRow> diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index c9625518f..ec89d77e0 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n'; import VnTable from 'components/VnTable/VnTable.vue'; import VnSection from 'src/components/common/VnSection.vue'; import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; import FetchData from 'src/components/FetchData.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import SupplierSummary from './Card/SupplierSummary.vue'; @@ -53,7 +52,7 @@ const columns = computed(() => [ label: t('globals.alias'), name: 'alias', columnFilter: { - name: 'search', + name: 'nickname', }, cardVisible: true, }, @@ -120,6 +119,21 @@ const columns = computed(() => [ ], }, ]); + +const filterColumns = computed(() => { + const copy = [...columns.value]; + copy.splice(copy.length - 1, 0, { + align: 'left', + label: t('globals.params.provinceFk'), + name: 'provinceFk', + options: provincesOptions.value, + columnFilter: { + component: 'select', + }, + }); + + return copy; +}); </script> <template> <FetchData @@ -130,7 +144,7 @@ const columns = computed(() => [ /> <VnSection :data-key="dataKey" - :columns="columns" + :columns="filterColumns" prefix="supplier" :array-data-props="{ url: 'Suppliers/filter', @@ -158,6 +172,7 @@ const columns = computed(() => [ > <template #more-create-dialog="{ data }"> <VnInput + class="col-span-2" :label="t('globals.name')" v-model="data.socialName" :uppercase="true" @@ -165,17 +180,6 @@ const columns = computed(() => [ </template> </VnTable> </template> - <template #moreFilterPanel="{ params, searchFn }"> - <VnSelect - :label="t('globals.params.provinceFk')" - v-model="params.provinceFk" - @update:model-value="searchFn()" - :options="provincesOptions" - filled - dense - class="q-px-sm q-pr-lg" - /> - </template> </VnSection> </template> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue index ef2eb75d6..3c2fe95e5 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue @@ -44,8 +44,8 @@ const getPriceDifference = async () => { shipped: ticket.value.shipped, }; const { data } = await axios.post( - `tickets/${ticket.value.id}/priceDifference`, - params + `tickets/${formData.value.id}/priceDifference`, + params, ); ticket.value.sale = data; }; @@ -71,8 +71,8 @@ const submit = async () => { }; const { data } = await axios.post( - `tickets/${ticket.value.id}/componentUpdate`, - params + `tickets/${formData.value.id}/componentUpdate`, + 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/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index c5f3233b1..743f2188c 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 DepartmentDescriptorProxy from 'pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import CardDescriptor from 'components/ui/CardDescriptor.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 :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 @@ -93,9 +108,9 @@ function ticketFilter(ticket) { <VnLv :label="t('globals.warehouse')" :value="entity.warehouse?.name" /> <VnLv :label="t('globals.alias')" :value="entity.nickname" /> </template> - <template #icons> + <template #icons="{ entity }"> <QCardActions class="q-gutter-x-xs"> - <TicketProblems :row="problems" /> + <TicketProblems :row="{ ...entity?.client, ...problems, ...entity }" /> </QCardActions> </template> <template #actions="{ entity }"> @@ -129,6 +144,15 @@ 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> 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/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue index f8084ff2f..e9e153b70 100644 --- a/src/pages/Ticket/Card/TicketExpedition.vue +++ b/src/pages/Ticket/Card/TicketExpedition.vue @@ -37,7 +37,6 @@ const expeditionStateTypes = ref([]); const expeditionsFilter = computed(() => ({ where: { ticketFk: route.params.id }, - order: ['created DESC'], })); const ticketArrayData = useArrayData('Ticket'); @@ -105,6 +104,9 @@ const columns = computed(() => [ name: 'created', align: 'left', cardVisible: true, + columnFilter: { + component: 'date', + }, format: (row) => toDateTimeFormat(row.created), }, { @@ -201,7 +203,7 @@ const getExpeditionState = async (expedition) => { const openGrafana = (expeditionFk) => { useOpenURL( - `https://grafana.verdnatura.es/d/de1njb6p5answd/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}` + `https://grafana.verdnatura.es/d/de1njb6p5answd/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}`, ); }; @@ -287,7 +289,7 @@ onMounted(async () => { openConfirmationModal( '', t('expedition.removeExpeditionSubtitle'), - deleteExpedition + deleteExpedition, ) " > @@ -302,7 +304,6 @@ onMounted(async () => { url="Expeditions/filter" search-url="expeditions" :columns="columns" - :filter="expeditionsFilter" v-model:selected="selectedRows" :table="{ 'row-key': 'id', @@ -316,11 +317,14 @@ onMounted(async () => { return { id: value }; case 'packageItemName': return { packagingItemFk: value }; + case 'created': + return { 'e.created': { gte: value } }; } } " :redirect="false" order="created DESC" + :filter="expeditionsFilter" > <template #column-freightItemName="{ row }"> <span class="link" @click.stop> 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 456a151a3..2fb305cc3 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -174,22 +174,26 @@ 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(); + selectedRows.value = []; }; const changeQuantity = async (sale) => { if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity) return; + else sale.originalQuantity = sale.quantity; if (!sale.id) return addSale(sale); if (await isSalePrepared(sale)) { @@ -235,7 +239,7 @@ const addSale = async (sale) => { notify('globals.dataSaved', 'positive'); sale.isNew = false; - arrayData.fetch({}); + resetChanges(); }; const changeConcept = async (sale) => { if (await isSalePrepared(sale)) { @@ -258,6 +262,18 @@ const DEFAULT_EDIT = { oldQuantity: null, }; const edit = ref({ ...DEFAULT_EDIT }); +const usesMana = ref(null); + +const getUsesMana = async () => { + const { data } = await axios.get('Sales/usesMana'); + usesMana.value = data; +}; + +const getMana = async () => { + const { data } = await axios.get(`Tickets/${route.params.id}/getDepartmentMana`); + mana.value = data; + await getUsesMana(); +}; const selectedValidSales = computed(() => { if (!sales.value) return; @@ -310,7 +326,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,12 +336,11 @@ 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 updateDiscount = async (sales, newDiscount = 0) => { + const salesIds = sales.map(({ id }) => id); const params = { - salesIds: saleIds, - newDiscount: _newDiscount, + salesIds, + newDiscount, manaCode: manaCode.value, }; await axios.post(`Tickets/${route.params.id}/updateDiscount`, params); @@ -474,7 +489,7 @@ const endNewRow = (row) => { }; async function confirmUpdate(cb) { - await quasar + quasar .dialog({ component: VnConfirm, componentProps: { @@ -664,6 +679,7 @@ watch( selection: 'multiple', }" :right-search="false" + :search-url="false" :column-search="false" :disable-option="{ card: true }" auto-load @@ -681,6 +697,17 @@ watch( :disabled-attr="isTicketEditable" > <template #column-statusIcons="{ row }"> + <QIcon + v-if="row.saleGroupFk" + name="inventory_2" + size="xs" + color="primary" + class="cursor-pointer" + > + <QTooltip class="no-pointer-events"> + {{ `saleGroup: ${row.saleGroupFk}` }} + </QTooltip> + </QIcon> <TicketProblems :row="row" /> </template> <template #body-cell-picture="{ row }"> @@ -692,7 +719,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 }"> @@ -740,7 +767,7 @@ watch( {{ row?.item?.subName.toUpperCase() }} </div> </div> - <FetchedTags :item="row" :max-length="6" /> + <FetchedTags v-if="row.item" :item="row.item" :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..773b0807f 100644 --- a/src/pages/Ticket/Card/TicketSaleMoreActions.vue +++ b/src/pages/Ticket/Card/TicketSaleMoreActions.vue @@ -62,8 +62,6 @@ const isClaimable = computed(() => { } 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); notify(t('SMS sent'), 'positive'); @@ -144,14 +142,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; @@ -252,18 +242,6 @@ const createRefund = async (withWarehouse) => { <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> diff --git a/src/pages/Ticket/Card/TicketService.vue b/src/pages/Ticket/Card/TicketService.vue index 6ce69a6aa..a44dce5c4 100644 --- a/src/pages/Ticket/Card/TicketService.vue +++ b/src/pages/Ticket/Card/TicketService.vue @@ -121,6 +121,50 @@ async function handleSave() { isSaving.value = false; } } +function validateFields(item) { + // Only validate fields that are being updated + const shouldExist = (field) => field in item; + + if (!shouldExist('ticketServiceTypeFk') && !item.ticketServiceTypeFk) { + notify('Description is required', 'negative'); + return false; + } + + if (!shouldExist('quantity') && (!item.quantity || item.quantity <= 0)) { + notify('Quantity must be greater than 0', 'negative'); + return false; + } + + if (!shouldExist('price') && (!item.price || item.price < 0)) { + notify('Price must be valid', 'negative'); + return false; + } + + return true; +} + +function beforeSave(data) { + const { creates = [], updates = [] } = data; + const validData = { creates: [], updates: [] }; + + // Validate creates + if (creates.length) { + for (const create of creates) { + create.ticketFk = route.params.id; + if (validateFields(create)) { + validData.creates.push(create); + } + } + } + + // Validate updates + if (updates.length) { + for (const update of updates) { + validData.updates.push(update); + } + } + return validData; +} </script> <template> @@ -141,6 +185,7 @@ async function handleSave() { v-model:selected="selected" :order="['description ASC']" :default-remove="false" + :beforeSaveFn="beforeSave" > <template #moreBeforeActions> <QBtn @@ -170,6 +215,7 @@ async function handleSave() { option-value="id" hide-selected sort-by="name ASC" + :required="true" > <template #form> <TicketCreateServiceType @@ -185,6 +231,7 @@ async function handleSave() { :label="col.label" v-model.number="row.quantity" type="number" + :required="true" min="0" :info="t('service.quantityInfo')" /> @@ -196,6 +243,7 @@ async function handleSave() { :label="col.label" v-model.number="row.price" type="number" + :required="true" min="0" @keyup.enter="handleSave" /> diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index 5838efa88..a19ab3779 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -13,9 +13,9 @@ 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'; @@ -152,12 +152,14 @@ onMounted(async () => { </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 :label="t('globals.agency')" :value="entity.agencyMode?.name" /> diff --git a/src/pages/Ticket/Card/TicketTracking.vue b/src/pages/Ticket/Card/TicketTracking.vue index acf464fb1..00610de44 100644 --- a/src/pages/Ticket/Card/TicketTracking.vue +++ b/src/pages/Ticket/Card/TicketTracking.vue @@ -81,6 +81,7 @@ const openCreateModal = () => createTrackingDialogRef.value.show(); ref="paginateRef" data-key="TicketTracking" :user-filter="paginateFilter" + search-url="table" url="TicketTrackings" auto-load order="created DESC" diff --git a/src/pages/Ticket/Card/TicketTransferProxy.vue b/src/pages/Ticket/Card/TicketTransferProxy.vue index 3f3f018df..7d5d82f85 100644 --- a/src/pages/Ticket/Card/TicketTransferProxy.vue +++ b/src/pages/Ticket/Card/TicketTransferProxy.vue @@ -42,7 +42,7 @@ const transferRef = ref(null); /> </div> - <div v-else> + <div style="display: flex; flex-direction: row" v-else> <TicketTransfer ref="transferRef" :ticket="$props.ticket" diff --git a/src/pages/Ticket/Card/TicketVolume.vue b/src/pages/Ticket/Card/TicketVolume.vue index 71b16f878..db78094cf 100644 --- a/src/pages/Ticket/Card/TicketVolume.vue +++ b/src/pages/Ticket/Card/TicketVolume.vue @@ -142,7 +142,7 @@ onMounted(() => (stateStore.rightDrawer = true)); <template #column-concept="{ row }"> <span>{{ row.item.name }}</span> <span class="color-vn-label q-pl-md">{{ row.item.subName }}</span> - <FetchedTags :item="row.item" /> + <FetchedTags :item="row.item" :columns="6" /> </template> <template #column-volume="{ rowIndex }"> <span>{{ packingTypeVolume?.[rowIndex]?.volume }}</span> 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/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index c82c0067f..f959157f6 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -46,7 +46,12 @@ const getGroupedStates = (data) => { " auto-load /> - <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> + <FetchData + url="AgencyModes" + :filter="{ fields: ['id', 'name'], order: ['name ASC'] }" + @on-fetch="(data) => (agencies = data)" + auto-load + /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> @@ -74,23 +79,33 @@ const getGroupedStates = (data) => { </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate + v-model="params.from" + :label="t('From')" + is-outlined + data-cy="From_date" + /> </QItemSection> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate + v-model="params.to" + :label="t('To')" + is-outlined + data-cy="To_date" + /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnSelectWorker - :label="t('globals.salesPerson')" - v-model="params.salesPersonFk" - :params="{ - departmentCodes: ['VT'], - }" - dense + <VnSelect outlined + dense rounded + :label="t('globals.params.departmentFk')" + v-model="params.departmentFk" + option-value="id" + option-label="name" + url="Departments" /> </QItemSection> </QItem> @@ -241,8 +256,6 @@ const getGroupedStates = (data) => { v-model="params.agencyModeFk" @update:model-value="searchFn()" :options="agencies" - option-value="id" - option-label="name" emit-value map-options use-input @@ -295,7 +308,6 @@ en: from: From shipped: Shipped to: To - salesPersonFk: Salesperson stateFk: State groupedStates: Grouped State refFk: Invoice Ref. @@ -323,7 +335,6 @@ es: from: Desde shipped: F. envío to: Hasta - salesPersonFk: Comercial stateFk: Estado groupedStates: Estado agrupado refFk: Ref. Factura @@ -342,7 +353,6 @@ es: Order ID: ID Pedido From: Desde To: Hasta - Salesperson: Comercial State: Estado Invoice Ref.: Ref. Factura My team: Mi equipo 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/TicketList.vue b/src/pages/Ticket/TicketList.vue index 33b841d2d..307f34dc2 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { computed, ref, onBeforeMount } from 'vue'; +import { computed, ref, onBeforeMount, watch, onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useStateStore } from 'stores/useStateStore'; import { useI18n } from 'vue-i18n'; @@ -17,12 +17,12 @@ 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'; import TicketProblems from 'src/components/TicketProblems.vue'; import VnSection from 'src/components/common/VnSection.vue'; -import { getClient } from 'src/pages/Customer/composables/getClient'; import { getAddresses } from 'src/pages/Customer/composables/getAddresses'; import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies'; @@ -51,10 +51,20 @@ const userParams = { onBeforeMount(() => { initializeFromQuery(); stateStore.rightDrawer = true; - if (!route.query.createForm) return; - onClientSelected(JSON.parse(route.query.createForm)); +}); +onMounted(async () => { + if (!route.query) return; + if (route.query?.createForm) { + await onClientSelected(JSON.parse(route.query?.createForm)); + } else if (route.query?.table) { + const query = route.query?.table; + const clientId = +JSON.parse(query)?.clientFk; + if (clientId) await onClientSelected({ clientId }); + } + if (tableRef.value) tableRef.value.create.formInitialData = formInitialData.value; }); const initializeFromQuery = () => { + if (!route) return; const query = route.query.table ? JSON.parse(route.query.table) : {}; from.value = query.from || from.toISOString(); to.value = query.to || to.toISOString(); @@ -69,6 +79,7 @@ const companiesOptions = ref([]); const accountingOptions = ref([]); const amountToReturn = ref(); const dataKey = 'TicketList'; +const formInitialData = ref({}); const columns = computed(() => [ { @@ -89,22 +100,16 @@ 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', @@ -119,12 +124,16 @@ const columns = computed(() => [ { align: 'left', name: 'shipped', + component: 'time', + columnFilter: false, label: t('ticketList.hour'), format: (row) => toTimeFormat(row.shipped), }, { align: 'left', name: 'zoneLanding', + component: 'time', + columnFilter: false, label: t('ticketList.closure'), format: (row, dashIfEmpty) => dashIfEmpty(toTimeFormat(row.zoneLanding)), }, @@ -144,9 +153,16 @@ const columns = computed(() => [ }, { align: 'left', - name: 'province', + name: 'provinceFk', label: t('ticketList.province'), - columnClass: 'expand', + component: 'select', + attrs: { + url: 'Provinces', + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.province), }, { align: 'left', @@ -180,9 +196,19 @@ const columns = computed(() => [ }, { align: 'left', - name: 'warehouse', - label: t('ticketList.warehouse'), - columnClass: 'expand', + name: 'warehouseFk', + label: t('globals.warehouse'), + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, + format: (row) => row.warehouse, + columnField: { + component: null, + }, + cardVisible: false, + create: false, }, { align: 'left', @@ -228,7 +254,44 @@ const columns = computed(() => [ ], }, ]); +const onClientSelected = async (formData) => { + resetAgenciesSelector(formData); + await fetchAddresses(formData); +}; +const fetchAddresses = async (formData) => { + if (!formData.clientId) { + addressesOptions.value = []; + formData.defaultAddressFk = null; + formData.addressId = null; + return; + } + const { data } = await getAddresses(formData.clientId); + + if (!data) { + formInitialData.value = { clientId: formData.clientId }; + return; + } + addressesOptions.value = data; + selectedClient.value = data[0].client; + formData.addressId = selectedClient.value.defaultAddressFk; + formInitialData.value = { + clientId: formData.clientId, + addressId: formData.addressId, + }; +}; +watch( + () => route.query.table, + async (newValue) => { + if (newValue) { + const clientId = +JSON.parse(newValue)?.clientFk; + if (clientId) await onClientSelected({ clientId }); + if (tableRef.value) + tableRef.value.create.formInitialData = formInitialData.value; + } + }, + { immediate: true }, +); function resetAgenciesSelector(formData) { agenciesOptions.value = []; if (formData) formData.agencyModeId = null; @@ -239,12 +302,6 @@ function redirectToLines(id) { window.open(url, '_blank'); } -const onClientSelected = async (formData) => { - resetAgenciesSelector(formData); - await fetchClient(formData); - await fetchAddresses(formData); -}; - const fetchAvailableAgencies = async (formData) => { resetAgenciesSelector(formData); const response = await getAgencies(formData, selectedClient.value); @@ -255,22 +312,6 @@ const fetchAvailableAgencies = async (formData) => { if (agency) formData.agencyModeId = agency.agencyModeFk; }; -const fetchClient = async (formData) => { - const response = await getClient(formData.clientId); - if (!response) return; - const [client] = response.data; - selectedClient.value = client; -}; - -const fetchAddresses = async (formData) => { - const response = await getAddresses(formData.clientId); - if (!response) return; - addressesOptions.value = response.data; - - const { defaultAddress } = selectedClient.value; - formData.addressId = defaultAddress.id; -}; - const getColor = (row) => { if (row.alertLevelCode === 'OK') return 'bg-success'; else if (row.alertLevelCode === 'FREE') return 'bg-notice'; @@ -299,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) { @@ -456,7 +492,7 @@ function setReference(data) { urlCreate: 'Tickets/new', title: t('ticketList.createTicket'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { clientId: null }, + formInitialData, }" default-mode="table" :columns="columns" @@ -473,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 }"> @@ -538,11 +574,9 @@ function setReference(data) { :label="t('ticketList.client')" v-model="data.clientId" :options="clientsOptions" - option-value="id" - option-label="name" hide-selected required - @update:model-value="(client) => onClientSelected(data)" + @update:model-value="() => onClientSelected(data)" :sort-by="'id ASC'" > <template #option="scope"> @@ -564,7 +598,6 @@ function setReference(data) { :label="t('basicData.address')" v-model="data.addressId" :options="addressesOptions" - option-value="id" option-label="nickname" hide-selected map-options @@ -610,6 +643,9 @@ function setReference(data) { {{ scope.opt?.city }} </span> </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> </QItemSection> </QItem> </template> @@ -633,8 +669,6 @@ function setReference(data) { :label="t('globals.warehouse')" v-model="data.warehouseId" :options="warehousesOptions" - option-value="id" - option-label="name" hide-selected required @update:model-value="() => fetchAvailableAgencies(data)" @@ -694,7 +728,6 @@ function setReference(data) { :label="t('ticketList.company')" v-model="dialogData.companyFk" :options="companiesOptions" - option-value="id" option-label="code" hide-selected > @@ -705,7 +738,6 @@ function setReference(data) { :label="t('ticketList.bank')" v-model="dialogData.bankFk" :options="accountingOptions" - option-value="id" option-label="bank" hide-selected @update:model-value="setReference" 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/locale/en.yml b/src/pages/Ticket/locale/en.yml index cdbb22d9b..9eb8ce8cb 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 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/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index cb09eafd6..479b47fb9 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -1,10 +1,10 @@ <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" diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index 29d342334..ae6e695be 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -73,6 +73,7 @@ warehouses(); /> <FetchData url="AgencyModes" + :filter="{ fields: ['id', 'name'], order: ['name ASC'] }" @on-fetch="(data) => (agenciesOptions = data)" auto-load /> diff --git a/src/pages/Travel/TravelCreate.vue b/src/pages/Travel/TravelCreate.vue index 72c34aad8..35a936134 100644 --- a/src/pages/Travel/TravelCreate.vue +++ b/src/pages/Travel/TravelCreate.vue @@ -39,6 +39,7 @@ const redirectToTravelBasicData = (_, { id }) => { <template> <FetchData url="AgencyModes" + :filter="{ fields: ['id', 'name'], order: ['name ASC'] }" @on-fetch="(data) => (agenciesOptions = data)" auto-load /> diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index 90901ee4d..4a9c80952 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -52,9 +52,8 @@ defineExpose({ states }); v-model="params.agencyModeFk" @update:model-value="searchFn()" url="agencyModes" + sort-by="name ASC" :use-like="false" - option-value="id" - option-label="name" option-filter="name" dense outlined 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 e716686d1..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,101 +77,110 @@ 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')" - :options="filteredWagonTypes" - :rules="[(val) => !!val || t('wagon.warnings.typeNotEmpty')]" - @filter="filterType" + <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> <i18n> es: Create new wagon: Crear nuevo vagón -</i18n> \ No newline at end of file +</i18n> diff --git a/src/pages/Worker/Card/WorkerBasicData.vue b/src/pages/Worker/Card/WorkerBasicData.vue index 56a9548c6..f2a16b7e1 100644 --- a/src/pages/Worker/Card/WorkerBasicData.vue +++ b/src/pages/Worker/Card/WorkerBasicData.vue @@ -1,5 +1,6 @@ <script setup> -import { ref } 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/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 0e946f1dd..6e3a5e83f 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -23,6 +23,10 @@ const $props = defineProps({ required: false, default: 'Worker', }, + summary: { + type: Object, + default: null, + }, }); const image = ref(null); @@ -51,6 +55,7 @@ const handlePhotoUpdated = (evt = false) => { <CardDescriptor ref="cardDescriptorRef" :data-key="dataKey" + :summary="$props.summary" url="Workers/summary" :filter="{ where: { id: entityId } }" title="user.nickname" @@ -116,7 +121,7 @@ const handlePhotoUpdated = (evt = false) => { :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" /> diff --git a/src/pages/Worker/Card/WorkerNotes.vue b/src/pages/Worker/Card/WorkerNotes.vue index 4f123206b..da274f3fa 100644 --- a/src/pages/Worker/Card/WorkerNotes.vue +++ b/src/pages/Worker/Card/WorkerNotes.vue @@ -5,9 +5,9 @@ import VnNotes from 'src/components/ui/VnNotes.vue'; const route = useRoute(); -const filter = { +const userFilter = { order: 'created DESC', - where: { workerFk: route.params.id }, + include: { relation: 'worker', scope: { @@ -22,11 +22,15 @@ const filter = { }, }; -const body = { - workerFk: route.params.id, -}; +const body = { workerFk: route.params.id }; </script> <template> - <VnNotes :add-note="true" url="WorkerObservations" :filter="filter" :body="body" /> + <VnNotes + :add-note="true" + url="WorkerObservations" + :user-filter="userFilter" + :filter="{ where: { workerFk: $route.params.id } }" + :body="body" + /> </template> 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/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..ad78a3fb9 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -39,6 +39,7 @@ onBeforeMount(async () => { url="Workers/summary" :user-filter="{ where: { id: entityId } }" data-key="Worker" + module-name="Worker" > <template #header="{ entity }"> <div>{{ entity.id }} - {{ entity.firstName }} {{ entity.lastName }}</div> @@ -50,7 +51,7 @@ onBeforeMount(async () => { <QCard class="vn-one"> <VnTitle :url="basicDataUrl" :text="t('globals.summary.basicData')" /> <VnLv :label="t('globals.name')" :value="worker.user?.nickname" /> - <VnLv :label="t('worker.list.department')"> + <VnLv :label="t('globals.department')"> <template #value> <span class="link" v-text="worker.department?.department?.name" /> <DepartmentDescriptorProxy diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 7def6e94c..9c0fa6758 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -343,19 +343,29 @@ const updateData = async () => { const getMailStates = async (date) => { const url = `WorkerTimeControls/${route.params.id}/getMailStates`; + const year = date.getFullYear(); const month = date.getMonth() + 1; - const prevMonth = month == 1 ? 12 : month - 1; - const params = { - month, - year: date.getFullYear(), + + const getMonthStates = async (month, year) => { + return (await axios.get(url, { params: { month, year } })).data; }; - const curMonthStates = (await axios.get(url, { params })).data; - const prevMonthStates = ( - await axios.get(url, { params: { ...params, month: prevMonth } }) - ).data; + const curMonthStates = await getMonthStates(month, year); - workerTimeControlMails.value = curMonthStates.concat(prevMonthStates); + const prevMonthStates = await getMonthStates( + month === 1 ? 12 : month - 1, + month === 1 ? year - 1 : year, + ); + + const postMonthStates = await getMonthStates( + month === 12 ? 1 : month + 1, + month === 12 ? year + 1 : year, + ); + workerTimeControlMails.value = [ + ...curMonthStates, + ...prevMonthStates, + ...postMonthStates, + ]; }; const showWorkerTimeForm = (propValue, formType) => { 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/WorkerList.vue b/src/pages/Worker/WorkerList.vue index d6eb0684d..b76790075 100644 --- a/src/pages/Worker/WorkerList.vue +++ b/src/pages/Worker/WorkerList.vue @@ -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" @@ -279,7 +279,11 @@ async function autofillBic(worker) { /> </VnRow> <VnRow> - <VnInput v-model="data.fi" :label="t('worker.create.fi')" /> + <VnInput + v-model="data.fi" + :label="t('worker.create.fi')" + required + /> <VnInputDate v-model="data.birth" :label="t('worker.create.birth')" diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 03013f011..9107a2c28 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -9,30 +9,30 @@ import VnInputTime from 'src/components/common/VnInputTime.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; const { t } = useI18n(); -const validAddresses = ref([]); const addresses = ref([]); const setFilteredAddresses = (data) => { - const validIds = new Set(validAddresses.value.map((item) => item.addressFk)); - addresses.value = data.filter((address) => validIds.has(address.id)); + addresses.value = data.map(({ address }) => address); }; </script> <template> <FetchData url="RoadmapAddresses" + :filter="{ + include: { relation: 'address' }, + }" auto-load - @on-fetch="(data) => (validAddresses = data)" + @on-fetch="setFilteredAddresses" /> - <FetchData url="Addresses" auto-load @on-fetch="setFilteredAddresses" /> <FormModel auto-load model="Zone"> <template #form="{ data, validate }"> <VnRow> <VnInput - data-cy="zone-basic-data-name" :label="t('Name')" clearable v-model="data.name" + data-cy="ZoneBasicDataName" :required="true" /> </VnRow> @@ -75,7 +75,6 @@ const setFilteredAddresses = (data) => { min="0" /> </VnRow> - <VnRow> <VnInput v-model="data.travelingDays" @@ -86,7 +85,6 @@ const setFilteredAddresses = (data) => { /> <VnInputTime v-model="data.hour" :label="t('Closing')" :required="true" /> </VnRow> - <VnRow> <VnInput v-model="data.price" @@ -95,6 +93,7 @@ const setFilteredAddresses = (data) => { min="0" :required="true" clearable + data-cy="ZoneBasicDataPrice" /> <VnInput v-model="data.priceOptimum" @@ -120,12 +119,10 @@ const setFilteredAddresses = (data) => { option-label="nickname" :options="addresses" :fields="['id', 'nickname']" - sort-by="id" + sort-by="nickname ASC" hide-selected map-options :rules="validate('data.addressFk')" - :filter-options="['id']" - :where="filterWhere" /> </VnRow> <VnRow> 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/ZoneDescriptorMenuItems.vue b/src/pages/Zone/Card/ZoneDescriptorMenuItems.vue index 3c45700cb..f8683773d 100644 --- a/src/pages/Zone/Card/ZoneDescriptorMenuItems.vue +++ b/src/pages/Zone/Card/ZoneDescriptorMenuItems.vue @@ -36,13 +36,13 @@ function openConfirmDialog(callback) { } </script> <template> - <QItem @click="openConfirmDialog('remove')" v-ripple clickable> + <QItem @click="openConfirmDialog('remove')" v-ripple clickable data-cy="Delete_button"> <QItemSection avatar> <QIcon name="delete" /> </QItemSection> <QItemSection>{{ t('deleteZone') }}</QItemSection> </QItem> - <QItem @click="openConfirmDialog('clone')" v-ripple clickable> + <QItem @click="openConfirmDialog('clone')" v-ripple clickable data-cy="Clone_button"> <QItemSection avatar> <QIcon name="content_copy" /> </QItemSection> 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 805d03b27..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, + 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'; @@ -123,19 +168,24 @@ onMounted(() => { dense val="day" :label="t('eventsInclusionForm.oneDay')" + data-cy="ZoneEventInclusionDayRadio" /> <QRadio + v-if="!props.isMasiveEdit" v-model="inclusionType" dense val="indefinitely" :label="t('eventsInclusionForm.indefinitely')" + data-cy="ZoneEventInclusionIndefinitelyRadio" /> <QRadio + v-if="!props.isMasiveEdit" v-model="inclusionType" dense val="range" :label="t('eventsInclusionForm.rangeOfDates')" class="q-mb-sm" + data-cy="ZoneEventInclusionRangeRadio" /> </div> <VnRow> @@ -221,7 +271,7 @@ onMounted(() => { openConfirmationModal( t('zone.deleteTitle'), t('zone.deleteSubtitle'), - () => deleteEvent() + () => deleteEvent(), ) " /> 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..2c56fa3e2 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> diff --git a/src/pages/Zone/ZoneCalendar.vue b/src/pages/Zone/ZoneCalendar.vue index c2abd15ff..7cae59698 100644 --- a/src/pages/Zone/ZoneCalendar.vue +++ b/src/pages/Zone/ZoneCalendar.vue @@ -185,6 +185,7 @@ const handleDateClick = (timestamp) => { :class="{ '--today': isToday(timestamp), }" + data-cy="ZoneCalendarDay" > <QPopupProxy v-if="isZoneDeliveryView"> <ZoneClosingTable 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 0a535afcb..a8cb05afc 100644 --- a/src/pages/Zone/ZoneDeliveryPanel.vue +++ b/src/pages/Zone/ZoneDeliveryPanel.vue @@ -46,7 +46,7 @@ watch( inq.value = { deliveryMethodFk: { inq: deliveryMethods.value[deliveryMethodFk.value] }, }; - } + }, ); </script> @@ -89,7 +89,7 @@ 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']" @@ -98,6 +98,7 @@ watch( outlined rounded map-key="geoFk" + data-cy="ZoneDeliveryDaysPostcodeSelect" > <template #option="{ itemProps, opt }"> <QItem v-bind="itemProps"> @@ -129,6 +130,7 @@ watch( dense outlined rounded + data-cy="ZoneDeliveryDaysAgencySelect" /> <VnSelect v-else diff --git a/src/pages/Zone/ZoneFilterPanel.vue b/src/pages/Zone/ZoneFilterPanel.vue deleted file mode 100644 index bbe12189a..000000000 --- a/src/pages/Zone/ZoneFilterPanel.vue +++ /dev/null @@ -1,68 +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'; - -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'] }" - @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 4df84e4bd..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,78 +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" - 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> + </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/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/invoiceIn.js b/src/router/modules/invoiceIn.js index fe70a1056..b8021e69f 100644 --- a/src/router/modules/invoiceIn.js +++ b/src/router/modules/invoiceIn.js @@ -1,10 +1,15 @@ import { RouterView } from 'vue-router'; +import { setRectificative } from 'src/pages/InvoiceIn/composables/setRectificative'; const invoiceInCard = { name: 'InvoiceInCard', path: ':id', component: () => import('src/pages/InvoiceIn/Card/InvoiceInCard.vue'), redirect: { name: 'InvoiceInSummary' }, + beforeEnter: async (to, from, next) => { + await setRectificative(to); + next(); + }, meta: { menu: [ 'InvoiceInBasicData', @@ -32,8 +37,7 @@ const invoiceInCard = { title: 'basicData', icon: 'vn:settings', }, - component: () => - import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'), + component: () => import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'), }, { name: 'InvoiceInVat', @@ -51,8 +55,7 @@ const invoiceInCard = { title: 'dueDay', icon: 'vn:calendar', }, - component: () => - import('src/pages/InvoiceIn/Card/InvoiceInDueDay.vue'), + component: () => import('src/pages/InvoiceIn/Card/InvoiceInDueDay.vue'), }, { name: 'InvoiceInIntrastat', @@ -61,8 +64,7 @@ const invoiceInCard = { title: 'intrastat', icon: 'vn:lines', }, - component: () => - import('src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'), + component: () => import('src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'), }, { name: 'InvoiceInCorrective', @@ -71,8 +73,7 @@ const invoiceInCard = { title: 'corrective', icon: 'attachment', }, - component: () => - import('src/pages/InvoiceIn/Card/InvoiceInCorrective.vue'), + component: () => import('src/pages/InvoiceIn/Card/InvoiceInCorrective.vue'), }, { name: 'InvoiceInLog', @@ -86,7 +87,7 @@ const invoiceInCard = { ], }; -export default { +export default { name: 'InvoiceIn', path: '/invoice-in', meta: { @@ -98,7 +99,7 @@ export default { component: RouterView, redirect: { name: 'InvoiceInMain' }, children: [ - { + { name: 'InvoiceInMain', path: '', component: () => import('src/components/common/VnModule.vue'), @@ -111,7 +112,7 @@ export default { component: () => import('src/pages/InvoiceIn/InvoiceInList.vue'), children: [ { - name: 'InvoiceInList', + name: 'InvoiceInList', path: 'list', meta: { title: 'list', @@ -137,9 +138,10 @@ export default { title: 'serial', icon: 'view_list', }, - component: () => import('src/pages/InvoiceIn/Serial/InvoiceInSerial.vue'), + component: () => + import('src/pages/InvoiceIn/Serial/InvoiceInSerial.vue'), }, ], }, ], -}; \ No newline at end of file +}; 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..62765a49c 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -264,11 +264,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', @@ -294,11 +294,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', @@ -315,11 +315,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', diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js index c085dd8dc..94ff274dc 100644 --- a/src/router/modules/shelving.js +++ b/src/router/modules/shelving.js @@ -111,15 +111,6 @@ export default { shelvingCard, ], }, - { - path: 'create', - name: 'ShelvingCreate', - meta: { - title: 'shelvingCreate', - icon: 'add', - }, - component: () => import('src/pages/Shelving/Card/ShelvingForm.vue'), - }, { path: 'parking', name: 'ParkingMain', 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/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__/useDescriptorStore.spec.js b/src/stores/__tests__/useDescriptorStore.spec.js new file mode 100644 index 000000000..61aab8d14 --- /dev/null +++ b/src/stores/__tests__/useDescriptorStore.spec.js @@ -0,0 +1,28 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import 'app/test/vitest/helper'; + +import { useDescriptorStore } from 'src/stores/useDescriptorStore'; +import { useStateStore } from 'stores/useStateStore'; + +describe('useDescriptorStore', () => { + const { get, has } = useDescriptorStore(); + const stateStore = useStateStore(); + + beforeEach(() => { + stateStore.setDescriptors({}); + }); + + function getDescriptors() { + return stateStore.descriptors; + } + + it('should get descriptors in stateStore', async () => { + expect(Object.keys(getDescriptors()).length).toBe(0); + get(); + expect(Object.keys(getDescriptors()).length).toBeGreaterThan(0); + }); + + it('should find ticketDescriptor if search ticketFk', async () => { + expect(has('ticketFk')).toBeDefined(); + }); +}); diff --git a/src/stores/useDescriptorStore.js b/src/stores/useDescriptorStore.js new file mode 100644 index 000000000..89189f32e --- /dev/null +++ b/src/stores/useDescriptorStore.js @@ -0,0 +1,35 @@ +import { defineAsyncComponent } from 'vue'; +import { defineStore } from 'pinia'; +import { useStateStore } from 'stores/useStateStore'; + +export const useDescriptorStore = defineStore('descriptorStore', () => { + const { descriptors, setDescriptors } = useStateStore(); + function get() { + if (Object.keys(descriptors).length) return descriptors; + + const currentDescriptors = {}; + const files = import.meta.glob(`/src/**/*DescriptorProxy.vue`); + const moduleParser = { + account: 'user', + client: 'customer', + }; + for (const file in files) { + const name = file.split('/').at(-1).slice(0, -19).toLowerCase(); + const descriptor = moduleParser[name] ?? name; + currentDescriptors[descriptor + 'Fk'] = defineAsyncComponent( + () => import(/* @vite-ignore */ 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/.gitignore b/test/cypress/.gitignore index 3a1fcbf37..52595efbc 100644 --- a/test/cypress/.gitignore +++ b/test/cypress/.gitignore @@ -5,3 +5,4 @@ downloads/* storage/* reports/* docker/logs/* +results/* diff --git a/test/cypress/cypressParallel.sh b/test/cypress/cypressParallel.sh new file mode 100644 index 000000000..8ef26bcde --- /dev/null +++ b/test/cypress/cypressParallel.sh @@ -0,0 +1,15 @@ +#!/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/integration/Order/orderCatalog.spec.js b/test/cypress/integration/Order/orderCatalog.spec.js index a106d0e8a..050dd396c 100644 --- a/test/cypress/integration/Order/orderCatalog.spec.js +++ b/test/cypress/integration/Order/orderCatalog.spec.js @@ -34,7 +34,7 @@ describe('OrderCatalog', () => { searchByCustomTagInput('Silver'); }); - it('filters by custom value dialog', () => { + it.skip('filters by custom value dialog', () => { Cypress.on('uncaught:exception', (err) => { if (err.message.includes('canceled')) { return false; @@ -55,9 +55,9 @@ describe('OrderCatalog', () => { it('removes a secondary tag', () => { cy.get(':nth-child(1) > [data-cy="catalogFilterCategory"]').click(); cy.selectOption('[data-cy="catalogFilterType"]', 'Anthurium'); - cy.dataCy('vnFilterPanelChip').should('exist'); + cy.dataCy('vnFilterPanelChip_typeFk').should('exist'); cy.get('[data-cy="catalogFilterCustomTag"] > .q-chip__icon--remove').click(); - cy.dataCy('vnFilterPanelChip').should('not.exist'); + cy.dataCy('vnFilterPanelChip_typeFk').should('not.exist'); }); it('Removes category tag', () => { diff --git a/test/cypress/integration/claim/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 7ca6472af..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)'; @@ -10,8 +10,6 @@ describe('ClaimDevelopment', () => { cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/claim/${claimId}/development`); - cy.intercept('GET', /\/api\/Workers\/search/).as('workers'); - cy.intercept('GET', /\/api\/Workers\/search/).as('workers'); cy.waitForElement('tbody'); }); @@ -21,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); @@ -36,7 +33,6 @@ describe('ClaimDevelopment', () => { }); it('should add and remove new line', () => { - cy.wait(['@workers', '@workers']); cy.addCard(); cy.waitForElement(thirdRow); @@ -52,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 @@ -66,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/claimNotes.spec.js b/test/cypress/integration/claim/claimNotes.spec.js index ae8b4186c..576671a38 100644 --- a/test/cypress/integration/claim/claimNotes.spec.js +++ b/test/cypress/integration/claim/claimNotes.spec.js @@ -1,4 +1,4 @@ -describe.skip('ClaimNotes', () => { +describe('ClaimNotes', () => { const saveBtn = '.q-field__append > .q-btn > .q-btn__content > .q-icon'; const firstNote = '.q-infinite-scroll :nth-child(1) > .q-card__section--vert'; beforeEach(() => { @@ -8,7 +8,10 @@ describe.skip('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; - cy.get('.q-textarea').should('not.be.disabled').type(message); + cy.get('.q-textarea') + .should('be.visible') + .should('not.be.disabled') + .type(message); cy.get(saveBtn).click(); cy.get(firstNote).should('have.text', message); diff --git a/test/cypress/integration/claim/claimPhoto.spec.js b/test/cypress/integration/claim/claimPhoto.spec.js index c3522cbfe..ac0460029 100755 --- a/test/cypress/integration/claim/claimPhoto.spec.js +++ b/test/cypress/integration/claim/claimPhoto.spec.js @@ -1,6 +1,7 @@ /// <reference types="cypress" /> -// redmine.verdnatura.es/issues/8417 describe.skip('ClaimPhoto', () => { + const carrouselClose = + '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon'; beforeEach(() => { const claimId = 1; cy.login('developer'); @@ -12,47 +13,38 @@ describe.skip('ClaimPhoto', () => { cy.get('label > .q-btn input').selectFile('test/cypress/fixtures/image.jpg', { force: true, }); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + cy.checkNotification('Data saved'); }); it('should add new file with drag and drop', () => { + cy.get('.container').should('be.visible').and('exist'); cy.get('.container').selectFile('test/cypress/fixtures/image.jpg', { action: 'drag-drop', }); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + cy.checkNotification('Data saved'); }); it('should open first image dialog change to second and close', () => { - cy.get(':nth-last-child(1) > .q-card').click(); - cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( - 'be.visible', - ); + cy.dataCy('file-1').click(); + cy.get(carrouselClose).click(); - cy.get('.q-carousel__control > button').click(); - - cy.get( - '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon', - ).click(); - cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( - 'not.be.visible', - ); + cy.dataCy('file-1').click(); + cy.get('.q-carousel__control > button').as('nextButton').click(); + cy.get('.q-carousel__slide > .q-ma-none').should('be.visible'); + cy.get(carrouselClose).click(); }); it('should remove third and fourth file', () => { - cy.get( - '.multimediaParent > :nth-last-child(1) > .q-btn > .q-btn__content > .q-icon', - ).click(); + cy.dataCy('delete-button-4').click(); cy.get( '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block', ).click(); - cy.get('.q-notification__message').should('have.text', 'Data deleted'); + cy.checkNotification('Data deleted'); - cy.get( - '.multimediaParent > :nth-last-child(1) > .q-btn > .q-btn__content > .q-icon', - ).click(); + cy.dataCy('delete-button-3').click(); cy.get( '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block', ).click(); - cy.get('.q-notification__message').should('have.text', 'Data deleted'); + cy.checkNotification('Data deleted'); }); }); diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/client/clientAddress.spec.js index 8673c9083..5d82aa4bc 100644 --- a/test/cypress/integration/client/clientAddress.spec.js +++ b/test/cypress/integration/client/clientAddress.spec.js @@ -17,7 +17,7 @@ describe('Client consignee', () => { const addressName = 'test'; cy.dataCy('Consignee_input').type(addressName); cy.dataCy('Location_select').click(); - cy.get('[role="listbox"] .q-item:nth-child(1)').click(); + cy.getOption(); cy.dataCy('Street address_input').type('TEST ADDRESS'); cy.get('.q-btn-group > .q-btn--standard').click(); cy.location('href').should('contain', '#/customer/1107/address'); diff --git a/test/cypress/integration/client/clientBalance.spec.js b/test/cypress/integration/client/clientBalance.spec.js index abfa74cec..fff6a5e04 100644 --- a/test/cypress/integration/client/clientBalance.spec.js +++ b/test/cypress/integration/client/clientBalance.spec.js @@ -1,11 +1,13 @@ /// <reference types="cypress" /> describe('Client balance', () => { beforeEach(() => { - cy.viewport(1280, 720); cy.login('developer'); cy.visit('#/customer/1101/balance'); }); - it('Should load layout', () => { - cy.get('.q-page').should('be.visible'); + 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/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js index f83d29278..caf94b8bd 100644 --- a/test/cypress/integration/client/clientList.spec.js +++ b/test/cypress/integration/client/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,19 +53,28 @@ 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); cy.checkValueForm(2, search); + cy.dataCy('Customer_select').should('have.value', search); + cy.dataCy('Address_select').should('have.value', search); }); it('Client founded create order', () => { const search = 'Jessica Jones'; - cy.searchByLabel('Name', search); + + cy.intercept('GET', /\/api\/Clients\/1110\/summary/).as('customer'); + cy.dataCy('Name_input').type(`${search}{enter}`); + cy.wait('@customer'); + cy.get('.actions > .q-card__actions').should('exist'); cy.clickButtonWith('icon', 'icon-basketadd'); + cy.url().should('include', `/customer/1110/summary`); cy.waitForElement('#formModel'); cy.waitForElement('.q-form'); cy.checkValueForm(1, search); + cy.dataCy('Client_select').should('have.value', search); + cy.dataCy('Address_select').should('have.value', search); }); }); diff --git a/test/cypress/integration/client/clientWebAccess.spec.js b/test/cypress/integration/client/clientWebAccess.spec.js index 6803336a3..970aab71c 100644 --- a/test/cypress/integration/client/clientWebAccess.spec.js +++ b/test/cypress/integration/client/clientWebAccess.spec.js @@ -12,7 +12,7 @@ describe('Client web-access', () => { cy.get('.q-btn-group > :nth-child(1)').should('not.be.disabled'); cy.get('.q-checkbox__inner').click(); cy.get('.q-btn-group > .q-btn--standard.q-btn--actionable').should( - 'not.be.disabled' + 'not.be.disabled', ); cy.get('.q-btn-group > .q-btn--flat').should('not.be.disabled'); cy.get('.q-btn-group > :nth-child(1)').click(); diff --git a/test/cypress/integration/entry/commands.js b/test/cypress/integration/entry/commands.js new file mode 100644 index 000000000..7c96a5440 --- /dev/null +++ b/test/cypress/integration/entry/commands.js @@ -0,0 +1,21 @@ +Cypress.Commands.add('selectTravel', (warehouse = '1') => { + cy.get('i[data-cy="Travel_icon"]').click(); + cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('button[data-cy="save-filter-travel-form"]').click(); + cy.get('tr').eq(1).click(); +}); + +Cypress.Commands.add('deleteEntry', () => { + cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); + cy.waitForElement('div[data-cy="delete-entry"]').click(); + cy.url().should('include', 'list'); +}); + +Cypress.Commands.add('createEntry', () => { + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + cy.selectTravel('one'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + cy.url().should('include', 'summary'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); +}); diff --git a/test/cypress/integration/entry/entryCard/entryBasicData.spec.js b/test/cypress/integration/entry/entryCard/entryBasicData.spec.js new file mode 100644 index 000000000..ba689b8c7 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryBasicData.spec.js @@ -0,0 +1,19 @@ +import '../commands.js'; + +describe('EntryBasicData', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Change Travel', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); + cy.selectTravel('two'); + cy.saveCard(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryBuys.spec.js b/test/cypress/integration/entry/entryCard/entryBuys.spec.js new file mode 100644 index 000000000..f8f5e6b80 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryBuys.spec.js @@ -0,0 +1,96 @@ +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.deleteEntry(); + }); + + function createBuy() { + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get('a[data-cy="EntryBuys-menu-item"]').click(); + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + + cy.get('input[data-cy="itemFk-create-popup"]').type('1'); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + } +}); diff --git a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js new file mode 100644 index 000000000..554471008 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js @@ -0,0 +1,44 @@ +import '../commands.js'; +describe('EntryDescriptor', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Clone entry and recalculate rates', () => { + cy.createEntry(); + + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.url().then((previousUrl) => { + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); + + cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); + + cy.url() + .should('not.eq', previousUrl) + .then(() => { + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="recalculate-rates"]').click(); + + cy.get('.q-notification__message') + .eq(2) + .should('have.text', 'Entry prices recalculated'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.deleteEntry(); + + cy.log(previousUrl); + + cy.visit(previousUrl); + + cy.waitForElement('[data-cy="entry-buys"]'); + cy.deleteEntry(); + }); + }); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryDms.spec.js b/test/cypress/integration/entry/entryCard/entryDms.spec.js new file mode 100644 index 000000000..f3f0ef20b --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryDms.spec.js @@ -0,0 +1,22 @@ +import '../commands.js'; +describe('EntryDms', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('should create edit and remove new dms', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.dataCy('EntryDms-menu-item').click(); + cy.dataCy('addButton').click(); + cy.dataCy('attachFile').click(); + cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { + force: true, + }); + cy.dataCy('FormModelPopup_save').click(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryLock.spec.js b/test/cypress/integration/entry/entryCard/entryLock.spec.js new file mode 100644 index 000000000..6ba4392ae --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryLock.spec.js @@ -0,0 +1,44 @@ +import '../commands.js'; +describe('EntryLock', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Should notify when entry is lock by another user', () => { + const checkLockMessage = () => { + cy.get('[role="dialog"]').should('be.visible'); + cy.get('[data-cy="VnConfirm_message"] > span').should( + 'contain.text', + 'This entry has been locked by buyerNick', + ); + }; + + cy.createEntry(); + goToEntryBuys(); + cy.get('.q-notification__message') + .eq(1) + .should('have.text', 'The entry has been locked successfully'); + + cy.login('logistic'); + cy.reload(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_cancel"]').click(); + cy.url().should('include', 'summary'); + + goToEntryBuys(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_confirm"]').click(); + cy.url().should('include', 'buys'); + + cy.deleteEntry(); + + function goToEntryBuys() { + const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; + cy.get(entryBuySelector).should('be.visible'); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get(entryBuySelector).click(); + } + }); +}); diff --git a/test/cypress/integration/entry/entryCard/entryNotes.spec.js b/test/cypress/integration/entry/entryCard/entryNotes.spec.js new file mode 100644 index 000000000..544ac23b0 --- /dev/null +++ b/test/cypress/integration/entry/entryCard/entryNotes.spec.js @@ -0,0 +1,50 @@ +import '../commands.js'; + +describe('EntryNotes', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + const createObservation = (type, description) => { + cy.dataCy('vnTableCreateBtn').click(); + cy.dataCy('Observation type_select').eq(1).should('be.visible').type(type); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.dataCy('Description_input').should('be.visible').type(description); + cy.dataCy('FormModelPopup_save').should('be.enabled').click(); + }; + + const editObservation = (rowIndex, type, description) => { + cy.get(`td[data-col-field="description"][data-row-index="${rowIndex}"]`) + .click() + .clear() + .type(description); + cy.get(`td[data-col-field="observationTypeFk"][data-row-index="${rowIndex}"]`) + .click() + .clear() + .type(type); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.saveCard(); + }; + + it('Create, delete, and edit observations', () => { + cy.createEntry(); + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.dataCy('EntryNotes-menu-item').click(); + + createObservation('Packager', 'test'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + + editObservation(0, 'Administrative', 'test2'); + + createObservation('Administrative', 'test'); + cy.get('.q-notification__message') + .eq(2) + .should('have.text', "The observation type can't be repeated"); + cy.dataCy('FormModelPopup_cancel').click(); + + cy.deleteEntry(); + }); +}); diff --git a/test/cypress/integration/entry/entryDms.spec.js b/test/cypress/integration/entry/entryDms.spec.js deleted file mode 100644 index 47dcdba9e..000000000 --- a/test/cypress/integration/entry/entryDms.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -describe('EntryDms', () => { - const entryId = 1; - - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('developer'); - cy.visit(`/#/entry/${entryId}/dms`); - }); - - it('should create edit and remove new dms', () => { - cy.addRow(); - cy.get('.icon-attach').click(); - cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', { - force: true, - }); - - cy.get('tbody > tr').then((value) => { - const u = undefined; - - //Create and check if exist new row - let newFileTd = Cypress.$(value).length; - cy.get('.q-btn--standard > .q-btn__content > .block').click(); - expect(value).to.have.length(newFileTd++); - const newRowSelector = `tbody > :nth-child(${newFileTd})`; - cy.waitForElement(newRowSelector); - cy.validateRow(newRowSelector, [u, u, u, u, u, 'ENTRADA ID 1']); - - //Edit new dms - const newDescription = 'entry id 1 modified'; - const textAreaSelector = - '.q-textarea > .q-field__inner > .q-field__control > .q-field__control-container'; - cy.get( - `tbody :nth-child(${newFileTd}) > .text-right > .no-wrap > :nth-child(2) > .q-btn > .q-btn__content > .q-icon` - ).click(); - - cy.get(textAreaSelector).clear(); - cy.get(textAreaSelector).type(newDescription); - cy.saveCard(); - cy.reload(); - - cy.validateRow(newRowSelector, [u, u, u, u, u, newDescription]); - }); - }); -}); diff --git a/test/cypress/integration/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js index d43ec895a..990f74261 100644 --- a/test/cypress/integration/entry/entryList.spec.js +++ b/test/cypress/integration/entry/entryList.spec.js @@ -1,223 +1,54 @@ -describe('Entry', () => { +import './commands'; + +describe('EntryList', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('buyer'); cy.visit(`/#/entry/list`); }); - it('Filter deleted entries and other fields', () => { - createEntry(); + it('View popup summary', () => { + cy.createEntry(); cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); cy.waitForElement('[data-cy="entry-buys"]'); - deleteEntry(); + cy.deleteEntry(); cy.typeSearchbar('{enter}'); - cy.get('span[title="Date"]').click().click(); - cy.typeSearchbar('{enter}'); - cy.url().should('include', 'order'); - cy.get('td[data-row-index="0"][data-col-field="landed"]').should( - 'have.text', - '-', - ); + cy.get('button[title="Summary"]').eq(1).should('be.visible').click(); + cy.dataCy('entry-summary').should('be.visible'); }); - it('Create entry, modify travel and add buys', () => { - createEntryAndBuy(); - cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); - selectTravel('two'); - cy.saveCard(); - cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); - deleteEntry(); + it('Show supplierDescriptor on click on supplierDescriptor', () => { + cy.typeSearchbar('{enter}'); + cy.get('td[data-col-field="supplierFk"] > div > span').eq(0).click(); + cy.get('div[role="menu"] > div[class="descriptor"]').should('be.visible'); }); - it('Clone entry and recalculate rates', () => { - createEntry(); + it('Landed badge should display the right color', () => { + cy.typeSearchbar('{enter}'); - cy.waitForElement('[data-cy="entry-buys"]'); - - cy.url().then((previousUrl) => { - cy.get('[data-cy="descriptor-more-opts"]').click(); - cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); - - cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); - - cy.url() - .should('not.eq', previousUrl) - .then(() => { - cy.waitForElement('[data-cy="entry-buys"]'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); - cy.get('div[data-cy="recalculate-rates"]').click(); - - cy.get('.q-notification__message') - .eq(2) - .should('have.text', 'Entry prices recalculated'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); - deleteEntry(); - - cy.log(previousUrl); - - cy.visit(previousUrl); - - cy.waitForElement('[data-cy="entry-buys"]'); - deleteEntry(); + const checkBadgeDate = (selector, comparisonFn) => { + cy.get(selector) + .should('exist') + .each(($badge) => { + const badgeText = $badge.text().trim(); + const badgeDate = new Date(badgeText); + const compareDate = new Date('01/01/2001'); + comparisonFn(badgeDate, compareDate); }); - }); - }); - - it('Should notify when entry is lock by another user', () => { - const checkLockMessage = () => { - cy.get('[role="dialog"]').should('be.visible'); - cy.get('[data-cy="VnConfirm_message"] > span').should( - 'contain.text', - 'This entry has been locked by buyerNick', - ); }; - createEntry(); - goToEntryBuys(); - cy.get('.q-notification__message') - .eq(1) - .should('have.text', 'The entry has been locked successfully'); - - cy.login('logistic'); - cy.reload(); - checkLockMessage(); - cy.get('[data-cy="VnConfirm_cancel"]').click(); - cy.url().should('include', 'summary'); - - goToEntryBuys(); - checkLockMessage(); - cy.get('[data-cy="VnConfirm_confirm"]').click(); - cy.url().should('include', 'buys'); - - deleteEntry(); - }); - - it('Edit buys and use toolbar actions', () => { - const COLORS = { - negative: 'rgb(251, 82, 82)', - positive: 'rgb(200, 228, 132)', - enabled: 'rgb(255, 255, 255)', - disable: 'rgb(168, 168, 168)', - }; - - const selectCell = (field, row = 0) => - cy.get(`td[data-col-field="${field}"][data-row-index="${row}"]`); - const selectSpan = (field, row = 0) => selectCell(field, row).find('div > span'); - const selectButton = (cySelector) => cy.get(`button[data-cy="${cySelector}"]`); - const clickAndType = (field, value, row = 0) => { - selectCell(field, row).click().type(`${value}{esc}`); - }; - const checkText = (field, expectedText, row = 0) => - selectCell(field, row).should('have.text', expectedText); - const checkColor = (field, expectedColor, row = 0) => - selectSpan(field, row).should('have.css', 'color', expectedColor); - - createEntryAndBuy(); - - selectCell('isIgnored').click().click().type('{esc}'); - checkText('isIgnored', 'close'); - - clickAndType('stickers', '1'); - checkText('stickers', '0/01'); - checkText('quantity', '1'); - checkText('amount', '50.00'); - clickAndType('packing', '2'); - checkText('packing', '12'); - checkText('weight', '12.0'); - checkText('quantity', '12'); - checkText('amount', '600.00'); - checkColor('packing', COLORS.enabled); - - selectCell('groupingMode').click().click().click(); - checkColor('packing', COLORS.disable); - checkColor('grouping', COLORS.enabled); - - selectCell('buyingValue').click().clear().type('{backspace}{backspace}1'); - checkText('amount', '12.00'); - checkColor('minPrice', COLORS.disable); - - selectCell('hasMinPrice').click().click(); - checkColor('minPrice', COLORS.enabled); - selectCell('hasMinPrice').click(); - - cy.saveCard(); - cy.get('span[data-cy="footer-stickers"]').should('have.text', '1'); - cy.get('.q-notification__message').contains('Data saved'); - - selectButton('change-quantity-sign').should('be.disabled'); - selectButton('check-buy-amount').should('be.disabled'); - cy.get('tr.cursor-pointer > .q-table--col-auto-width > .q-checkbox').click(); - selectButton('change-quantity-sign').should('be.enabled'); - selectButton('check-buy-amount').should('be.enabled'); - - selectButton('change-quantity-sign').click(); - selectButton('set-negative-quantity').click(); - checkText('quantity', '-12'); - selectButton('set-positive-quantity').click(); - checkText('quantity', '12'); - checkColor('amount', COLORS.disable); - - selectButton('check-buy-amount').click(); - selectButton('uncheck-amount').click(); - checkColor('amount', COLORS.disable); - - selectButton('check-amount').click(); - checkColor('amount', COLORS.positive); - cy.saveCard(); - - cy.get('span[data-cy="footer-amount"]').should( - 'have.css', - 'color', - COLORS.positive, + checkBadgeDate( + 'td[data-col-field="landed"] > div .bg-warning', + (badgeDate, compareDate) => { + expect(badgeDate.getTime()).to.be.greaterThan(compareDate.getTime()); + }, ); - deleteEntry(); + checkBadgeDate( + 'td[data-col-field="landed"] > div .bg-info', + (badgeDate, compareDate) => { + expect(badgeDate.getTime()).to.be.lessThan(compareDate.getTime()); + }, + ); }); - - function goToEntryBuys() { - const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; - cy.get(entryBuySelector).should('be.visible'); - cy.waitForElement('[data-cy="entry-buys"]'); - cy.get(entryBuySelector).click(); - } - - function deleteEntry() { - cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); - cy.waitForElement('div[data-cy="delete-entry"]').click(); - cy.url().should('include', 'list'); - } - - function createEntryAndBuy() { - createEntry(); - createBuy(); - } - - function createEntry() { - cy.get('button[data-cy="vnTableCreateBtn"]').click(); - selectTravel('one'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); - cy.url().should('include', 'summary'); - cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); - } - - function selectTravel(warehouse) { - cy.get('i[data-cy="Travel_icon"]').click(); - cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); - cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); - cy.get('button[data-cy="save-filter-travel-form"]').click(); - cy.get('tr').eq(1).click(); - } - - function createBuy() { - cy.get('a[data-cy="EntryBuys-menu-item"]').click(); - cy.get('a[data-cy="EntryBuys-menu-item"]').click(); - cy.get('button[data-cy="vnTableCreateBtn"]').click(); - - cy.get('input[data-cy="itemFk-create-popup"]').type('1'); - cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); - cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); - } }); diff --git a/test/cypress/integration/entry/entryStockBought.spec.js b/test/cypress/integration/entry/entryStockBought.spec.js new file mode 100644 index 000000000..3fad44d91 --- /dev/null +++ b/test/cypress/integration/entry/entryStockBought.spec.js @@ -0,0 +1,23 @@ +describe('EntryStockBought', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/stock-Bought`); + }); + + it('Should edit the reserved space adjust the purchased spaces and check detail', () => { + cy.get('[data-cy="edit-travel"]').should('be.visible').click(); + cy.get('input[aria-label="m3"]').clear().type('60'); + cy.get('[data-cy="FormModelPopup_save"]').click(); + cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); + + cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); + cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); + cy.get('input[name="reserve"]').type('10{enter}'); + cy.get('button[title="Save"]').click(); + cy.checkNotification('Data saved'); + + cy.get('[data-cy="searchBtn"]').eq(0).click(); + cy.get('tBody > tr').eq(1).its('length').should('eq', 1); + }); +}); diff --git a/test/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/entrySupplier.spec.js similarity index 71% rename from test/cypress/integration/entry/myEntry.spec.js rename to test/cypress/integration/entry/entrySupplier.spec.js index ed469d9e2..83deecea5 100644 --- a/test/cypress/integration/entry/myEntry.spec.js +++ b/test/cypress/integration/entry/entrySupplier.spec.js @@ -1,4 +1,4 @@ -describe('EntryMy when is supplier', () => { +describe('EntrySupplier when is supplier', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('supplier'); @@ -13,5 +13,7 @@ describe('EntryMy when is supplier', () => { cy.dataCy('cardBtn').eq(2).click(); cy.dataCy('printLabelsBtn').click(); cy.window().its('open').should('be.called'); + cy.dataCy('seeLabelBtn').eq(0).should('be.visible').click(); + cy.window().its('open').should('be.called'); }); }); diff --git a/test/cypress/integration/entry/entryWasteRecalc.spec.js b/test/cypress/integration/entry/entryWasteRecalc.spec.js new file mode 100644 index 000000000..1b358676c --- /dev/null +++ b/test/cypress/integration/entry/entryWasteRecalc.spec.js @@ -0,0 +1,22 @@ +import './commands'; +describe('EntryDms', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyerBoss'); + cy.visit(`/#/entry/waste-recalc`); + }); + + it('should recalc waste for today', () => { + cy.waitForElement('[data-cy="wasteRecalc"]'); + cy.dataCy('recalc').should('be.disabled'); + + cy.dataCy('dateFrom').should('be.visible').click().type('01-01-2001'); + cy.dataCy('dateTo').should('be.visible').click().type('01-01-2001'); + + cy.dataCy('recalc').should('be.enabled').click(); + cy.get('.q-notification__message').should( + 'have.text', + 'The wastes were successfully recalculated', + ); + }); +}); diff --git a/test/cypress/integration/entry/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js deleted file mode 100644 index 91e0d507e..000000000 --- a/test/cypress/integration/entry/stockBought.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -describe('EntryStockBought', () => { - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('buyer'); - cy.visit(`/#/entry/stock-Bought`); - }); - it('Should edit the reserved space', () => { - cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); - cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); - cy.get('input[name="reserve"]').type('10{enter}'); - cy.get('button[title="Save"]').click(); - cy.checkNotification('Data saved'); - }); - it('Should add a new reserved space for buyerBoss', () => { - cy.addBtnClick(); - cy.get('input[aria-label="Reserve"]').type('1'); - cy.get('input[aria-label="Date"]').eq(1).clear(); - cy.get('input[aria-label="Date"]').eq(1).type('01-01'); - cy.get('input[aria-label="Buyer"]').type('itNick'); - cy.get('div[role="listbox"] > div > div[role="option"]') - .eq(1) - .should('be.visible') - .click(); - - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get('.q-notification__message').should('have.text', 'Data created'); - - cy.get('[data-col-field="reserve"][data-row-index="1"]').click().clear(); - cy.get('[data-cy="searchBtn"]').eq(1).click(); - cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata') - .should('have.text', 'warningNo data available') - .type('{esc}'); - cy.get('[data-col-field="reserve"][data-row-index="1"]') - .click() - .type('{backspace}{enter}'); - cy.get('[data-cy="crudModelDefaultSaveBtn"]').should('be.enabled').click(); - cy.get('.q-notification__message').eq(1).should('have.text', 'Data saved'); - }); - it('Should check detail for the buyer', () => { - cy.get('[data-cy="searchBtn"]').eq(0).click(); - cy.get('tBody > tr').eq(1).its('length').should('eq', 1); - }); - - it('Should edit travel m3 and refresh', () => { - cy.get('[data-cy="edit-travel"]').should('be.visible').click(); - cy.get('input[aria-label="m3"]').clear().type('60'); - cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); - }); -}); diff --git a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js index 11ca1bb59..ee4d9fb74 100644 --- a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js @@ -1,26 +1,40 @@ /// <reference types="cypress" /> +import moment from 'moment'; describe('InvoiceInBasicData', () => { - const firstFormSelect = '.q-card > .vn-row:nth-child(1) > .q-select'; const dialogInputs = '.q-dialog input'; - const resetBtn = '.q-btn-group--push > .q-btn--flat'; const getDocumentBtns = (opt) => `[data-cy="dms-buttons"] > :nth-child(${opt})`; + const futureDate = moment().add(1, 'days').format('DD-MM-YYYY'); + const mock = { + invoiceInBasicDataSupplier: { val: 'Bros nick', type: 'select' }, + invoiceInBasicDataSupplierRef_input: 'mockInvoice41', + invoiceInBasicDataIssued: { val: futureDate, type: 'date' }, + invoiceInBasicDataOperated: { val: futureDate, type: 'date' }, + invoiceInBasicDatabookEntried: { val: futureDate, type: 'date' }, + invoiceInBasicDataBooked: { + val: moment().add(5, 'days').format('DD-MM-YYYY'), + type: 'date', + }, + invoiceInBasicDataDeductibleExpenseFk: { + val: '4751000000', + type: 'select', + }, + invoiceInBasicDataCurrencyFk: { val: 'USD', type: 'select' }, + invoiceInBasicDataCompanyFk: { val: 'CCs', type: 'select' }, + invoiceInBasicDataWithholdingSageFk: { + val: 'Arrendamiento y subarrendamiento', + type: 'select', + }, + }; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/basic-data`); }); - it('should edit the provideer and supplier ref', () => { - cy.dataCy('UnDeductibleVatSelect').type('4751000000'); - cy.get('.q-menu .q-item').contains('4751000000').click(); - cy.get(resetBtn).click(); - - cy.waitForElement('#formModel').within(() => { - cy.dataCy('vnSupplierSelect').type('Bros nick'); - }) - cy.get('.q-menu .q-item').contains('Bros nick').click(); + it('should edit every field', () => { + cy.fillInForm(mock, { attr: 'data-cy' }); cy.saveCard(); - cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Bros nick'); + cy.validateForm(mock, { attr: 'data-cy' }); }); it('should edit, remove and create the dms data', () => { @@ -44,7 +58,7 @@ describe('InvoiceInBasicData', () => { cy.checkNotification('Data saved'); //create - cy.get('[data-cy="dms-create"]').eq(0).click(); + cy.get('[data-cy="invoiceInBasicDataDmsAdd"]').eq(0).click(); cy.get('[data-cy="VnDms_inputFile"').selectFile( 'test/cypress/fixtures/image.jpg', { diff --git a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js index 731174040..275fa1358 100644 --- a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js @@ -1,22 +1,59 @@ -/// <reference types="cypress" /> -describe('InvoiceInCorrective', () => { - const saveDialog = '.q-card > .q-card__actions > .q-btn--standard '; +describe('invoiceInCorrective', () => { + beforeEach(() => cy.login('administrative')); - it('should create a correcting invoice', () => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit(`/#/invoice-in/1/summary`); + it('should modify the invoice', () => { + cy.visit('/#/invoice-in/1/summary'); cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + cy.intercept('POST', '/api/InvoiceInCorrections/crud').as('crud'); + cy.intercept('GET', /InvoiceInCorrections\?filter=.+/).as('getCorrective'); - cy.openActionsDescriptor(); + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); - cy.dataCy('createCorrectiveItem').click(); - cy.get(saveDialog).click(); - cy.wait('@corrective').then((interception) => { - const correctingId = interception.response.body; - cy.url().should('include', `/invoice-in/${correctingId}/summary`); - cy.visit(`/#/invoice-in/${correctingId}/corrective`); + cy.wait('@corrective').then(({ response }) => { + const correctingFk = response.body; + cy.url().should('include', `/invoice-in/${correctingFk}/summary`); + cy.visit(`/#/invoice-in/${correctingFk}/corrective`); + cy.selectOption('[data-cy="invoiceInCorrective_class"]', 'r4'); + cy.selectOption('[data-cy="invoiceInCorrective_type"]', 'sustitución'); + cy.selectOption('[data-cy="invoiceInCorrective_reason"]', 'vat'); + cy.dataCy('crudModelDefaultSaveBtn').click(); + + cy.wait('@crud'); + cy.reload(); + cy.wait('@getCorrective'); + cy.validateRow('tbody > :nth-of-type(1)', [ + , + 'S – Por sustitución', + 'R4', + 'Error in VAT calculation', + ]); }); - cy.get('tbody > tr:visible').should('have.length', 1); + }); + + it('should not be able to modify the invoice if the original invoice is booked', () => { + cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + cy.visit('/#/invoice-in/4/summary'); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); + + cy.wait('@corrective').then(({ response }) => { + const correctingFk = response.body; + cy.url().should('include', `/invoice-in/${correctingFk}/summary`); + cy.visit(`/#/invoice-in/${correctingFk}/corrective`); + + cy.dataCy('invoiceInCorrective_class').should('be.disabled'); + cy.dataCy('invoiceInCorrective_type').should('be.disabled'); + cy.dataCy('invoiceInCorrective_reason').should('be.disabled'); + }); + }); + + it('should show/hide the section if it is a corrective invoice', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.get('[data-cy="InvoiceInCorrective-menu-item"]').should('not.exist'); + cy.clicDescriptorAction(4); + cy.get('[data-cy="InvoiceInCorrective-menu-item"]').should('exist'); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js index 97a9fe976..7058e154c 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js @@ -1,21 +1,147 @@ describe('InvoiceInDescriptor', () => { - const book = '.summaryHeader > .no-wrap > .q-btn'; - const firstDescritorOpt = '.q-menu > .q-list > :nth-child(5) > .q-item__section'; - const checkbox = ':nth-child(5) > .q-checkbox'; + beforeEach(() => cy.login('administrative')); - it('should booking and unbooking the invoice properly', () => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit('/#/invoice-in/1/summary'); - cy.waitForElement('.q-page'); + describe('more options', () => { + it('should booking and unbooking the invoice properly', () => { + const checkbox = '[data-cy="vnLvIs booked"] > .q-checkbox'; + cy.visit('/#/invoice-in/2/summary'); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox); + cy.selectDescriptorOption(); + cy.dataCy('VnConfirm_confirm').click(); + cy.validateCheckbox(checkbox, false); + }); - cy.get(book).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.get(checkbox).invoke('attr', 'aria-checked').should('eq', 'true'); + it('should delete the invoice properly', () => { + cy.visit('/#/invoice-in/2/summary'); + cy.selectDescriptorOption(2); + cy.clickConfirm(); + cy.checkNotification('invoice deleted'); + }); - cy.dataCy('descriptor-more-opts').first().click(); - cy.get(firstDescritorOpt).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.get(checkbox).invoke('attr', 'aria-checked').should('eq', 'false'); + it('should clone the invoice properly', () => { + cy.visit('/#/invoice-in/3/summary'); + cy.selectDescriptorOption(3); + cy.clickConfirm(); + cy.checkNotification('Invoice cloned'); + }); + + it('should show the agricultural PDF properly', () => { + cy.visit('/#/invoice-in/6/summary'); + cy.validatePdfDownload( + /api\/InvoiceIns\/6\/invoice-in-pdf\?access_token=.*/, + () => cy.selectDescriptorOption(4), + ); + }); + + it('should send the agricultural PDF properly', () => { + cy.intercept('POST', 'api/InvoiceIns/6/invoice-in-email').as('sendEmail'); + cy.visit('/#/invoice-in/6/summary'); + cy.selectDescriptorOption(5); + + cy.dataCy('SendEmailNotifiactionDialogInput_input').type( + '{selectall}jorgito@gmail.mx', + ); + cy.clickConfirm(); + cy.checkNotification('Notification sent'); + cy.wait('@sendEmail').then(({ request, response }) => { + expect(request.body).to.deep.equal({ + recipientId: 2, + recipient: 'jorgito@gmail.mx', + }); + expect(response.statusCode).to.equal(200); + }); + }); + // https://redmine.verdnatura.es/issues/8767 + it.skip('should download the file properly', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.validateDownload(() => cy.selectDescriptorOption(5)); + }); + }); + + describe('buttons', () => { + beforeEach(() => cy.visit('/#/invoice-in/1/summary')); + + it('should navigate to the supplier summary', () => { + cy.clicDescriptorAction(1); + cy.url().should('to.match', /supplier\/\d+\/summary/); + }); + + it('should navigate to the entry summary', () => { + cy.clicDescriptorAction(2); + cy.url().should('to.match', /entry\/\d+\/summary/); + }); + + it('should navigate to the invoiceIn list', () => { + cy.clicDescriptorAction(3); + cy.url().should('to.match', /invoice-in\/list\?table=\{.*supplierFk.+\}/); + }); + }); + + describe('corrective', () => { + const originalId = 4; + + beforeEach(() => cy.visit(`/#/invoice-in/${originalId}/summary`)); + + it('should create a correcting invoice and redirect to original invoice', () => { + createCorrective(); + redirect(originalId); + }); + + it('should create a correcting invoice and navigate to list filtered by corrective', () => { + createCorrective(); + redirect(originalId); + + cy.clicDescriptorAction(4); + cy.validateVnTableRows({ + cols: [ + { + name: 'supplierRef', + val: '1237', + operation: 'include', + }, + ], + }); + }); + }); + + describe('link', () => { + it('should open the supplier descriptor popup', () => { + cy.visit('/#/invoice-in/1/summary'); + cy.intercept('GET', /Suppliers\/\d+/).as('getSupplier'); + + cy.dataCy('invoiceInDescriptor_supplier').then(($el) => { + const alias = $el.text().trim(); + $el.click(); + cy.wait('@getSupplier').then(() => + cy.validateDescriptor({ listbox: { 1: alias }, popup: true }), + ); + }); + }); }); }); + +function createCorrective() { + cy.intercept('POST', '/api/InvoiceIns/corrective').as('corrective'); + + cy.selectDescriptorOption(4); + cy.dataCy('saveCorrectiveInvoice').click(); + + cy.wait('@corrective').then(({ response }) => { + const correctingId = response.body; + cy.url().should('include', `/invoice-in/${correctingId}/summary`); + cy.visit(`/#/invoice-in/${correctingId}/corrective`); + cy.dataCy('invoiceInCorrective_class').should('contain.value', 'R2'); + cy.dataCy('invoiceInCorrective_type').should('contain.value', 'diferencias'); + cy.dataCy('invoiceInCorrective_reason').should('contain.value', 'sales details'); + }); +} + +function redirect(subtitle) { + const regex = new RegExp(`InvoiceIns/${subtitle}\\?filter=.*`); + cy.intercept('GET', regex).as('getOriginal'); + cy.clicDescriptorAction(4); + cy.wait('@getOriginal'); + cy.validateDescriptor({ subtitle }); +} diff --git a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js index 5a5becd22..2fc34a7ae 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js @@ -4,7 +4,7 @@ describe('InvoiceInDueDay', () => { const addBtn = '.q-page-sticky > div > .q-btn > .q-btn__content'; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/6/due-day`); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js index 4c2550548..6a1c18785 100644 --- a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js @@ -6,7 +6,7 @@ describe('InvoiceInIntrastat', () => { const firstRowAmount = `${firstRow} > :nth-child(3)`; beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/intrastat`); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js index d9ab3f7e7..44a61609e 100644 --- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -1,13 +1,21 @@ /// <reference types="cypress" /> + describe('InvoiceInList', () => { const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; const firstId = `${firstRow} > td:nth-child(2) span`; const firstDetailBtn = `${firstRow} .q-btn:nth-child(1)`; const summaryHeaders = '.summaryBody .header-link'; + const mockInvoiceRef = `createMockInvoice${Math.floor(Math.random() * 100)}`; + const mock = { + vnSupplierSelect: { val: 'farmer king', type: 'select' }, + 'Invoice nº_input': mockInvoiceRef, + Company_select: { val: 'orn', type: 'select' }, + 'Expedition date_inputDate': '16-11-2001', + }; beforeEach(() => { cy.viewport(1920, 1080); - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/list`); cy.get('#searchbar input').type('{enter}'); }); @@ -27,4 +35,18 @@ describe('InvoiceInList', () => { cy.get(summaryHeaders).eq(1).contains('Basic data'); cy.get(summaryHeaders).eq(4).contains('Vat'); }); + + it('should create a new Invoice', () => { + cy.dataCy('vnTableCreateBtn').click(); + cy.fillInForm({ ...mock }, { attr: 'data-cy' }); + cy.dataCy('FormModelPopup_save').click(); + cy.intercept('GET', /\/api\/InvoiceIns\/\d+\/getTotals$/).as('invoice'); + cy.wait('@invoice').then(() => + cy.validateDescriptor({ + title: mockInvoiceRef, + listBox: { 0: '11/16/2001', 3: 'The farmer' }, + }), + ); + cy.get('[data-cy="vnLvCompany"]').should('contain.text', 'ORN'); + }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js new file mode 100644 index 000000000..faad22f12 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInSerial.spec.js @@ -0,0 +1,23 @@ +describe('InvoiceInSerial', () => { + beforeEach(() => { + cy.login('administrative'); + cy.visit('#/invoice-in/serial'); + }); + + it('should filter by serial number', () => { + cy.dataCy('serial_input').type('R{enter}'); + cy.validateVnTableRows({ cols: [{ name: 'serial', val: 'r' }] }); + }); + + it('should filter by last days ', () => { + let before; + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((total) => (before = +total)); + + cy.dataCy('Last days_input').type('{selectall}1{enter}'); + cy.dataCy('vnTableCell_total') + .invoke('text') + .then((total) => expect(+total).to.be.lessThan(before)); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js b/test/cypress/integration/invoiceIn/invoiceInSummary.spec.js new file mode 100644 index 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 1e7ce1003..e9412244f 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -8,10 +8,8 @@ describe('InvoiceInVat', () => { const randomInt = Math.floor(Math.random() * 100); beforeEach(() => { - cy.login('developer'); + cy.login('administrative'); cy.visit(`/#/invoice-in/1/vat`); - cy.intercept('GET', '/api/InvoiceIns/1/getTotals').as('lastCall'); - cy.wait('@lastCall'); }); it('should edit the sage iva', () => { 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/invoiceOutMakeInvoice.spec.js b/test/cypress/integration/invoiceOut/invoiceOutMakeInvoice.spec.js index 145f492a1..e93326f1d 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutMakeInvoice.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutMakeInvoice.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('InvoiceOut manual invoice', () => { +describe.skip('InvoiceOut manual invoice', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); @@ -10,12 +10,17 @@ describe('InvoiceOut manual invoice', () => { it('should create an invoice from a ticket and go to that invoice', () => { cy.searchByLabel('Customer ID', '1101'); cy.get( - '[data-q-vs-anchor=""] > :nth-child(1) > .q-checkbox > .q-checkbox__inner' + '[data-q-vs-anchor=""] > :nth-child(1) > .q-checkbox > .q-checkbox__inner', ).click(); cy.dataCy('ticketListMakeInvoiceBtn').click(); cy.checkNotification('Data saved'); cy.get('.q-virtual-scroll__content > :nth-child(1) > :nth-child(3)').click(); cy.get(':nth-child(8) > .value > .link').click(); - cy.get('.header > :nth-child(3) > .q-btn__content').click(); + cy.get('.q-menu > .descriptor > .header').should('be.visible'); + cy.get( + '.q-menu > .descriptor > .header > [data-cy="descriptor-more-opts"] > .q-btn__content', + ).click(); + cy.get('[data-cy="descriptor-more-opts-menu"] > .q-list > :nth-child(4)').click(); + cy.dataCy('VnConfirm_confirm').click(); }); }); diff --git a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js index 4d530de05..9c6eef2ed 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js @@ -9,16 +9,16 @@ describe('InvoiceOut negative bases', () => { cy.visit(`/#/invoice-out/negative-bases`); }); - it('should open the posible descriptors', () => { + it.skip('should open the posible descriptors', () => { cy.get(getDescriptors('clientId')).click(); cy.get('.descriptor').should('be.visible'); cy.get('.q-item > .q-item__label').should('include.text', '1101'); 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 333f7e2c4..63e828f55 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutSummary.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutSummary.spec.js @@ -7,17 +7,14 @@ describe('InvoiceOut summary', () => { const firstRowDescriptors = (opt) => `tbody > :nth-child(1) > :nth-child(${opt}) > .q-btn`; - const toCustomerSummary = '[href="#/customer/1101"]'; const toTicketList = '[href="#/ticket/list?table={%22refFk%22:%22T1111111%22}"]'; const selectMenuOption = (opt) => `.q-menu > .q-list > :nth-child(${opt})`; const confirmSend = '.q-btn--unelevated'; beforeEach(() => { - cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/invoice-out/1/summary`); }); - it('open the descriptors', () => { cy.get(firstRowDescriptors(1)).click(); cy.get('.descriptor').should('be.visible'); @@ -27,16 +24,17 @@ describe('InvoiceOut summary', () => { cy.get('.q-item > .q-item__label').should('include.text', '1101'); }); - it('should open the client summary and the ticket list', () => { - cy.get(toCustomerSummary).click(); + it('should open the client summary', () => { + cy.dataCy('invoiceOutDescriptorCustomerCard').click(); cy.get('.descriptor').should('be.visible'); cy.get('.q-item > .q-item__label').should('include.text', '1101'); }); it('should open the ticket list', () => { cy.get(toTicketList).click(); - cy.get('.descriptor').should('be.visible'); - cy.dataCy('vnFilterPanelChip').should('include.text', 'T1111111'); + cy.get('[data-col-field="stateFk"]').each(($el) => { + cy.wrap($el).contains('T1111111'); + }); }); it('should transfer the invoice ', () => { @@ -52,6 +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.get(confirmSend).click(); cy.checkNotification('Notification sent'); }); @@ -60,18 +59,11 @@ 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.get(confirmSend).click(); cy.checkNotification('Notification sent'); }); - it('should delete an invoice ', () => { - cy.typeSearchbar('T2222222{enter}'); - cy.dataCy('descriptor-more-opts').click(); - cy.get(selectMenuOption(4)).click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.checkNotification('InvoiceOut deleted'); - }); - it('should book the invoice', () => { cy.dataCy('descriptor-more-opts').click(); cy.get(selectMenuOption(5)).click(); diff --git a/test/cypress/integration/item/ItemFixedPrice.spec.js b/test/cypress/integration/item/ItemFixedPrice.spec.js index 2cf9c2caf..404e8e365 100644 --- a/test/cypress/integration/item/ItemFixedPrice.spec.js +++ b/test/cypress/integration/item/ItemFixedPrice.spec.js @@ -3,7 +3,6 @@ function goTo(n = 1) { return `.q-virtual-scroll__content > :nth-child(${n})`; } const firstRow = goTo(); -`.q-virtual-scroll__content > :nth-child(2)`; describe('Handle Items FixedPrice', () => { beforeEach(() => { cy.viewport(1280, 720); diff --git a/test/cypress/integration/item/itemBarcodes.spec.js b/test/cypress/integration/item/itemBarcodes.spec.js index 844768d9e..1f6698f9c 100644 --- a/test/cypress/integration/item/itemBarcodes.spec.js +++ b/test/cypress/integration/item/itemBarcodes.spec.js @@ -3,23 +3,22 @@ describe('ItemBarcodes', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit(`/#/item/list`); - cy.typeSearchbar('1{enter}'); + cy.visit(`/#/item/1/barcode`); }); it('should throw an error if the barcode exists', () => { - cy.get('[href="#/item/1/barcode"]').click(); - cy.get('.q-card > .q-btn > .q-btn__content > .q-icon').click(); - cy.dataCy('Code_input').eq(3).type('1111111111'); - cy.dataCy('crudModelDefaultSaveBtn').click(); + newBarcode('1111111111'); cy.checkNotification('Codes can not be repeated'); }); it('should create a new barcode', () => { - cy.get('[href="#/item/1/barcode"]').click(); - cy.get('.q-card > .q-btn > .q-btn__content > .q-icon').click(); - cy.dataCy('Code_input').eq(3).type('1231231231'); - cy.dataCy('crudModelDefaultSaveBtn').click(); + newBarcode('1231231231'); cy.checkNotification('Data saved'); }); + + function newBarcode(text) { + cy.dataCy('addBarcode_input').click(); + cy.dataCy('Code_input').eq(3).should('exist').type(text); + cy.dataCy('crudModelDefaultSaveBtn').click(); + } }); diff --git a/test/cypress/integration/item/itemBotanical.spec.js b/test/cypress/integration/item/itemBotanical.spec.js index 08886d9a8..6105ef179 100644 --- a/test/cypress/integration/item/itemBotanical.spec.js +++ b/test/cypress/integration/item/itemBotanical.spec.js @@ -7,11 +7,9 @@ describe('Item botanical', () => { }); it('should modify the botanical', () => { - cy.dataCy('AddGenusSelectDialog').type('Abies'); - cy.get('.q-menu .q-item').contains('Abies').click(); - cy.dataCy('AddSpeciesSelectDialog').type('dealbata'); - cy.get('.q-menu .q-item').contains('dealbata').click(); - cy.get('.q-btn-group > .q-btn--standard').click(); + cy.selectOption('[data-cy="AddGenusSelectDialog"]', 'Abies'); + cy.selectOption('[data-cy="AddSpeciesSelectDialog"]', 'dealbata'); + cy.saveCard(); cy.checkNotification('Data saved'); }); diff --git a/test/cypress/integration/item/itemList.spec.js b/test/cypress/integration/item/itemList.spec.js index f0c744f21..10e388580 100644 --- a/test/cypress/integration/item/itemList.spec.js +++ b/test/cypress/integration/item/itemList.spec.js @@ -1,6 +1,6 @@ /// <reference types="cypress" /> -describe('Item list', () => { +describe.skip('Item list', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); @@ -16,8 +16,7 @@ describe('Item list', () => { cy.get('.q-virtual-scroll__content > :nth-child(4) > :nth-child(4)').click(); }); - // https://redmine.verdnatura.es/issues/8421 - it.skip('should create an item', () => { + it('should create an item', () => { const data = { Description: { val: `Test item` }, Type: { val: `Crisantemo`, type: 'select' }, diff --git a/test/cypress/integration/order/orderList.spec.js b/test/cypress/integration/order/orderList.spec.js new file mode 100644 index 000000000..34cd2bffc --- /dev/null +++ b/test/cypress/integration/order/orderList.spec.js @@ -0,0 +1,74 @@ +/// <reference types="cypress" /> +describe('OrderList', () => { + const clientCreateSelect = '#formModel [data-cy="Client_select"]'; + const addressCreateSelect = '#formModel [data-cy="Address_select"]'; + const agencyCreateSelect = '#formModel [data-cy="Agency_select"]'; + + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/order/list'); + }); + + it('create order', () => { + cy.get('[data-cy="vnTableCreateBtn"]').click(); + cy.selectOption(clientCreateSelect, 1101); + cy.get(addressCreateSelect).click(); + cy.get( + '.q-menu > div> div.q-item:nth-child(1) >div.q-item__section--avatar > i', + ).should('have.text', 'star'); + cy.dataCy('landedDate').find('input').type('06/01/2001'); + 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'); + cy.get('.q-item > .q-item__label.subtitle').then((text) => { + const id = text.text().trim().split('#')[1]; + cy.get('.q-item > .q-item__label').should('have.text', ` #${id}`); + }); + cy.url().should('include', `/order`); + }); + + it.skip('filter list and create order', () => { + cy.dataCy('Customer ID_input').type('1101{enter}'); + cy.dataCy('vnTableCreateBtn').click(); + cy.dataCy('landedDate').find('input').type('06/01/2001'); + 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'); + cy.get('.q-item > .q-item__label.subtitle').then((text) => { + const id = text.text().trim().split('#')[1]; + cy.get('.q-item > .q-item__label').should('have.text', ` #${id}`); + }); + cy.url().should('include', `/order`); + }); + + it('create order from customer summary', function () { + const clientId = 1101; + cy.dataCy('Customer ID_input').type(`${clientId}{enter}`); + cy.get( + ':nth-child(1) > [data-col-field="clientFk"] > .no-padding > .link', + ).click(); + cy.get( + `[href="#/order/list?createForm={%22clientFk%22:${clientId},%22addressId%22:1}"] > .q-btn__content > .q-icon`, + ).click(); + cy.dataCy('vnTableCreateBtn').click(); + + 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.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'); + cy.get('.q-item > .q-item__label.subtitle').then((text) => { + const id = text.text().trim().split('#')[1]; + cy.get('.q-item > .q-item__label').should('have.text', ` #${id}`); + }); + cy.url().should('include', `/order`); + }); +}); diff --git a/test/cypress/integration/outLogin/login.spec.js b/test/cypress/integration/outLogin/login.spec.js index 2bd5a8c3b..22e30dd8e 100755 --- a/test/cypress/integration/outLogin/login.spec.js +++ b/test/cypress/integration/outLogin/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/outLogin/logout.spec.js index bcdacec78..9f022617d 100644 --- a/test/cypress/integration/outLogin/logout.spec.js +++ b/test/cypress/integration/outLogin/logout.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('Logout', () => { +describe.skip('Logout', () => { beforeEach(() => { cy.login('developer'); cy.visit(`/#/dashboard`); @@ -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/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/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/routeExtendedList.spec.js b/test/cypress/integration/route/routeExtendedList.spec.js index e3505ad60..fb2885f35 100644 --- a/test/cypress/integration/route/routeExtendedList.spec.js +++ b/test/cypress/integration/route/routeExtendedList.spec.js @@ -1,4 +1,4 @@ -describe.skip('Route extended list', () => { +describe('Route extended list', () => { const getSelector = (colField) => `tr:last-child > [data-col-field="${colField}"]`; const selectors = { @@ -8,6 +8,8 @@ describe.skip('Route extended list', () => { date: getSelector('dated'), description: getSelector('description'), served: getSelector('isOk'), + firstRowSelectCheckBox: + 'tbody > tr:first-child > :nth-child(1) .q-checkbox__inner', lastRowSelectCheckBox: 'tbody > tr:last-child > :nth-child(1) .q-checkbox__inner', removeBtn: '[title="Remove"]', resetBtn: '[title="Reset"]', @@ -19,7 +21,7 @@ describe.skip('Route extended list', () => { markServedBtn: '#st-actions > .q-btn-group > :nth-child(3)', searchbar: 'searchbar', firstTicketsRowSelectCheckBox: - '.q-card > :nth-child(2) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(1) > .q-table--col-auto-width > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg > .q-checkbox__svg', + '.q-card .q-table > tbody > :nth-child(1) .q-checkbox', }; const checkboxState = { @@ -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 }, ]; @@ -57,11 +59,11 @@ describe.skip('Route extended list', () => { break; case 'input': cy.get(selector).should('be.visible').click(); - cy.dataCy('null_input').clear().type(`${value}{enter}`); + cy.dataCy('null_input').clear().type(`${value}`); break; case 'date': cy.get(selector).should('be.visible').click(); - cy.dataCy('null_inputDate').clear().type(`${value}{enter}`); + cy.dataCy('null_inputDate').clear().type(`${value}`); break; case 'checkbox': cy.get(selector).should('be.visible').click().click(); @@ -76,15 +78,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 +90,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 }, @@ -126,12 +119,21 @@ describe.skip('Route extended list', () => { }); }); - it('Should clone selected route', () => { - cy.get(selectors.lastRowSelectCheckBox).click(); + it('Should clone selected route and add ticket', () => { + cy.get(selectors.firstRowSelectCheckBox).click(); cy.get(selectors.cloneBtn).click(); - cy.dataCy('route.Starting date_inputDate').type('10-05-2001{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', () => { @@ -142,10 +144,6 @@ describe.skip('Route extended list', () => { 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', () => { @@ -156,10 +154,10 @@ describe.skip('Route extended list', () => { cy.validateContent(selectors.served, checkboxState.check); }); - it('Should delete the selected route', () => { + it('Should delete the selected routes', () => { cy.get(selectors.lastRowSelectCheckBox).click(); - cy.get(selectors.removeBtn).click(); + cy.dataCy(selectors.confirmBtn).click(); cy.checkNotification(dataSaved); @@ -175,18 +173,15 @@ describe.skip('Route extended list', () => { cy.typeSearchbar('{enter}'); - updateFields.forEach(({ selector, value }) => { + updateFields.forEach(({ selector, value, type }) => { + if (type === 'date') { + const [month, day, year] = value.split('/'); + value = `${day}/${month}/${year}`; + } cy.validateContent(selector, 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/shelving/parking/parkingBasicData.spec.js b/test/cypress/integration/shelving/parking/parkingBasicData.spec.js index e28d7eeca..81c158684 100644 --- a/test/cypress/integration/shelving/parking/parkingBasicData.spec.js +++ b/test/cypress/integration/shelving/parking/parkingBasicData.spec.js @@ -6,13 +6,16 @@ describe('ParkingBasicData', () => { beforeEach(() => { cy.login('developer'); cy.visit(`/#/shelving/parking/1/basic-data`); + cy.get('[data-cy="loading-spinner"]', { timeout: 10000 }).should( + 'not.be.visible', + ); }); it('should give an error if the code aldready exists', () => { cy.get(codeInput).eq(0).should('have.value', '700-01').clear(); cy.get(codeInput).eq(0).type('700-02'); cy.saveCard(); - cy.get('.q-notification__message').should('have.text', 'The code already exists'); + cy.checkNotification('The code already exists'); }); it('should edit the code and sector', () => { @@ -24,7 +27,8 @@ describe('ParkingBasicData', () => { cy.dataCy('Picking order_input').clear().type(80230); cy.saveCard(); - cy.get('.q-notification__message').should('have.text', 'Data saved'); + cy.checkNotification('Data saved'); + cy.get(sectorSelect).should('have.value', 'First sector'); cy.get(codeInput).should('have.value', '700-01'); cy.dataCy('Picking order_input').should('have.value', 80230); diff --git a/test/cypress/integration/shelving/parking/parkingList.spec.js b/test/cypress/integration/shelving/parking/parkingList.spec.js index ecee8aab7..7372da164 100644 --- a/test/cypress/integration/shelving/parking/parkingList.spec.js +++ b/test/cypress/integration/shelving/parking/parkingList.spec.js @@ -1,8 +1,8 @@ /// <reference types="cypress" /> describe('ParkingList', () => { const searchbar = '#searchbar input'; - const firstCard = ':nth-child(1) > .q-card > .no-margin > .q-py-none'; - const summaryHeader = '.summaryBody .header'; + const firstCard = ':nth-child(1) > .q-card > .no-margin > .q-py-none'; + const summaryHeader = '.header-link'; beforeEach(() => { cy.viewport(1920, 1080); diff --git a/test/cypress/integration/shelving/shelvingBasicData.spec.js b/test/cypress/integration/shelving/shelvingBasicData.spec.js new file mode 100644 index 000000000..d7b0dc692 --- /dev/null +++ b/test/cypress/integration/shelving/shelvingBasicData.spec.js @@ -0,0 +1,24 @@ +/// <reference types="cypress" /> +describe('ShelvingList', () => { + const parking = + '.q-card > :nth-child(1) > .q-select > .q-field__inner > .q-field__control > .q-field__control-container'; + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/shelving/1/basic-data`); + }); + + it('should give an error if the code aldready exists', () => { + cy.dataCy('Code_input').should('exist').clear().type('AA7'); + cy.saveCard(); + cy.get('.q-notification__message').should('have.text', 'The code already exists'); + }); + it('should edit the data and save', () => { + cy.selectOption(parking, 'P-01-1'); + cy.dataCy('Code_input').clear().type('AA1'); + cy.dataCy('Priority_input').clear().type('10'); + cy.get(':nth-child(2) > .q-checkbox > .q-checkbox__inner').click(); + cy.saveCard(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); +}); diff --git a/test/cypress/integration/shelving/shelvingList.spec.js b/test/cypress/integration/shelving/shelvingList.spec.js new file mode 100644 index 000000000..20b72e419 --- /dev/null +++ b/test/cypress/integration/shelving/shelvingList.spec.js @@ -0,0 +1,41 @@ +/// <reference types="cypress" /> +describe('ShelvingList', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/shelving/list`); + }); + + it('should redirect on clicking a shelving', () => { + cy.typeSearchbar('{enter}'); + cy.dataCy('cardBtn').eq(0).click(); + cy.get('.summaryHeader > .header > .q-icon').click(); + cy.url().should('include', '/shelving/1/summary'); + }); + + it('should redirect from preview to basic-data', () => { + cy.typeSearchbar('{enter}'); + cy.dataCy('cardBtn').eq(0).click(); + cy.get('.summaryHeader > .header > .q-icon').click(); + cy.url().should('include', '/shelving/1/summary'); + }); + + it('should filter and redirect if only one result', () => { + cy.selectOption('[data-cy="Parking_select"]', 'P-02-2'); + cy.dataCy('Parking_select').type('{enter}'); + cy.url().should('match', /\/shelving\/\d+\/summary/); + }); + + it('should create a new shelving', () => { + cy.dataCy('vnTableCreateBtn').click(); + cy.dataCy('code-create-popup').type('Test'); + cy.dataCy('Priority_input').type('10'); + cy.selectOption( + '.grid-create > .q-select > .q-field__inner > .q-field__control > .q-field__control-container', + '100-01', + ); + cy.dataCy('FormModelPopup_save').click(); + cy.checkNotification('Data created'); + cy.url().should('match', /\/shelving\/\d+\/basic-data/); + }); +}); diff --git a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js index 9ea1cff63..b4997fa69 100644 --- a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js +++ b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js @@ -1,5 +1,5 @@ /// <reference types="cypress" /> -describe('Ticket Lack detail', () => { +describe.skip('Ticket Lack detail', () => { beforeEach(() => { cy.login('developer'); cy.intercept('GET', /\/api\/Tickets\/itemLack\/5.*$/, { @@ -37,11 +37,11 @@ describe('Ticket Lack detail', () => { ], }).as('getItemLack'); - cy.visit('/#/ticket/negative/5'); + cy.visit('/#/ticket/negative/5', false); cy.wait('@getItemLack'); }); describe('Table actions', () => { - it.skip('should display only one row in the lack list', () => { + it('should display only one row in the lack list', () => { cy.location('href').should('contain', '#/ticket/negative/5'); cy.get('[data-cy="changeItem"]').should('be.disabled'); @@ -138,8 +138,8 @@ describe('Ticket Lack detail', () => { cy.get('[data-cy="itemProposal"]').click(); cy.wait('@getItemGetSimilar'); }); - describe('Replace item if', () => { - it.only('Quantity is less than available', () => { + 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/ticketDescriptor.spec.js b/test/cypress/integration/ticket/ticketDescriptor.spec.js index 3fc2842d3..0ba2723a2 100644 --- a/test/cypress/integration/ticket/ticketDescriptor.spec.js +++ b/test/cypress/integration/ticket/ticketDescriptor.spec.js @@ -14,8 +14,6 @@ describe('Ticket descriptor', () => { it('should clone the ticket without warehouse', () => { cy.visit('/#/ticket/1/summary'); - cy.intercept('GET', /\/api\/Tickets\/\d/).as('ticket'); - cy.wait('@ticket'); cy.openActionsDescriptor(); cy.contains(listItem, toCloneOpt).click(); cy.clickConfirm(); diff --git a/test/cypress/integration/ticket/ticketExpedition.spec.js b/test/cypress/integration/ticket/ticketExpedition.spec.js index 6d7dc6721..95ec330dc 100644 --- a/test/cypress/integration/ticket/ticketExpedition.spec.js +++ b/test/cypress/integration/ticket/ticketExpedition.spec.js @@ -10,10 +10,8 @@ describe('Ticket expedtion', () => { it('should change the state', () => { cy.visit('#/ticket/1/expedition'); - cy.intercept('GET', /\/api\/Expeditions\/filter/).as('show'); cy.intercept('POST', /\/api\/ExpeditionStates\/addExpeditionState/).as('add'); - cy.wait('@show'); cy.selectRows([1, 2]); cy.dataCy('change-state').click(); diff --git a/test/cypress/integration/ticket/ticketFilter.spec.js b/test/cypress/integration/ticket/ticketFilter.spec.js new file mode 100644 index 000000000..2e5a3f3ce --- /dev/null +++ b/test/cypress/integration/ticket/ticketFilter.spec.js @@ -0,0 +1,15 @@ +/// <reference types="cypress" /> +describe('TicketFilter', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/list'); + }); + + it('use search button', function () { + cy.waitForElement('.q-page'); + cy.get('[data-cy="Customer ID_input"]').type('1105'); + cy.searchBtnFilterPanel(); + cy.location('href').should('contain', '#/ticket/15/summary'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 593021e6e..a3d8fe908 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -1,23 +1,20 @@ /// <reference types="cypress" /> describe('TicketList', () => { - const firstRow = 'tbody > :nth-child(1)'; + const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); - cy.visit('/#/ticket/list'); + cy.visit('/#/ticket/list', false); }); const searchResults = (search) => { - cy.dataCy('vn-searchbar').find('input').focus(); - if (search) cy.dataCy('vn-searchbar').find('input').type(search); + if (search) cy.typeSearchbar().type(search); cy.dataCy('vn-searchbar').find('input').type('{enter}'); - cy.dataCy('ticketListTable').should('exist'); cy.get(firstRow).should('exist'); }; it('should search results', () => { - cy.dataCy('ticketListTable').should('not.exist'); cy.get('.q-field__control').should('exist'); searchResults(); }); @@ -27,7 +24,7 @@ describe('TicketList', () => { cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); }); - cy.get(firstRow).find('.q-btn:first').click(); + cy.get(firstRow).should('be.visible').find('.q-btn:first').click(); cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/); }); @@ -38,7 +35,22 @@ describe('TicketList', () => { cy.get('.summaryBody').should('exist'); }); - it('Client list create new client', () => { + it.skip('filter client and create ticket', () => { + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); + searchResults(); + cy.wait('@ticketSearchbar'); + + 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.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); + cy.dataCy('Address_select').click(); + + cy.getOption().click(); + cy.dataCy('Address_select').should('have.value', 'Bruce Wayne'); + }); + it('Client list create new ticket', () => { cy.dataCy('vnTableCreateBtn').should('exist'); cy.dataCy('vnTableCreateBtn').click(); const data = { @@ -54,7 +66,7 @@ describe('TicketList', () => { cy.url().should('match', /\/ticket\/\d+\/summary/); }); - it('should show the corerct problems', () => { + it('should show the correct problems', () => { cy.intercept('GET', '**/api/Tickets/filter*', (req) => { req.headers['cache-control'] = 'no-cache'; req.headers['pragma'] = 'no-cache'; diff --git a/test/cypress/integration/ticket/ticketSale.spec.js b/test/cypress/integration/ticket/ticketSale.spec.js index b59765ca6..6d84f214c 100644 --- a/test/cypress/integration/ticket/ticketSale.spec.js +++ b/test/cypress/integration/ticket/ticketSale.spec.js @@ -1,20 +1,102 @@ /// <reference types="cypress" /> +const firstRow = 'tbody > :nth-child(1)'; describe('TicketSale', () => { - describe.skip('Free ticket #31', () => { + describe('Ticket #23', () => { + beforeEach(() => { + cy.login('developer'); + 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(); + cy.dataCy('Price_input').type(price); + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + + 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(); + 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}`); + }); + }); + describe('Ticket to add claim #24', () => { + 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.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(); + }); + }); + describe('Free ticket #31', () => { 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,6 +126,7 @@ 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', () => { @@ -58,6 +141,7 @@ describe('TicketSale', () => { cy.dataCy('saveManaBtn').click(); cy.waitForElement('.q-notification__message'); cy.checkNotification('Data saved'); + cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); }); it('adds claim', () => { @@ -65,28 +149,7 @@ describe('TicketSale', () => { 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.checkNotification('Future ticket date not allowed'); }); it('refunds row with warehouse', () => { @@ -105,102 +168,33 @@ describe('TicketSale', () => { cy.checkNotification('The following refund ticket have been created'); }); - it('transfer sale to a new ticket', () => { - cy.visit('/#/ticket/32/sale'); - cy.get('.q-item > .q-item__label').should('have.text', ' #32'); - selectFirstRow(); - cy.dataCy('ticketSaleTransferBtn').click(); - cy.dataCy('ticketTransferPopup').should('exist'); - cy.dataCy('ticketTransferNewTicketBtn').click(); - //check the new ticket has been created succesfully - cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); - }); - it('should redirect to ticket logs', () => { cy.get(firstRow).find('.q-btn:last').click(); cy.url().should('match', /\/ticket\/31\/log/); }); }); - describe.skip('Ticket prepared #23', () => { + describe('Ticket to transfer #32', () => { beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); - cy.visit('/#/ticket/23/sale'); + cy.visit('/#/ticket/32/sale'); }); - - const firstRow = 'tbody > :nth-child(1)'; - - const selectFirstRow = () => { - cy.waitForElement(firstRow); - cy.get(firstRow).find('.q-checkbox__inner').click(); - }; - - it('update price', () => { - const price = Number((Math.random() * 99 + 1).toFixed(2)); - cy.waitForElement(firstRow); - cy.get(':nth-child(10) > .q-btn').click(); - cy.waitForElement('[data-cy="ticketEditManaProxy"]'); - cy.dataCy('ticketEditManaProxy').should('exist'); - cy.waitForElement('[data-cy="Price_input"]'); - cy.dataCy('Price_input').clear(); - cy.dataCy('Price_input').type(price); - cy.dataCy('saveManaBtn').click(); - handleVnConfirm(); - - cy.get(':nth-child(10) > .q-btn > .q-btn__content').should( - 'have.text', - `€${price}`, - ); - }); - it('update dicount', () => { - const discount = Math.floor(Math.random() * 100) + 1; + it('transfer sale to a new ticket', () => { + cy.get('.q-item > .q-item__label').should('have.text', ' #32'); selectFirstRow(); - cy.get(':nth-child(11) > .q-btn').click(); - cy.waitForElement('[data-cy="ticketEditManaProxy"]'); - cy.dataCy('ticketEditManaProxy').should('exist'); - cy.waitForElement('[data-cy="Disc_input"]'); - cy.dataCy('Disc_input').clear(); - cy.dataCy('Disc_input').type(discount); - cy.dataCy('saveManaBtn').click(); - handleVnConfirm(); - - cy.get(':nth-child(11) > .q-btn > .q-btn__content').should( - 'have.text', - `${discount}.00%`, - ); - }); - - it('change concept', () => { - const quantity = Math.floor(Math.random() * 100) + 1; - cy.waitForElement(firstRow); - cy.get(':nth-child(8) > .row').click(); - cy.get( - '.q-menu > [data-v-ca3f07a4=""] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="undefined_input"]', - ) - .type(quantity) - .type('{enter}'); - handleVnConfirm(); - - cy.get(':nth-child(8) >.row').should('contain.text', `${quantity}`); - }); - it('changequantity ', () => { - const quantity = Math.floor(Math.random() * 100) + 1; - cy.waitForElement(firstRow); - cy.dataCy('ticketSaleQuantityInput').clear(); - cy.dataCy('ticketSaleQuantityInput').type(quantity).trigger('tab'); - cy.get('.q-page > :nth-child(6)').click(); - - handleVnConfirm(); - - cy.get('[data-cy="ticketSaleQuantityInput"]') - .find('[data-cy="undefined_input"]') - .should('have.value', `${quantity}`); + cy.dataCy('ticketSaleTransferBtn').click(); + cy.dataCy('ticketTransferPopup').should('exist'); + cy.dataCy('ticketTransferNewTicketBtn').click(); + cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); }); }); }); - +function selectFirstRow() { + cy.waitForElement(firstRow); + cy.get(firstRow).find('.q-checkbox__inner').click(); +} function handleVnConfirm() { - cy.get('[data-cy="VnConfirm_confirm"] > .q-btn__content > .block').click(); + cy.get('[data-cy="VnConfirm_confirm"]').click(); cy.waitForElement('.q-notification__message'); cy.get('.q-notification__message').should('be.visible'); diff --git a/test/cypress/integration/vnComponent/UserPanel.spec.js b/test/cypress/integration/vnComponent/UserPanel.spec.js index e83d07954..25724e873 100644 --- a/test/cypress/integration/vnComponent/UserPanel.spec.js +++ b/test/cypress/integration/vnComponent/UserPanel.spec.js @@ -18,7 +18,7 @@ describe('UserPanel', () => { cy.get(userWarehouse).should('have.value', 'VNL').click(); // Actualizo la opción - getOption(3); + cy.getOption(3); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); @@ -26,7 +26,7 @@ describe('UserPanel', () => { //Restauro el valor cy.get(userWarehouse).click(); - getOption(2); + cy.getOption(2); }); it('should notify when update user company', () => { const userCompany = @@ -39,7 +39,7 @@ describe('UserPanel', () => { cy.get(userCompany).should('have.value', 'Warehouse One').click(); //Actualizo la opción - getOption(2); + cy.getOption(2); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); @@ -47,12 +47,6 @@ describe('UserPanel', () => { //Restauro el valor cy.get(userCompany).click(); - getOption(1); + cy.getOption(1); }); }); - -function getOption(index) { - cy.waitForElement('[role="listbox"]'); - const option = `[role="listbox"] .q-item:nth-child(${index})`; - cy.get(option).click(); -} diff --git a/test/cypress/integration/vnComponent/VnAccountNumber.spec.js b/test/cypress/integration/vnComponent/VnAccountNumber.spec.js index 63ab646fe..053902f35 100644 --- a/test/cypress/integration/vnComponent/VnAccountNumber.spec.js +++ b/test/cypress/integration/vnComponent/VnAccountNumber.spec.js @@ -1,35 +1,37 @@ -describe('VnInput Component', () => { +describe('VnAccountNumber', () => { + const accountInput = 'input[data-cy="supplierFiscalDataAccount_input"]'; beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); cy.visit('/#/supplier/1/fiscal-data'); }); - it('should replace character at cursor position in insert mode', () => { - // Simula escribir en el input - cy.dataCy('supplierFiscalDataAccount').clear(); - cy.dataCy('supplierFiscalDataAccount').type('4100000001'); - // Coloca el cursor en la posición 0 - cy.dataCy('supplierFiscalDataAccount').type('{movetostart}'); - // Escribe un número y verifica que se reemplace correctamente - cy.dataCy('supplierFiscalDataAccount').type('999'); - cy.dataCy('supplierFiscalDataAccount').should('have.value', '9990000001'); + 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 replace character at cursor position in insert mode', () => { - // Simula escribir en el input - cy.dataCy('supplierFiscalDataAccount').clear(); - cy.dataCy('supplierFiscalDataAccount').type('4100000001'); - // Coloca el cursor en la posición 0 - cy.dataCy('supplierFiscalDataAccount').type('{movetostart}'); - // Escribe un número y verifica que se reemplace correctamente en la posicion incial - cy.dataCy('supplierFiscalDataAccount').type('999'); - cy.dataCy('supplierFiscalDataAccount').should('have.value', '9990000001'); - }); - - it('should respect maxlength prop', () => { - cy.dataCy('supplierFiscalDataAccount').clear(); - cy.dataCy('supplierFiscalDataAccount').type('123456789012345'); - cy.dataCy('supplierFiscalDataAccount').should('have.value', '1234567890'); // asumiendo que maxlength es 10 + 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/VnLocation.spec.js b/test/cypress/integration/vnComponent/VnLocation.spec.js index 986cbcaaf..ee49d6065 100644 --- a/test/cypress/integration/vnComponent/VnLocation.spec.js +++ b/test/cypress/integration/vnComponent/VnLocation.spec.js @@ -88,7 +88,7 @@ describe('VnLocation', () => { cy.get( firstOption.concat(' > .q-item__section > .q-item__label--caption'), ).should('have.text', postCodeLabel); - cy.get(firstOption).click(); + cy.getOption(); cy.get('.q-btn-group > .q-btn--standard > .q-btn__content > .q-icon').click(); cy.reload(); cy.waitForElement('.q-form'); diff --git a/test/cypress/integration/vnComponent/VnLog.spec.js b/test/cypress/integration/vnComponent/VnLog.spec.js index 80b9d07df..e857457a7 100644 --- a/test/cypress/integration/vnComponent/VnLog.spec.js +++ b/test/cypress/integration/vnComponent/VnLog.spec.js @@ -22,4 +22,10 @@ describe('VnLog', () => { cy.get('.q-page').click(); cy.validateContent(chips[0], 'Claim'); }); + + it('should show claimDescriptor', () => { + cy.dataCy('iconLaunch-claimFk').first().click(); + cy.dataCy('cardDescriptor_subtitle').contains('1'); + cy.dataCy('iconLaunch-claimFk').first().click(); + }); }); diff --git a/test/cypress/integration/vnComponent/VnSearchBar.spec.js b/test/cypress/integration/vnComponent/VnSearchBar.spec.js index 11d9bbe6a..8fed23643 100644 --- a/test/cypress/integration/vnComponent/VnSearchBar.spec.js +++ b/test/cypress/integration/vnComponent/VnSearchBar.spec.js @@ -27,7 +27,7 @@ describe('VnSearchBar', () => { const searchAndCheck = (searchTerm, expectedText) => { cy.clearSearchbar(); cy.typeSearchbar(`${searchTerm}{enter}`); - cy.get(idGap).should('have.text', expectedText); + cy.get(idGap).should('include.text', expectedText); }; const checkTableLength = (expectedLength) => { diff --git a/test/cypress/integration/wagon/wagonCreate.spec.js b/test/cypress/integration/wagon/wagonCreate.spec.js index 4e78e57de..88855fdf9 100644 --- a/test/cypress/integration/wagon/wagonCreate.spec.js +++ b/test/cypress/integration/wagon/wagonCreate.spec.js @@ -16,8 +16,8 @@ describe('WagonCreate', () => { cy.get( '.grid-create > [label="Volume"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="Volume_input"]', ).type('100'); - cy.dataCy('Type_select').type('{downarrow}{enter}'); - + 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/workerCreate.spec.js b/test/cypress/integration/worker/workerCreate.spec.js index 6349f04a0..71fd6b347 100644 --- a/test/cypress/integration/worker/workerCreate.spec.js +++ b/test/cypress/integration/worker/workerCreate.spec.js @@ -1,4 +1,4 @@ -describe.skip('WorkerCreate', () => { +describe('WorkerCreate', () => { const externalRadio = '.q-radio:nth-child(2)'; const developerBossId = 120; const payMethodCross = 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/worker/workerNotes.spec.js b/test/cypress/integration/worker/workerNotes.spec.js new file mode 100644 index 000000000..661314ac9 --- /dev/null +++ b/test/cypress/integration/worker/workerNotes.spec.js @@ -0,0 +1,13 @@ +/// <reference types="cypress" /> +describe('WorkerNotes', () => { + const userId = 1106; + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit(`/#/worker/${userId}/notes`); + }); + + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/worker/workerNotificationsManager.spec.js b/test/cypress/integration/worker/workerNotificationsManager.spec.js index 0907cc4ad..ad48d8a6c 100644 --- a/test/cypress/integration/worker/workerNotificationsManager.spec.js +++ b/test/cypress/integration/worker/workerNotificationsManager.spec.js @@ -22,7 +22,7 @@ describe('WorkerNotificationsManager', () => { ); }); - it.skip('should active a notification that is yours', () => { + it('should active a notification that is yours', () => { cy.login('developer'); cy.visit(`/#/worker/${developerId}/notifications`); cy.waitForElement(activeList); 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/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..c50b2c943 100644 --- a/test/cypress/integration/worker/workerSummary.spec.js +++ b/test/cypress/integration/worker/workerSummary.spec.js @@ -1,4 +1,6 @@ describe('WorkerSummary', () => { + const departmentDescriptor = ':nth-child(1) > :nth-child(3) > .value > .link'; + const roleDescriptor = ':nth-child(3) > :nth-child(4) > .value > .link'; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -10,7 +12,17 @@ describe('WorkerSummary', () => { cy.get('.summaryHeader > div').should('have.text', '19 - salesboss salesboss'); cy.get(':nth-child(1) > :nth-child(2) > .value > span').should( 'have.text', - 'salesBossNick' + 'salesBossNick', ); }); + + it('should try descriptors', () => { + cy.waitForElement('.summaryHeader'); + cy.get(departmentDescriptor).click(); + cy.get('.descriptor').should('be.visible'); + cy.get('.q-item > .q-item__label').should('include.text', '43'); + cy.get(roleDescriptor).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/zoneBasicData.spec.js b/test/cypress/integration/zone/zoneBasicData.spec.js index 6db39b072..2d255d959 100644 --- a/test/cypress/integration/zone/zoneBasicData.spec.js +++ b/test/cypress/integration/zone/zoneBasicData.spec.js @@ -1,5 +1,5 @@ describe('ZoneBasicData', () => { - const priceBasicData = '[data-cy="Price_input"]'; + const priceBasicData = '[data-cy="ZoneBasicDataPrice"]'; const saveBtn = '.q-btn-group > .q-btn--standard'; beforeEach(() => { @@ -8,20 +8,13 @@ describe('ZoneBasicData', () => { cy.visit('/#/zone/4/basic-data'); }); - it('should throw an error if the name is empty', () => { - cy.get('[data-cy="zone-basic-data-name"] input').type('{selectall}{backspace}'); - - cy.get(saveBtn).click(); - cy.checkNotification("can't be blank"); - }); - it('should throw an error if the price is empty', () => { cy.get(priceBasicData).clear(); cy.get(saveBtn).click(); - cy.checkNotification('cannot be blank'); + cy.get('.q-field__messages > div').should('have.text', 'Field required'); }); - it("should edit the basicData's zone", () => { + it("should edit the basicData's zone name", () => { cy.get('.q-card > :nth-child(1)').type(' modified'); cy.get(saveBtn).click(); cy.checkNotification('Data saved'); diff --git a/test/cypress/integration/zone/zoneCalendar.spec.js b/test/cypress/integration/zone/zoneCalendar.spec.js new file mode 100644 index 000000000..68b85d1d2 --- /dev/null +++ b/test/cypress/integration/zone/zoneCalendar.spec.js @@ -0,0 +1,50 @@ +describe('ZoneCalendar', () => { + const addEventBtn = '.q-page-sticky > div > .q-btn'; + const submitBtn = '.q-mt-lg > .q-btn--standard'; + const deleteBtn = 'ZoneEventsPanelDeleteBtn'; + + beforeEach(() => { + cy.login('developer'); + 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('01/01/2001'); + cy.get(submitBtn).click(); + cy.dataCy(deleteBtn).click(); + cy.dataCy('VnConfirm_confirm').click(); + }); + + it('should include an indefinitely event for monday and tuesday', () => { + cy.get(addEventBtn).click(); + 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.dataCy(deleteBtn).click(); + cy.dataCy('VnConfirm_confirm').click(); + }); + + it('should include a range of dates event', () => { + cy.get(addEventBtn).click(); + cy.dataCy('ZoneEventInclusionRangeRadio').click(); + cy.get('.flex > .q-gutter-x-sm > :nth-child(1)').click(); + cy.dataCy('From_inputDate').type('01/01/2001'); + cy.dataCy('To_inputDate').type('31/01/2001'); + cy.get(submitBtn).click(); + cy.dataCy(deleteBtn).click(); + cy.dataCy('VnConfirm_confirm').click(); + }); + + it('should exclude an event', () => { + cy.get('.q-mb-sm > .q-radio__inner').click(); + cy.get('.q-current-day > .q-calendar-month__day--label__wrapper').click(); + cy.get('.q-mt-lg > .q-btn--standard').click(); + cy.get( + '.q-current-day > .q-calendar-month__day--content > [data-cy="ZoneCalendarDay"]', + ).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 0f630db5d..fadf5b07f 100644 --- a/test/cypress/integration/zone/zoneCreate.spec.js +++ b/test/cypress/integration/zone/zoneCreate.spec.js @@ -9,7 +9,6 @@ describe('ZoneCreate', () => { }; beforeEach(() => { - cy.viewport(1280, 720); cy.login('developer'); cy.visit('/#/zone/list'); cy.get('.q-page-sticky > div > .q-btn').click(); diff --git a/test/cypress/integration/zone/zoneDeliveryDays.spec.js b/test/cypress/integration/zone/zoneDeliveryDays.spec.js index 1e1fc8ff5..a89def12d 100644 --- a/test/cypress/integration/zone/zoneDeliveryDays.spec.js +++ b/test/cypress/integration/zone/zoneDeliveryDays.spec.js @@ -1,15 +1,59 @@ describe('ZoneDeliveryDays', () => { + const postcode = '46680'; + const agency = 'Gotham247Expensive'; + const submitForm = '.q-form > .q-btn > .q-btn__content'; beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); cy.visit(`/#/zone/delivery-days`); }); - it('should query for the day', () => { + it('should return no data when querying without params', () => { cy.get('.q-form > .q-btn > .q-btn__content').click(); cy.get('.q-notification__message').should( 'have.text', - 'No service for the specified zone' + 'No service for the specified zone', ); }); + + it('should query for delivery', () => { + cy.intercept('GET', /\/api\/Zones\/getEvents/, (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('events'); + + cy.dataCy('ZoneDeliveryDaysPostcodeSelect').type(postcode); + cy.get('.q-menu .q-item').contains(postcode).click(); + cy.get('.q-menu').then(($menu) => { + if ($menu.is(':visible')) { + cy.get('[data-cy="ZoneDeliveryDaysPostcodeSelect"]') + .as('focusedElement') + .focus(); + cy.get('@focusedElement').blur(); + } + }); + + cy.dataCy('ZoneDeliveryDaysAgencySelect').type(agency); + cy.get('.q-menu .q-item').contains(agency).click(); + cy.get('.q-menu').then(($menu) => { + if ($menu.is(':visible')) { + cy.get('[data-cy="ZoneDeliveryDaysAgencySelect"]') + .as('focusedElement') + .focus(); + cy.get('@focusedElement').blur(); + } + }); + + cy.get(submitForm).click(); + cy.wait('@events').then((interception) => { + cy.log('interception: ', interception); + const data = interception.response.body.events; + expect(data.length).to.be.greaterThan(0); + }); + }); }); diff --git a/test/cypress/integration/zone/zoneList.spec.js b/test/cypress/integration/zone/zoneList.spec.js index 68e924635..c84b1b017 100644 --- a/test/cypress/integration/zone/zoneList.spec.js +++ b/test/cypress/integration/zone/zoneList.spec.js @@ -1,21 +1,25 @@ 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('zoneFilterPanelNameInput').type('{downArrow}{enter}'); + cy.typeSearchbar('{enter}'); }); it('should open the zone summary', () => { - 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.get(firstSummaryIcon).click(); + cy.get('.header > .q-icon').click(); + cy.url().should('include', 'zone/1/summary'); + }); + + it('should clone the zone', () => { + cy.get('.router-link-active > .q-icon').click(); + cy.dataCy('tableAction-1').eq(1).click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.url().should('not.include', 'zone/2/'); + cy.url().should('match', /zone\/\d+\/basic-data/); }); }); diff --git a/test/cypress/integration/zone/zoneLocations.spec.js b/test/cypress/integration/zone/zoneLocations.spec.js new file mode 100644 index 000000000..dabd3eb2b --- /dev/null +++ b/test/cypress/integration/zone/zoneLocations.spec.js @@ -0,0 +1,59 @@ +describe('ZoneLocations', () => { + const cp = 46680; + const searchIcon = '.router-link-active > .q-icon'; + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/zone/2/location`); + }); + + 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 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/zoneSummary.spec.js b/test/cypress/integration/zone/zoneSummary.spec.js new file mode 100644 index 000000000..fa9c5353c --- /dev/null +++ b/test/cypress/integration/zone/zoneSummary.spec.js @@ -0,0 +1,22 @@ +describe('ZoneSummary', () => { + const agency = 'inhouse pickup'; + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('/#/zone/2/summary'); + }); + + it('should clone the zone, then delete it', () => { + cy.dataCy('descriptor-more-opts').click(); + cy.dataCy('Clone_button').click(); + 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'); + cy.get('.q-page').should('exist'); + cy.dataCy('descriptor-more-opts').click(); + cy.dataCy('Delete_button').click(); + cy.dataCy('VnConfirm_confirm').click(); + }); +}); diff --git a/test/cypress/integration/zone/zoneUpcomingDeliveries.spec.js b/test/cypress/integration/zone/zoneUpcomingDeliveries.spec.js index 28e2222d4..576b2ea70 100644 --- a/test/cypress/integration/zone/zoneUpcomingDeliveries.spec.js +++ b/test/cypress/integration/zone/zoneUpcomingDeliveries.spec.js @@ -1,9 +1,17 @@ describe('ZoneUpcomingDeliveries', () => { + const tableFields = (opt) => + `:nth-child(1) > .q-table__container > .q-table__middle > .q-table > thead > tr > :nth-child(${opt})`; + beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); cy.visit(`/#/zone/upcoming-deliveries`); }); - it('should show the page', () => {}); + it('should show the page', () => { + cy.get('.q-card').should('be.visible'); + cy.get(tableFields(1)).should('be.visible').should('have.text', 'Province'); + cy.get(tableFields(2)).should('be.visible').should('have.text', 'Closing'); + cy.get(tableFields(3)).should('be.visible').should('have.text', 'Id'); + }); }); diff --git a/test/cypress/integration/zone/zoneWarehouse.spec.js b/test/cypress/integration/zone/zoneWarehouse.spec.js index 0f646f33a..d7a9854bb 100644 --- a/test/cypress/integration/zone/zoneWarehouse.spec.js +++ b/test/cypress/integration/zone/zoneWarehouse.spec.js @@ -1,32 +1,29 @@ describe('ZoneWarehouse', () => { const data = { - Warehouse: { val: 'Warehouse One', type: 'select' }, + Warehouse: { val: 'Warehouse Two', type: 'select' }, }; - const dataError = 'The introduced warehouse already exists'; const saveBtn = '.q-btn--standard > .q-btn__content > .block'; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit(`/#/zone/2/warehouses`); + cy.visit(`/#/zone/1/warehouses`); }); it('should throw an error if the warehouse chosen is already put in the zone', () => { cy.addBtnClick(); - cy.dataCy('Warehouse_select').type('Warehouse Two{enter}'); + cy.dataCy('Warehouse_select').type('Warehouse One{enter}'); cy.get(saveBtn).click(); 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(); cy.get('.q-mt-lg > .q-btn--standard').click(); cy.get('tbody > :nth-child(2) > :nth-child(2) > .q-icon').click(); cy.get('[title="Confirm"]').click(); - - cy.reload(); }); }); diff --git a/test/cypress/run.sh b/test/cypress/run.sh new file mode 100755 index 000000000..0f8c59902 --- /dev/null +++ b/test/cypress/run.sh @@ -0,0 +1,47 @@ +#!/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 + +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/summary.sh b/test/cypress/summary.sh new file mode 100644 index 000000000..4bca3255d --- /dev/null +++ b/test/cypress/summary.sh @@ -0,0 +1,3 @@ +pnpm exec junit-merge --dir junit --out junit/junit-final.xml +pnpm exec xunit-viewer -r junit/junit-final.xml -o junit/report.html +xdg-open junit/report.html diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 096a29dc1..de25959b2 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -27,7 +27,9 @@ // DO NOT REMOVE // Imports Quasar Cypress AE predefined commands // import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress'; +import moment from 'moment'; import waitUntil from './waitUntil'; + Cypress.Commands.add('waitUntil', { prevSubject: 'optional' }, waitUntil); Cypress.Commands.add('resetDB', () => { @@ -57,14 +59,15 @@ Cypress.Commands.add('login', (user = 'developer') => { }); }); -Cypress.Commands.overwrite('visit', (originalFn, url, options) => { +Cypress.Commands.overwrite('visit', (originalFn, url, options, waitRequest = true) => { originalFn(url, options); cy.waitUntil(() => cy.document().then((doc) => doc.readyState === 'complete')); cy.waitUntil(() => cy.get('main').should('exist')); + if (waitRequest) cy.waitSpinner(); }); -Cypress.Commands.add('waitForElement', (element, timeout = 10000) => { - cy.get(element, { timeout }).should('be.visible').and('not.be.disabled'); +Cypress.Commands.add('waitForElement', (element) => { + cy.get(element).should('be.visible').and('not.be.disabled'); }); Cypress.Commands.add('getValue', (selector) => { @@ -92,6 +95,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); @@ -109,11 +120,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); @@ -128,6 +141,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) { @@ -148,14 +162,20 @@ Cypress.Commands.add('countSelectOptions', (selector, option) => { cy.get('.q-menu .q-item').should('have.length', option); }); -Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { +Cypress.Commands.add('fillInForm', (obj, opts = {}) => { + cy.waitSpinner(); + const { form = '.q-form > .q-card', attr = 'aria-label' } = opts; cy.waitForElement(form); cy.get(`${form} input`).each(([el]) => { cy.wrap(el) - .invoke('attr', 'aria-label') - .then((ariaLabel) => { - const field = obj[ariaLabel]; + .invoke('attr', attr) + .then((key) => { + const field = obj[key]; if (!field) return; + if (typeof field == 'string') + return cy + .wrap(el) + .type(`{selectall}{backspace}${field}`, { delay: 0 }); const { type, val } = field; switch (type) { @@ -163,7 +183,9 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { cy.selectOption(el, val); break; case 'date': - cy.get(el).type(val.split('-').join('')); + cy.get(el).type( + `{selectall}{backspace}${val.split('-').join('')}`, + ); break; case 'time': cy.get(el).click(); @@ -172,13 +194,47 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { cy.get('.q-time .q-time__link').contains(val.x).click(); break; default: - cy.wrap(el).type(val); + cy.wrap(el).type(`{selectall}${val}`, { delay: 0 }); break; } }); }); }); +Cypress.Commands.add('validateForm', (obj, opts = {}) => { + const { form = '.q-form > .q-card', attr = 'data-cy' } = opts; + cy.waitForElement(form); + cy.get(`${form} input`).each(([el]) => { + cy.wrap(el) + .invoke('attr', attr) + .then((key) => { + const field = obj[key]; + if (!field) return; + + const { type, val } = field; + cy.get(el) + .invoke('val') + .then((elVal) => { + if (typeof field == 'string') + expect(elVal.toLowerCase()).to.equal(field.toLowerCase()); + else + switch (type) { + case 'date': + const elDate = moment(elVal, 'DD-MM-YYYY'); + const mockDate = moment(val, 'DD-MM-YYYY'); + expect(elDate.isSame(mockDate, 'day')).to.be.true; + break; + default: + expect(elVal.toLowerCase()).to.equal( + val.toLowerCase(), + ); + break; + } + }); + }); + }); +}); + Cypress.Commands.add('checkOption', (selector) => { cy.get(selector).find('.q-checkbox__inner').click(); }); @@ -195,14 +251,17 @@ Cypress.Commands.add('saveCard', () => { Cypress.Commands.add('resetCard', () => { cy.get('[title="Reset"]').click(); }); + Cypress.Commands.add('removeCard', () => { cy.get('[title="Remove"]').click(); }); + Cypress.Commands.add('addCard', () => { cy.waitForElement('tbody'); cy.waitForElement('.q-page-sticky > div > .q-btn'); cy.get('.q-page-sticky > div > .q-btn').click(); }); + Cypress.Commands.add('clickConfirm', () => { cy.waitForElement('.q-dialog__inner > .q-card'); cy.get('.q-card__actions > .q-btn--unelevated > .q-btn__content > .block').click(); @@ -283,6 +342,7 @@ Cypress.Commands.add('removeRow', (rowIndex) => { }); }); }); + Cypress.Commands.add('openListSummary', (row) => { cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click(); }); @@ -310,20 +370,8 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => { cy.get(selector).should('have.text', expectedValue); }); -Cypress.Commands.add('openActionDescriptor', (opt) => { - cy.openActionsDescriptor(); - const listItem = '[role="menu"] .q-list .q-item'; - cy.contains(listItem, opt).click(); -}); - Cypress.Commands.add('openActionsDescriptor', () => { - cy.get('[data-cy="descriptor-more-opts"]').click(); -}); - -Cypress.Commands.add('clickButtonDescriptor', (id) => { - cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`) - .invoke('removeAttr', 'target') - .click(); + cy.get('[data-cy="cardDescriptor"] [data-cy="descriptor-more-opts"]').click(); }); Cypress.Commands.add('openUserPanel', () => { @@ -331,7 +379,7 @@ Cypress.Commands.add('openUserPanel', () => { }); Cypress.Commands.add('checkNotification', (text) => { - cy.get('.q-notification', { timeout: 10000 }) + cy.get('.q-notification') .should('be.visible') .should('have.length.greaterThan', 0) .should(($elements) => { @@ -341,7 +389,6 @@ Cypress.Commands.add('checkNotification', (text) => { expect(found).to.be.true; }); }); - Cypress.Commands.add('openActions', (row) => { cy.get('tbody > tr').eq(row).find('.actions > .q-btn').click(); }); @@ -384,6 +431,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'); @@ -393,6 +441,156 @@ Cypress.Commands.add('clickButtonWithIcon', (iconClass) => { cy.wrap($btn).click(); }); }); + Cypress.Commands.add('clickButtonWithText', (buttonText) => { cy.get('.q-btn').contains(buttonText).click(); }); + +Cypress.Commands.add('getOption', (index = 1) => { + cy.waitForElement('[role="listbox"]'); + cy.get(`[role="listbox"] .q-item:nth-child(${index})`).click(); +}); + +Cypress.Commands.add('searchBtnFilterPanel', () => { + cy.get( + '.q-scrollarea__content > .q-btn--standard > .q-btn__content > .q-icon', + ).click(); +}); + +Cypress.Commands.add('waitRequest', (alias, cb) => { + cy.wait(alias).then(cb); +}); + +Cypress.Commands.add('validateDescriptor', (toCheck = {}) => { + const { title, description, subtitle, listbox = {}, popup = false } = toCheck; + + const popupSelector = popup ? '[role="menu"] ' : ''; + + if (title) cy.get(`${popupSelector}[data-cy="cardDescriptor_title"]`).contains(title); + if (description) + cy.get(`${popupSelector}[data-cy="cardDescriptor_description"]`).contains( + description, + ); + if (subtitle) + cy.get(`${popupSelector}[data-cy="cardDescriptor_subtitle"]`).contains(subtitle); + + for (const index in listbox) + cy.get(`${popupSelector}[data-cy="cardDescriptor_listbox"] > *`) + .eq(index) + .should('contain.text', listbox[index]); +}); + +Cypress.Commands.add('validateVnTableRows', (opts = {}) => { + let { cols = [], rows = [] } = opts; + if (!Array.isArray(cols)) cols = [cols]; + const rowSelector = rows.length + ? rows.map((row) => `> :nth-child(${row})`).join(', ') + : '> *'; + cy.get(`[data-cy="vnTable"] .q-virtual-scroll__content`).within(() => { + cy.get(`${rowSelector}`).each(($el) => { + for (const { name, type = 'string', val, operation = 'equal' } of cols) { + cy.wrap($el) + .find(`[data-cy="vnTableCell_${name}"]`) + .invoke('text') + .then((text) => { + if (type === 'string') + expect(text.trim().toLowerCase()).to[operation]( + val.toLowerCase(), + ); + if (type === 'number') cy.checkNumber(text, val, operation); + if (type === 'date') cy.checkDate(text, val, operation); + }); + } + }); + }); +}); + +Cypress.Commands.add('checkDate', (rawDate, expectedVal, operation) => { + const date = moment(rawDate.trim(), 'MM/DD/YYYY'); + const compareDate = moment(expectedVal, 'DD/MM/YYYY'); + + switch (operation) { + case 'equal': + expect(text.trim()).to.equal(compareDate); + break; + case 'before': + expect(date.isBefore(compareDate)).to.be.true; + break; + case 'after': + expect(date.isAfter(compareDate)).to.be.true; + } +}); + +Cypress.Commands.add('selectDescriptorOption', (opt = 1) => { + const listItem = '[data-cy="descriptor-more-opts_list"]'; + cy.get('body').then(($body) => { + if (!$body.find(listItem).length) cy.openActionsDescriptor(); + }); + + cy.waitForElement(listItem); + cy.get(`${listItem} > :not(template):nth-of-type(${opt})`).click(); +}); + +Cypress.Commands.add('validateCheckbox', (selector, expectedVal = 'true') => { + cy.get(selector).should('have.attr', 'aria-checked', expectedVal.toString()); +}); + +Cypress.Commands.add('validateDownload', (trigger, opts = {}) => { + const { + url = /api\/dms\/\d+\/downloadFile\?access_token=.+/, + types = ['text/plain', 'image/jpeg'], + alias = 'download', + } = opts; + cy.intercept('GET', url).as(alias); + trigger().then(() => { + cy.wait(`@${alias}`).then(({ response }) => { + expect(response.statusCode).to.equal(200); + const isValidType = types.some((type) => + response.headers['content-type'].includes(type), + ); + expect(isValidType).to.be.true; + }); + }); +}); + +Cypress.Commands.add('validatePdfDownload', (match, trigger) => { + cy.window().then((win) => { + cy.stub(win, 'open') + .callsFake(() => null) + .as('pdf'); + }); + trigger(); + cy.get('@pdf') + .should('be.calledOnce') + .then((stub) => { + const [url] = stub.getCall(0).args; + expect(url).to.match(match); + cy.request(url).then((response) => + expect(response.headers['content-type']).to.include('application/pdf'), + ); + }); +}); + +Cypress.Commands.add('clicDescriptorAction', (index = 1) => { + cy.get(`[data-cy="descriptor_actions"] .q-btn:nth-of-type(${index})`).click(); +}); + +Cypress.Commands.add('checkQueryParams', (expectedParams = {}) => { + cy.url().then((url) => { + const urlParams = new URLSearchParams(url.split('?')[1]); + + for (const key in expectedParams) { + const expected = expectedParams[key]; + const param = JSON.parse(decodeURIComponent(urlParams.get(key))); + + if (typeof expected === 'object') { + const { subkey, val } = expected; + expect(param[subkey]).to.equal(val); + } else expect(param).to.equal(expected); + } + }); +}); + +Cypress.Commands.add('waitTableScrollLoad', () => + cy.waitForElement('[data-q-vs-anchor]'), +); diff --git a/test/cypress/support/index.js b/test/cypress/support/index.js index 075e0c8eb..b0f0fb3b1 100644 --- a/test/cypress/support/index.js +++ b/test/cypress/support/index.js @@ -40,4 +40,35 @@ 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; + + function checkApi() { + return cy + .request({ + url, + failOnStatusCode: false, + }) + .then((response) => { + if (response.status !== 200 && retries < maxRetries) { + retries++; + cy.wait(delay); + return checkApi(); + } + expect(response.status).to.eq(200); + }); + } + + return checkApi(); +}; + +before(() => { + waitForApiReady('/api/Applications/status'); +}); + export { randomString, randomNumber, randomizeValue };