HOTFIX: #6943 CustomerList form salesPersons options #790

Closed
jsegarra wants to merge 84 commits from hotfix_newCustomer_SalesPerson into master
322 changed files with 6160 additions and 6575 deletions
Showing only changes of commit 1a6fcea160 - Show all commits

View File

@ -1,3 +1,189 @@
# Version 24.48 - 2024-11-25
### Added 🆕
- chore: correct checkNotification (fix_customer_issues) by:alexm
- chore: perf (warmFix_order_equalSalix) by:alexm
- chore: refs #6818 add spaces by:jorgep
- chore: refs #6818 drop useless code & comment by:jorgep
- chore: refs #7273 sticky add btn & refactor by:jorgep
- chore: refs #7524 fix test by:jorgep
- chore: refs #8039 not required by:alexm
- chore: refs #8078 fiz tests by:jorgep
- chore: refs #8078 rollback ref by:jorgep
- chore: remove console.log (warmFix_invoiceOut_Global) by:alexm
- chore: typo (fix_itemType-redirection) by:alexm
- feat: #6943 use openURL quasar by:Javier Segarra
- feat: #7782 add cypress report by:Javier Segarra
- feat: #7782 cypress.config watchForFileChanges by:Javier Segarra
- feat: #7782 npm run resetDatabase by:Javier Segarra
- feat: #7782 waitUntil domContentLoad by:Javier Segarra
- feat: added composable to confirm orders by:Jon
- feat: add /reports in gitignore (warmFix_reports_in_gitignore) by:alexm
- feat: apply changes for customerModule by:Javier Segarra
- feat: disabled buttons by:Javier Segarra
- feat: move buttons to DescriptorMenu by:Javier Segarra
- feat: refs #6818 add icon by:jorgep
- feat: refs #6818 fetch url & default channel by:jorgep
- feat: refs #6818 saysimple integration by:jorgep
- feat: refs #6839 module searching (6839-addSearchMenu) by:jorgep
- feat: refs #6839 normalize search by:jorgep
- feat: refs #6919 sync entry data by:jorgep
- feat: refs #7006 itemType basic data new inputs by:guillermo
- feat: refs #7006 itemTypeLog added by:guillermo
- feat: refs #7193 modified parking to use the scope and corrected small errors by:Jon
- feat: refs #7206 added inactive label and corrected minor errors by:Jon
- feat: refs #7308 #7308 remove warnings related to useSession by:Javier Segarra
- feat: refs #7349 usa back con permisos by:jgallego
- feat: refs #7524 add front test by:jorgep
- feat: refs #7874 improve vn-notes ui by:jorgep
- feat: refs #7970 notify changes by:Jon
- feat(): refs #8039 canceledError not notify by:alexm
- feat: refs #8039 notify error unify by:alexm
- feat: refs #8039 show duplicate request in local by:alexm
- feat: refs #8078 add shortcut multi selection by:jorgep
- feat: refs #8078 add tests by:jorgep
- feat: refs#8087 Redadas en travel by:Carlos Andrés
- feat: refs #8087 Traspasar redadas a travels by:Carlos Andrés
- feat: remove comments by:Javier Segarra
- feat(Supplier): add companySize by:alexm
- feat: use composable to unify logic by:Javier Segarra
- feat(VnInput): empty to null by:alexm
- feat(VnSelect): order data equal salix by:alexm
- feat(VnSelect): refs #7136 add scroll (7136-vnSelect_paginate_simplify_2) by:alexm
### Changed 📦
- chore: perf (warmFix_order_equalSalix) by:alexm
- chore: refs #7273 sticky add btn & refactor by:jorgep
- fix: better performance (warmFix_accountAcls) by:alexm
- perf: minor bugs detected by:Javier Segarra
- perf: refs #6943 #6943 merge command by:Javier Segarra
- perf: refs #7283 #7283 declare composable inst4ead code duplicated by:Javier Segarra
- perf: refs #7283 #7283 handle composable i18n by:Javier Segarra
- perf: refs #7283 #7283 handle i18n by:Javier Segarra
- perf: refs #7283 #7283 i18n params by:Javier Segarra
- perf: refs #7308 #7308 remove comments by:Javier Segarra
- perf: remove appendParams by:Javier Segarra
- perf: use const in VnLocation by:Javier Segarra
- perf: use required instead :required="true" by:Javier Segarra
- refactor: apply QPopupProxy by:wbuezas
- refactor: changed confirmOrder directory by:Jon
- refactor: change keyup.enter for update:model-value by:wbuezas
- refactor(InvoiceInBasicData): use VnDms by:alexm
- refactor: modified composable by:Jon
- refactor: refs #6818 change channel source by:jorgep
- refactor: refs #6818 channel logic by:jorgep
- refactor: refs #6919 export filter by:jorgep
- refactor: refs #7132 1st wave of changes in global translations files by:Jon
- refactor: refs #7132 account's module translations by:Jon
- refactor: refs #7132 customer's module translations by:Jon
- refactor: refs #7132 deleted pageTitles repeated by:Jon
- refactor: refs #7132 delete duplicate translations' keys by:Jon
- refactor: refs #7132 deleted useless code by:Jon
- refactor: refs #7132 global translations files changed by:Jon
- refactor: refs #7266 Changed method name by:guillermo
- refactor: refs #7950 Created cmr model by:guillermo
- refactor: refs #7970 added emit by:Jon
- refactor: refs #7970 refactored VnConfirm to emit events by:Jon
- refactor: refs #8185 modified LeftMenu to avoid duplicates by:Jon
- refactor: remove unused variable by:wbuezas
- refactor: revert catalog changes by:Jon
- refactor: small change by:wbuezas
- test: refactor e2e by:alexm
- test: refs #8039 add hasNotify and, refactor: agencyWorkCenter test by:alexm
### Fixed 🛠️
- chore: refs #7524 fix test by:jorgep
- fix: better performance (warmFix_accountAcls) by:alexm
- fix: catalog view category and type filter by:wbuezas
- fix: category and tags filters by:Jon
- fix: changed route.query by:Jon
- fix: change type vnput by:Javier Segarra
- fix(ClaimList): stateCode orderBy priority by:alexm
- fix: entryFilters by:carlossa
- fix: filter panel by:Jon
- fix(InvoiceOutGlobal): parallelism by:alexm
- fix: itemBotanical by:Javier Segarra
- fix: itemType redirection and fix filters by:alexm
- fix: logout spec (warmFix_logout.spec) by:alexm
- fix: merge errors by:alexm
- fix: order catalog by:wbuezas
- fix: order catalog fixes by:wbuezas
- fix: refs #6818 use right icon by:jorgep
- fix: refs #6896 fixed module problems by:Jon
- fix: refs #7193 fixed e2e test by:Jon
- fix: refs #7206 deleted duplicate code by:Jon
- fix: refs #7273 use same filter by:jorgep
- fix: refs #7283 #7283 bugs by:Javier Segarra
- fix: refs #7283 #7283 ItemDiary subToolbar by:Javier Segarra
- fix: refs #7283 #7283 ItemSummary bugs by:Javier Segarra
- fix: refs #7283 Account image resolution by:guillermo
- fix: refs #7283 css by:jorgep
- fix: refs #7283 filter by:carlossa
- fix: refs #7283 fix image by:carlossa
- fix: refs #7283 fix pr by:carlossa
- fix: refs #7283 fix preview by:carlossa
- fix: refs #7283 fix required by:carlossa
- fix: refs #7283 item filters by:carlossa
- fix: refs #7283 itemtype fix by:carlossa
- fix: refs #7283 order translation by:carlossa
- fix: refs #7283 preview by:carlossa
- fix: refs #7283 tooltips !Item by:Javier Segarra
- fix: refs #7306 clean warning by:carlossa
- fix: refs #7310 clean warning by:carlossa
- fix: refs #7323 locale #7396 by:jorgep
- fix: refs #7323 show advanced fields by:jorgep
- fix: refs #7349 dependencia no usada by:jgallego
- fix: refs #7524 e2e & worker module by:jorgep
- fix: refs #7874 add title by:jorgep
- fix: refs #7874 show name by:jorgep
- fix: refs #7943 use correct data-key by:jorgep
- fix: refs #7943 use summary by:jorgep
- fix: refs #8039 bad tests by:alexm
- fix: refs #8039 o not handle unnecessary errors by:alexm
- fix: refs #8078 e2e #7970 by:jorgep
- fix: refs #8078 handleSelection by:jorgep
- fix: refs #8078 improve cy command (8078-enableMultiSelection) by:jorgep
- fix: refs #8078 improve handleSelection by:jorgep
- fix: reset category by:wbuezas
- fix: tag chips by:Jon
- fix: vnSearchbar spec (warmFix_vnSearchBar.spec) by:alexm
- fix(VnSelect): setOptions when applyFilter by:alexm
- fix: worker test e2e by:Jon
- Merge branch 'dev' into fix_customer_issues by:Javier Segarra
- refactor: revert catalog changes by:Jon
- refs #7283 fix conflicts by:carlossa
- refs #7283 fix descriptorproxy by:carlossa
- refs #7283 fixedPrice by:carlossa
- refs #7283 fixedPrices by:carlossa
- refs #7283 fix itemFixed by:carlossa
- refs #7283 fix itemFixedPrice by:carlossa
- refs #7283 fix itemMigration by:carlossa
- refs #7283 fix itemMigration list filters by:carlossa
- refs #7283 fix items by:carlossa
- refs #7283 fix items error get images by:carlossa
- refs #7283 fix items images by:carlossa
- refs #7283 fix request by:carlossa
- refs #7283 fix searchbar by:carlossa
- refs #7283 fix viewSummary by:carlossa
- refs #7283 fix yml list basicData by:carlossa
- refs #7283 itemRequest fix by:carlossa
- refs #7283 itemRequest fix deny by:carlossa
- refs #7283 itemRequest fix reload by:carlossa
- refs #72983 fix filters by:carlossa
- revert: commit by:Javier Segarra
- revert e57a253c6f649382da187d1129449d265fb26d3b by:Javier Segarra
- test: #8162 fix clientList spec by:Javier Segarra
- test: #8162 fix vnLocation spec by:Javier Segarra
- test: fix arrayData by:Javier Segarra
- test: fix e2e by:alexm
- test: fix e2e by:Javier Segarra
- test: refs #8039 fix WorkerNotification e2e by:alexm
- test: refs #8039 fix ZoneWarehouse e2e by:alexm
- warmfix: ItemLastEntries to date (origin/warmfix_itemLastEntriesFilter) by:Javier Segarra
# Version 24.40 - 2024-10-02
### Added 🆕

View File

@ -11,12 +11,22 @@ module.exports = defineConfig({
video: false,
specPattern: 'test/cypress/integration/**/*.spec.js',
experimentalRunAllSpecs: true,
watchForFileChanges: true,
reporter: 'cypress-mochawesome-reporter',
reporterOptions: {
charts: true,
reportPageTitle: 'Cypress Inline Reporter',
embeddedScreenshots: true,
reportDir: 'test/cypress/reports',
inlineAssets: true,
},
component: {
componentFolder: 'src',
testFiles: '**/*.spec.js',
supportFile: 'test/cypress/support/unit.js',
},
setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
// implement node event listeners here
},
},

View File

@ -1,16 +1,17 @@
{
"name": "salix-front",
"version": "24.44.0",
"version": "24.48.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
"private": true,
"packageManager": "pnpm@8.15.1",
"scripts": {
"resetDatabase": "cd ../salix && gulp docker",
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run",
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest",
"test:unit:ci": "vitest run",
@ -42,6 +43,7 @@
"@vue/test-utils": "^2.4.4",
"autoprefixer": "^10.4.14",
"cypress": "^13.6.6",
"cypress-mochawesome-reporter": "^3.8.2",
"eslint": "^8.41.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.13.3",
@ -62,4 +64,4 @@
"vite": "^5.1.4",
"vitest": "^0.31.1"
}
}
}

View File

@ -70,6 +70,9 @@ devDependencies:
cypress:
specifier: ^13.6.6
version: 13.6.6
cypress-mochawesome-reporter:
specifier: ^3.8.2
version: 3.8.2(cypress@13.6.6)(mocha@10.7.3)
eslint:
specifier: ^8.41.0
version: 8.56.0
@ -829,8 +832,8 @@ packages:
vue-i18n:
optional: true
dependencies:
'@intlify/message-compiler': 10.0.0-beta.5
'@intlify/shared': 10.0.0-beta.5
'@intlify/message-compiler': 10.0.0
'@intlify/shared': 10.0.0
jsonc-eslint-parser: 1.4.1
source-map: 0.6.1
vue-i18n: 9.9.1(vue@3.4.19)
@ -844,11 +847,11 @@ packages:
'@intlify/message-compiler': 9.9.1
'@intlify/shared': 9.9.1
/@intlify/message-compiler@10.0.0-beta.5:
resolution: {integrity: sha512-hLLchnM1dmtSEruerkzvU9vePsLqBXz3RU85SCx/Vd12fFQiymP+/5Rn9MJ8MyfLmIOLDEx4PRh+/GkIQP6oog==}
/@intlify/message-compiler@10.0.0:
resolution: {integrity: sha512-OcaWc63NC/9p1cMdgoNKBj4d61BH8sUW1Hfs6YijTd9656ZR4rNqXAlRnBrfS5ABq0vjQjpa8VnyvH9hK49yBw==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 10.0.0-beta.5
'@intlify/shared': 10.0.0
source-map-js: 1.0.2
dev: true
@ -859,8 +862,8 @@ packages:
'@intlify/shared': 9.9.1
source-map-js: 1.0.2
/@intlify/shared@10.0.0-beta.5:
resolution: {integrity: sha512-g9bq5Y1bOcC9qxtNk4UWtF3sXm6Wh0fGISb7vD5aLyF7yQv7ZFjxQjJzBP2GqG/9+PAGYutqjP1GGadNqFtyAQ==}
/@intlify/shared@10.0.0:
resolution: {integrity: sha512-6ngLfI7DOTew2dcF9WMJx+NnMWghMBhIiHbGg+wRvngpzD5KZJZiJVuzMsUQE1a5YebEmtpTEfUrDp/NqVGdiw==}
engines: {node: '>= 16'}
dev: true
@ -884,7 +887,7 @@ packages:
optional: true
dependencies:
'@intlify/bundle-utils': 4.0.0(vue-i18n@9.9.1)
'@intlify/shared': 10.0.0-beta.5
'@intlify/shared': 10.0.0
'@rollup/pluginutils': 4.2.1
'@vue/compiler-sfc': 3.4.19
debug: 4.3.4(supports-color@8.1.1)
@ -1999,6 +2002,10 @@ packages:
dependencies:
fill-range: 7.0.1
/browser-stdout@1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
dev: true
/browserslist@4.23.0:
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@ -2106,6 +2113,16 @@ packages:
upper-case: 1.1.3
dev: true
/camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: true
/camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
dev: true
/camelcase@7.0.1:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
engines: {node: '>=14.16'}
@ -2255,6 +2272,22 @@ packages:
engines: {node: '>= 10'}
dev: true
/cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
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'}
@ -2558,6 +2591,23 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
/cypress-mochawesome-reporter@3.8.2(cypress@13.6.6)(mocha@10.7.3):
resolution: {integrity: sha512-oJZkNzhNmN9ZD+LmZyFuPb8aWaIijyHyqYh52YOBvR6B6ckfJNCHP3A98a+/nG0H4t46CKTNwo+wNpMa4d2kjA==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
cypress: '>=6.2.0'
dependencies:
commander: 10.0.1
cypress: 13.6.6
fs-extra: 10.1.0
mochawesome: 7.1.3(mocha@10.7.3)
mochawesome-merge: 4.3.0
mochawesome-report-generator: 6.2.0
transitivePeerDependencies:
- mocha
dev: true
/cypress@13.6.6:
resolution: {integrity: sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A==}
engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
@ -2627,6 +2677,10 @@ packages:
time-zone: 1.0.0
dev: true
/dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
dev: true
/dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
dev: true
@ -2676,6 +2730,29 @@ packages:
ms: 2.1.2
supports-color: 8.1.1
/debug@4.3.7(supports-color@8.1.1):
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
supports-color: 8.1.1
dev: true
/decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
dev: true
/decamelize@4.0.0:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
dev: true
/decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
@ -2758,6 +2835,11 @@ packages:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
/diff@5.2.0:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
dev: true
/doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
@ -3550,6 +3632,14 @@ packages:
transitivePeerDependencies:
- supports-color
/find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: true
/find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@ -3646,6 +3736,15 @@ packages:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: true
/fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.1
dev: true
/fs-extra@11.2.0:
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
engines: {node: '>=14.14'}
@ -3654,6 +3753,15 @@ packages:
jsonfile: 6.1.0
universalify: 2.0.1
/fs-extra@7.0.1:
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
engines: {node: '>=6 <7 || >=8'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 4.0.0
universalify: 0.1.2
dev: true
/fs-extra@9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@ -3675,6 +3783,10 @@ packages:
dev: true
optional: true
/fsu@1.1.1:
resolution: {integrity: sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==}
dev: true
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@ -3775,6 +3887,18 @@ packages:
once: 1.4.0
path-is-absolute: 1.0.1
/glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
deprecated: Glob versions prior to v9 are no longer supported
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.6
once: 1.4.0
dev: true
/global-directory@4.0.1:
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
engines: {node: '>=18'}
@ -4189,6 +4313,11 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
/is-plain-obj@2.1.0:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
dev: true
/is-plain-obj@3.0.0:
resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==}
engines: {node: '>=10'}
@ -4361,6 +4490,12 @@ packages:
resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==}
dev: true
/jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies:
graceful-fs: 4.2.11
dev: true
/jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
@ -4452,6 +4587,13 @@ packages:
engines: {node: '>=14'}
dev: true
/locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: true
/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@ -4486,10 +4628,26 @@ packages:
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
dev: true
/lodash.isempty@4.4.0:
resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==}
dev: true
/lodash.isfunction@3.0.9:
resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
dev: true
/lodash.isobject@3.0.2:
resolution: {integrity: sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==}
dev: true
/lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
dev: true
/lodash.isstring@4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
dev: true
/lodash.kebabcase@4.1.1:
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
dev: true
@ -4552,6 +4710,13 @@ packages:
wrap-ansi: 6.2.0
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
dependencies:
js-tokens: 4.0.0
dev: true
/loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
dependencies:
@ -4722,6 +4887,79 @@ packages:
ufo: 1.4.0
dev: true
/mocha@10.7.3:
resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==}
engines: {node: '>= 14.0.0'}
hasBin: true
dependencies:
ansi-colors: 4.1.3
browser-stdout: 1.3.1
chokidar: 3.6.0
debug: 4.3.7(supports-color@8.1.1)
diff: 5.2.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 8.1.0
he: 1.2.0
js-yaml: 4.1.0
log-symbols: 4.1.0
minimatch: 5.1.6
ms: 2.1.3
serialize-javascript: 6.0.2
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-unparser: 2.0.0
dev: true
/mochawesome-merge@4.3.0:
resolution: {integrity: sha512-1roR6g+VUlfdaRmL8dCiVpKiaUhbPVm1ZQYUM6zHX46mWk+tpsKVZR6ba98k2zc8nlPvYd71yn5gyH970pKBSw==}
engines: {node: '>=10.0.0'}
hasBin: true
dependencies:
fs-extra: 7.0.1
glob: 7.2.3
yargs: 15.4.1
dev: true
/mochawesome-report-generator@6.2.0:
resolution: {integrity: sha512-Ghw8JhQFizF0Vjbtp9B0i//+BOkV5OWcQCPpbO0NGOoxV33o+gKDYU0Pr2pGxkIHnqZ+g5mYiXF7GMNgAcDpSg==}
hasBin: true
dependencies:
chalk: 4.1.2
dateformat: 4.6.3
escape-html: 1.0.3
fs-extra: 10.1.0
fsu: 1.1.1
lodash.isfunction: 3.0.9
opener: 1.5.2
prop-types: 15.8.1
tcomb: 3.2.29
tcomb-validation: 3.4.1
validator: 13.11.0
yargs: 17.7.2
dev: true
/mochawesome@7.1.3(mocha@10.7.3):
resolution: {integrity: sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==}
peerDependencies:
mocha: '>=7'
dependencies:
chalk: 4.1.2
diff: 5.2.0
json-stringify-safe: 5.0.1
lodash.isempty: 4.4.0
lodash.isfunction: 3.0.9
lodash.isobject: 3.0.2
lodash.isstring: 4.0.1
mocha: 10.7.3
mochawesome-report-generator: 6.2.0
strip-ansi: 6.0.1
uuid: 8.3.2
dev: true
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
@ -4870,6 +5108,11 @@ packages:
is-wsl: 2.2.0
dev: false
/opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
dev: true
/optionator@0.9.3:
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
engines: {node: '>= 0.8.0'}
@ -4915,6 +5158,13 @@ packages:
engines: {node: '>=12.20'}
dev: false
/p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: true
/p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@ -4929,6 +5179,13 @@ packages:
yocto-queue: 1.0.0
dev: true
/p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: true
/p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
@ -4950,6 +5207,11 @@ packages:
aggregate-error: 3.1.0
dev: true
/p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: true
/package-json@8.1.1:
resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==}
engines: {node: '>=14.16'}
@ -5139,6 +5401,14 @@ packages:
engines: {node: '>=0.4.0'}
dev: false
/prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
dev: true
/proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@ -5242,6 +5512,10 @@ packages:
strip-json-comments: 2.0.1
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: true
/react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
dev: true
@ -5328,6 +5602,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
dev: true
/requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
@ -5573,6 +5851,10 @@ packages:
transitivePeerDependencies:
- supports-color
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: true
/set-function-length@1.2.1:
resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==}
engines: {node: '>= 0.4'}
@ -5829,6 +6111,16 @@ packages:
readable-stream: 3.6.2
dev: true
/tcomb-validation@3.4.1:
resolution: {integrity: sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==}
dependencies:
tcomb: 3.2.29
dev: true
/tcomb@3.2.29:
resolution: {integrity: sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==}
dev: true
/text-extensions@2.4.0:
resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
engines: {node: '>=8'}
@ -6048,6 +6340,11 @@ packages:
crypto-random-string: 4.0.0
dev: false
/universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
dev: true
/universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
@ -6137,7 +6434,6 @@ packages:
/validator@13.11.0:
resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
engines: {node: '>= 0.10'}
dev: false
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
@ -6484,6 +6780,10 @@ packages:
engines: {node: '>=12'}
dev: true
/which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
dev: true
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@ -6511,6 +6811,10 @@ packages:
resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==}
dev: true
/workerpool@6.5.1:
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
dev: true
/wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@ -6559,6 +6863,10 @@ packages:
engines: {node: '>=12'}
dev: true
/y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: true
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -6584,11 +6892,64 @@ packages:
engines: {node: '>= 6'}
dev: true
/yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
dependencies:
camelcase: 5.3.1
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'}
dev: true
/yargs-unparser@2.0.0:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
dependencies:
camelcase: 6.3.0
decamelize: 4.0.0
flat: 5.0.2
is-plain-obj: 2.1.0
dev: true
/yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
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.1.2
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'}

View File

@ -39,37 +39,7 @@ const onResponse = (response) => {
const onResponseError = (error) => {
stateQuery.remove(error.config);
let message = '';
const response = error.response;
const responseData = response && response.data;
const responseError = responseData && response.data.error;
if (responseError) {
message = responseError.message;
}
switch (response?.status) {
case 422:
if (error.name == 'ValidationError')
message +=
' "' +
responseError.details.context +
'.' +
Object.keys(responseError.details.codes).join(',') +
'"';
break;
case 500:
message = 'errors.statusInternalServerError';
break;
case 502:
message = 'errors.statusBadGateway';
break;
case 504:
message = 'errors.statusGatewayTimeout';
break;
}
if (session.isLoggedIn() && response?.status === 401) {
if (session.isLoggedIn() && error.response?.status === 401) {
session.destroy(false);
const hash = window.location.hash;
const url = hash.slice(1);
@ -78,8 +48,6 @@ const onResponseError = (error) => {
return Promise.reject(error);
}
notify(message, 'negative');
return Promise.reject(error);
};

View File

@ -2,13 +2,50 @@ import { boot } from 'quasar/wrappers';
import qFormMixin from './qformMixin';
import keyShortcut from './keyShortcut';
import useNotify from 'src/composables/useNotify.js';
import { CanceledError } from 'axios';
const { notify } = useNotify();
export default boot(({ app }) => {
app.mixin(qFormMixin);
app.directive('shortcut', keyShortcut);
app.config.errorHandler = function (err) {
console.error(err);
notify('globals.error', 'negative', 'error');
app.config.errorHandler = (error) => {
let message;
const response = error.response;
const responseData = response?.data;
const responseError = responseData && response.data.error;
if (responseError) {
message = responseError.message;
}
switch (response?.status) {
case 422:
if (error.name == 'ValidationError')
message +=
' "' +
responseError.details.context +
'.' +
Object.keys(responseError.details.codes).join(',') +
'"';
break;
case 500:
message = 'errors.statusInternalServerError';
break;
case 502:
message = 'errors.statusBadGateway';
break;
case 504:
message = 'errors.statusGatewayTimeout';
break;
}
console.error(error);
if (error instanceof CanceledError) {
const env = process.env.NODE_ENV;
if (env && env !== 'development') return;
message = 'Duplicate request';
}
notify(message ?? 'globals.error', 'negative', 'error');
};
});

View File

@ -9,8 +9,6 @@ import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue';
import { useState } from 'src/composables/useState';
defineProps({ showEntityField: { type: Boolean, default: true } });
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const bicInputRef = ref(null);
@ -18,17 +16,16 @@ const state = useState();
const customer = computed(() => state.get('customer'));
const countriesFilter = {
fields: ['id', 'name', 'code'],
};
const bankEntityFormData = reactive({
name: null,
bic: null,
countryFk: customer.value?.countryFk,
id: null,
});
const countriesFilter = {
fields: ['id', 'name', 'code'],
};
const countriesOptions = ref([]);
const onDataSaved = (...args) => {
@ -44,7 +41,6 @@ onMounted(async () => {
<template>
<FetchData
url="Countries"
:filter="countriesFilter"
auto-load
@on-fetch="(data) => (countriesOptions = data)"
/>
@ -54,6 +50,7 @@ onMounted(async () => {
:title="t('title')"
:subtitle="t('subtitle')"
:form-initial-data="bankEntityFormData"
:filter="countriesFilter"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data, validate }">
@ -85,7 +82,13 @@ onMounted(async () => {
:rules="validate('bankEntity.countryFk')"
/>
</div>
<div v-if="showEntityField" class="col">
<div
v-if="
countriesOptions.find((c) => c.id === data.countryFk)?.code ==
'ES'
"
class="col"
>
<VnInput
:label="t('id')"
v-model="data.id"

View File

@ -10,6 +10,7 @@ import VnPaginate from 'components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import SkeletonTable from 'components/ui/SkeletonTable.vue';
import { tMobile } from 'src/composables/tMobile';
import getDifferences from 'src/filters/getDifferences';
const { push } = useRouter();
const quasar = useQuasar();
@ -77,7 +78,7 @@ const isLoading = ref(false);
const hasChanges = ref(false);
const originalData = ref();
const vnPaginateRef = ref();
const formData = ref();
const formData = ref([]);
const saveButtonRef = ref(null);
const watchChanges = ref();
const formUrl = computed(() => $props.url);
@ -94,6 +95,7 @@ defineExpose({
saveChanges,
getChanges,
formData,
originalData,
vnPaginateRef,
});
@ -267,28 +269,6 @@ function getChanges() {
return changes;
}
function getDifferences(obj1, obj2) {
let diff = {};
delete obj1.$index;
delete obj2.$index;
for (let key in obj1) {
if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
diff[key] = obj2[key];
}
}
for (let key in obj2) {
if (
obj1[key] === undefined ||
JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
) {
diff[key] = obj2[key];
}
}
return diff;
}
function isEmpty(obj) {
if (obj == null) return true;
if (obj === undefined) return true;

View File

@ -156,26 +156,22 @@ const rotateRight = () => {
};
const onSubmit = () => {
try {
if (!newPhoto.files && !newPhoto.url) {
notify(t('Select an image'), 'negative');
return;
}
const options = {
type: 'blob',
};
editor.value
.result(options)
.then((result) => {
const file = new File([result], newPhoto.files?.name || '');
newPhoto.blob = file;
})
.then(() => makeRequest());
} catch (err) {
console.error('Error uploading image');
if (!newPhoto.files && !newPhoto.url) {
notify(t('Select an image'), 'negative');
return;
}
const options = {
type: 'blob',
};
editor.value
.result(options)
.then((result) => {
const file = new File([result], newPhoto.files?.name || '');
newPhoto.blob = file;
})
.then(() => makeRequest());
};
const makeRequest = async () => {

View File

@ -51,21 +51,17 @@ const onDataSaved = () => {
};
const onSubmit = async () => {
try {
isLoading.value = true;
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
const payload = {
field: selectedField.value.field,
newValue: newValue.value,
lines: rowsToEdit,
};
isLoading.value = true;
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
const payload = {
field: selectedField.value.field,
newValue: newValue.value,
lines: rowsToEdit,
};
await axios.post($props.editUrl, payload);
onDataSaved();
isLoading.value = false;
} catch (err) {
console.error('Error submitting table cell edit');
}
await axios.post($props.editUrl, payload);
onDataSaved();
isLoading.value = false;
};
const closeForm = () => {

View File

@ -50,25 +50,25 @@ const loading = ref(false);
const tableColumns = computed(() => [
{
label: t('entry.buys.id'),
label: t('globals.id'),
name: 'id',
field: 'id',
align: 'left',
},
{
label: t('entry.buys.name'),
label: t('globals.name'),
name: 'name',
field: 'name',
align: 'left',
},
{
label: t('entry.buys.size'),
label: t('globals.size'),
name: 'size',
field: 'size',
align: 'left',
},
{
label: t('entry.buys.producer'),
label: t('globals.producer'),
name: 'producerName',
field: 'producer',
align: 'left',
@ -84,34 +84,30 @@ const tableColumns = computed(() => [
]);
const onSubmit = async () => {
try {
let filter = itemFilter;
const params = itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
let filter = itemFilter;
const params = itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'name':
where[key] = { like: `%${value}%` };
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
switch (key) {
case 'name':
where[key] = { like: `%${value}%` };
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
filter.where = where;
const { data } = await axios.get(props.url, {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
} catch (err) {
console.error('Error fetching entries items');
}
filter.where = where;
const { data } = await axios.get(props.url, {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
};
const closeForm = () => {
@ -152,10 +148,10 @@ const selectItem = ({ id }) => {
</span>
<h1 class="title">{{ t('Filter item') }}</h1>
<VnRow>
<VnInput :label="t('entry.buys.name')" v-model="itemFilterParams.name" />
<VnInput :label="t('globals.name')" v-model="itemFilterParams.name" />
<VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" />
<VnSelect
:label="t('entry.buys.producer')"
:label="t('globals.producer')"
:options="producersOptions"
hide-selected
option-label="name"
@ -163,7 +159,7 @@ const selectItem = ({ id }) => {
v-model="itemFilterParams.producerFk"
/>
<VnSelect
:label="t('entry.buys.type')"
:label="t('globals.type')"
:options="ItemTypesOptions"
hide-selected
option-label="name"

View File

@ -48,13 +48,13 @@ const loading = ref(false);
const tableColumns = computed(() => [
{
label: t('entry.basicData.id'),
label: t('globals.id'),
name: 'id',
field: 'id',
align: 'left',
},
{
label: t('entry.basicData.warehouseOut'),
label: t('globals.warehouseOut'),
name: 'warehouseOutFk',
field: 'warehouseOutFk',
align: 'left',
@ -62,7 +62,7 @@ const tableColumns = computed(() => [
warehousesOptions.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('entry.basicData.warehouseIn'),
label: t('globals.warehouseIn'),
name: 'warehouseInFk',
field: 'warehouseInFk',
align: 'left',
@ -70,14 +70,14 @@ const tableColumns = computed(() => [
warehousesOptions.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('entry.basicData.shipped'),
label: t('globals.shipped'),
name: 'shipped',
field: 'shipped',
align: 'left',
format: (val) => toDate(val),
},
{
label: t('entry.basicData.landed'),
label: t('globals.landed'),
name: 'landed',
field: 'landed',
align: 'left',
@ -86,32 +86,28 @@ const tableColumns = computed(() => [
]);
const onSubmit = async () => {
try {
let filter = travelFilter;
const params = travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
let filter = travelFilter;
const params = travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'agencyModeFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
switch (key) {
case 'agencyModeFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
filter.where = where;
const { data } = await axios.get('Travels', {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
} catch (err) {
console.error('Error fetching travels');
}
filter.where = where;
const { data } = await axios.get('Travels', {
params: { filter: JSON.stringify(filter) },
});
tableRows.value = data;
};
const closeForm = () => {
@ -146,7 +142,7 @@ const selectTravel = ({ id }) => {
<h1 class="title">{{ t('Filter travels') }}</h1>
<VnRow>
<VnSelect
:label="t('entry.basicData.agency')"
:label="t('globals.agency')"
:options="agenciesOptions"
hide-selected
option-label="name"
@ -154,7 +150,7 @@ const selectTravel = ({ id }) => {
v-model="travelFilterParams.agencyModeFk"
/>
<VnSelect
:label="t('entry.basicData.warehouseOut')"
:label="t('globals.warehouseOut')"
:options="warehousesOptions"
hide-selected
option-label="name"
@ -162,7 +158,7 @@ const selectTravel = ({ id }) => {
v-model="travelFilterParams.warehouseOutFk"
/>
<VnSelect
:label="t('entry.basicData.warehouseIn')"
:label="t('globals.warehouseIn')"
:options="warehousesOptions"
hide-selected
option-label="name"
@ -170,11 +166,11 @@ const selectTravel = ({ id }) => {
v-model="travelFilterParams.warehouseInFk"
/>
<VnInputDate
:label="t('entry.basicData.shipped')"
:label="t('globals.shipped')"
v-model="travelFilterParams.shipped"
/>
<VnInputDate
:label="t('entry.basicData.landed')"
:label="t('globals.landed')"
v-model="travelFilterParams.landed"
/>
</VnRow>

View File

@ -106,6 +106,7 @@ const originalData = ref({});
const formData = computed(() => state.get(modelValue));
const defaultButtons = computed(() => ({
save: {
dataCy: 'saveDefaultBtn',
color: 'primary',
icon: 'save',
label: 'globals.save',
@ -113,6 +114,7 @@ const defaultButtons = computed(() => ({
type: 'submit',
},
reset: {
dataCy: 'resetDefaultBtn',
color: 'primary',
icon: 'restart_alt',
label: 'globals.reset',
@ -203,7 +205,9 @@ async function save() {
isLoading.value = true;
try {
formData.value = trimData(formData.value);
const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
const body = $props.mapper
? $props.mapper(formData.value, originalData.value)
: formData.value;
const method = $props.urlCreate ? 'post' : 'patch';
const url =
$props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url;
@ -217,9 +221,6 @@ async function save() {
updateAndEmit('onDataSaved', formData.value, response?.data);
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
} catch (err) {
console.error(err);
notify('errors.writeRequest', 'negative');
} finally {
isLoading.value = false;
}
@ -275,6 +276,7 @@ defineExpose({
hasChanges,
reset,
fetch,
formData,
});
</script>
<template>
@ -319,6 +321,7 @@ defineExpose({
:title="t(defaultButtons.reset.label)"
/>
<QBtnDropdown
data-cy="saveAndContinueDefaultBtn"
v-if="$props.goTo"
@click="saveAndGo"
:label="tMobile('globals.saveAndContinue')"

View File

@ -61,6 +61,7 @@ defineExpose({
:loading="isLoading"
@click="emit('onDataCanceled')"
v-close-popup
data-cy="FormModelPopup_cancel"
/>
<QBtn
:label="t('globals.save')"
@ -70,6 +71,7 @@ defineExpose({
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
data-cy="FormModelPopup_save"
/>
</div>
</template>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
const emit = defineEmits(['onSubmit']);
defineProps({
const $props = defineProps({
title: {
type: String,
default: '',
@ -25,16 +25,21 @@ defineProps({
type: String,
default: '',
},
submitOnEnter: {
type: Boolean,
default: true,
},
});
const { t } = useI18n();
const closeButton = ref(null);
const isLoading = ref(false);
const onSubmit = () => {
emit('onSubmit');
closeForm();
if ($props.submitOnEnter) {
emit('onSubmit');
closeForm();
}
};
const closeForm = () => {

View File

@ -88,20 +88,16 @@ const applyTags = (params, search) => {
};
const fetchItemTypes = async (id) => {
try {
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
} catch (err) {
console.error('Error fetching item types', err);
}
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
};
const getCategoryClass = (category, params) => {
@ -111,23 +107,19 @@ const getCategoryClass = (category, params) => {
};
const getSelectedTagValues = async (tag) => {
try {
if (!tag?.selectedTag?.id) return;
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
if (!tag?.selectedTag?.id) return;
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
} catch (err) {
console.error('Error getting selected tag values');
}
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
};
const removeTag = (index, params, search) => {
@ -248,7 +240,7 @@ const removeTag = (index, params, search) => {
>
<QItemSection class="col">
<VnSelect
:label="t('components.itemsFilterPanel.tag')"
:label="t('globals.tag')"
v-model="value.selectedTag"
:options="tagOptions"
option-label="name"

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { onMounted, watch, ref, reactive } from 'vue';
import { onMounted, watch, ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { QSeparator, useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
@ -9,6 +9,7 @@ import { toLowerCamel } from 'src/filters';
import routes from 'src/router/modules';
import LeftMenuItem from './LeftMenuItem.vue';
import LeftMenuItemGroup from './LeftMenuItemGroup.vue';
import VnInput from './common/VnInput.vue';
const { t } = useI18n();
const route = useRoute();
@ -21,17 +22,52 @@ const props = defineProps({
default: 'main',
},
});
const initialized = ref(false);
const items = ref([]);
const expansionItemElements = reactive({});
const pinnedModules = computed(() => {
const map = new Map();
items.value.forEach((item) => item.isPinned && map.set(item.name, item));
return map;
});
const search = ref(null);
const filteredItems = computed(() => {
if (!search.value) return items.value;
const normalizedSearch = normalize(search.value);
return items.value.filter((item) => {
const locale = normalize(t(item.title));
return locale.includes(normalizedSearch);
});
});
const filteredPinnedModules = computed(() => {
if (!search.value) return pinnedModules.value;
const normalizedSearch = search.value
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
const map = new Map();
for (const [key, pinnedModule] of pinnedModules.value) {
const locale = t(pinnedModule.title)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
if (locale.includes(normalizedSearch)) map.set(key, pinnedModule);
}
return map;
});
onMounted(async () => {
await navigation.fetchPinned();
getRoutes();
initialized.value = true;
});
watch(
() => route.matched,
() => {
if (!initialized.value) return;
items.value = [];
getRoutes();
},
@ -66,8 +102,6 @@ function addChildren(module, route, parent) {
}
}
const items = ref([]);
function getRoutes() {
if (props.source === 'main') {
const modules = Object.assign([], navigation.getModules().value);
@ -123,21 +157,57 @@ async function togglePinned(item, event) {
const handleItemExpansion = (itemName) => {
expansionItemElements[itemName].scrollToLastElement();
};
function normalize(text) {
return text
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
</script>
<template>
<QList padding class="column-max-width">
<template v-if="$props.source === 'main'">
<template v-if="$route?.matched[1]?.name === 'Dashboard'">
<QItem class="header">
<QItemSection avatar>
<QIcon name="view_module" />
</QItemSection>
<QItemSection> {{ t('globals.modules') }}</QItemSection>
<QItem class="q-pb-md">
<VnInput
v-model="search"
:label="t('Search modules')"
class="full-width"
filled
dense
/>
</QItem>
<QSeparator />
<template v-for="item in items" :key="item.name">
<template v-if="item.children">
<template v-if="filteredPinnedModules.size">
<LeftMenuItem
v-for="[key, pinnedModule] of filteredPinnedModules"
:key="key"
:item="pinnedModule"
group="modules"
>
<template #side>
<QBtn
v-if="pinnedModule.isPinned === true"
@click="togglePinned(pinnedModule, $event)"
icon="remove_circle"
size="xs"
flat
round
>
<QTooltip>
{{ t('components.leftMenu.removeFromPinned') }}
</QTooltip>
</QBtn>
</template>
</LeftMenuItem>
<QSeparator />
</template>
<template v-for="item in filteredItems" :key="item.name">
<template
v-if="item.children && !filteredPinnedModules.has(item.name)"
>
<LeftMenuItem :item="item" group="modules">
<template #side>
<QBtn
@ -256,3 +326,7 @@ const handleItemExpansion = (itemName) => {
color: var(--vn-label-color);
}
</style>
<i18n>
es:
Search modules: Buscar módulos
</i18n>

View File

@ -39,14 +39,10 @@ const refund = async () => {
invoiceCorrectionTypeFk: invoiceParams.invoiceCorrectionTypeFk,
};
try {
const { data } = await axios.post('InvoiceOuts/refundAndInvoice', params);
notify(t('Refunded invoice'), 'positive');
const [id] = data?.refundId || [];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
} catch (err) {
console.error('Error refunding invoice', err);
}
const { data } = await axios.post('InvoiceOuts/refundAndInvoice', params);
notify(t('Refunded invoice'), 'positive');
const [id] = data?.refundId || [];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
};
</script>

View File

@ -49,36 +49,32 @@ const makeInvoice = async () => {
makeInvoice: checked.value,
};
try {
if (checked.value && hasToInvoiceByAddress) {
const response = await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Bill destination client'),
message: t('transferInvoiceInfo'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
if (!response) {
return;
}
if (checked.value && hasToInvoiceByAddress) {
const response = await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Bill destination client'),
message: t('transferInvoiceInfo'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
if (!response) {
return;
}
const { data } = await axios.post('InvoiceOuts/transfer', params);
notify(t('Transferred invoice'), 'positive');
const id = data?.[0];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
} catch (err) {
console.error('Error transfering invoice', err);
}
const { data } = await axios.post('InvoiceOuts/transfer', params);
notify(t('Transferred invoice'), 'positive');
const id = data?.[0];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
};
</script>

View File

@ -25,7 +25,7 @@ const $props = defineProps({
},
searchUrl: {
type: String,
default: 'params',
default: 'table',
},
});

View File

@ -17,7 +17,7 @@ const $props = defineProps({
},
searchUrl: {
type: String,
default: 'params',
default: 'table',
},
vertical: {
type: Boolean,

View File

@ -151,8 +151,8 @@ const tableModes = [
},
];
onBeforeMount(() => {
setUserParams(route.query[$props.searchUrl]);
hasParams.value = params.value && Object.keys(params.value).length !== 0;
const urlParams = route.query[$props.searchUrl];
hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
});
onMounted(() => {
@ -185,7 +185,8 @@ watch(
watch(
() => route.query[$props.searchUrl],
(val) => setUserParams(val)
(val) => setUserParams(val),
{ immediate: true, deep: true }
);
const isTableMode = computed(() => mode.value == TABLE_MODE);
@ -325,11 +326,27 @@ function handleOnDataSaved(_) {
}
function handleScroll() {
if ($props.crudModel.disableInfiniteScroll) return;
const tMiddle = tableRef.value.$el.querySelector('.q-table__middle');
const { scrollHeight, scrollTop, clientHeight } = tMiddle;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40;
if (isAtBottom) CrudModelRef.value.vnPaginateRef.paginate();
}
function handleSelection({ evt, added, rows: selectedRows }, rows) {
if (evt?.shiftKey && added) {
const rowIndex = selectedRows[0].$index;
const selectedIndexes = new Set(selected.value.map((row) => row.$index));
for (const row of rows) {
if (row.$index == rowIndex) break;
if (!selectedIndexes.has(row.$index)) {
selected.value.push(row);
selectedIndexes.add(row.$index);
}
}
}
}
</script>
<template>
<QDrawer
@ -430,6 +447,7 @@ function handleScroll() {
@virtual-scroll="handleScroll"
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@update:selected="emit('update:selected', $event)"
@selection="(details) => handleSelection(details, rows)"
>
<template #top-left v-if="!$props.withoutHeader">
<slot name="top-left"></slot>

View File

@ -58,79 +58,71 @@ const getConfig = async (url, filter) => {
};
const fetchViewConfigData = async () => {
try {
const userConfigFilter = {
where: { tableCode: $props.tableCode, userFk: user.value.id },
};
const userConfig = await getConfig('UserConfigViews', userConfigFilter);
const userConfigFilter = {
where: { tableCode: $props.tableCode, userFk: user.value.id },
};
const userConfig = await getConfig('UserConfigViews', userConfigFilter);
if (userConfig) {
initialUserConfigViewData.value = userConfig;
setUserConfigViewData(userConfig.configuration);
return;
}
if (userConfig) {
initialUserConfigViewData.value = userConfig;
setUserConfigViewData(userConfig.configuration);
return;
}
const defaultConfigFilter = { where: { tableCode: $props.tableCode } };
const defaultConfig = await getConfig('DefaultViewConfigs', defaultConfigFilter);
const defaultConfigFilter = { where: { tableCode: $props.tableCode } };
const defaultConfig = await getConfig('DefaultViewConfigs', defaultConfigFilter);
if (defaultConfig) {
// Si el backend devuelve una configuración por defecto la usamos
setUserConfigViewData(defaultConfig.columns);
return;
} else {
// Si no hay configuración por defecto mostramos todas las columnas
const defaultColumns = {};
$props.allColumns.forEach((col) => (defaultColumns[col] = true));
setUserConfigViewData(defaultColumns);
}
} catch (err) {
console.error('Error fetching config view data', err);
if (defaultConfig) {
// Si el backend devuelve una configuración por defecto la usamos
setUserConfigViewData(defaultConfig.columns);
return;
} else {
// Si no hay configuración por defecto mostramos todas las columnas
const defaultColumns = {};
$props.allColumns.forEach((col) => (defaultColumns[col] = true));
setUserConfigViewData(defaultColumns);
}
};
const saveConfig = async () => {
try {
const params = {};
const configuration = {};
const params = {};
const configuration = {};
formattedCols.value.forEach((col) => {
const { name, active } = col;
configuration[name] = active;
});
formattedCols.value.forEach((col) => {
const { name, active } = col;
configuration[name] = active;
});
// Si existe una view config del usuario hacemos un update si no la creamos
if (initialUserConfigViewData.value) {
params.updates = [
{
data: {
configuration: configuration,
},
where: {
id: initialUserConfigViewData.value.id,
},
},
];
} else {
params.creates = [
{
userFk: user.value.id,
tableCode: $props.tableCode,
tableConfig: $props.tableCode,
// Si existe una view config del usuario hacemos un update si no la creamos
if (initialUserConfigViewData.value) {
params.updates = [
{
data: {
configuration: configuration,
},
];
}
const response = await axios.post('UserConfigViews/crud', params);
if (response.data && response.data[0]) {
initialUserConfigViewData.value = response.data[0];
}
emitSavedConfig();
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
} catch (err) {
console.error('Error saving user view config', err);
where: {
id: initialUserConfigViewData.value.id,
},
},
];
} else {
params.creates = [
{
userFk: user.value.id,
tableCode: $props.tableCode,
tableConfig: $props.tableCode,
configuration: configuration,
},
];
}
const response = await axios.post('UserConfigViews/crud', params);
if (response.data && response.data[0]) {
initialUserConfigViewData.value = response.data[0];
}
emitSavedConfig();
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
};
const emitSavedConfig = () => {

View File

@ -1,5 +1,5 @@
<script setup>
import { computed, defineModel } from 'vue';
import { computed } from 'vue';
const model = defineModel(undefined, { required: true });
const $props = defineProps({

View File

@ -31,6 +31,10 @@ const $props = defineProps({
type: String,
default: null,
},
description: {
type: String,
default: null,
},
});
const warehouses = ref();
@ -43,7 +47,8 @@ const dms = ref({});
onMounted(() => {
defaultData();
if (!$props.formInitialData)
dms.value.description = t($props.model + 'Description', dms.value);
dms.value.description =
$props.description ?? t($props.model + 'Description', dms.value);
});
function onFileChange(files) {
dms.value.hasFileAttached = !!files;
@ -54,7 +59,6 @@ function mapperDms(data) {
const formData = new FormData();
const { files } = data;
if (files) formData.append(files?.name, files);
delete data.files;
const dms = {
hasFile: !!data.hasFile,
@ -78,6 +82,7 @@ async function save() {
const body = mapperDms(dms.value);
const response = await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params, response);
delete dms.value.files;
return response;
}
@ -158,13 +163,14 @@ function addDefaultData(data) {
/>
<QFile
ref="inputFileRef"
:label="t('entry.buys.file')"
:label="t('globals.file')"
v-model="dms.files"
:multiple="false"
:accept="allowedContentTypes"
@update:model-value="onFileChange(dms.files)"
class="required"
:display-value="dms.file"
data-cy="VnDms_inputFile"
>
<template #append>
<QIcon

View File

@ -1,8 +1,11 @@
<script setup>
import { computed, ref } from 'vue';
import { computed, ref, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import { useValidator } from 'src/composables/useValidator';
import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const { t } = useI18n();
const emit = defineEmits([
'update:modelValue',
'update:options',
@ -27,11 +30,11 @@ const $props = defineProps({
type: Boolean,
default: true,
},
emptyToNull: {
type: Boolean,
default: true,
},
});

Evitamos tener que hacer :required="true", ahora solo hace falta required

Evitamos tener que hacer :required="true", ahora solo hace falta required
const { validations } = useValidator();
const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const vnInputRef = ref(null);
const value = computed({
@ -39,6 +42,7 @@ const value = computed({
return $props.modelValue;
},
set(value) {
if ($props.emptyToNull && value === '') value = null;
emit('update:modelValue', value);
},
});
@ -60,8 +64,6 @@ const focus = () => {
defineExpose({
focus,
});
import { useAttrs } from 'vue';
const $attrs = useAttrs();
const mixinRules = [
requiredFieldRule,
@ -85,7 +87,7 @@ const mixinRules = [
v-model="value"
v-bind="{ ...$attrs, ...styleAttrs }"
:type="$attrs.type"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
@keyup.enter="emit('keyup.enter')"
:clearable="false"
:rules="mixinRules"
@ -99,7 +101,13 @@ const mixinRules = [
<QIcon
name="close"
size="xs"
v-if="hover && value && !$attrs.disabled && $props.clearable"
v-if="
hover &&
value &&
!$attrs.disabled &&
!$attrs.readonly &&
$props.clearable
"
@click="
() => {
value = null;

View File

@ -4,8 +4,13 @@ import { date } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useAttrs } from 'vue';
import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const model = defineModel({ type: [String, Date] });
const { t } = useI18n();
const $props = defineProps({
isOutlined: {
type: Boolean,
@ -16,18 +21,13 @@ const $props = defineProps({
default: true,
},
});
import { useValidator } from 'src/composables/useValidator';
const { validations } = useValidator();
const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const vnInputDateRef = ref(null);
const dateFormat = 'DD/MM/YYYY';
const isPopupOpen = ref();
const hover = ref();
const mask = ref();
const $attrs = useAttrs();
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -104,7 +104,7 @@ const manageDate = (date) => {
:mask="mask"
placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
:rules="mixinRules"
:clearable="false"
@click="isPopupOpen = true"

View File

@ -1,8 +1,13 @@
<script setup>
import VnInput from 'src/components/common/VnInput.vue';
import { ref } from 'vue';
import { useAttrs } from 'vue';
const model = defineModel({ type: [Number, String] });
const $attrs = useAttrs();
const step = ref($attrs.step || 0.01);
</script>
<template>
<VnInput v-bind="$attrs" v-model.number="model" type="number" />
<VnInput v-bind="$attrs" v-model.number="model" type="number" :step="step" />

Daba error en los inputs de tipo numero al introducir 2 decimales

Daba error en los inputs de tipo numero al introducir 2 decimales
</template>

View File

@ -2,11 +2,12 @@
import { computed, ref, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import { date } from 'quasar';
import { useValidator } from 'src/composables/useValidator';
import VnTime from './VnTime.vue';
import { useRequired } from 'src/composables/useRequired';
const { validations } = useValidator();
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const { t } = useI18n();
const model = defineModel({ type: String });
const props = defineProps({
timeOnly: {
@ -20,8 +21,6 @@ const props = defineProps({
});
const vnInputTimeRef = ref(null);
const initialDate = ref(model.value ?? Date.vnNew());
const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const dateFormat = 'HH:mm';
const isPopupOpen = ref();
@ -78,7 +77,7 @@ function dateToTime(newDate) {
placeholder="--:--"
v-model="formattedTime"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
style="min-width: 100px"
:rules="mixinRules"
@click="isPopupOpen = false"

View File

@ -3,9 +3,12 @@ import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnSelectDialog from 'components/common/VnSelectDialog.vue';
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { useAttrs } from 'vue';
import { useRequired } from 'src/composables/useRequired';
const { t } = useI18n();
const emit = defineEmits(['update:model-value', 'update:options']);
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const props = defineProps({
location: {
type: Object,
@ -13,6 +16,7 @@ const props = defineProps({
},
});
const mixinRules = [requiredFieldRule];
const locationProperties = [
'postcode',
(obj) =>
@ -69,11 +73,13 @@ const handleModelValue = (data) => {
:label="t('Location')"
:placeholder="t('search_by_postalcode')"
:input-debounce="300"
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
v-bind="$attrs"
clearable
:emit-value="false"
:tooltip="t('Create new location')"
:rules="mixinRules"
:lazy-rules="true"
>
<template #form>
<CreateNewPostcode

View File

@ -1,10 +1,14 @@
<script setup>
import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
import { useValidator } from 'src/composables/useValidator';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired';
import dataByOrder from 'src/utils/dataByOrder';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
const $attrs = useAttrs();
const { t } = useI18n();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const $props = defineProps({
modelValue: {
type: [String, Number, Object],
@ -86,25 +90,26 @@ const $props = defineProps({
type: Boolean,
default: false,
},
dataKey: {
type: String,
default: null,
},
});
const { validations } = useValidator();
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const $attrs = useAttrs();
const { t } = useI18n();
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const { optionLabel, optionValue, optionFilter, optionFilterValue, options, modelValue } =
toRefs($props);
const myOptions = ref([]);
const myOptionsOriginal = ref([]);
const vnSelectRef = ref();
const dataRef = ref();
const lastVal = ref();
const noOneText = t('globals.noOne');
const noOneOpt = ref({
[optionValue.value]: false,
[optionLabel.value]: noOneText,
});
const isLoading = ref(false);
const useURL = computed(() => $props.url);
const value = computed({
get() {
return $props.modelValue;
@ -120,7 +125,7 @@ watch(options, (newValue) => {
});
watch(modelValue, async (newValue) => {
if (!myOptions.value.some((option) => option[optionValue.value] == newValue))
if (!myOptions?.value?.some((option) => option[optionValue.value] == newValue))
await fetchFilter(newValue);
if ($props.noOne) myOptions.value.unshift(noOneOpt.value);
@ -128,17 +133,25 @@ watch(modelValue, async (newValue) => {
onMounted(() => {
setOptions(options.value);
if ($props.url && $props.modelValue && !findKeyInOptions())
if (useURL.value && $props.modelValue && !findKeyInOptions())
fetchFilter($props.modelValue);
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
});
defineExpose({ opts: myOptions });
const arrayDataKey =
$props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label);
const arrayData = useArrayData(arrayDataKey, { url: $props.url, searchUrl: false });
function findKeyInOptions() {
if (!$props.options) return;
return filter($props.modelValue, $props.options)?.length;
}
function setOptions(data) {
data = dataByOrder(data, $props.sortBy);
myOptions.value = JSON.parse(JSON.stringify(data));
myOptionsOriginal.value = JSON.parse(JSON.stringify(data));
emit('update:options', data);
@ -166,7 +179,7 @@ function filter(val, options) {
}
async function fetchFilter(val) {
if (!$props.url || !dataRef.value) return;
if (!$props.url) return;
const { fields, include, sortBy, limit } = $props;
const key =
@ -188,8 +201,11 @@ async function fetchFilter(val) {
const fetchOptions = { where, include, limit };
if (fields) fetchOptions.fields = fields;
if (sortBy) fetchOptions.order = sortBy;
arrayData.reset(['skip', 'filter.skip', 'page']);
return dataRef.value.fetch(fetchOptions);
const { data } = await arrayData.applyFilter({ filter: fetchOptions });
setOptions(data);
return data;
}
async function filterHandler(val, update) {
@ -229,20 +245,23 @@ function nullishToTrue(value) {
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
defineExpose({ opts: myOptions });
async function onScroll({ to, direction, from, index }) {
const lastIndex = myOptions.value.length - 1;
if (from === 0 && index === 0) return;
if (!useURL.value && !$props.fetchRef) return;
if (direction === 'decrease') return;
if (to === lastIndex && arrayData.store.hasMoreData && !isLoading.value) {
isLoading.value = true;
await arrayData.loadMore();
setOptions(arrayData.store.data);
vnSelectRef.value.scrollTo(lastIndex);
isLoading.value = false;
}
}
</script>
<template>
<FetchData
ref="dataRef"
:url="$props.url"
@on-fetch="(data) => setOptions(data)"
:where="where || { [optionValue]: value }"
:limit="limit"
:sort-by="sortBy"
:fields="fields"
:params="params"
/>
<QSelect
v-model="value"
:options="myOptions"
@ -257,10 +276,13 @@ defineExpose({ opts: myOptions });
:fill-input="nullishToTrue($attrs['fill-input'])"
ref="vnSelectRef"
lazy-rules
:class="{ required: $attrs.required }"
:class="{ required: isRequired }"
:rules="mixinRules"
virtual-scroll-slice-size="options.length"
hide-bottom-space
:input-debounce="useURL ? '300' : '0'"
:loading="isLoading"
@virtual-scroll="onScroll"
>
<template v-if="isClearable" #append>
<QIcon

View File

@ -167,6 +167,7 @@ const toModule = computed(() =>
icon="more_vert"
round
size="md"
data-cy="descriptor-more-opts"
>
<QTooltip>
{{ t('components.cardDescriptor.moreOptions') }}

View File

@ -83,7 +83,7 @@ async function fetch() {
<slot name="header" :entity="entity" dense>
<VnLv :label="`${entity.id} -`" :value="entity.name" />
</slot>
<slot name="header-right">
<slot name="header-right" :entity="entity">
<span></span>
</slot>
</div>

View File

@ -30,10 +30,11 @@ const props = defineProps({
},
});
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
const emit = defineEmits(['confirm', 'cancel', ...useDialogPluginComponent.emits]);
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
const { dialogRef, onDialogOK } = useDialogPluginComponent();
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
const title = props.title || t('Confirm');
const message =
@ -53,9 +54,14 @@ async function confirm() {
}
onDialogOK(props.data);
}
function cancel() {
emit('cancel');
onDialogCancel();
}
</script>
<template>
<QDialog ref="dialogRef">
<QDialog ref="dialogRef" @hide="onDialogHide">
<QCard class="q-pa-sm">
<QCardSection class="row items-center q-pb-none">
<QAvatar
@ -67,7 +73,14 @@ async function confirm() {
/>
<span class="text-h6">{{ title }}</span>
<QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
<QBtn
icon="close"
:disable="isLoading"
flat
round
dense
@click="cancel()"
/>
</QCardSection>
<QCardSection class="q-pb-none">
<span v-if="message !== false" v-html="message" />
@ -81,7 +94,7 @@ async function confirm() {
color="primary"
:disable="isLoading"
flat
v-close-popup
@click="cancel()"
/>
<QBtn
:label="t('globals.confirm')"
@ -90,6 +103,7 @@ async function confirm() {
@click="confirm()"
unelevated
autofocus
data-cy="VnConfirm_confirm"
/>
</QCardActions>
</QCard>

View File

@ -49,7 +49,7 @@ const $props = defineProps({
},
searchUrl: {
type: String,
default: 'params',
default: 'table',
},
redirect: {
type: Boolean,
@ -57,7 +57,6 @@ const $props = defineProps({
},
});
defineExpose({ search, sanitizer });
const emit = defineEmits([
'update:modelValue',
'refresh',
@ -76,6 +75,9 @@ const arrayData = useArrayData($props.dataKey, {
const route = useRoute();
const store = arrayData.store;
const userParams = ref({});
defineExpose({ search, sanitizer, params: userParams });
onMounted(() => {
userParams.value = $props.modelValue ?? {};
emit('init', { params: userParams.value });
@ -170,16 +172,36 @@ const tagsList = computed(() => {
return tagList;
});
const formatTags = (tags) => {
const formattedTags = [];
tags.forEach((tag) => {
if (tag.label === 'and') {
tag.value.forEach((item) => {
for (const key in item) {
formattedTags.push({ label: key, value: item[key] });
}
});
} else {
formattedTags.push(tag);
}
});
return formattedTags;
};
const tags = computed(() => {
return tagsList.value.filter((tag) => !($props.customTags || []).includes(tag.label));
const filteredTags = tagsList.value.filter(
(tag) => !($props.customTags || []).includes(tag.label)
);
return formatTags(filteredTags);
});
const customTags = computed(() =>
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label))
);
async function remove(key) {
userParams.value[key] = undefined;
search();
await search();
emit('remove', key);
emit('update:modelValue', userParams.value);
}
@ -193,7 +215,12 @@ function formatValue(value) {
function sanitizer(params) {
for (const [key, value] of Object.entries(params)) {
if (value && typeof value === 'object') {
if (key === 'and' && Array.isArray(value)) {
value.forEach((item) => {
Object.assign(params, item);
});
delete params[key];
Outdated
Review

Pero esto ya lo tiene QForm no?

Pero esto ya lo tiene QForm no?

?Pero no está funcionando en master.
Al parecer en test si que va.
Los usuarios nos lo piden mucho, podemos eliminar la linea y...sabemos que se ha cambiado para que funcione correctamente?

?Pero no está funcionando en master. Al parecer en test si que va. Los usuarios nos lo piden mucho, podemos eliminar la linea y...sabemos que se ha cambiado para que funcione correctamente?
Outdated
Review

Yo lo quitaria, puede que de problemas.
Prueba un filtro lateral que genere VnTable

Yo lo quitaria, puede que de problemas. Prueba un filtro lateral que genere VnTable

Se mantiene, todavia no está el evento que lo dispara

Se mantiene, todavia no está el evento que lo dispara
} else if (value && typeof value === 'object') {
const param = Object.values(value)[0];
if (typeof param == 'string') params[key] = param.replaceAll('%', '');
}

View File

@ -1,16 +1,50 @@
<script setup>
defineProps({ phoneNumber: { type: [String, Number], default: null } });
import { reactive, useAttrs, onBeforeMount, capitalize } from 'vue';
import axios from 'axios';
import { parsePhone } from 'src/filters';
const props = defineProps({
phoneNumber: { type: [String, Number], default: null },
channel: { type: Number, default: null },
});
const config = reactive({
sip: { icon: 'phone', href: `sip:${props.phoneNumber}` },
'say-simple': {
icon: 'vn:saysimple',
href: null,
channel: props.channel,
},
});
const type = Object.keys(config).find((key) => key in useAttrs()) || 'sip';
onBeforeMount(async () => {
let { channel } = config[type];
if (type === 'say-simple') {
const { url, defaultChannel } = (await axios.get('SaySimpleConfigs/findOne'))
.data;
if (!channel) channel = defaultChannel;
config[type].href = `${url}?customerIdentity=%2B${parsePhone(
props.phoneNumber
)}&channelId=${channel}`;
}
});
</script>
<template>
<QBtn
v-if="phoneNumber"
flat
round
icon="phone"
:icon="config[type].icon"
size="sm"
color="primary"
padding="none"
:href="`sip:${phoneNumber}`"
:href="config[type].href"
@click.stop
/>
>
<QTooltip>
{{ capitalize(type).replace('-', '') }}
</QTooltip>
</QBtn>
</template>

View File

@ -65,13 +65,9 @@ onBeforeRouteLeave((to, from, next) => {
auto-load
@on-fetch="(data) => (observationTypes = data)"
/>
<QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote">
<QCard class="q-pa-xs q-mb-lg full-width" v-if="$props.addNote">
<QCardSection horizontal>
<VnAvatar :worker-id="currentUser.id" size="md" />
<div class="full-width row justify-between q-pa-xs">
<VnUserLink :name="t('New note')" :worker-id="currentUser.id" />
{{ t('globals.now') }}
</div>
{{ t('New note') }}
</QCardSection>
<QCardSection class="q-px-xs q-my-none q-py-none">
<VnRow class="full-width">
@ -105,6 +101,7 @@ onBeforeRouteLeave((to, from, next) => {
@click="insert"
class="q-mb-xs"
dense
data-cy="saveNote"
/>
</template>
</VnInput>
@ -144,7 +141,7 @@ onBeforeRouteLeave((to, from, next) => {
<div class="full-width row justify-between q-pa-xs">
<div>
<VnUserLink
:name="`${note.worker.user.nickname}`"
:name="`${note.worker.user.name}`"
:worker-id="note.worker.id"
/>
<QBadge

View File

@ -44,7 +44,7 @@ const props = defineProps({
},
limit: {
type: Number,
default: 10,
default: 20,
},
userParams: {
type: Object,
@ -100,7 +100,7 @@ const arrayData = useArrayData(props.dataKey, {
const store = arrayData.store;
onMounted(async () => {
if (props.autoLoad) await fetch();
if (props.autoLoad && !store.data?.length) await fetch();
mounted.value = true;
});
@ -115,7 +115,11 @@ watch(
watch(
() => store.data,
(data) => emit('onChange', data)
(data) => {
if (!mounted.value) return;
emit('onChange', data);
},
{ immediate: true }
);
watch(
@ -128,10 +132,24 @@ const addFilter = async (filter, params) => {
async function fetch(params) {
useArrayData(props.dataKey, params);
arrayData.reset(['filter.skip', 'skip']);
await arrayData.fetch({ append: false });
if (!store.hasMoreData) isLoading.value = false;
arrayData.reset(['filter.skip', 'skip', 'page']);
await arrayData.fetch({ append: false, updateRouter: mounted.value });
return emitStoreData();
}
async function update(params) {
useArrayData(props.dataKey, params);
const { limit, skip } = store;
store.limit = limit + skip;
store.skip = 0;
await arrayData.fetch({ append: false });
store.limit = limit;
store.skip = skip;
return emitStoreData();
}
function emitStoreData() {
if (!store.hasMoreData) isLoading.value = false;
emit('onFetch', store.data);
return store.data;
}
@ -177,7 +195,7 @@ async function onLoad(index, done) {
done(isDone);
}
defineExpose({ fetch, addFilter, paginate });
defineExpose({ fetch, update, addFilter, paginate });
</script>
<template>

View File

@ -1,6 +1,3 @@
<script setup>
defineProps({ wrap: { type: Boolean, default: false } });
</script>
<template>
<div class="vn-row q-gutter-md q-mb-md">
<slot />

View File

@ -45,7 +45,7 @@ const props = defineProps({
},
limit: {
type: Number,
default: 10,
default: 20,
},
userParams: {
type: Object,

View File

@ -1,6 +1,5 @@
<script setup>
import { useRoute } from 'vue-router';
import { defineProps } from 'vue';
const props = defineProps({
routeName: {

View File

@ -0,0 +1,11 @@
import axios from 'axios';
import { useRole } from './useRole';
export async function useAdvancedSummary(model, id, roles = ['hr']) {
if (useRole().hasAny(roles)) {
const { data } = await axios.get(`${model}/advancedSummary`, {
params: { filter: { where: { id } } },
});
return Array.isArray(data) ? data[0] : data;
}
}

View File

@ -75,18 +75,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
limit: store.limit,
};
let exprFilter;
let userParams = { ...store.userParams };
if (store?.exprBuilder) {
const where = buildFilter(userParams, (param, value) => {
const res = store.exprBuilder(param, value);
if (res) delete userParams[param];
return res;
});
exprFilter = where ? { where } : null;
}
Object.assign(filter, store.userFilter, exprFilter);
Object.assign(filter, store.userFilter);
let where;
if (filter?.where || store.filter?.where)
where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
@ -95,12 +87,29 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const params = { filter };
Object.assign(params, userParams);
params.filter.skip = store.skip;
if (store.order && store.order.length) params.filter.order = store.order;
if (params.filter) params.filter.skip = store.skip;
if (store?.order && typeof store?.order == 'string') store.order = [store.order];
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order;
store.currentFilter = JSON.parse(JSON.stringify(params));
delete store.currentFilter.filter.include;
store.currentFilter.filter = JSON.stringify(store.currentFilter.filter);
let exprFilter;
if (store?.exprBuilder) {
exprFilter = buildFilter(params, (param, value) => {
if (param == 'filter') return;
const res = store.exprBuilder(param, value);
if (res) delete params[param];
return res;
});
}
if (params.filter.where || exprFilter)
params.filter.where = { ...params.filter.where, ...exprFilter };
params.filter = JSON.stringify(params.filter);
store.currentFilter = params;
store.isLoading = true;
const response = await axios.get(store.url, {
signal: canceller.signal,
@ -271,7 +280,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const pushUrl = { path: to };
if (to.endsWith('/list') || to.endsWith('/'))
pushUrl.query = newUrl.query;
destroy();
else destroy();
return router.push(pushUrl);
}
}

View File

@ -0,0 +1,8 @@
import { openURL } from 'quasar';
const defaultWindowFeatures = {
noopener: true,
noreferrer: true,
};
export default function (url, windowFeatures = defaultWindowFeatures, fn = undefined) {
openURL(url, fn, windowFeatures);
}

View File

@ -0,0 +1,13 @@
import { useValidator } from 'src/composables/useValidator';
export function useRequired($attrs) {
const { validations } = useValidator();
const isRequired = Object.keys($attrs).includes('required');
const requiredFieldRule = (val) => validations().required(isRequired, val);
return {
isRequired,
requiredFieldRule,
};
}

View File

@ -0,0 +1,21 @@
export default function getDifferences(obj1, obj2) {
let diff = {};
delete obj1.$index;
delete obj2.$index;
for (let key in obj1) {
if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
diff[key] = obj2[key];
}
}
for (let key in obj2) {
if (
obj1[key] === undefined ||
JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
) {
diff[key] = obj2[key];
}
}
return diff;
}

View File

@ -9,12 +9,20 @@ function parseJSON(str, fallback) {
}
export default function (route, param) {
// catch route query params
const params = parseJSON(route?.query?.params, {});
const params = parseJSON(route?.query?.table, {});
// extract and parse filter from params
const { filter: filterStr = '{}' } = params;
const where = parseJSON(filterStr, {})?.where;
if (where && where[param] !== undefined) {
if (where && !param) {
return where;
} else if (where && where.and) {
const foundParam = where.and.find((p) => p[param]);
if (foundParam) {
return foundParam[param];
}
} else if (where && where[param]) {
return where[param];
}
return null;

View File

@ -0,0 +1,6 @@
export default function getUpdatedValues(keys, formData) {
return keys.reduce((acc, key) => {
acc[key] = formData[key];
return acc;
}, {});
}

View File

@ -11,11 +11,18 @@ import dashIfEmpty from './dashIfEmpty';
import dateRange from './dateRange';
import toHour from './toHour';
import dashOrCurrency from './dashOrCurrency';
import getDifferences from './getDifferences';
import getUpdatedValues from './getUpdatedValues';
import getParamWhere from './getParamWhere';
import isDialogOpened from './isDialogOpened';
import parsePhone from './parsePhone';
import isDialogOpened from './isDialogOpened';
export {
getUpdatedValues,
getDifferences,
isDialogOpened,
parsePhone,
toLowerCase,
toLowerCamel,
toDate,

12
src/filters/parsePhone.js Normal file
View File

@ -0,0 +1,12 @@
export default function (phone, prefix = 34) {
if (phone.startsWith('+')) {
return `${phone.slice(1)}`;
}
if (phone.startsWith('00')) {
return `${phone.slice(2)}`;
}
if (phone.startsWith(prefix) && phone.length === prefix.length + 9) {
return `${prefix}${phone.slice(prefix.length)}`;
}
return `${prefix}${phone}`;
}

View File

@ -59,8 +59,8 @@ globals:
downloadCSVSuccess: CSV downloaded successfully
reference: Reference
agency: Agency
wareHouseOut: Warehouse Out
wareHouseIn: Warehouse In
warehouseOut: Warehouse Out
warehouseIn: Warehouse In
landed: Landed
shipped: Shipped
totalEntries: Total entries
@ -106,6 +106,29 @@ globals:
weight: Weight
error: Ups! Something went wrong
recalc: Recalculate
alias: Alias
vat: VAT
intrastat: Intrastat
tags: Tags
size: Size
producer: Producer
origin: Origin
state: State
subtotal: Subtotal
visible: Visible
price: Price
client: Client
country: Country
phone: Phone
mobile: Mobile
postcode: Postcode
street: Street
tag: Tag
ticketId: Ticket ID
confirmed: Confirmed
small: Small
medium: Medium
big: Big
pageTitles:
logIn: Login
addressEdit: Update address
@ -278,6 +301,7 @@ globals:
RouteExtendedList: Router
wasteRecalc: Waste recaclulate
operator: Operator
parking: Parking
supplier: Supplier
created: Created
worker: Worker
@ -296,13 +320,10 @@ globals:
maxTemperature: Max
minTemperature: Min
params:
id: ID
clientFk: Client id
salesPersonFk: Sales person
warehouseFk: Warehouse
provinceFk: Province
from: From
To: To
stateFk: State
email: Email
SSN: SSN
@ -312,6 +333,7 @@ globals:
changePass: Change password
deleteConfirmTitle: Delete selected elements
changeState: Change state
raid: 'Raid {daysInForward} days'
errors:
statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred
@ -331,16 +353,12 @@ login:
loginError: Invalid username or password
fieldRequired: This field is required
twoFactorRequired: Two-factor verification required
twoFactor:
code: Code
twoFactorRequired:
validate: Validate
insert: Enter the verification code
explanation: >-
Please, enter the verification code that we have sent to your email in the
next 5 minutes
verifyEmail:
pageTitles:
verifyEmail: Email verification
recoverPassword:
userOrEmail: User or recovery email
explanation: >-
@ -352,15 +370,7 @@ resetPassword:
entry:
list:
newEntry: New entry
landed: Landed
invoiceNumber: Invoice number
supplier: Supplier
booked: Booked
confirmed: Confirmed
ordered: Ordered
tableVisibleColumns:
id: Id
reference: Reference
created: Creation
supplierFk: Supplier
isBooked: Booked
@ -369,244 +379,121 @@ entry:
companyFk: Company
travelFk: Travel
isExcludedFromAvailable: Inventory
isRaid: Raid
invoiceAmount: Import
summary:
commission: Commission
currency: Currency
company: Company
reference: Reference
invoiceNumber: Invoice number
ordered: Ordered
confirmed: Confirmed
booked: Booked
raid: Raid
excludedFromAvailable: Inventory
travelReference: Reference
travelAgency: Agency
travelShipped: Shipped
travelWarehouseOut: Warehouse Out
travelDelivered: Delivered
travelLanded: Landed
travelWarehouseIn: Warehouse In
travelReceived: Received
buys: Buys
quantity: Quantity
stickers: Stickers
package: Package
weight: Weight
packing: Packing
grouping: Grouping
buyingValue: Buying value
import: Import
pvp: PVP
item: Item
basicData:
supplier: Supplier
travel: Travel
reference: Reference
invoiceNumber: Invoice number
company: Company
currency: Currency
commission: Commission
observation: Observation
ordered: Ordered
confirmed: Confirmed
booked: Booked
raid: Raid
excludedFromAvailable: Inventory
agency: Agency
warehouseOut: Warehouse Out
warehouseIn: Warehouse In
shipped: Shipped
landed: Landed
id: ID
buys:
groupingPrice: Grouping price
packingPrice: Packing price
reference: Reference
observations: Observations
item: Item
size: Size
packing: Packing
grouping: Grouping
buyingValue: Buying value
packagingFk: Box
file: File
name: Name
producer: Producer
type: Type
color: Color
id: ID
printedStickers: Printed stickers
notes:
observationType: Observation type
descriptor:
agency: Agency
landed: Landed
warehouseOut: Warehouse Out
latestBuys:
tableVisibleColumns:
image: Picture
itemFk: Item ID
packing: Packing
grouping: Grouping
quantity: Quantity
size: Size
tags: Tags
type: Type
intrastat: Intrastat
origin: Origin
weightByPiece: Weight/Piece
isActive: Active
family: Family
entryFk: Entry
buyingValue: Buying value
freightValue: Freight value
comissionValue: Commission value
description: Description
packageValue: Package value
isIgnored: Is ignored
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
weight: Weight
packagingFk: Package
packingOut: Package out
landing: Landing
isExcludedFromAvailable: Es inventory
isRaid: Raid
ticket:
pageTitles:
tickets: Tickets
list: List
ticketCreate: New ticket
summary: Summary
basicData: Basic Data
boxing: Boxing
sms: Sms
notes: Notes
sale: Sale
dms: File management
volume: Volume
observation: Notes
ticketAdvance: Advance tickets
futureTickets: Future tickets
purchaseRequest: Purchase request
weeklyTickets: Weekly tickets
list:
nickname: Nickname
state: State
shipped: Shipped
landed: Landed
salesPerson: Sales person
total: Total
card:
ticketId: Ticket ID
state: State
customerId: Customer ID
salesPerson: Sales person
agency: Agency
shipped: Shipped
warehouse: Warehouse
customerCard: Customer card
alias: Alias
ticketList: Ticket List
newOrder: New Order
boxing:
expedition: Expedition
item: Item
created: Created
worker: Worker
selectTime: 'Select time:'
selectVideo: 'Select video:'
notFound: No videos available
summary:
state: State
salesPerson: Sales person
agency: Agency
zone: Zone
warehouse: Warehouse
collection: Collection
route: Route
invoice: Invoice
shipped: Shipped
landed: Landed
consigneePhone: Consignee phone
consigneeMobile: Consignee mobile
consigneeAddress: Consignee address
clientPhone: Client phone
clientMobile: Client mobile
consignee: Consignee
subtotal: Subtotal
vat: VAT
total: Total
saleLines: Line items
item: Item
visible: Visible
available: Available
quantity: Quantity
price: Price
discount: Discount
packing: Packing
hasComponentLack: Component lack
itemShortage: Not visible
claim: Claim
reserved: Reserved
created: Created
package: Package
taxClass: Tax class
services: Services
requester: Requester
atender: Atender
request: Request
weight: Weight
goTo: Go to
summaryAmount: Summary
purchaseRequest: Purchase request
service: Service
description: Description
attender: Attender
ok: Ok
create:
client: Client
address: Address
landed: Landed
warehouse: Warehouse
agency: Agency
invoiceOut:
list:
ref: Reference
issued: Issued
shortIssued: Issued
client: Client
created: Created
shortCreated: Created
company: Company
dued: Due date
shortDued: Due date
amount: Amount
card:
issued: Issued
client: Client
company: Company
customerCard: Customer card
summary:
issued: Issued
created: Created
dued: Due
booked: Booked
company: Company
taxBreakdown: Tax breakdown
type: Type
taxableBase: Taxable base
rate: Rate
fee: Fee
tickets: Tickets
ticketId: Ticket id
nickname: Alias
shipped: Shipped
totalWithVat: Amount
globalInvoices:
errors:
@ -620,22 +507,14 @@ invoiceOut:
noTicketsToInvoice: There are not tickets to invoice
criticalInvoiceError: 'Critical invoicing error, process stopped'
table:
client: Client
addressId: Address id
streetAddress: Street
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} of {getNAddresses}'
pdfsNumberText: '{nPdfs} of {totalPdfs} PDFs'
negativeBases:
from: From
to: To
company: Company
country: Country
clientId: Client Id
client: Client
amount: Amount
base: Base
ticketId: Ticket Id
active: Active
hasToInvoice: Has to Invoice
verifiedData: Verified Data
@ -648,77 +527,39 @@ shelving:
priority: Priority
newShelving: New Shelving
summary:
code: Code
parking: Parking
priority: Priority
worker: Worker
recyclable: Recyclable
basicData:
code: Code
parking: Parking
priority: Priority
recyclable: Recyclable
parking:
pickingOrder: Picking order
sector: Sector
row: Row
column: Column
pageTitles:
parking: Parking
searchBar:
info: You can search by parking code
label: Search parking...
order:
field:
salesPersonFk: Sales Person
clientFk: Client
isConfirmed: Confirmed
created: Created
landed: Landed
hour: Hour
agency: Agency
total: Total
form:
clientFk: Client
addressFk: Address
landed: Landed
agencyModeFk: Agency
list:
newOrder: New Order
summary:
basket: Basket
nickname: Nickname
company: Company
confirmed: Confirmed
notConfirmed: Not confirmed
created: Created
landed: Landed
phone: Phone
createdFrom: Created From
address: Address
notes: Notes
subtotal: Subtotal
total: Total
vat: VAT
state: State
alias: Alias
items: Items
orderTicketList: Order Ticket List
details: Details
item: Item
quantity: Quantity
price: Price
amount: Amount
confirm: Confirm
confirmLines: Confirm lines
department:
pageTitles:
basicData: Basic data
department: Department
summary: Summary
name: Name
code: Code
chat: Chat
bossDepartment: Boss Department
email: Email
selfConsumptionCustomer: Self-consumption customer
telework: Telework
notifyOnErrors: Notify on errors
@ -727,47 +568,11 @@ department:
hasToSendMail: Send check-ins by email
departmentRemoved: Department removed
worker:
pageTitles:
workers: Workers
list: List
basicData: Basic data
summary: Summary
notifications: Notifications
workerCreate: New worker
department: Department
pda: PDA
notes: Notas
dms: My documentation
pbx: Private Branch Exchange
log: Log
calendar: Calendar
timeControl: Time control
locker: Locker
balance: Balance
medical: Medical
operator: Operator
list:
name: Name
email: Email
phone: Phone
mobile: Mobile
active: Active
department: Department
schedule: Schedule
newWorker: New worker
card:
workerId: Worker ID
user: User
name: Name
email: Email
phone: Phone
mobile: Mobile
active: Active
warehouse: Warehouse
agency: Agency
salesPerson: Sales person
summary:
basicData: Basic data
boss: Boss
phoneExtension: Phone extension
entPhone: Enterprise phone
@ -778,15 +583,15 @@ worker:
role: Role
sipExtension: Extension
locker: Locker
fiDueDate: Fecha de caducidad del DNI
sex: Sexo
seniority: Antigüedad
fiDueDate: FI due date
sex: Sex
seniority: Seniority
fi: DNI/NIE/NIF
birth: Fecha de nacimiento
isFreelance: Autónomo
birth: Birth
isFreelance: Freelance
isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para llevar maquinaria
isDisable: Trabajador desactivado
hasMachineryAuthorized: Machinery authorized
isDisable: Disable
notificationsManager:
activeNotifications: Active notifications
availableNotifications: Available notifications
@ -799,19 +604,12 @@ worker:
serialNumber: Serial number
removePDA: Deallocate PDA
create:
name: Name
lastName: Last name
birth: Birth
fi: Fi
code: Worker code
phone: Phone
postcode: Postcode
province: Province
city: City
street: Street
webUser: Web user
personalEmail: Personal email
company: Company
boss: Boss
payMethods: Pay method
iban: IBAN
@ -823,16 +621,13 @@ worker:
endDate: End date
center: Training center
invoice: Invoice
amount: Amount
remark: Remark
hasDiploma: Has diploma
medical:
tableVisibleColumns:
date: Date
time: Hour
center: Formation Center
invoice: Invoice
amount: Amount
isFit: Fit
remark: Observations
imageNotFound: Image not found
@ -856,18 +651,7 @@ worker:
isOnReservationMode: Reservation mode
machine: Machine
wagon:
pageTitles:
wagons: Wagons
wagonsList: Wagons List
wagonCreate: Create wagon
wagonEdit: Edit wagon
typesList: Types List
typeCreate: Create type
typeEdit: Edit type
wagonCounter: Trolley counter
wagonTray: Tray List
type:
name: Name
submit: Submit
reset: Reset
trayColor: Tray color
@ -875,13 +659,10 @@ wagon:
list:
plate: Plate
volume: Volume
type: Type
remove: Remove
removeItem: Wagon removed successfully
create:
plate: Plate
volume: Volume
type: Type
label: Label
warnings:
noData: No data available
@ -898,26 +679,17 @@ wagon:
supplier:
list:
payMethod: Pay method
payDeadline: Pay deadline
payDay: Pay day
account: Account
newSupplier: New supplier
tableVisibleColumns:
id: Id
name: Name
nif: NIF/CIF
nickname: Alias
account: Account
payMethod: Pay Method
payDay: Pay Day
country: Country
summary:
responsible: Responsible
notes: Notes
verified: Verified
isActive: Is active
billingData: Billing data
payMethod: Pay method
payDeadline: Pay deadline
payDay: Pay day
account: Account
@ -930,20 +702,16 @@ supplier:
fiscalAddress: Fiscal address
socialName: Social name
taxNumber: Tax number
street: Street
city: City
postCode: Postcode
province: Province
country: Country
create:
supplierName: Supplier name
basicData:
alias: Alias
workerFk: Responsible
isSerious: Verified
isReal: Verified
isActive: Active
isPayMethodChecked: PayMethod checked
note: Notes
size: Size
fiscalData:
name: Social name *
nif: Tax number *
@ -952,36 +720,18 @@ supplier:
sageWithholdingFk: Sage withholding
sageTransactionTypeFk: Sage transaction type
supplierActivityFk: Supplier activity
healthRegister: Health register
street: Street
postcode: Postcode
city: City *
provinceFk: Province
country: Country
isTrucker: Trucker
isVies: Vies
billingData:
payMethodFk: Billing data
payDemFk: Payment deadline
payDay: Pay day
accounts:
iban: Iban
bankEntity: Bank entity
beneficiary: Beneficiary
contacts:
name: Name
phone: Phone
mobile: Mobile
email: Email
observation: Notes
addresses:
street: Street
postcode: Postcode
phone: Phone
name: Name
city: City
province: Province
mobile: Mobile
agencyTerms:
agencyFk: Agency
minimumM3: Minimum M3
@ -993,25 +743,16 @@ supplier:
addRow: Add row
consumption:
entry: Entry
date: Date
reference: Reference
travel:
travelList:
tableVisibleColumns:
id: Id
ref: Reference
agency: Agency
shipped: Shipped
landed: Landed
shipHour: Shipment Hour
landHour: Landing Hour
warehouseIn: Warehouse in
warehouseOut: Warehouse out
totalEntries: Total entries
totalEntriesTooltip: Total entries
daysOnward: Landed days onwards
summary:
confirmed: Confirmed
entryId: Entry Id
freight: Freight
package: Package
@ -1024,66 +765,31 @@ travel:
AddEntry: Add entry
thermographs: Thermographs
hb: HB
variables:
search: Id/Reference
agencyModeFk: Agency
warehouseInFk: ' Warehouse In'
warehouseOutFk: Warehouse Out
landedFrom: Landed from
landedTo: Landed to
continent: Continent out
totalEntries: Total entries
basicData:
reference: Reference
agency: Agency
shipped: Shipped
landed: Landed
warehouseOut: Warehouse Out
warehouseIn: Warehouse In
delivered: Delivered
received: Received
daysInForward: Days in forward
isRaid: Raid
thermographs:
code: Code
temperature: Temperature
state: State
destination: Destination
created: Created
thermograph: Thermograph
reference: Reference
type: Type
company: Company
warehouse: Warehouse
travelFileDescription: 'Travel id { travelId }'
file: File
item:
descriptor:
item: Item
buyer: Buyer
color: Color
category: Category
stems: Stems
visible: Visible
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
producer: Producer
list:
id: Identifier
grouping: Grouping
packing: Packing
description: Description
stems: Stems
category: Category
typeName: Type
intrastat: Intrastat
isActive: Active
size: Size
origin: Origin
userName: Buyer
weightByPiece: Weight/Piece
stemMultiplier: Multiplier
producer: Producer
landed: Landed
fixedPrice:
itemFk: Item ID
groupingPrice: Grouping price
@ -1092,81 +798,57 @@ item:
minPrice: Min price
started: Started
ended: Ended
warehouse: Warehouse
create:
name: Name
tag: Tag
priority: Priority
type: Type
intrastat: Intrastat
origin: Origin
buyRequest:
ticketId: 'Ticket ID'
shipped: 'Shipped'
requester: 'Requester'
requested: 'Requested'
price: 'Price'
attender: 'Atender'
item: 'Item'
achieved: 'Achieved'
concept: 'Concept'
state: 'State'
requester: Requester
requested: Requested
attender: Atender
achieved: Achieved
concept: Concept
summary:
basicData: 'Basic data'
otherData: 'Other data'
description: 'Description'
tax: 'Tax'
tags: 'Tags'
botanical: 'Botanical'
barcode: 'Barcode'
name: 'Nombre'
completeName: 'Nombre completo'
family: 'Familia'
size: 'Medida'
origin: 'Origen'
stems: 'Tallos'
multiplier: 'Multiplicador'
buyer: 'Comprador'
doPhoto: 'Do photo'
intrastatCode: 'Código intrastat'
intrastat: 'Intrastat'
ref: 'Referencia'
relevance: 'Relevancia'
weight: 'Peso (gramos)/tallo'
units: 'Unidades/caja'
expense: 'Gasto'
generic: 'Genérico'
recycledPlastic: 'Plástico reciclado'
nonRecycledPlastic: 'Plástico no reciclado'
minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus'
specie: 'Specie'
otherData: Other data
tax: Tax
botanical: Botanical
barcode: Barcode
completeName: Complete name
family: Familiy
stems: Stems
multiplier: Multiplier
buyer: Buyer
doPhoto: Do photo
intrastatCode: Intrastat code
ref: Reference
relevance: Relevance
weight: Weight (gram)/stem
units: Units/box
expense: Expense
generic: Generic
recycledPlastic: Recycled plastic
nonRecycledPlastic: Non recycled plastic
minSalesQuantity: Min sales quantity
genus: Genus
specie: Specie
components:
topbar: {}
itemsFilterPanel:
typeFk: Type
tag: Tag
value: Value
# ItemFixedPriceFilter
buyerFk: Buyer
warehouseFk: Warehouse
started: From
ended: To
mine: For me
hasMinPrice: Minimum price
# LatestBuysFilter
salesPersonFk: Buyer
supplierFk: Supplier
from: From
to: To
active: Is active
visible: Is visible
floramondo: Is floramondo
showBadDates: Show future items
userPanel:
copyToken: Token copied to clipboard
settings: Settings
logOut: Log Out
localWarehouse: Local warehouse
localBank: Local bank
localCompany: Local company
@ -1174,7 +856,6 @@ components:
userCompany: User company
smartCard:
downloadFile: Download file
clone: Clone
openCard: View
openSummary: Summary
cardDescriptor:

View File

@ -58,8 +58,8 @@ globals:
downloadCSVSuccess: Descarga de CSV exitosa
reference: Referencia
agency: Agencia
wareHouseOut: Alm. salida
wareHouseIn: Alm. entrada
warehouseOut: Alm. salida
warehouseIn: Alm. entrada
landed: F. entrega
shipped: F. envío
totalEntries: Ent. totales
@ -108,6 +108,29 @@ globals:
weight: Peso
error: ¡Ups! Algo salió mal
recalc: Recalcular
alias: Alias
vat: IVA
intrastat: Intrastat
tags: Etiquetas
size: Medida
producer: Productor
origin: Origen
state: Estado
subtotal: Subtotal
visible: Visible
price: Precio
client: Cliente
country: País
phone: Teléfono
mobile: Móvil
postcode: Código postal
street: Dirección
tag: Etiqueta
ticketId: ID ticket
confirmed: Confirmado
small: Pequeño/a
medium: Mediano/a
big: Grande
pageTitles:
logIn: Inicio de sesión
addressEdit: Modificar consignatario
@ -282,6 +305,7 @@ globals:
medical: Mutua
wasteRecalc: Recalcular mermas
operator: Operario
parking: Parking
supplier: Proveedor
created: Fecha creación
worker: Trabajador
@ -300,13 +324,10 @@ globals:
maxTemperature: Máx
minTemperature: Mín
params:
id: Id
clientFk: Id cliente
salesPersonFk: Comercial
warehouseFk: Almacén
provinceFk: Provincia
from: Desde
To: Hasta
stateFk: Estado
departmentFk: Departamento
email: Correo
@ -316,6 +337,7 @@ globals:
changePass: Cambiar contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado
raid: 'Redada {daysInForward} días'
errors:
statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor
@ -336,13 +358,9 @@ login:
fieldRequired: Este campo es obligatorio
twoFactorRequired: Verificación de doble factor requerida
twoFactor:
code: Código
validate: Validar
insert: Introduce el código de verificación
explanation: Por favor introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
verifyEmail:
pageTitles:
verifyEmail: Verificación de correo
recoverPassword:
userOrEmail: Usuario o correo de recuperación
explanation: >-
@ -354,15 +372,7 @@ resetPassword:
entry:
list:
newEntry: Nueva entrada
landed: F. entrega
invoiceNumber: Núm. factura
supplier: Proveedor
booked: Asentado
confirmed: Confirmado
ordered: Pedida
tableVisibleColumns:
id: Id
reference: Referencia
created: Creación
supplierFk: Proveedor
isBooked: Asentado
@ -371,18 +381,13 @@ entry:
companyFk: Empresa
travelFk: Envio
isExcludedFromAvailable: Inventario
isRaid: Redada
invoiceAmount: Importe
summary:
commission: Comisión
currency: Moneda
company: Empresa
reference: Referencia
invoiceNumber: Núm. factura
ordered: Pedida
confirmed: Confirmada
booked: Contabilizada
raid: Redada
excludedFromAvailable: Inventario
travelReference: Referencia
travelAgency: Agencia
@ -390,232 +395,108 @@ entry:
travelWarehouseOut: Alm. salida
travelDelivered: Enviada
travelLanded: F. entrega
travelWarehouseIn: Alm. entrada
travelReceived: Recibida
buys: Compras
quantity: Cantidad
stickers: Etiquetas
package: Embalaje
weight: Peso
packing: Packing
grouping: Grouping
buyingValue: Coste
import: Importe
pvp: PVP
item: Artículo
basicData:
supplier: Proveedor
travel: Envío
reference: Referencia
invoiceNumber: Núm. factura
company: Empresa
currency: Moneda
observation: Observación
commission: Comisión
ordered: Pedida
confirmed: Confirmado
booked: Asentado
raid: Redada
excludedFromAvailable: Inventario
agency: Agencia
warehouseOut: Alm. salida
warehouseIn: Alm. entrada
shipped: F. envío
landed: F. entrega
id: ID
buys:
groupingPrice: Precio grouping
packingPrice: Precio packing
reference: Referencia
observations: Observaciónes
item: Artículo
size: Medida
packing: Packing
grouping: Grouping
buyingValue: Coste
packagingFk: Embalaje
file: Fichero
name: Nombre
producer: Productor
type: Tipo
color: Color
id: ID
printedStickers: Etiquetas impresas
notes:
observationType: Tipo de observación
descriptor:
agency: Agencia
landed: F. entrega
warehouseOut: Alm. salida
latestBuys:
tableVisibleColumns:
image: Foto
itemFk: Id Artículo
packing: packing
grouping: Grouping
quantity: Cantidad
size: Medida
tags: Etiquetas
type: Tipo
intrastat: Intrastat
origin: Origen
weightByPiece: Peso (gramos)/tallo
isActive: Activo
family: Familia
entryFk: Entrada
buyingValue: Coste
freightValue: Porte
comissionValue: Comisión
description: Descripción
packageValue: Embalaje
isIgnored: Ignorado
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
weight: Peso
packagingFk: Embalaje
packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Es inventario
isRaid: Redada
ticket:
pageTitles:
tickets: Tickets
list: Listado
ticketCreate: Nuevo ticket
summary: Resumen
basicData: Datos básicos
boxing: Encajado
sms: Sms
notes: Notas
sale: Lineas del pedido
dms: Gestión documental
volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro
expedition: Expedición
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
saleTracking: Líneas preparadas
services: Servicios
tracking: Estados
components: Componentes
pictures: Fotos
packages: Embalajes
list:
nickname: Alias
state: Estado
shipped: Enviado
landed: Entregado
salesPerson: Comercial
total: Total
card:
ticketId: ID ticket
state: Estado
customerId: ID cliente
salesPerson: Comercial
agency: Agencia
shipped: Enviado
warehouse: Almacén
customerCard: Ficha del cliente
alias: Alias
ticketList: Listado de tickets
newOrder: Nuevo pedido
boxing:
expedition: Expedición
item: Artículo
created: Creado
worker: Trabajador
selectTime: 'Seleccionar hora:'
selectVideo: 'Seleccionar vídeo:'
notFound: No hay vídeos disponibles
summary:
state: Estado
salesPerson: Comercial
agency: Agencia
zone: Zona
warehouse: Almacén
collection: Colección
route: Ruta
invoice: Factura
shipped: Enviado
landed: Entregado
consigneePhone: Tel. consignatario
consigneeMobile: Móv. consignatario
consigneeAddress: Dir. consignatario
clientPhone: Tel. cliente
clientMobile: Móv. cliente
consignee: Consignatario
subtotal: Subtotal
vat: IVA
total: Total
saleLines: Líneas del pedido
item: Artículo
visible: Visible
available: Disponible
quantity: Cantidad
price: Precio
discount: Descuento
packing: Encajado
hasComponentLack: Faltan componentes
itemShortage: No visible
claim: Reclamación
reserved: Reservado
created: Fecha creación
package: Embalaje
taxClass: Tipo IVA
services: Servicios
requester: Solicitante
atender: Comprador
request: Petición de compra
weight: Peso
goTo: Ir a
summaryAmount: Resumen
purchaseRequest: Petición de compra
service: Servicio
description: Descripción
attender: Consignatario
create:
client: Cliente
address: Dirección
landed: F. entrega
warehouse: Almacén
agency: Agencia
invoiceOut:
list:
ref: Referencia
issued: Fecha emisión
shortIssued: F. emisión
client: Cliente
created: Fecha creación
shortCreated: F. creación
company: Empresa
dued: Fecha vencimineto
shortDued: F. vencimiento
amount: Importe
card:
issued: Fecha emisión
client: Cliente
company: Empresa
customerCard: Ficha del cliente
ticketList: Listado de tickets
summary:
issued: Fecha
created: Fecha creación
dued: Vencimiento
booked: Contabilizada
company: Empresa
taxBreakdown: Desglose impositivo
type: Tipo
taxableBase: Base imp.
rate: Tarifa
fee: Cuota
tickets: Tickets
ticketId: Id ticket
nickname: Alias
shipped: F. envío
totalWithVat: Importe
globalInvoices:
errors:
@ -629,20 +510,14 @@ invoiceOut:
noTicketsToInvoice: No existen tickets para facturar
criticalInvoiceError: Error crítico en la facturación proceso detenido
table:
client: Cliente
addressId: Id dirección
streetAddress: Dirección fiscal
statusCard:
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
negativeBases:
company: Empresa
country: País
clientId: Id cliente
client: Cliente
amount: Importe
base: Base
ticketId: Id ticket
active: Activo
hasToInvoice: Facturar
verifiedData: Datos comprobados
@ -652,79 +527,44 @@ invoiceOut:
order:
field:
salesPersonFk: Comercial
clientFk: Cliente
isConfirmed: Confirmada
created: Creado
landed: F. entrega
hour: Hora
agency: Agencia
total: Total
form:
clientFk: Cliente
addressFk: Dirección
landed: F. entrega
agencyModeFk: Agencia
list:
newOrder: Nuevo Pedido
summary:
basket: Cesta
nickname: Alias
company: Empresa
confirmed: Confirmada
notConfirmed: No confirmada
created: Creado
landed: F. entrega
phone: Teléfono
createdFrom: Creado desde
address: Dirección
notes: Notas
subtotal: Subtotal
total: Total
vat: IVA
state: Estado
alias: Alias
items: Items
items: Artículos
orderTicketList: Tickets del pedido
details: Detalles
item: Item
quantity: Cantidad
price: Precio
amount: Monto
confirm: Confirmar
confirmLines: Confirmar lineas
shelving:
list:
parking: Parking
priority: Prioridad
newShelving: Nuevo Carro
summary:
code: Código
parking: Parking
priority: Prioridad
worker: Trabajador
recyclable: Reciclable
basicData:
code: Código
parking: Parking
priority: Prioridad
recyclable: Reciclable
parking:
pickingOrder: Orden de recogida
row: Fila
column: Columna
pageTitles:
parking: Parking
searchBar:
info: Puedes buscar por código de parking
label: Buscar parking...
department:
pageTitles:
basicData: Basic data
department: Departamentos
summary: Resumen
name: Nombre
code: Código
chat: Chat
bossDepartment: Jefe de departamento
email: Email
selfConsumptionCustomer: Cliente autoconsumo
telework: Teletrabaja
notifyOnErrors: Notificar errores
@ -733,52 +573,15 @@ department:
hasToSendMail: Enviar fichadas por mail
departmentRemoved: Departamento eliminado
worker:
pageTitles:
workers: Trabajadores
list: Listado
basicData: Datos básicos
summary: Resumen
notifications: Notificaciones
workerCreate: Nuevo trabajador
department: Departamentos
pda: PDA
notes: Notas
dms: Mi documentación
pbx: Centralita
log: Historial
calendar: Calendario
timeControl: Control de horario
locker: Taquilla
balance: Balance
formation: Formación
medical: Mutua
operator: Operario
list:
name: Nombre
email: Email
phone: Teléfono
mobile: Móvil
active: Activo
department: Departamento
schedule: Horario
newWorker: Nuevo trabajador
card:
workerId: ID Trabajador
user: Usuario
name: Nombre
email: Correo personal
phone: Teléfono
mobile: Móvil
active: Activo
warehouse: Almacén
agency: Empresa
salesPerson: Comercial
summary:
basicData: Datos básicos
boss: Jefe
phoneExtension: Extensión de teléfono
entPhone: Teléfono de empresa
personalPhone: Teléfono personal
phoneExtension: Ext. de teléfono
entPhone: Tel. de empresa
personalPhone: Tel. personal
noBoss: Sin jefe
userData: Datos de usuario
userId: ID del usuario
@ -797,19 +600,12 @@ worker:
serialNumber: Número de serie
removePDA: Desasignar PDA
create:
name: Nombre
lastName: Apellido
birth: Fecha de nacimiento
fi: DNI/NIF/NIE
code: Código de trabajador
phone: Teléfono
postcode: Código postal
province: Provincia
city: Población
street: Dirección
webUser: Usuario Web
personalEmail: Correo personal
company: Empresa
boss: Jefe
payMethods: Método de pago
iban: IBAN
@ -821,16 +617,13 @@ worker:
endDate: Fecha Fin
center: Centro Formación
invoice: Factura
amount: Importe
remark: Bonficado
hasDiploma: Diploma
medical:
tableVisibleColumns:
date: Fecha
time: Hora
center: Centro de Formación
invoice: Factura
amount: Importe
isFit: Apto
remark: Observaciones
imageNotFound: No se ha encontrado la imagen
@ -855,18 +648,7 @@ worker:
machine: Máquina
wagon:
pageTitles:
wagons: Vagones
wagonsList: Listado vagones
wagonCreate: Crear tipo
wagonEdit: Editar tipo
typesList: Listado tipos
typeCreate: Crear tipo
typeEdit: Editar tipo
wagonCounter: Contador de carros
wagonTray: Listado bandejas
type:
name: Nombre
submit: Guardar
reset: Deshacer cambios
trayColor: Color de la bandeja
@ -874,13 +656,9 @@ wagon:
list:
plate: Matrícula
volume: Volumen
type: Tipo
remove: Borrar
removeItem: Vagón borrado correctamente
create:
plate: Matrícula
volume: Volumen
type: Tipo
label: Etiqueta
warnings:
noData: Sin datos disponibles
@ -896,26 +674,16 @@ wagon:
supplier:
list:
payMethod: Método de pago
payDeadline: Plazo de pago
payDay: Día de pago
account: Cuenta
newSupplier: Nuevo proveedor
tableVisibleColumns:
id: Id
name: Nombre
nif: NIF/CIF
nickname: Alias
account: Cuenta
payMethod: Método de pago
payDay: Dia de pago
country: País
summary:
responsible: Responsable
notes: Notas
verified: Verificado
isActive: Está activo
billingData: Forma de pago
payMethod: Método de pago
payDeadline: Plazo de pago
payDay: Día de pago
account: Cuenta
@ -928,20 +696,17 @@ supplier:
fiscalAddress: Dirección fiscal
socialName: Razón social
taxNumber: NIF/CIF
street: Dirección
city: Población
postCode: Código postal
province: Provincia
country: País
create:
supplierName: Nombre del proveedor
basicData:
alias: Alias
workerFk: Responsable
isSerious: Verificado
isReal: Verificado
isActive: Activo
isPayMethodChecked: Método de pago validado
note: Notas
size: Tamaño
fiscalData:
name: Razón social *
nif: NIF/CIF *
@ -950,36 +715,17 @@ supplier:
sageWithholdingFk: Retención sage
sageTransactionTypeFk: Tipo de transacción sage
supplierActivityFk: Actividad proveedor
healthRegister: Pasaporte sanitario
street: Calle
postcode: Código postal
city: Población *
provinceFk: Provincia
country: País
isTrucker: Transportista
isVies: Vies
billingData:
payMethodFk: Forma de pago
payDemFk: Plazo de pago
payDay: Día de pago
accounts:
iban: Iban
bankEntity: Entidad bancaria
beneficiary: Beneficiario
contacts:
name: Nombre
phone: Teléfono
mobile: Móvil
email: Email
observation: Notas
addresses:
street: Dirección
postcode: Código postal
phone: Teléfono
name: Nombre
city: Población
province: Provincia
mobile: Móvil
agencyTerms:
agencyFk: Agencia
minimumM3: M3 mínimos
@ -991,25 +737,16 @@ supplier:
addRow: Añadir fila
consumption:
entry: Entrada
date: Fecha
reference: Referencia
travel:
travelList:
tableVisibleColumns:
id: Id
ref: Referencia
agency: Agencia
shipped: F.envío
shipHour: Hora de envío
landHour: Hora de llegada
landed: F.entrega
warehouseIn: Alm.salida
warehouseOut: Alm.entrada
totalEntries:
totalEntriesTooltip: Entradas totales
daysOnward: Días de llegada en adelante
summary:
confirmed: Confirmado
entryId: Id entrada
freight: Porte
package: Embalaje
@ -1022,66 +759,31 @@ travel:
AddEntry: Añadir entrada
thermographs: Termógrafos
hb: HB
variables:
search: Id/Referencia
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: ' Alm. salida'
landedFrom: Llegada desde
landedTo: Llegada hasta
continent: Cont. Salida
totalEntries: Ent. totales
basicData:
reference: Referencia
agency: Agencia
shipped: F. Envío
landed: F. entrega
warehouseOut: Alm. salida
warehouseIn: Alm. entrada
delivered: Enviada
received: Recibida
daysInForward: Días redada
isRaid: Redada
thermographs:
code: Código
temperature: Temperatura
state: Estado
destination: Destino
created: Fecha creación
thermograph: Termógrafo
reference: Referencia
type: Tipo
company: Empresa
warehouse: Almacén
travelFileDescription: 'Id envío { travelId }'
file: Fichero
item:
descriptor:
item: Artículo
buyer: Comprador
color: Color
category: Categoría
stems: Tallos
visible: Visible
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
producer: Productor
list:
id: Identificador
grouping: Grouping
packing: Packing
description: Descripción
stems: Tallos
category: Reino
typeName: Tipo
intrastat: Intrastat
isActive: Activo
size: Medida
origin: Origen
weightByPiece: Peso (gramos)/tallo
userName: Comprador
stemMultiplier: Multiplicador
producer: Productor
landed: F. entrega
fixedPrice:
itemFk: ID Artículo
groupingPrice: Precio grouping
@ -1090,79 +792,56 @@ item:
minPrice: Precio min
started: Inicio
ended: Fin
warehouse: Almacén
create:
name: Nombre
tag: Etiqueta
priority: Prioridad
type: Tipo
intrastat: Intrastat
origin: Origen
summary:
basicData: 'Datos básicos'
otherData: 'Otros datos'
description: 'Descripción'
tax: 'IVA'
tags: 'Etiquetas'
botanical: 'Botánico'
barcode: 'Código de barras'
name: 'Nombre'
completeName: 'Nombre completo'
family: 'Familia'
size: 'Medida'
origin: 'Origen'
stems: 'Tallos'
multiplier: 'Multiplicador'
buyer: 'Comprador'
doPhoto: 'Hacer foto'
intrastatCode: 'Código intrastat'
intrastat: 'Intrastat'
ref: 'Referencia'
relevance: 'Relevancia'
weight: 'Peso (gramos)/tallo'
units: 'Unidades/caja'
expense: 'Gasto'
generic: 'Genérico'
recycledPlastic: 'Plástico reciclado'
nonRecycledPlastic: 'Plástico no reciclado'
minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus'
specie: 'Specie'
otherData: Otros datos
tax: IVA
botanical: Botánico
barcode: Código de barras
completeName: Nombre completo
family: Familia
stems: Tallos
multiplier: Multiplicador
buyer: Comprador
doPhoto: Hacer foto
intrastatCode: Código intrastat
ref: Referencia
relevance: Relevancia
weight: Peso (gramos)/tallo
units: Unidades/caja
expense: Gasto
generic: Genérico
recycledPlastic: Plástico reciclado
nonRecycledPlastic: Plástico no reciclado
minSalesQuantity: Cantidad mínima de venta
genus: Genus
specie: Specie
buyRequest:
ticketId: 'ID Ticket'
shipped: 'F. envío'
requester: 'Solicitante'
requested: 'Solicitado'
price: 'Precio'
attender: 'Comprador'
item: 'Artículo'
achieved: 'Conseguido'
concept: 'Concepto'
state: 'Estado'
requester: Solicitante
requested: Solicitado
attender: Comprador
achieved: Conseguido
concept: Concepto
components:
topbar: {}
itemsFilterPanel:
typeFk: Tipo
tag: Etiqueta
value: Valor
# ItemFixedPriceFilter
buyerFk: Comprador
warehouseFk: Almacén
started: Desde
ended: Hasta
mine: Para mi
hasMinPrice: Precio mínimo
# LatestBuysFilter
salesPersonFk: Comprador
supplierFk: Proveedor
active: Activo
visible: Visible
floramondo: Floramondo
showBadDates: Ver items a futuro
userPanel:
copyToken: Token copiado al portapapeles
settings: Configuración
logOut: Cerrar sesión
localWarehouse: Almacén local
localBank: Banco local
localCompany: Empresa local
@ -1170,7 +849,6 @@ components:
userCompany: Empresa del usuario
smartCard:
downloadFile: Descargar archivo
clone: Clonar
openCard: Ficha
openSummary: Detalles
viewSummary: Vista previa

View File

@ -11,21 +11,13 @@ const { t } = useI18n();
const { notify } = useNotify();
const onSynchronizeAll = async () => {
try {
notify(t('Synchronizing in the background'), 'positive');
await axios.patch(`Accounts/syncAll`);
} catch (error) {
console.error('Error synchronizing all accounts', error);
}
notify(t('Synchronizing in the background'), 'positive');
await axios.patch(`Accounts/syncAll`);
};
const onSynchronizeRoles = async () => {
try {
await axios.patch(`RoleInherits/sync`);
notify(t('Roles synchronized!'), 'positive');
} catch (error) {
console.error('Error synchronizing roles', error);
}
await axios.patch(`RoleInherits/sync`);
notify(t('Roles synchronized!'), 'positive');
};
</script>

View File

@ -9,6 +9,8 @@ import { useQuasar } from 'quasar';
import VnTable from 'components/VnTable/VnTable.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import FetchData from 'src/components/FetchData.vue';
import { useValidator } from 'src/composables/useValidator';
defineProps({
id: {
@ -23,11 +25,18 @@ const stateStore = useStateStore();
const quasar = useQuasar();
const tableRef = ref();
const roles = ref();
const validationsStore = useValidator();
const { models } = validationsStore;
const exprBuilder = (param, value) => {
switch (param) {
case 'search':
return { model: { like: `%${value}%` } };
return {
or: [
{ model: { like: `%${value}%` } },
{ property: { like: `%${value}%` } },
],
};
default:
return { [param]: value };
}
@ -47,6 +56,13 @@ const columns = computed(() => [
label: t('model'),
cardVisible: true,
create: true,
columnCreate: {
label: t('model'),
component: 'select',
attrs: {
options: Object.keys(models),
},
},
},
{
align: 'left',
@ -55,9 +71,10 @@ const columns = computed(() => [
cardVisible: true,
component: 'select',
attrs: {
url: 'VnRoles',
options: roles,
optionLabel: 'name',
optionValue: 'name',
inputDebounce: 0,
},
create: true,
},
@ -94,29 +111,25 @@ const columns = computed(() => [
},
]);
const deleteAcl = async ({ id }) => {
try {
await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Remove ACL'),
message: t('Do you want to remove this ACL?'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
await axios.delete(`ACLs/${id}`);
tableRef.value.reload();
notify('ACL removed', 'positive');
} catch (error) {
console.error('Error deleting Acl: ', error);
}
await new Promise((resolve) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('Remove ACL'),
message: t('Do you want to remove this ACL?'),
},
})
.onOk(() => {
resolve(true);
})
.onCancel(() => {
resolve(false);
});
});
await axios.delete(`ACLs/${id}`);
tableRef.value.reload();
notify('ACL removed', 'positive');
};
</script>
@ -130,6 +143,11 @@ const deleteAcl = async ({ id }) => {
/>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
</QDrawer>
<FetchData
url="VnRoles?fields=['name']"
auto-load
@on-fetch="(data) => (roles = data)"
/>
<VnTable
ref="tableRef"
data-key="AccountAcls"

View File

@ -34,13 +34,9 @@ const refresh = () => paginateRef.value.fetch();
const navigate = (id) => router.push({ name: 'AccountSummary', params: { id } });
const killSession = async ({ userId, created }) => {
try {
await axios.post(`${urlPath}/killSession`, { userId, created });
paginateRef.value.fetch();
notify(t('Session killed'), 'positive');
} catch (error) {
console.error('Error killing session', error);
}
await axios.post(`${urlPath}/killSession`, { userId, created });
paginateRef.value.fetch();
notify(t('Session killed'), 'positive');
};
</script>

View File

@ -37,7 +37,7 @@ const redirectToAccountBasicData = (_, { id }) => {
<div class="column q-gutter-sm">
<VnInput
v-model="data.name"
:label="t('account.create.name')"
:label="t('globals.name')"
:rules="validate('VnUser.name')"
/>
<VnInput
@ -47,12 +47,12 @@ const redirectToAccountBasicData = (_, { id }) => {
/>
<VnInput
v-model="data.email"
:label="t('account.create.email')"
:label="t('globals.params.email')"
type="email"
:rules="validate('VnUser.email')"
/>
<VnSelect
:label="t('account.create.role')"
:label="t('account.card.role')"
v-model="data.roleFk"
:options="rolesOptions"
option-value="id"
@ -63,7 +63,7 @@ const redirectToAccountBasicData = (_, { id }) => {
/>
<VnInput
v-model="data.password"
:label="t('account.create.password')"
:label="t('ldap.password')"
type="password"
:rules="validate('VnUser.password')"
/>

View File

@ -33,6 +33,7 @@ const rolesOptions = ref([]);
:search-button="true"
:hidden-tags="['search']"
:redirect="false"
search-url="table"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
@ -44,7 +45,7 @@ const rolesOptions = ref([]);
<QItem class="q-my-sm">
<QItemSection>
<VnInput
:label="t('account.card.name')"
:label="t('globals.name')"
v-model="params.name"
lazy-rules
is-outlined

View File

@ -40,12 +40,8 @@ const formUrlCreate = ref(null);
const formUrlUpdate = ref(null);
const formCustomFn = ref(null);
const onTestConection = async () => {
try {
await axios.get(`LdapConfigs/test`);
notify(t('LDAP connection established!'), 'positive');
} catch (error) {
console.error('Error testing connection', error);
}
await axios.get(`LdapConfigs/test`);
notify(t('LDAP connection established!'), 'positive');
};
const getInitialLdapConfig = async () => {
try {
@ -72,14 +68,10 @@ const getInitialLdapConfig = async () => {
}
};
const deleteMailForward = async () => {
try {
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error deleting mail forward', err);
}
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
};
onMounted(async () => await getInitialLdapConfig());
@ -102,11 +94,11 @@ onMounted(async () => await getInitialLdapConfig());
<QBtn
class="q-ml-none"
color="primary"
:label="t('ldap.testConnection')"
:label="t('account.card.testConnection')"
@click="onTestConection()"
>
<QTooltip>
{{ t('ldap.testConnection') }}
{{ t('account.card.testConnection') }}
</QTooltip>
</QBtn>
</template>
@ -114,7 +106,7 @@ onMounted(async () => await getInitialLdapConfig());
<VnRow class="row q-gutter-md">
<div class="col">
<QCheckbox
:label="t('ldap.enableSync')"
:label="t('account.card.enableSync')"
v-model="data.hasData"
@update:model-value="($event) => (hasData = $event)"
:toggle-indeterminate="false"
@ -146,7 +138,7 @@ onMounted(async () => await getInitialLdapConfig());
/>
<VnInput :label="t('ldap.userDN')" clearable v-model="data.userDn" />
<VnInput
:label="t('ldap.groupDN')"
:label="t('account.card.groupDN')"
clearable
v-model="data.groupDn"
/>

View File

@ -74,7 +74,7 @@ const columns = computed(() => [
name: 'tableActions',
actions: [
{
title: t('View Summary'),
title: t('components.smartCard.viewSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, AccountSummary),
isPrimary: true,
@ -104,7 +104,7 @@ const exprBuilder = (param, value) => {
<template>
<VnSearchbar
data-key="AccountUsers"
data-key="AccountList"
:expr-builder="exprBuilder"
:label="t('account.search')"
:info="t('account.searchInfo')"
@ -112,12 +112,12 @@ const exprBuilder = (param, value) => {
/>
<RightMenu>
<template #right-panel>
<AccountFilter data-key="AccountUsers" />
<AccountFilter data-key="AccountList" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="AccountUsers"
data-key="AccountList"
url="VnUsers/preview"
:filter="filter"
order="id DESC"

View File

@ -46,12 +46,8 @@ const formUrlUpdate = ref(null);
const formCustomFn = ref(null);
const onTestConection = async () => {
try {
await axios.get(`SambaConfigs/test`);
notify(t('Samba connection established!'), 'positive');
} catch (error) {
console.error('Error testing connection', error);
}
await axios.get(`SambaConfigs/test`);
notify(t('Samba connection established!'), 'positive');
};
const getInitialSambaConfig = async () => {
@ -79,14 +75,10 @@ const getInitialSambaConfig = async () => {
};
const deleteMailForward = async () => {
try {
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error deleting mail forward', err);
}
await axios.delete(URL_UPDATE);
initialData.value = { ...DEFAULT_DATA };
hasData.value = false;
notify(t('globals.dataSaved'), 'positive');
};
onMounted(async () => await getInitialSambaConfig());
@ -110,12 +102,12 @@ onMounted(async () => await getInitialSambaConfig());
<QBtn
class="q-ml-none"
color="primary"
:label="t('samba.testConnection')"
:label="t('account.card.testConnection')"
:disable="formModel.hasChanges"
@click="onTestConection()"
>
<QTooltip>
{{ t('samba.testConnection') }}
{{ t('account.card.testConnection') }}
</QTooltip>
</QBtn>
</template>
@ -123,7 +115,7 @@ onMounted(async () => await getInitialSambaConfig());
<VnRow class="row q-gutter-md">
<div class="col">
<QCheckbox
:label="t('samba.enableSync')"
:label="t('account.card.enableSync')"
v-model="data.hasData"
@update:model-value="($event) => (hasData = $event)"
:toggle-indeterminate="false"

View File

@ -36,15 +36,12 @@ const onDataSaved = ({ id }) => {
<template #form-inputs="{ data }">
<VnRow>
<div class="col">
<VnInput v-model="data.alias" :label="t('mailAlias.name')" />
<VnInput v-model="data.alias" :label="t('globals.name')" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
v-model="data.description"
:label="t('mailAlias.description')"
/>
<VnInput v-model="data.description" :label="t('role.description')" />
</div>
</VnRow>
</template>

View File

@ -11,8 +11,8 @@ const { t } = useI18n();
<FormModel model="Alias">
<template #form="{ data }">
<div class="column q-gutter-y-md">
<VnInput v-model="data.alias" :label="t('mailAlias.name')" />
<VnInput v-model="data.description" :label="t('mailAlias.description')" />
<VnInput v-model="data.alias" :label="t('globals.name')" />
<VnInput v-model="data.description" :label="t('role.description')" />
<QCheckbox :label="t('mailAlias.isPublic')" v-model="data.isPublic" />
</div>
</template>

View File

@ -44,13 +44,9 @@ const removeAlias = () => {
cancel: true,
})
.onOk(async () => {
try {
await axios.delete(`MailAliases/${entityId.value}`);
notify(t('Alias removed'), 'positive');
router.push({ name: 'AccountAlias' });
} catch (err) {
console.error('Error removing alias');
}
await axios.delete(`MailAliases/${entityId.value}`);
notify(t('Alias removed'), 'positive');
router.push({ name: 'AccountAlias' });
});
};
</script>
@ -71,7 +67,7 @@ const removeAlias = () => {
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('mailAlias.description')" :value="entity.description" />
<VnLv :label="t('role.description')" :value="entity.description" />
</template>
</CardDescriptor>
</template>

View File

@ -42,8 +42,8 @@ const entityId = computed(() => $props.id || route.params.id);
<QIcon name="open_in_new" />
</router-link>
</QCardSection>
<VnLv :label="t('mailAlias.id')" :value="alias.id" />
<VnLv :label="t('mailAlias.description')" :value="alias.description" />
<VnLv :label="t('role.id')" :value="alias.id" />
<VnLv :label="t('role.description')" :value="alias.description" />
</QCard>
</template>
</CardSummary>

View File

@ -46,13 +46,9 @@ const columns = computed(() => [
]);
const deleteAlias = async (row) => {
try {
await axios.delete(`${urlPath.value}/${row.id}`);
notify(t('User removed'), 'positive');
fetchAliases();
} catch (error) {
console.error(error);
}
await axios.delete(`${urlPath.value}/${row.id}`);
notify(t('User removed'), 'positive');
fetchAliases();
};
watch(

View File

@ -36,7 +36,7 @@ watch(
<div class="q-gutter-y-sm">
<VnInput v-model="data.name" :label="t('account.card.nickname')" />
<VnInput v-model="data.nickname" :label="t('account.card.alias')" />
<VnInput v-model="data.email" :label="t('account.card.email')" />
<VnInput v-model="data.email" :label="t('globals.params.email')" />
<VnSelect
url="Languages"
v-model="data.lang"

View File

@ -54,7 +54,7 @@ const hasAccount = ref(false);
</template>
<template #before>
<!-- falla id :id="entityId.value" collection="user" size="160x160" -->
<VnImg :id="entityId" collection="user" resolution="160x160" class="photo">
<VnImg :id="entityId" collection="user" resolution="520x520" class="photo">
<template #error>
<div
class="absolute-full picture text-center q-pa-md flex flex-center"

View File

@ -61,23 +61,15 @@ const fetchAccountExistence = async () => {
};
const deleteMailAlias = async (row) => {
try {
await axios.delete(`${urlPath}/${row.id}`);
fetchMailAliases();
notify(t('Unsubscribed from alias!'), 'positive');
} catch (error) {
console.error(error);
}
await axios.delete(`${urlPath}/${row.id}`);
fetchMailAliases();
notify(t('Unsubscribed from alias!'), 'positive');
};
const createMailAlias = async (mailAliasFormData) => {
try {
await axios.post(urlPath, mailAliasFormData);
notify(t('Subscribed to alias!'), 'positive');
fetchMailAliases();
} catch (error) {
console.error(error);
}
await axios.post(urlPath, mailAliasFormData);
notify(t('Subscribed to alias!'), 'positive');
fetchMailAliases();
};
const fetchMailAliases = async () => {

View File

@ -41,35 +41,22 @@ const fetchAccountExistence = async () => {
};
const fetchMailForwards = async () => {
try {
const response = await axios.get(`MailForwards/${route.params.id}`);
return response.data;
} catch (err) {
console.error('Error fetching mail forwards', err);
return null;
}
const response = await axios.get(`MailForwards/${route.params.id}`);
return response.data;
};
const deleteMailForward = async () => {
try {
await axios.delete(`MailForwards/${route.params.id}`);
formData.value.forwardTo = null;
initialData.value.forwardTo = null;
initialData.value.hasData = hasData.value;
notify(t('globals.dataSaved'), 'positive');
} catch (err) {
console.error('Error deleting mail forward', err);
}
await axios.delete(`MailForwards/${route.params.id}`);
formData.value.forwardTo = null;
initialData.value.forwardTo = null;
initialData.value.hasData = hasData.value;
notify(t('globals.dataSaved'), 'positive');
};
const updateMailForward = async () => {
try {
await axios.patch('MailForwards', formData.value);
initialData.value = { ...formData.value };
initialData.value.hasData = hasData.value;
} catch (err) {
console.error('Error creating mail forward', err);
}
await axios.patch('MailForwards', formData.value);
initialData.value = { ...formData.value };
initialData.value.hasData = hasData.value;
};
const onSubmit = async () => {

View File

@ -54,7 +54,7 @@ const columns = computed(() => [
name: 'tableActions',
actions: [
{
title: t('View Summary'),
title: t('components.smartCard.viewSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, RoleSummary),
isPrimary: true,
@ -82,14 +82,14 @@ const exprBuilder = (param, value) => {
<template>
<VnSearchbar
data-key="Roles"
data-key="AccountRolesList"
:expr-builder="exprBuilder"
:label="t('role.searchRoles')"
:info="t('role.searchInfo')"
/>
<VnTable
ref="tableRef"
data-key="Roles"
data-key="AccountRolesList"
:url="`VnRoles`"
:create="{
urlCreate: 'VnRoles',

View File

@ -29,7 +29,7 @@ const props = defineProps({
<QItem class="q-my-sm">
<QItemSection>
<VnInput
:label="t('role.name')"
:label="t('globals.name')"
v-model="params.name"
lazy-rules
is-outlined

View File

@ -12,15 +12,12 @@ const { t } = useI18n();
<template #form="{ data }">
<VnRow>
<div class="col">
<VnInput v-model="data.name" :label="t('role.card.name')" />
<VnInput v-model="data.name" :label="t('globals.name')" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
v-model="data.description"
:label="t('role.card.description')"
/>
<VnInput v-model="data.description" :label="t('role.description')" />
</div>
</VnRow>
</template>

View File

@ -9,7 +9,7 @@ const { t } = useI18n();
<VnCard
data-key="Role"
:descriptor="RoleDescriptor"
search-data-key="AccountRoles"
search-data-key="AccountRolesList"
:searchbar-props="{
url: 'VnRoles',
label: t('role.searchRoles'),

View File

@ -32,12 +32,8 @@ const filter = {
where: { id: entityId },
};
const removeRole = async () => {
try {
await axios.delete(`VnRoles/${entityId.value}`);
notify(t('Role removed'), 'positive');
} catch (error) {
console.error('Error deleting role', error);
}
await axios.delete(`VnRoles/${entityId.value}`);
notify(t('Role removed'), 'positive');
};
</script>
@ -58,7 +54,7 @@ const removeRole = async () => {
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('role.card.description')" :value="entity.description" />
<VnLv :label="t('role.description')" :value="entity.description" />
</template>
</CardDescriptor>
</template>

View File

@ -22,15 +22,12 @@ const { t } = useI18n();
<template #form-inputs="{ data }">
<VnRow>
<div class="col">
<VnInput v-model="data.name" :label="t('role.card.name')" />
<VnInput v-model="data.name" :label="t('globals.name')" />
</div>
</VnRow>
<VnRow>
<div class="col">
<VnInput
v-model="data.description"
:label="t('role.card.description')"
/>
<VnInput v-model="data.description" :label="t('role.description')" />
</div>
</VnRow>
</template>

View File

@ -44,9 +44,9 @@ const filter = {
<QIcon name="open_in_new" />
</a>
</QCardSection>
<VnLv :label="t('role.card.id')" :value="role.id" />
<VnLv :label="t('role.card.name')" :value="role.name" />
<VnLv :label="t('role.card.description')" :value="role.description" />
<VnLv :label="t('role.id')" :value="role.id" />
<VnLv :label="t('globals.name')" :value="role.name" />
<VnLv :label="t('role.description')" :value="role.description" />
</QCard>
</template>
</CardSummary>

View File

@ -46,29 +46,15 @@ const columns = computed(() => [
]);
const deleteSubRole = async (row) => {
try {
await axios.delete(`${urlPath.value}/${row.id}`);
fetchSubRoles();
notify(
t('Role removed. Changes will take a while to fully propagate.'),
'positive'
);
} catch (error) {
console.error(error);
}
await axios.delete(`${urlPath.value}/${row.id}`);
fetchSubRoles();
notify(t('Role removed. Changes will take a while to fully propagate.'), 'positive');
};
const createSubRole = async (subRoleFormData) => {
try {
await axios.post(urlPath.value, subRoleFormData);
notify(
t('Role added! Changes will take a while to fully propagate.'),
'positive'
);
fetchSubRoles();
} catch (error) {
console.error(error);
}
await axios.post(urlPath.value, subRoleFormData);
notify(t('Role added! Changes will take a while to fully propagate.'), 'positive');
fetchSubRoles();
};
watch(

View File

@ -1,32 +1,15 @@
account:
pageTitles:
users: Users
list: Users
roles: Roles
alias: Mail aliasses
accounts: Accounts
ldap: LDAP
samba: Samba
acls: ACLs
connections: Connections
inheritedRoles: Inherited Roles
subRoles: Sub Roles
newRole: New role
privileges: Privileges
mailAlias: Mail Alias
mailForwarding: Mail Forwarding
accountCreate: New user
aliasUsers: Users
card:
name: Name
nickname: User
role: Role
email: Email
alias: Alias
lang: Language
roleFk: Role
newUser: New user
ticketTracking: Ticket tracking
enableSync: Habilitar sincronización
groupDN: DN grupos
testConnection: Probar conexión
privileges:
delegate: Can delegate privileges
enabled: Account enabled!
@ -74,11 +57,7 @@ account:
search: Search user
searchInfo: You can search by id, name or nickname
create:
name: Name
nickname: Nickname
email: Email
role: Role
password: Password
active: Active
mailForwarding:
forwardingMail: Forward email
@ -86,50 +65,30 @@ account:
enableMailForwarding: Enable mail forwarding
mailInputInfo: All emails will be forwarded to the specified address.
role:
pageTitles:
inheritedRoles: Inherited Roles
subRoles: Sub Roles
card:
description: Description
id: Id
name: Name
newRole: New role
searchRoles: Search role
searchInfo: Search role by id or name
name: Name
description: Description
id: Id
mailAlias:
pageTitles:
aliasUsers: Users
search: Search mail alias
searchInfo: Search alias by id or name
alias: Alias
description: Description
id: Id
newAlias: New alias
name: Name
isPublic: Public
ldap:
enableSync: Enable synchronization
server: Server
rdn: RDN
userDN: User DN
filter: Filter
groupDN: Group DN
testConnection: Test connection
success: LDAP connection established!
password: Password
samba:
enableSync: Enable synchronization
domainController: Domain controller
domainAD: AD domain
userAD: AD user
groupDN: Group DN
passwordAD: AD password
domainPart: User DN (without domain part)
verifyCertificate: Verify certificate
testConnection: Test connection
success: Samba connection established!
accounts:
homedir: Homedir base
@ -147,8 +106,6 @@ connections:
created: Created
killSession: Kill session
acls:
role: Role
accessType: Access type
permissions: Permission
search: Search acls
searchInfo: Search acls by model name

View File

@ -1,27 +1,7 @@
account:
pageTitles:
users: Usuarios
list: Usuarios
roles: Roles
alias: Alias de correo
accounts: Cuentas
ldap: LDAP
samba: Samba
acls: ACLs
connections: Conexiones
inheritedRoles: Roles heredados
newRole: Nuevo rol
subRoles: Subroles
privileges: Privilegios
mailAlias: Alias de correo
mailForwarding: Reenvío de correo
accountCreate: Nuevo usuario
aliasUsers: Usuarios
card:
nickname: Usuario
name: Nombre
role: Rol
email: Mail
alias: Alias
lang: Idioma
roleFk: Rol
@ -33,6 +13,9 @@ account:
deactivated: ¡Usuario desactivado!
newUser: Nuevo usuario
twoFactor: Doble factor
enableSync: Habilitar sincronización
groupDN: DN grupos
testConnection: Probar conexión
privileges:
delegate: Puede delegar privilegios
actions:
@ -73,11 +56,7 @@ account:
search: Buscar usuario
searchInfo: Puedes buscar por id, nombre o usuario
create:
name: Nombre
nickname: Nombre mostrado
email: Email
role: Rol
password: Contraseña
active: Activo
mailForwarding:
forwardingMail: Dirección de reenvío
@ -85,51 +64,30 @@ account:
enableMailForwarding: Habilitar redirección de correo
mailInputInfo: Todos los correos serán reenviados a la dirección especificada, no se mantendrá copia de los mismos en el buzón del usuario.
role:
pageTitles:
inheritedRoles: Roles heredados
subRoles: Subroles
newRole: Nuevo rol
card:
description: Descripción
id: Id
name: Nombre
newRole: Nuevo rol
searchRoles: Buscar roles
searchInfo: Buscar rol por id o nombre
name: Nombre
description: Descripción
id: Id
mailAlias:
pageTitles:
aliasUsers: Usuarios
search: Buscar alias de correo
searchInfo: Buscar alias por id o nombre
alias: Alias
description: Descripción
id: Id
newAlias: Nuevo alias
name: Nombre
isPublic: Público
ldap:
password: Contraseña
enableSync: Habilitar sincronización
server: Servidor
rdn: RDN
userDN: DN usuarios
filter: Filtro
groupDN: DN grupos
testConnection: Probar conexión
success: ¡Conexión con LDAP establecida!
samba:
enableSync: Habilitar sincronización
domainController: Controlador de dominio
domainAD: Dominio AD
groupDN: DN grupos
userAD: Usuario AD
passwordAD: Contraseña AD
domainPart: DN usuarios (sin la parte del dominio)
verifyCertificate: Verificar certificado
testConnection: Probar conexión
success: ¡Conexión con Samba establecida!
accounts:
homedir: Directorio base para carpetas de usuario
@ -147,8 +105,6 @@ connections:
created: Creado
killSession: Matar sesión
acls:
role: Rol
accessType: Tipo de acceso
permissions: Permiso
search: Buscar acls
searchInfo: Buscar acls por nombre

View File

@ -130,7 +130,7 @@ function cancel() {
<template #body-cell-description="{ row, value }">
<QTd auto-width align="right" class="link">
{{ value }}
<ItemDescriptorProxy :id="row.itemFk"></ItemDescriptorProxy>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd>
</template>
</QTable>

View File

@ -25,7 +25,7 @@ const claimFilter = computed(() => {
include: {
relation: 'user',
scope: {
fields: ['id', 'nickname'],
fields: ['id', 'nickname', 'name'],
},
},
},

View File

@ -23,7 +23,7 @@ defineExpose({ states });
<template>
<FetchData url="ClaimStates" @on-fetch="(data) => (states = data)" auto-load />
<VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table">
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>

View File

@ -95,6 +95,7 @@ const columns = computed(() => [
optionLabel: 'description',
},
},
orderBy: 'priority',
},
{
align: 'right',

View File

@ -2,6 +2,7 @@
import { onBeforeMount, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import axios from 'axios';
@ -52,7 +53,6 @@ const addressFilter = {
onBeforeMount(() => {
const { id } = route.params;
getAddressesData(id);
getClientData(id);
});
@ -60,23 +60,10 @@ watch(
() => route.params.id,
(newValue) => {
if (!newValue) return;
getAddressesData(newValue);
getClientData(newValue);
}
);
const getAddressesData = async (id) => {
try {
const { data } = await axios.get(`Clients/${id}/addresses`, {
params: { filter: JSON.stringify(addressFilter) },
});
addresses.value = data;
sortAddresses();
} catch (error) {
return error;
}
};
const getClientData = async (id) => {
try {
const { data } = await axios.get(`Clients/${id}`);
@ -101,9 +88,9 @@ const setDefault = (address) => {
});
};
const sortAddresses = () => {
if (!client.value || !addresses.value) return;
addresses.value = addresses.value.sort((a, b) => {
const sortAddresses = (data) => {
if (!client.value || !data) return;
addresses.value = data.sort((a, b) => {
return isDefaultAddress(b) - isDefaultAddress(a);
});
};
@ -124,8 +111,17 @@ const toCustomerAddressEdit = (addressId) => {
</script>
<template>
<FetchData
@on-fetch="sortAddresses"
auto-load
data-key="CustomerAddresses"
order="id DESC"
ref="vnPaginateRef"
:filter="addressFilter"
:url="`Clients/${route.params.id}/addresses`"
/>
<div class="full-width flex justify-center">
<QCard class="card-width q-pa-lg" v-if="addresses.length">
<QCard class="card-width q-pa-lg">
<QCardSection>
<div
v-for="(item, index) in addresses"
@ -167,7 +163,7 @@ const toCustomerAddressEdit = (addressId) => {
<div>{{ item.street }}</div>
<div>
{{ item.postalCode }} - {{ item.city }},
{{ item.province.name }}
{{ item.province?.name }}
</div>
<div>
{{ item.phone }}

View File

@ -256,10 +256,10 @@ const showBalancePdf = ({ id }) => {
{{ toCurrency(balances[rowIndex]?.balance) }}
</template>
<template #column-description="{ row }">
<div class="link" v-if="row.isInvoice">
<span class="link" v-if="row.isInvoice" @click.stop>
{{ t('bill', { ref: row.description }) }}
<InvoiceOutDescriptorProxy :id="row.description" />
</div>
<InvoiceOutDescriptorProxy :id="row.id" />
</span>
<span v-else class="q-pa-xs dotted rounded-borders" :title="row.description">
{{ row.description }}
</span>

View File

@ -9,6 +9,7 @@ import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnAvatar from 'src/components/ui/VnAvatar.vue';
import { getDifferences, getUpdatedValues } from 'src/filters';
const route = useRoute();
const { t } = useI18n();
@ -30,6 +31,13 @@ const exprBuilder = (param, value) => {
and: [{ active: { neq: false } }, handleSalesModelValue(value)],
};
};
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData
);
}
</script>
<template>
<FetchData
@ -43,7 +51,12 @@ const exprBuilder = (param, value) => {
@on-fetch="(data) => (businessTypes = data)"
auto-load
/>
<FormModel :url="`Clients/${route.params.id}`" auto-load model="customer">
<FormModel
:url="`Clients/${route.params.id}`"
auto-load
model="customer"
:mapper="onBeforeSave"
>
<template #form="{ data, validate }">
<VnRow>
<VnInput
@ -55,7 +68,7 @@ const exprBuilder = (param, value) => {
/>
<VnSelect
:input-debounce="0"
:label="t('customer.basicData.businessType')"
:label="t('customer.summary.businessType')"
:options="businessTypes"
:rules="validate('client.businessTypeFk')"
emit-value
@ -67,13 +80,13 @@ const exprBuilder = (param, value) => {
</VnRow>
<VnRow>
<VnInput
:label="t('customer.basicData.contact')"
:label="t('customer.summary.contact')"
:rules="validate('client.contact')"
clearable
v-model="data.contact"
/>
<VnInput
:label="t('customer.basicData.email')"
:label="t('globals.params.email')"
:rules="validate('client.email')"
clearable
type="email"
@ -90,13 +103,14 @@ const exprBuilder = (param, value) => {
</VnRow>
<VnRow>
<VnInput
:label="t('customer.basicData.phone')"
:label="t('customer.extendedList.tableVisibleColumns.phone')"
:rules="validate('client.phone')"
clearable
v-model="data.phone"
data-cy="customerPhone"
/>
<VnInput
:label="t('customer.basicData.mobile')"
:label="t('customer.summary.mobile')"
:rules="validate('client.mobile')"
clearable
v-model="data.mobile"
@ -106,7 +120,7 @@ const exprBuilder = (param, value) => {
<VnSelect
url="Workers/search"
v-model="data.salesPersonFk"
:label="t('customer.basicData.salesPerson')"
:label="t('customer.summary.salesPerson')"
:params="{
departmentCodes: ['VT', 'shopping'],
}"
@ -144,7 +158,7 @@ const exprBuilder = (param, value) => {
option-value="id"
option-label="name"
emit-value
:label="t('customer.basicData.contactChannel')"
:label="t('customer.summary.contactChannel')"
map-options
:rules="validate('client.contactChannelFk')"
:input-debounce="0"

View File

@ -28,12 +28,7 @@ const getBankEntities = (data, formData) => {
</script>
<template>
<FormModel
:url-update="`Clients/${route.params.id}`"
:url="`Clients/${route.params.id}/getCard`"
auto-load
model="customer"
>
<FormModel :url-update="`Clients/${route.params.id}`" auto-load model="customer">
<template #form="{ data, validate }">
<VnRow>
<VnSelect

View File

@ -36,9 +36,12 @@ const entityId = computed(() => {
});
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity?.name, entity?.id));
const setData = (entity) => {
data.value = useCardDescription(entity?.name, entity?.id);
if (customer.value) customer.value.webAccess = data.value?.account?.isActive;
};
const debtWarning = computed(() => {
return customer.value?.debt > customer.value.credit ? 'negative' : 'primary';
return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary';
});
</script>
@ -56,11 +59,17 @@ const debtWarning = computed(() => {
<CustomerDescriptorMenu :customer="entity" />
</template>
<template #body="{ entity }">
<VnLv :label="t('customer.card.payMethod')" :value="entity.payMethod.name" />
<VnLv :label="t('customer.card.credit')" :value="toCurrency(entity.credit)" />
<VnLv
:label="t('customer.card.securedCredit')"
:label="t('customer.summary.payMethod')"
:value="entity.payMethod.name"
/>
<VnLv
:label="t('customer.summary.credit')"
:value="toCurrency(entity.credit)"
/>
<VnLv
:label="t('customer.summary.securedCredit')"
:value="toCurrency(entity.creditInsurance)"
/>
@ -69,7 +78,7 @@ const debtWarning = computed(() => {
:value="toCurrency(entity.debt)"
:info="t('customer.summary.riskInfo')"
/>
<VnLv :label="t('customer.card.salesPerson')">
<VnLv :label="t('customer.summary.salesPerson')">
<template #value>
<VnUserLink
v-if="entity.salesPersonUser"
@ -80,7 +89,7 @@ const debtWarning = computed(() => {
</template>
</VnLv>
<VnLv
:label="t('customer.card.businessTypeFk')"
:label="t('customer.extendedList.tableVisibleColumns.businessTypeFk')"
:value="entity.businessType.description"
/>
</template>
@ -154,7 +163,7 @@ const debtWarning = computed(() => {
</QCardActions>
</template>
<template #actions="{ entity }">
<QCardActions class="flex justify-center">
<QCardActions class="flex justify-center" style="padding-inline: 0">
<QBtn
:to="{
name: 'TicketList',

View File

@ -6,8 +6,8 @@ import axios from 'axios';
import { useQuasar } from 'quasar';
import useNotify from 'src/composables/useNotify';
import VnSmsDialog from 'src/components/common/VnSmsDialog.vue';
import useOpenURL from 'src/composables/useOpenURL';
const $props = defineProps({
customer: {
@ -15,7 +15,6 @@ const $props = defineProps({
required: true,
},
});
const { notify } = useNotify();
const { t } = useI18n();
const quasar = useQuasar();
@ -41,48 +40,37 @@ const sendSms = async (payload) => {
}
};
const openTicketCreateForm = () => {
const openCreateForm = (type) => {
const query = {
table: {
clientFk: $props.customer.id,
},
createForm: {
clientId: $props.customer.id,
addressId: $props.customer.defaultAddressFk,
},
};
openWindow('ticket', query);
};
const openOrderCreateForm = () => {
const query = {
table: {
Outdated
Review

Se quitan?

Se quitan?

Se mueve la funcionalidad del DescriptorMenu a botón en Descriptor actions

Se mueve la funcionalidad del DescriptorMenu a botón en Descriptor actions
clientFk: $props.customer.id,
},
createForm: {
clientFk: $props.customer.id,
addressId: $props.customer.defaultAddressFk,
},
const clientFk = {
ticket: 'clientId',
order: 'clientFk',
};
openWindow('order', query);
};
const key = clientFk[type];
if (!key) return;
query.createForm[key] = $props.customer.id;
jsegarra marked this conversation as resolved Outdated

los comentarios no suben

los comentarios no suben
const openWindow = (type, { createForm, table }) => {
window.open(
`/#/${type}/list?createForm=${JSON.stringify(createForm)}&table=${JSON.stringify(
table
)}`,
'_blank'
);
const params = Object.entries(query)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join('&');
useOpenURL(`/#/${type}/list?${params}`);
};
</script>
<template>
<QItem v-ripple clickable @click="openTicketCreateForm()">
<QItem v-ripple clickable @click="openCreateForm('ticket')">
<QItemSection>
{{ t('globals.pageTitles.createTicket') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openOrderCreateForm()">
<QItem v-ripple clickable @click="openCreateForm('order')">
<QItemSection>
{{ t('globals.pageTitles.createOrder') }}
</QItemSection>

View File

@ -34,7 +34,6 @@ function handleLocation(data, location) {
/>
<FormModel
:url-update="`Clients/${route.params.id}/updateFiscalData`"
:url="`Clients/${route.params.id}/getCard`"
auto-load
model="customer"
>

View File

@ -2,7 +2,7 @@
import { computed, ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toCurrency, toPercentage, toDate } from 'src/filters';
import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue';
import { getUrl } from 'src/composables/getUrl';
import VnLv from 'src/components/ui/VnLv.vue';
@ -30,16 +30,16 @@ const clientUrl = ref();
onMounted(async () => {
clientUrl.value = (await getUrl('client/')) + entityId.value + '/';
Outdated
Review

no se usa?

no se usa?
Outdated
Review

Se sigue sin usar

Se sigue sin usar
});
const defaulterAmount = computed(() => customer.value.defaulters[0]?.amount);
const balanceDue = computed(() => {
return (
customer.value &&
customer.value.defaulters.length &&
customer.value.defaulters[0].amount
);
const amount = defaulterAmount.value;
if (!amount || amount < 0) {
return null;
}
return amount;
});
const balanceDueWarning = computed(() => (balanceDue.value ? 'negative' : ''));
const balanceDueWarning = computed(() => (defaulterAmount.value ? 'negative' : ''));
const claimRate = computed(() => {
return customer.value.claimsRatio.claimingRate;
@ -80,7 +80,7 @@ const creditWarning = computed(() => {
<VnLv :label="t('customer.summary.contact')" :value="entity.contact" />
<VnLv :value="entity.phone">
<template #label>
{{ t('customer.summary.phone') }}
{{ t('customer.extendedList.tableVisibleColumns.phone') }}
<VnLinkPhone :phone-number="entity.phone" />
</template>
</VnLv>
@ -88,11 +88,17 @@ const creditWarning = computed(() => {
<template #label>
{{ t('customer.summary.mobile') }}
<VnLinkPhone :phone-number="entity.mobile" />
<VnLinkPhone
say-simple
:phone-number="entity.mobile"
:channel="entity.country?.saySimpleCountry?.channel"
class="q-ml-xs"
/>
</template>
</VnLv>
<VnLv :value="entity.email" copy
><template #label>
{{ t('customer.summary.email') }}
{{ t('globals.params.email') }}
<VnLinkMail email="entity.email"></VnLinkMail> </template
></VnLv>
<VnLv
@ -129,7 +135,7 @@ const creditWarning = computed(() => {
<VnLv
v-if="entity.country"
:label="t('customer.summary.country')"
:value="entity.country.country"
:value="entity.country.name"
/>
<VnLv :label="t('customer.summary.street')" :value="entity.street" />
</QCard>
@ -204,7 +210,7 @@ const creditWarning = computed(() => {
:value="entity.defaultAddress.city"
/>
<VnLv
:label="t('customer.summary.addressStreet')"
:label="t('customer.summary.street')"
:value="entity.defaultAddress.street"
/>
</QCard>
@ -290,7 +296,7 @@ const creditWarning = computed(() => {
<VnLv
v-if="entity.defaulters"
:label="t('customer.summary.balanceDue')"
:value="toCurrency(balanceDue)"
:value="dashOrCurrency(balanceDue)()"
:class="balanceDueWarning"
:info="t('customer.summary.balanceDueInfo')"
/>
@ -310,7 +316,7 @@ const creditWarning = computed(() => {
:value="entity.recommendedCredit"
/>
</QCard>
<QCard class="vn-one">
<QCard class="vn-max">
<VnTitle :text="t('Latest tickets')" />
<CustomerSummaryTable />
</QCard>

View File

@ -1,165 +1,82 @@
<script setup>
import { computed, onBeforeMount, ref, watch, nextTick } from 'vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore';
import FormModel from 'components/FormModel.vue';
import FetchData from 'components/FetchData.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnRow from 'components/ui/VnRow.vue';
const formModelRef = ref(false);
const { t } = useI18n();
const route = useRoute();
const { notify } = useNotify();
const stateStore = useStateStore();
const amountInputRef = ref(null);
const initialDated = Date.vnNew();
const unpaidClient = ref(false);
const isLoading = ref(false);
const amount = ref(null);
const dated = ref(initialDated);
const initialData = ref({
dated: initialDated,
dated: Date.vnNew(),
amount: null,
});
const hasChanged = computed(() => {
return (
initialData.value.dated !== dated.value ||
initialData.value.amount !== amount.value
);
});
onBeforeMount(() => {
getData(route.params.id);
});
watch(
() => route.params.id,
(newValue) => {
if (!newValue) return;
getData(newValue);
}
);
const getData = async (id) => {
const filter = { where: { clientFk: id } };
try {
const { data } = await axios.get('ClientUnpaids', {
params: { filter: JSON.stringify(filter) },
});
if (data.length) {
setValues(data[0]);
} else {
defaultValues();
}
} catch (error) {
defaultValues();
}
};
const setValues = (data) => {
unpaidClient.value = true;
amount.value = data.amount;
dated.value = data.dated;
initialData.value = data;
};
const defaultValues = () => {
unpaidClient.value = false;
initialData.value.amount = null;
setInitialData();
};
const setInitialData = () => {
amount.value = initialData.value.amount;
dated.value = initialData.value.dated;
};
const onSubmit = async () => {
isLoading.value = true;
const payload = {
amount: amount.value,
const filterClientFindOne = {
fields: ['unpaid', 'dated', 'amount'],
where: {
clientFk: route.params.id,
dated: dated.value,
};
try {
await axios.patch('ClientUnpaids', payload);
notify('globals.dataSaved', 'positive');
unpaidClient.value = true;
} catch (error) {
notify('errors.writeRequest', 'negative');
} finally {
isLoading.value = false;
}
},
};
watch(
() => unpaidClient.value,
async (val) => {
await nextTick();
if (val) amountInputRef.value.focus();
}
);
</script>
<template>
<Teleport v-if="stateStore?.isSubToolbarShown()" to="#st-actions">
<QBtnGroup push class="q-gutter-x-sm">
<QBtn
:disabled="!hasChanged"
:label="t('globals.reset')"
:loading="isLoading"
@click="setInitialData"
color="primary"
flat
icon="restart_alt"
type="reset"
/>
<QBtn
:disabled="!hasChanged"
:label="t('globals.save')"
:loading="isLoading"
@click="onSubmit"
color="primary"
icon="save"
/>
</QBtnGroup>
</Teleport>
<div class="full-width flex justify-center">
<QCard class="card-width q-pa-lg">
<QForm>
<FetchData
:filter="filterClientFindOne"
auto-load
url="ClientUnpaids"
@on-fetch="
(data) => {
const unpaid = data.length == 1;
initialData = { ...data[0], unpaid };
}
"
/>
<QCard>
<FormModel
v-if="'unpaid' in initialData"
:observe-form-changes="false"
ref="formModelRef"
model="unpaid"
url-update="ClientUnpaids"
:mapper="(formData) => ({ ...formData, clientFk: route.params.id })"
:form-initial-data="initialData"
>
<template #form="{ data }">
<VnRow>
<div class="col">
<QCheckbox :label="t('Unpaid client')" v-model="unpaidClient" />
</div>
<QCheckbox :label="t('Unpaid client')" v-model="data.unpaid" />
</VnRow>
<VnRow class="row q-gutter-md q-mb-md" v-show="unpaidClient">
<VnRow class="row q-gutter-md q-mb-md" v-show="data.unpaid">
<div class="col">
<VnInputDate :label="t('Date')" v-model="dated" />
<VnInputDate
data-cy="customerUnpaidDate"
:label="t('Date')"
v-model="data.dated"
/>
</div>
<div class="col">
<VnInput
<VnInputNumber
data-cy="customerUnpaidAmount"
ref="amountInputRef"
:label="t('Amount')"
clearable
type="number"
v-model="amount"
v-model="data.amount"
autofocus
>
<template #append></template></VnInput
<template #append></template></VnInputNumber
>
</div>
</VnRow>
</QForm>
</QCard>
</div>
</template>
</FormModel>
</QCard>
</template>
<i18n>

View File

@ -25,10 +25,9 @@ async function hasCustomerRole() {
</script>
<template>
<FormModel
url="VnUsers/preview"
:url-update="`Clients/${route.params.id}/updateUser`"
:filter="filter"
model="webAccess"
model="customer"
:mapper="
({ active, name, email }) => {
return {
@ -42,7 +41,7 @@ async function hasCustomerRole() {
auto-load
>
<template #form="{ data, validate }">
<QCheckbox :label="t('Enable web access')" v-model="data.active" />
<QCheckbox :label="t('Enable web access')" v-model="data.account.active" />
<VnInput :label="t('User')" clearable v-model="data.name" />
<VnInput
:label="t('Recovery email')"

View File

@ -1,9 +1,9 @@
<script setup>
import { computed, onBeforeMount, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
import FetchData from 'src/components/FetchData.vue';
import { toCurrency, toDateHourMin } from 'src/filters';
@ -20,10 +20,11 @@ const filter = {
{ relation: 'mandateType', scope: { fields: ['id', 'name'] } },
{ relation: 'company', scope: { fields: ['id', 'code'] } },
],
where: { clientFk: null },
where: { clientFk: route.params.id },
order: ['created DESC'],
limit: 20,
};
const ClientDmsRef = ref(false);
const tableColumnComponents = {
state: {
@ -50,7 +51,7 @@ const tableColumnComponents = {
component: CustomerCheckIconTooltip,
props: ({ row }) => ({
transaction: row,
promise: refreshData,
promise: () => ClientDmsRef.value.fetch(),
}),
event: () => {},
},
@ -89,72 +90,45 @@ const columns = computed(() => [
name: 'validate',
},
]);
onBeforeMount(() => {
getData(route.params.id);
});
watch(
() => route.params.id,
(newValue) => {
if (!newValue) return;
getData(newValue);
}
);
const getData = async (id) => {
filter.where.clientFk = id;
try {
const { data } = await axios.get('clients/transactions', {
params: { filter: JSON.stringify(filter) },
});
rows.value = data;
} catch (error) {
return error;
}
};
const refreshData = () => {
getData(route.params.id);
};
</script>
<template>
<div class="full-width flex justify-center">
<QPage class="card-width q-pa-lg">
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 12 }"
:rows="rows"
class="full-width q-mt-md"
row-key="id"
v-if="rows?.length"
>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props">
<component
:is="tableColumnComponents[props.col.name].component"
@click="
tableColumnComponents[props.col.name].event(props)
"
class="rounded-borders q-pa-sm"
v-bind="
tableColumnComponents[props.col.name].props(props)
"
>
{{ props.value }}
</component>
</QTr>
</QTd>
</template>
</QTable>
<FetchData
ref="ClientDmsRef"
:filter="filter"
@on-fetch="(data) => (rows = data)"
auto-load
url="Clients/transactions"
/>
<QPage class="card-width q-pa-lg">
<QTable
:columns="columns"
:pagination="{ rowsPerPage: 12 }"
:rows="rows"
class="full-width q-mt-md"
row-key="id"
v-if="rows?.length"
>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props">
<component
:is="tableColumnComponents[props.col.name].component"
@click="tableColumnComponents[props.col.name].event(props)"
class="rounded-borders q-pa-sm"
v-bind="tableColumnComponents[props.col.name].props(props)"
>
{{ props.value }}
</component>
</QTr>
</QTd>
</template>
</QTable>
<h5 class="flex justify-center color-vn-label" v-else>
{{ t('globals.noResults') }}
</h5>
</QPage>
</div>
<h5 class="flex justify-center color-vn-label" v-else>
{{ t('globals.noResults') }}
</h5>
</QPage>
</template>
<i18n>

View File

@ -28,7 +28,7 @@ const exprBuilder = (param, value) => {
</script>
<template>
<VnFilterPanel :data-key="dataKey" :search-button="true" search-url="table">
<VnFilterPanel :data-key="dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
@ -48,7 +48,7 @@ const exprBuilder = (param, value) => {
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('customerFilter.filter.name')"
:label="t('globals.name')"
v-model="params.name"
is-outlined
/>
@ -57,7 +57,7 @@ const exprBuilder = (param, value) => {
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('customerFilter.filter.socialName')"
:label="t('customer.summary.socialName')"
v-model="params.socialName"
is-outlined
/>
@ -101,8 +101,8 @@ const exprBuilder = (param, value) => {
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection
><VnSelect
<QItemSection>
<VnSelect
url="Provinces"
:label="t('Province')"
v-model="params.provinceFk"
@ -120,32 +120,31 @@ const exprBuilder = (param, value) => {
/>
</QItemSection>
</QItem>
<QItem class="q-mb-md">
<QItem class="q-mb-sm">
<QItemSection>
<VnInput :label="t('City')" v-model="params.city" is-outlined />
</QItemSection>
</QItem>
<QSeparator />
<QExpansionItem :label="t('More options')" expand-separator>
<QItem>
<QItemSection>
<VnInput :label="t('Phone')" v-model="params.phone" is-outlined>
<template #prepend>
<QIcon name="phone" size="xs" />
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput :label="t('Email')" v-model="params.email" is-outlined>
<template #prepend>
<QIcon name="email" size="sm" />
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput :label="t('Phone')" v-model="params.phone" is-outlined>
<template #prepend>
<QIcon name="phone" size="xs" />
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput :label="t('Email')" v-model="params.email" is-outlined>
<template #prepend>
<QIcon name="email" size="sm" />
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnSelect
url="Zones"
:label="t('Zone')"
@ -160,18 +159,17 @@ const exprBuilder = (param, value) => {
outlined
rounded
auto-load
/></QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('Postcode')"
v-model="params.postcode"
is-outlined
/>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('Postcode')"
v-model="params.postcode"
is-outlined
/>
</QItemSection>
</QItem>
</QExpansionItem>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
@ -203,7 +201,6 @@ es:
Salesperson: Comercial
Province: Provincia
City: Ciudad
More options: Más opciones
Phone: Teléfono
Email: Email
Zone: Zona

View File

@ -67,7 +67,8 @@ const columns = computed(() => [
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
optionFilter: 'firstName'
optionFilter: 'firstName',
useLike: false,
},
create: false,
columnField: {
@ -77,7 +78,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.credit'),
label: t('customer.summary.credit'),
name: 'credit',
columnFilter: {
component: 'number',
@ -115,7 +116,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.mobile'),
label: t('customer.summary.mobile'),
name: 'mobile',
cardVisible: true,
columnFilter: {
@ -162,17 +163,17 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.city'),
label: t('customer.summary.city'),
name: 'city',
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.postcode'),
label: t('customer.summary.postcode'),
name: 'postcode',
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.email'),
label: t('globals.params.email'),
name: 'email',
cardVisible: true,
},
@ -207,7 +208,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
label: t('customer.summary.payMethodFk'),
name: 'payMethodFk',
columnFilter: {
component: 'select',
@ -250,7 +251,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.isActive'),
label: t('customer.summary.isActive'),
name: 'isActive',
chip: {
color: null,
@ -279,7 +280,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
label: t('customer.summary.isEqualizated'),
name: 'isEqualizated',
create: true,
columnFilter: {
@ -325,7 +326,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
label: t('customer.summary.hasLcr'),
name: 'hasLcr',
columnFilter: {
inWhere: true,
@ -333,7 +334,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
label: t('customer.summary.hasCoreVnl'),
name: 'hasCoreVnl',
columnFilter: {
inWhere: true,
@ -394,16 +395,16 @@ function handleLocation(data, location) {
<VnSearchbar
:info="t('You can search by customer id or name')"
:label="t('Search customer')"
data-key="Customer"
data-key="CustomerList"
/>
<RightMenu>
<template #right-panel>
<CustomerFilter data-key="Customer" />
<CustomerFilter data-key="CustomerList" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="Customer"
data-key="CustomerList"
url="Clients/filter"
:create="{
urlCreate: 'Clients/createWithUser',
@ -424,7 +425,7 @@ function handleLocation(data, location) {
<VnSelect
url="Workers/search"
v-model="data.salesPersonFk"
:label="t('customer.basicData.salesPerson')"
:label="t('customer.summary.salesPerson')"
:params="{
departmentCodes: ['VT', 'shopping'],
}"

View File

@ -1,9 +1,8 @@
<script setup>
import { onBeforeMount, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import VnLocation from 'src/components/common/VnLocation.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
@ -25,20 +24,6 @@ const agencyModes = ref([]);
const incoterms = ref([]);
const customsAgents = ref([]);
onBeforeMount(() => {
urlCreate.value = `Clients/${route.params.id}/createAddress`;
getCustomsAgents();
});
const getCustomsAgents = async () => {
const { data } = await axios.get('CustomsAgents');
customsAgents.value = data;
};
const refreshData = () => {
getCustomsAgents();
};
const toCustomerAddress = () => {
router.push({
name: 'CustomerAddress',
@ -54,6 +39,11 @@ function handleLocation(data, location) {
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
function onAgentCreated(requestResponse, data) {
customsAgents.value.push(requestResponse);
data.customsAgentFk = requestResponse.id;
}
</script>
<template>
@ -139,6 +129,7 @@ function handleLocation(data, location) {
/>
<VnSelectDialog
url="CustomsAgents"
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
@ -148,7 +139,12 @@ function handleLocation(data, location) {
:tooltip="t('Create a new expense')"
>
<template #form>
<CustomerNewCustomsAgent @on-data-saved="refreshData()" />
<CustomerNewCustomsAgent
@on-data-saved="
(_, requestResponse) =>
onAgentCreated(requestResponse, data)
"
/>
</template>
</VnSelectDialog>
</VnRow>

View File

@ -23,6 +23,7 @@ const incoterms = ref([]);
const customsAgents = ref([]);
const observationTypes = ref([]);
const notes = ref([]);
let originalNotes = [];
const deletes = ref([]);
onBeforeMount(() => {
@ -42,7 +43,8 @@ const getData = async (observations) => {
});
if (data.length) {
notes.value = data
originalNotes = data;
notes.value = originalNotes
.map((observation) => {
const type = observationTypes.value.find(
(type) => type.id === observation.observationTypeFk
@ -81,14 +83,24 @@ const deleteNote = (id, index) => {
};
const onDataSaved = async () => {
let payload = {};
const creates = notes.value.filter((note) => note.$isNew);
if (creates.length) {
payload.creates = creates;
}
if (deletes.value.length) {
payload.deletes = deletes.value;
}
let payload = {
creates: notes.value.filter((note) => note.$isNew),
deletes: deletes.value,
updates: notes.value
.filter((note) =>
originalNotes.some(
(oNote) =>
oNote.id === note.id &&
(note.description !== oNote.description ||
note.observationTypeFk !== oNote.observationTypeFk)
)
)
.map((note) => ({
data: note,
where: { id: note.id },
})),
};
await axios.post('AddressObservations/crud', payload);
notes.value = [];
deletes.value = [];
@ -132,7 +144,7 @@ function handleLocation(data, location) {
:url="`Addresses/${route.params.addressId}`"
@on-data-saved="onDataSaved()"
auto-load
model="client"
model="customer"
>
<template #moreActions>
<QBtn

View File

@ -106,28 +106,24 @@ const setParams = (params) => {
};
const getPreview = async () => {
try {
const params = {
recipientId: entityId,
};
const validationMessage = validateMessage();
if (validationMessage) return notify(t(validationMessage), 'negative');
const params = {
recipientId: entityId,
};
const validationMessage = validateMessage();
if (validationMessage) return notify(t(validationMessage), 'negative');
setParams(params);
setParams(params);
const path = `${sampleType.value.model}/${entityId.value}/${sampleType.value.code}-html`;
const { data } = await axios.get(path, { params });
const path = `${sampleType.value.model}/${entityId.value}/${sampleType.value.code}-html`;
const { data } = await axios.get(path, { params });
if (!data) return;
quasar.dialog({
component: CustomerSamplesPreview,
componentProps: {
htmlContent: data,
},
});
} catch (err) {
notify('Errors getting preview', 'negative');
}
if (!data) return;
quasar.dialog({
component: CustomerSamplesPreview,
componentProps: {
htmlContent: data,
},
});
};
const onSubmit = async () => {

View File

@ -154,6 +154,8 @@ const setShippedColor = (date) => {
if (difference == 0) return 'warning';
if (difference < 0) return 'success';
};
const rowClick = ({ id }) =>
window.open(router.resolve({ params: { id }, name: 'TicketSummary' }).href, '_blank');
const getItemPackagingType = (ticketSales) => {
if (!ticketSales?.length) return '-';
@ -183,7 +185,7 @@ const getItemPackagingType = (ticketSales) => {
:columns="columns"
:without-header="true"
auto-load
redirect="ticket"
:row-click="rowClick"
order="shipped DESC, id"
Outdated
Review

Creo que poniendo redirect ya lo hace no?

Creo que poniendo redirect ya lo hace no?

espectacular

espectacular
:disable-option="{ card: true, table: true }"
class="full-width"

Some files were not shown because too many files have changed in this diff Show More