diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe18a10a..58b68b7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1519 +1,1804 @@ +# Version 25.06 - 2025-02-18 + +### Added 🆕 + +- chore: refs #7405 remove examples documentation by:jorgep +- chore: refs #7405 remove VitePress cache files and update .gitignore by:jorgep +- chore: refs #8316 remove search and searchInfo entries from shelving in English and Spanish locales by:jtubau +- feat: #8258 added hover and description to uppercase button by:PAU ROVIRA ROSALENY +- feat: add addressFk by:Javier Segarra +- feat: add inactive car icon by:jorgep +- feat: downgrade pnpm by:Javier Segarra +- feat: new command by:Javier Segarra +- feat: refs #6629 addressObservation by:robert +- feat: refs #6629 change values by:robert +- feat: refs #6629 customerAddressEdit by:robert +- feat: refs #6629 delete consolelog by:robert +- feat: refs #6629 traduction message by:robert +- feat: refs #6629 update by:robert +- feat: refs #6822 by:robert +- feat: refs #6822 change request by:robert +- feat: refs #6822 change traduction Partial delay (origin/6822-changeTitlePartialDelay) by:robert +- feat: refs #6822 redirection by:robert +- feat: refs #6943 addressPropagate by:Javier Segarra +- feat: refs #6943 updateAndEmit param as object by:Javier Segarra +- feat: refs #7065 created unit tests for UserPanel by:provira +- feat: refs #7068 created VnVisibleColumns unit test by:Jon +- feat: refs #7103 created test for VnSearchbar by:provira +- feat: refs #7134 #7124 handle columns by:Javier Segarra +- feat: refs #7134 #7124 handle filter by:Javier Segarra +- feat: refs #7134 #7134 Create new route by:Javier Segarra +- feat: refs #7134 #7134 Create SupplierBalance layout by:Javier Segarra +- feat: refs #7134 #7134 split newPayment by:Javier Segarra +- feat: refs #7134 add bank by:Javier Segarra +- feat: refs #7134 apply supplierBalanceFilter by:Javier Segarra +- feat: refs #7134 default currency parameter by:Javier Segarra +- feat: refs #7134 minor changes by:Javier Segarra +- feat: refs #7134 order by:Javier Segarra +- feat: refs #7134 perf VnTable by:Javier Segarra +- feat: refs #7134 remove add btn by:Javier Segarra +- feat: refs #7134 unremovableParams by:Javier Segarra +- feat: refs #7134 use tableFooter by:Javier Segarra +- feat: refs #7134 use VnAccountNumber by:Javier Segarra +- feat: refs #7134 vnTable setTableFooter by:Javier Segarra +- feat: refs #7184 added myTeam filter at WorkerFilter by:Jon +- feat: refs #7196 update vite and q-calendar by:alexm +- feat: refs #7305 deleted warnings by:Jon +- feat: refs #7308 remove warning by:Javier Segarra +- feat: refs #7317 deleted warnings in fiscalData and dms by:Jon +- feat: refs #7322 add address selection for ticket transfer by:jtubau +- feat: refs #7405 add initial documentation and components for Lilium by:jorgep +- feat: refs #7405 add navigation links and documentation for useArrayData composable by:jorgep +- feat: refs #7826 add error handling and refresh icon to NavBar by:Javier Segarra +- feat: refs #8077 change request by:robert +- feat: refs #8077 changes request by:robert +- feat: refs #8077 sumDefaulter by:robert +- feat: refs #8120 added new style to summary popups by:Jon +- feat: refs #8120 use new prop in the requierd modules by:Jon +- feat: refs #8197 create advancedMenu and add in VnSection by:alexm +- feat: refs #8316 added order param by:jtubau +- feat: refs #8316 add slots on VnTable from VnFilterPanel by:alexm +- feat: refs #8316 parking inside shelving by:alexm +- feat: refs #8322 added RouteRoadmap and Agency by:provira +- feat: refs #8322 fix route.js and unify with /agency by:alexm +- feat: refs #8322 fix route.js and unify with /roadmap by:alexm +- feat: refs #8339 define global.spreview by:Javier Segarra +- feat: refs #8381 add carrier field to travel thermographs and update localization by:jgallego +- feat: refs #8387 changes by:robert +- feat: refs #8387 changes request by:robert +- feat: refs #8387 crudModel by:robert +- feat: refs #8387 refs#8387 change request by:robert +- feat: refs #8395 added computed to calculate and display amounts by:provira +- feat: refs #8395 added total column in invoiceInVat by:provira +- feat: refs #8398 modify previous changes by:robert +- feat: refs #8398 moveTicketsFuture by:robert +- feat: refs #8409 added VnSelectSupplier by:Jon +- feat: refs #8410 added new feature to module searchbar by:provira +- feat: refs #8418 add data-cy attribute for print labels button in EntryBuysTableDialog by:jtubau +- feat: refs #8450 added new version by:Jon +- feat: toCurrency in risk icon by:Javier Segarra +- feat: update quasar version by:Javier Segarra +- feat: update vitest to 1.0 by:Javier Segarra +- feat: update vue to 3.5 by:Javier Segarra +- style: customerDescriptor by:Javier Segarra +- style: refs #6943 order imports by:Javier Segarra + +### Changed 📦 + +- feat: refs #7134 perf VnTable by:Javier Segarra +- perf: pnpm-lock by:Javier Segarra +- perf: refs #7134 #7134 changes by:Javier Segarra +- perf: refs #7134 #7134 fix filter panel by:Javier Segarra +- perf: refs #7134 #7134 global dialog newPayment and composable getRisk by:Javier Segarra +- perf: refs #7134 currencies fetch by:Javier Segarra +- perf: refs #7134 format columns by:Javier Segarra +- perf: refs #7134 imports by:Javier Segarra +- perf: refs #7134 use ForModelPopup by:Javier Segarra +- perf: refs #7134 use map-key by:Javier Segarra +- perf: refs #7134 use where to get only EUR currency by:Javier Segarra +- perf: refs #7196 update eslint by:alexm +- perf: refs #7308 call 1 time useSession by:Javier Segarra +- perf: refs #7826 code onError by:Javier Segarra +- perf: refs #7826 improve condition by:Javier Segarra +- perf: refs #8197 default is object by:alexm +- perf: refs #8197 fix and imrpove filters by:alexm +- perf: refs #8197 perf by:alexm +- perf: refs #8339 minor changes by:Javier Segarra +- perf: refs #8339 removew preview tag by:Javier Segarra +- perf: use util in OutLayout by:Javier Segarra +- perf: vitest to 0.34.0 by:Javier Segarra +- refactor: advancedMenu button inside searchbar by:alexm +- refactor: move remaining data to descriptorMenu by:Jon +- refactor: refs #6822 transferEntry moved to descriptor menu by:robert +- refactor: refs #7068 adjust variables by:Jon +- refactor: refs #7068 requested changes by:Jon +- refactor: refs #7317 requested changes by:Jon +- refactor: refs #7322 extract repeated functions and create tests by:jtubau +- refactor: refs #7322 update API functions to accept filters for enhanced data retrieval by:jtubau +- refactor: refs #7322 update getAgencies to handle client and return default agency by:jtubau +- refactor: refs #8120 change prop and classes' names by:Jon +- refactor: refs #8120 requested changes by:Jon +- refactor: refs #8120 use only defineProps by:Jon +- refactor: refs #8316 added shelvingCardBeta and localizations by:jtubau +- refactor: refs #8316 add new localization keys and update existing ones for invoiceIn components by:jtubau +- refactor: refs #8316 add new localization keys and update existing ones for invoiceOut components by:jtubau +- refactor: refs #8316 moved userFilter to array-data-props by:jtubau +- refactor: refs #8316 remove invoiceInSearchbar by:alexm +- refactor: refs #8316 remove unused ItemTypeSearchbar component by:jtubau +- refactor: refs #8316 restore exprBuilder function to filter invoice data by:jtubau +- refactor: refs #8316 restore filter for supplier and related entities in InvoiceInCard by:jtubau +- refactor: refs #8316 unify router item and itemType by:alexm +- refactor: refs #8316 update prefix casing for InvoiceIn component by:jtubau +- refactor: refs #8316 update Spanish translations for ItemsFilterPanel by:jtubau +- refactor: refs #8316 used VnSection and VnBetaCard by:jtubau +- refactor: refs #8316 used VnSection and VnCardBeta by:jtubau +- refactor: refs #8316 used VnSection and VnCardBeta on ItemCard by:jtubau +- refactor: refs #8322 changed Route component to use VnSection/VnCardBeta by:provira +- refactor: refs #8322 changed Travel component to use VnSection/VnCardBeta by:provira +- refactor: refs #8351 deleted skip and fixed TicketList e2e by:Jon +- refactor: refs #8351 put appropriate name by:Jon +- refactor: refs #8380 remove unnecessary stubs in VnImg test wrapper by:jtubau +- refactor: refs #8381 update travel data handling in TravelThermographs component by:jgallego +- refactor: refs #8409 deleted unused variable by:Jon +- refactor: refs #8409 use defineModel instead or defineProps by:Jon +- refactor: refs #8410 restructured code by:provira +- refactor: refs #8418 remove commented issue reference from myEntry.spec.js by:jtubau +- refactor: refs #8418 update data-cy attribute for print labels button in EntryBuysTableDialog by:jtubau +- refactor: refs #8418 update selector to use cy.dataCy instead cy.get by:jtubau +- refactor: request changes by:Jon + +### Fixed 🛠️ + +- feat: refs #8322 fix route.js and unify with /agency by:alexm +- feat: refs #8322 fix route.js and unify with /roadmap by:alexm +- fix: added witdth when opening summary by:Jon +- fix: defineProps not import by:alexm +- fix: deleted duplicate request by:Jon +- fix: fixed descriptor e2e by:Jon +- fix: fixed InvoiceOutList e2e by:Jon +- fix: fixed list and e2e by:Jon +- fix: fixed pagiante by:Jon +- fix: fixed rectificative class by:Jon +- fix: fixed states column in claim list and filter by:Jon +- fix: fixed VnLocation and warnings by:Jon +- fix: fixed wagons e2e (origin/Fix-WagonModuleE2E) by:Jon +- fix: fix grid two by:carlossa +- fix: improve method (origin/warmfix_reload_scriptIsMissing) by:Javier Segarra +- fix: init by:Javier Segarra +- fix: minor cli error by:Javier Segarra +- fix: modified front to show new field by:Jon +- fix: move dialog to descriptorMenu by:Jon +- fix: refs #6553 clean pr by:carlossa +- fix: refs #6553 fix BeforeMount filters by:carlossa +- fix: refs #6553 fix front and translations by:carlossa +- fix: refs #6553 fix pr by:carlossa +- fix: refs #6553 fix PR, fix vnTableCard by:carlossa +- fix: refs #6553 fix qScrollArea by:carlossa +- fix: refs #6553 fix summary by:carlossa +- fix: refs #6553 fix user-filter by:carlossa +- fix: refs #6553 fix vnTable by:carlossa +- fix: refs #6553 fix vnTable css by:carlossa +- fix: refs #6553 front advanced by:carlossa +- fix: refs #6553 front by:carlossa +- fix: refs #6553 label css by:carlossa +- fix: refs #6553 onBeforeMount by:carlossa +- fix: refs #6943 minor changes by:Javier Segarra +- fix: refs #6943 redirect when change addressId by:Javier Segarra +- fix: refs #6943 required by:Javier Segarra +- fix: refs #7065 made consts for repeated values by:provira +- fix: refs #7065 removed unnecessary code by:provira +- fix: refs #7103 removed unused code on spies by:provira +- fix: refs #7103 updated tests for new changes by:provira +- fix: refs #7103 used consts for repeated variables by:provira +- fix: refs #7134 getRiskComposable by:Javier Segarra +- fix: refs #7134 minor change by:Javier Segarra +- fix: refs #7134 params filter by:Javier Segarra +- fix: refs #7134 remove risk by:Javier Segarra +- fix: refs #7134 remove supplierRisk by:Javier Segarra +- fix: refs #7134 solve comments by:Javier Segarra +- fix: refs #7196 not neccessary by:alexm +- fix: refs #7196 sass by:alexm +- fix: refs #7322 handle null responses in client, agency and address fetching by:jtubau +- fix: refs #7826 init by:Javier Segarra +- fix: refs #8120 ticket descriptor & summary by:Jon +- fix: refs #8172 Remove unused row and column fields from ParkingBasicData by:guillermo +- fix: refs #8197 improve code robustness by adding optional chaining and fixing syntax errors by:alexm +- fix: refs #8197 use rightMenu by:alexm +- fix: refs #8197 use RightMenu in subsections by:alexm +- fix: refs #8227 clean pr (origin/8227-warmfixRoute) by:carlossa +- fix: refs #8227 fix front descriptor, Form by:carlossa +- fix: refs #8227 warmfix by:carlossa +- fix: refs #8316 advanced-menu by:alexm +- fix: refs #8316 filter by:alexm +- fix: refs #8316 fix broken localizations for entry descriptor menu and items filter panel by:jtubau +- fix: refs #8316 icon by:alexm +- fix: refs #8316 redirections by:alexm +- fix: refs #8316 translations by:alexm +- fix: refs #8316 user-filter by:alexm +- fix: refs #8322 add userFilter by:alexm +- fix: refs #8322 filter and params by:alexm +- fix: refs #8322 fixed route creation url by:provira +- fix: refs #8322 moved filter inside array-data-props by:provira +- fix: refs #8322 use userFilter by:alexm +- fix: refs #8347 remove skip, fix unpaid by:carlossa +- fix: refs #8352 fix datacy by:carlossa +- fix: refs #8352 fix right by:carlossa +- fix: refs #8352 fix rightPanel vnLog by:carlossa +- fix: refs #8381 update travel data fetching to use correct URL and include necessary fields by:jgallego +- fix: refs #8381 update travel data reference in TravelThermographs component by:jgallego +- fix: refs #8395 update label for total column by:provira +- fix: refs #8409 deleted code due to merge by:Jon +- fix: refs #8409 deleted code of merge by:Jon +- fix: refs #8410 removed ref from searching boolean by:provira +- fix: refs #8410 removed unused code by:provira +- fix: refs #8410 removed unused condition by:provira +- fix: refs #8410 removed unused ref by:provira +- fix: refs #8410 simplified searchModule function by:provira +- fix: refs #8418 adjusted route for button click by:jtubau +- fix: refs #8418 correct casing in translation keys for supplier reference and issued date labels by:jtubau +- fix: refs #8419 modified list and fixed e2e by:Jon +- fix: refs #8420 ensure search bar is visible before typing and enable details test by:jtubau +- fix: refs #8422 fixed ItemTag e2e test not working by:provira +- fix: refs #8422 optimized get and dataCy by:provira +- fix: refs #8423 fixed zoneWarehouse e2e test not working by:provira +- fix: refs #8423 removed data-cy usage by:provira +- fix: refs #8423 used dataCy to get data-cy by:provira +- fix: refs #8524 parking section router by:alexm +- fix: refs #8524 parking test (origin/8524-devToTest, 8524-devToTest) by:alexm +- fix: remove console by:Javier Segarra +- fix: replace labels by:Javier Segarra +- fix: rightAdvancedMenu by:alexm +- fix: routeCard use customUrl by:alexm +- fix: show descriptors when click on it by:Javier Segarra +- fix: update query parameters for thermograph routing by:jgallego +- fix: update selector for buyLabel button in myEntry.spec.js (origin/fix-myEntryTest) by:jtubau +- fix: update setupNodeEvents to use async/await for plugin import by:jgallego +- fix: use model by:alexm +- fix: use rightMenu by:alexm +- fix(VnSection): destroy data when unmounted by:alexm +- fix(VnSection): refs #8197 check route by:alexm +- fix(WorkerBusiness): fix card label by:alexm +- fix: workerSummary by:alexm +- perf: refs #7134 #7134 fix filter panel by:Javier Segarra +- perf: refs #8197 fix and imrpove filters by:alexm +- refactor: refs #8316 update prefix casing for InvoiceIn component by:jtubau +- refactor: refs #8351 deleted skip and fixed TicketList e2e by:Jon +- refs #6553 fix business slot by:carlossa +- refs #6553 fix business summary by:carlossa +- refs #6553 fix business summary traductions by:carlossa +- refs #6553 fix front ibject by:carlossa +- refs #6553 fix front trad by:carlossa +- refs #6553 fix names by:carlossa +- refs #6553 fix reactivateWorker by:carlossa +- refs #6553 fix relations by:carlossa +- refs #6553 fix VnTable by:carlossa +- refs #7917 fix routeCard by:carlossa +- revert: refs #7134 change by:Javier Segarra +- revert: refs #7134 customer changes by:Javier Segarra +- revert: vitest to 0.31.1 by:Javier Segarra +- test: fix clientList spec by:Javier Segarra +- test: fix component by:Javier Segarra +- test: fix VnSearchbar by:alexm +- test: refs #6943 fix tests by:Javier Segarra +- test: refs #7308 fix axios.spec.js by:Javier Segarra +- test: refs #8524 fix by:alexm + # Version 25.04 - 2025-01-28 ### Added 🆕 -- chore: add task comment by:jorgep -- chore: refs #8198 rollback by:jorgep -- chore: refs #8322 unnecessary prop by:alexm -- feat: refs #7055 added new test case by:provira -- feat: refs #7055 created FilterItemForm test by:provira -- feat: refs #7077 created test for VnInputTime by:provira -- feat: refs #7078 created test for VnJsonValue by:provira -- feat: refs #7087 added more test cases by:provira -- feat: refs #7087 added new test by:provira -- feat: refs #7087 created CardSummary test by:provira -- feat: refs #7088 created test for FetchedTags by:provira -- feat: refs #7202 added new field by:Jon -- feat: refs #7882 Added coords to create a address by:guillermo -- feat: refs #7957 add tooltip and i18n support for search link in VnSearchbar component by:jorgep -- feat: refs #7957 enhance search functionality and improve data filtering logic by:jorgep -- feat: refs #7957 open in new tab by:jorgep -- feat: refs #7957 simplify fn to by:jorgep -- feat: refs #7957 update VnSearchbar component with improved search URL handling and styling enhancements by:jorgep -- feat: refs #8117 filters and values added as needed by:jtubau -- feat: refs #8197 useHasContent and use in VnSection and RightMenu by:alexm -- feat: refs #8219 added invoice out e2e tests by:Jon -- feat: refs #8219 global invoicing e2e by:Jon -- feat: refs #8220 added barcodes e2e test by:Jon -- feat: refs #8220 created items e2e by:Jon -- feat: refs #8220 modified create item form and added respective e2e by:Jon -- feat: refs #8225 added account and invoiceOut modules by:Jon -- feat: refs #8225 added entry module and fixed translations by:Jon -- feat: refs #8225 added invoiceIn and travel module by:Jon -- feat: refs #8225 added moreOptions and use it in customer and ticket summary by:Jon -- feat: refs #8225 added route and shelving module by:Jon -- feat: refs #8225 added worker and zone modules by:Jon -- feat: refs #8225 use it in claim, item and order modules by:Jon -- feat: refs #8258 added button to pass to uppercase by:provira -- feat: refs #8258 added uppercase option to VnInput by:provira -- feat: refs #8258 added uppercase validation on supplier create by:provira -- feat: refs #8298 add price optimum input and update translations for bonus and price optimum by:jgallego -- feat: refs #8316 add entryFilter prop to VnTable component in EntryList by:jtubau -- feat: refs #8322 added department changes by:provira -- feat: refs #8372 workerPBX by:robert -- feat: refs #8381 add initial and final temperature fields to entry forms and summaries by:jgallego -- feat: refs #8381 add initial and final temperature labels in English and Spanish locales by:jgallego -- feat: refs #8381 add toCelsius filter and update temperature fields in entry forms and summaries by:jgallego -- feat: skip tests by:jorgep -- style: refs #7957 update VnSearchbar padding for improved layout by:jorgep +- chore: add task comment by:jorgep +- chore: refs #8198 rollback by:jorgep +- chore: refs #8322 unnecessary prop by:alexm +- feat: refs #7055 added new test case by:provira +- feat: refs #7055 created FilterItemForm test by:provira +- feat: refs #7077 created test for VnInputTime by:provira +- feat: refs #7078 created test for VnJsonValue by:provira +- feat: refs #7087 added more test cases by:provira +- feat: refs #7087 added new test by:provira +- feat: refs #7087 created CardSummary test by:provira +- feat: refs #7088 created test for FetchedTags by:provira +- feat: refs #7202 added new field by:Jon +- feat: refs #7882 Added coords to create a address by:guillermo +- feat: refs #7957 add tooltip and i18n support for search link in VnSearchbar component by:jorgep +- feat: refs #7957 enhance search functionality and improve data filtering logic by:jorgep +- feat: refs #7957 open in new tab by:jorgep +- feat: refs #7957 simplify fn to by:jorgep +- feat: refs #7957 update VnSearchbar component with improved search URL handling and styling enhancements by:jorgep +- feat: refs #8117 filters and values added as needed by:jtubau +- feat: refs #8197 useHasContent and use in VnSection and RightMenu by:alexm +- feat: refs #8219 added invoice out e2e tests by:Jon +- feat: refs #8219 global invoicing e2e by:Jon +- feat: refs #8220 added barcodes e2e test by:Jon +- feat: refs #8220 created items e2e by:Jon +- feat: refs #8220 modified create item form and added respective e2e by:Jon +- feat: refs #8225 added account and invoiceOut modules by:Jon +- feat: refs #8225 added entry module and fixed translations by:Jon +- feat: refs #8225 added invoiceIn and travel module by:Jon +- feat: refs #8225 added moreOptions and use it in customer and ticket summary by:Jon +- feat: refs #8225 added route and shelving module by:Jon +- feat: refs #8225 added worker and zone modules by:Jon +- feat: refs #8225 use it in claim, item and order modules by:Jon +- feat: refs #8258 added button to pass to uppercase by:provira +- feat: refs #8258 added uppercase option to VnInput by:provira +- feat: refs #8258 added uppercase validation on supplier create by:provira +- feat: refs #8298 add price optimum input and update translations for bonus and price optimum by:jgallego +- feat: refs #8316 add entryFilter prop to VnTable component in EntryList by:jtubau +- feat: refs #8322 added department changes by:provira +- feat: refs #8372 workerPBX by:robert +- feat: refs #8381 add initial and final temperature fields to entry forms and summaries by:jgallego +- feat: refs #8381 add initial and final temperature labels in English and Spanish locales by:jgallego +- feat: refs #8381 add toCelsius filter and update temperature fields in entry forms and summaries by:jgallego +- feat: skip tests by:jorgep +- style: refs #7957 update VnSearchbar padding for improved layout by:jorgep ### Changed 📦 -- perf: refs #8219 #8219 minor change by:Javier Segarra -- perf: refs #8220 on-fetch and added missing translations by:Jon -- perf: refs #8220 on-fetch by:Jon -- perf: refs #8220 translations by:Jon -- perf: refs #8220 use searchbar selector in e2e tests by:Jon -- perf: remove warning default value by:Javier Segarra -- refactor: redirect using params by:Jon -- refactor: refs #7077 removed some comments by:provira -- refactor: refs #7087 removed unused imports by:provira -- refactor: refs #7100 added const mockData by:jtubau -- refactor: refs #7100 delete unnecesary set prop by:jtubau -- refactor: refs #7100 refactorized with methods by:jtubau -- refactor: refs #7957 remove blank by:jorgep -- refactor: refs #8198 simplify data fetching and filtering logic by:jorgep -- refactor: refs #8198 simplify state management and data fetching in ItemDiary component by:jorgep -- refactor: refs #8219 modified e2e tests and fixed some translations by:Jon -- refactor: refs #8219 modified list test, created cypress download folder and added to gitignore by:Jon -- refactor: refs #8219 requested changes by:Jon -- refactor: refs #8219 use checkNotification command by:Jon -- refactor: refs #8220 added data-cy for e2e tests by:Jon -- refactor: refs #8220 requested changes by:Jon -- refactor: refs #8220 skip failling test and modifed tag test by:Jon -- refactor: refs #8225 requested changes by:Jon -- refactor: refs #8247 use new acl for sysadmin by:Jon -- refactor: refs #8316 added claimFilter by:jtubau -- refactor: refs #8316 added entryFilter by:jtubau -- refactor: refs #8316 add new localization keys and update existing ones for entry components by:jtubau -- refactor: refs #8316 moved localizations to local locale by:jtubau -- refactor: refs #8316 move order localization by:jtubau -- refactor: refs #8316 remove unused OrderSearchbar component by:jtubau -- refactor: refs #8316 update EntryCard to use user-filter prop and remove exprBuilder from EntryList by:jtubau -- refactor: refs #8316 used VnSection and VnCardBeta by:jtubau -- refactor: refs #8322 changed translations by:provira -- refactor: refs #8322 changed Worker component to use VnSection/VnCardBeta by:provira -- refactor: refs #8322 set department inside worker by:alexm -- refactor: skip intermitent failing test by:Jon +- perf: refs #8219 #8219 minor change by:Javier Segarra +- perf: refs #8220 on-fetch and added missing translations by:Jon +- perf: refs #8220 on-fetch by:Jon +- perf: refs #8220 translations by:Jon +- perf: refs #8220 use searchbar selector in e2e tests by:Jon +- perf: remove warning default value by:Javier Segarra +- refactor: redirect using params by:Jon +- refactor: refs #7077 removed some comments by:provira +- refactor: refs #7087 removed unused imports by:provira +- refactor: refs #7100 added const mockData by:jtubau +- refactor: refs #7100 delete unnecesary set prop by:jtubau +- refactor: refs #7100 refactorized with methods by:jtubau +- refactor: refs #7957 remove blank by:jorgep +- refactor: refs #8198 simplify data fetching and filtering logic by:jorgep +- refactor: refs #8198 simplify state management and data fetching in ItemDiary component by:jorgep +- refactor: refs #8219 modified e2e tests and fixed some translations by:Jon +- refactor: refs #8219 modified list test, created cypress download folder and added to gitignore by:Jon +- refactor: refs #8219 requested changes by:Jon +- refactor: refs #8219 use checkNotification command by:Jon +- refactor: refs #8220 added data-cy for e2e tests by:Jon +- refactor: refs #8220 requested changes by:Jon +- refactor: refs #8220 skip failling test and modifed tag test by:Jon +- refactor: refs #8225 requested changes by:Jon +- refactor: refs #8247 use new acl for sysadmin by:Jon +- refactor: refs #8316 added claimFilter by:jtubau +- refactor: refs #8316 added entryFilter by:jtubau +- refactor: refs #8316 add new localization keys and update existing ones for entry components by:jtubau +- refactor: refs #8316 moved localizations to local locale by:jtubau +- refactor: refs #8316 move order localization by:jtubau +- refactor: refs #8316 remove unused OrderSearchbar component by:jtubau +- refactor: refs #8316 update EntryCard to use user-filter prop and remove exprBuilder from EntryList by:jtubau +- refactor: refs #8316 used VnSection and VnCardBeta by:jtubau +- refactor: refs #8322 changed translations by:provira +- refactor: refs #8322 changed Worker component to use VnSection/VnCardBeta by:provira +- refactor: refs #8322 set department inside worker by:alexm +- refactor: skip intermitent failing test by:Jon ### Fixed 🛠️ -- feat: refs #8225 added entry module and fixed translations by:Jon -- fix: added missing translations in InvoiceIn by:provira -- fix: changed invoiceIn for InvoiceIn by:provira -- fix: changed translations to only use "invoicein" by:provira -- fix: department descriptor link by:Jon -- fix: e2e tests by:Jon -- fix: entry summary view and build warnings by:Jon -- fix: fixed InvoiceIn filter translations by:provira -- fix: modified setData in customerDescriptor to show the icons by:Jon -- fix: redirect to TicketSale from OrderLines (origin/Fix-RedirectToTicketSale) by:Jon -- fix: redirect when confirming lines by:Jon -- fix: refs #7055 #7055 #7055 fixed some tests by:provira -- fix: refs #7077 removed unused imports by:provira -- fix: refs #7078 added missing case with array by:provira -- fix: refs #7087 fixed some tests by:provira -- fix: refs #7088 changed "vm.vm" to "vm" by:provira -- fix: refs #7088 changed wrapper to vm by:provira -- fix: refs #7699 add icons and hint by:carlossa -- fix: refs #7699 add pwd vnInput by:carlossa -- fix: refs #7699 fix component by:carlossa -- fix: refs #7699 fix password visibility by:carlossa -- fix: refs #7699 fix tfront clean code by:carlossa -- fix: refs #7699 fix vnChangePassword, clean VnInput by:carlossa -- fix: refs #7699 fix vnInputPassword by:carlossa -- fix: refs #7957 add missing closing brace by:jorgep -- fix: refs #7957 css by:jorgep -- fix: refs #7957 rollback by:jorgep -- fix: refs #7957 update data-cy by:jorgep -- fix: refs #7957 update visibility handling for clear icon in VnInput component by:jorgep -- fix: refs #7957 vn-searchbar test by:jorgep -- fix: refs #8117 update salesPersonFk filter options and URL for improved data retrieval by:jtubau -- fix: refs #8197 not use yet by:alexm -- fix: refs #8198 update query param by:jorgep -- fix: refs #8219 fixed e2e tests by:Jon -- fix: refs #8219 fixed summary and global tests by:Jon -- fix: refs #8219 forgotten dataCy by:Jon -- fix: refs #8219 global e2e by:Jon -- fix: refs #8219 requested changes by:Jon -- fix: refs #8220 itemTag test by:Javier Segarra -- fix: refs #8225 invoice in translations by:Jon -- fix: refs #8243 fixed SkeletonSummary by:provira -- fix: refs #8247 conflicts by:Jon -- fix: refs #8247 fixed acls and added lost options by:Jon -- fix: refs #8316 ref="claimFilterRef" by:alexm -- fix: refs #8316 userFilter by:alexm -- fix: refs #8316 use rightMenu by:alexm -- fix: refs #8316 use section-searchbar by:alexm -- fix: refs #8317 disable action buttons when no rows are selected in ItemFixedPrice by:jtubau -- fix: refs #8322 unnecessary section by:alexm -- fix: refs #8338 fixed VnTable translations by:provira -- fix: refs #8338 removed chipLocale property/added more translations by:provira -- fix: refs #8448 e2e by:Jon -- fix: refs #8448 not use croppie by:alexm -- fix: remove departmentCode by:Javier Segarra -- fix: removed unused searchbar by:PAU ROVIRA ROSALENY -- fix: skip failling e2e by:Jon -- fix: sort by name in description by:Jon -- fix: translations by:Jon -- fix: use entryFilter by:alexm -- fix(VnCardBeta): add userFilter by:alexm -- refactor: refs #8219 modified e2e tests and fixed some translations by:Jon -- revert: revert header by:alexm -- test: fix expedition e2e by:alexm +- feat: refs #8225 added entry module and fixed translations by:Jon +- fix: added missing translations in InvoiceIn by:provira +- fix: changed invoiceIn for InvoiceIn by:provira +- fix: changed translations to only use "invoicein" by:provira +- fix: department descriptor link by:Jon +- fix: e2e tests by:Jon +- fix: entry summary view and build warnings by:Jon +- fix: fixed InvoiceIn filter translations by:provira +- fix: modified setData in customerDescriptor to show the icons by:Jon +- fix: redirect to TicketSale from OrderLines (origin/Fix-RedirectToTicketSale) by:Jon +- fix: redirect when confirming lines by:Jon +- fix: refs #7055 #7055 #7055 fixed some tests by:provira +- fix: refs #7077 removed unused imports by:provira +- fix: refs #7078 added missing case with array by:provira +- fix: refs #7087 fixed some tests by:provira +- fix: refs #7088 changed "vm.vm" to "vm" by:provira +- fix: refs #7088 changed wrapper to vm by:provira +- fix: refs #7699 add icons and hint by:carlossa +- fix: refs #7699 add pwd vnInput by:carlossa +- fix: refs #7699 fix component by:carlossa +- fix: refs #7699 fix password visibility by:carlossa +- fix: refs #7699 fix tfront clean code by:carlossa +- fix: refs #7699 fix vnChangePassword, clean VnInput by:carlossa +- fix: refs #7699 fix vnInputPassword by:carlossa +- fix: refs #7957 add missing closing brace by:jorgep +- fix: refs #7957 css by:jorgep +- fix: refs #7957 rollback by:jorgep +- fix: refs #7957 update data-cy by:jorgep +- fix: refs #7957 update visibility handling for clear icon in VnInput component by:jorgep +- fix: refs #7957 vn-searchbar test by:jorgep +- fix: refs #8117 update salesPersonFk filter options and URL for improved data retrieval by:jtubau +- fix: refs #8197 not use yet by:alexm +- fix: refs #8198 update query param by:jorgep +- fix: refs #8219 fixed e2e tests by:Jon +- fix: refs #8219 fixed summary and global tests by:Jon +- fix: refs #8219 forgotten dataCy by:Jon +- fix: refs #8219 global e2e by:Jon +- fix: refs #8219 requested changes by:Jon +- fix: refs #8220 itemTag test by:Javier Segarra +- fix: refs #8225 invoice in translations by:Jon +- fix: refs #8243 fixed SkeletonSummary by:provira +- fix: refs #8247 conflicts by:Jon +- fix: refs #8247 fixed acls and added lost options by:Jon +- fix: refs #8316 ref="claimFilterRef" by:alexm +- fix: refs #8316 userFilter by:alexm +- fix: refs #8316 use rightMenu by:alexm +- fix: refs #8316 use section-searchbar by:alexm +- fix: refs #8317 disable action buttons when no rows are selected in ItemFixedPrice by:jtubau +- fix: refs #8322 unnecessary section by:alexm +- fix: refs #8338 fixed VnTable translations by:provira +- fix: refs #8338 removed chipLocale property/added more translations by:provira +- fix: refs #8448 e2e by:Jon +- fix: refs #8448 not use croppie by:alexm +- fix: remove departmentCode by:Javier Segarra +- fix: removed unused searchbar by:PAU ROVIRA ROSALENY +- fix: skip failling e2e by:Jon +- fix: sort by name in description by:Jon +- fix: translations by:Jon +- fix: use entryFilter by:alexm +- fix(VnCardBeta): add userFilter by:alexm +- refactor: refs #8219 modified e2e tests and fixed some translations by:Jon +- revert: revert header by:alexm +- test: fix expedition e2e by:alexm # Version 25.00 - 2025-01-14 ### Added 🆕 -- chore: refs #7056 move test by:jorgep -- feat: refs #7050 7050 add object check by:Jtubau -- feat: refs #7050 7050 add test to isEmpty() by:Jtubau -- feat: refs #7056 add tests in FormModel by:jorgep -- feat: refs #7056 update route meta information and add FormModel tests by:jorgep -- feat: refs #7074 tests for fns setData(), parseDms() and showFormDialog() (7074-makeFrontTestToVnDmsList) by:Jtubau -- feat: refs #7079 created VnLocation front test by:provira -- feat: refs #7189 add Accept-Language header to axios requests by:jorgep -- feat: refs #7924 add custom inspection checkbox and localization support by:jgallego -- feat: refs #7924 update custom inspection label for clarity in English and Spanish locales by:jgallego -- feat: refs #8004 enhance FetchedTags component with column support and styling updates by:pablone -- feat: refs #8004 hide rightFilter by:pablone -- feat: refs #8246 added new field in list by:Jon -- feat: refs #8266 added descriptor to item name by:jtubau -- feat: refs #8293 add zone filter by:Jtubau -- feat: refs #8293 include zone data in each record by:Jtubau -- fix: refs #8004 more list style issues by:pablone -- fix: refs #8004 some style issues on all list by:pablone -- style: refs #8004 update layout and styling in FetchedTags and ItemList components by:pablone -- style: update CustomerBalance.vue to set label color by:jgallego +- chore: refs #7056 move test by:jorgep +- feat: refs #7050 7050 add object check by:Jtubau +- feat: refs #7050 7050 add test to isEmpty() by:Jtubau +- feat: refs #7056 add tests in FormModel by:jorgep +- feat: refs #7056 update route meta information and add FormModel tests by:jorgep +- feat: refs #7074 tests for fns setData(), parseDms() and showFormDialog() (7074-makeFrontTestToVnDmsList) by:Jtubau +- feat: refs #7079 created VnLocation front test by:provira +- feat: refs #7189 add Accept-Language header to axios requests by:jorgep +- feat: refs #7924 add custom inspection checkbox and localization support by:jgallego +- feat: refs #7924 update custom inspection label for clarity in English and Spanish locales by:jgallego +- feat: refs #8004 enhance FetchedTags component with column support and styling updates by:pablone +- feat: refs #8004 hide rightFilter by:pablone +- feat: refs #8246 added new field in list by:Jon +- feat: refs #8266 added descriptor to item name by:jtubau +- feat: refs #8293 add zone filter by:Jtubau +- feat: refs #8293 include zone data in each record by:Jtubau +- fix: refs #8004 more list style issues by:pablone +- fix: refs #8004 some style issues on all list by:pablone +- style: refs #8004 update layout and styling in FetchedTags and ItemList components by:pablone +- style: update CustomerBalance.vue to set label color by:jgallego ### Changed 📦 -- perf: order by:alexm -- perf: redirect transition list to card by:alexm -- perf: refs #8201 onDataSaved fetch by:Jon -- perf: revert processData by:alexm -- perf: simplify if by:alexm -- perf: simplify if (perf_redirectTransition) by:alexm -- refactor: item fixedPrice by:Jon -- refactor: refs #7050 refactorize by:jtubau -- refactor: refs #7050 removed blank spaces by:jtubau -- refactor: refs #7052 move EditTableCellValueForm tests to a new location and enhance test coverage by:jgallego -- refactor: refs #7052 remove unnecessary console logs from EditTableCellValueForm tests by:jgallego -- refactor: refs #7074 move dms constant to global scope by:Jtubau -- refactor: refs #7079 removed useless code by:provira -- refactor: refs #7924 simplify custom inspection icon rendering in ExtraCommunity.vue by:jgallego -- refactor: refs #8004 remove console log from CardSummary component on mount by:pablone -- refactor: refs #8004 remove consoleLogs by:pablone -- refactor: refs #8004 remove unused stateStore import in InvoiceInList.vue by:pablone -- refactor: refs #8004 remove unused travelFilterRef and chip definition in TravelList.vue by:pablone -- refactor: refs #8004 replace VnSelect with VnSelectWorker in CustomerList component by:pablone -- refactor: refs #8201 added onMounted to stablish the value to show icons by:Jon -- refactor: refs #8201 deleted condition by:Jon -- refactor: refs #8201 deleted log by:Jon -- refactor: refs #8201 deleted logs by:Jon -- refactor: refs #8266 8266 change expedition item name by:Jtubau -- refactor: refs #8266 change expedition label by:Jtubau -- refactor: refs #8293 remove redundant attributes by:Jtubau -- refactor: refs #8320 changed folder names from "specs" to "**tests**" by:provira -- refactor: refs #8320 moved front tests to their respective sections by:provira -- refactor: refs #8813 removed unused class property by:provira +- perf: order by:alexm +- perf: redirect transition list to card by:alexm +- perf: refs #8201 onDataSaved fetch by:Jon +- perf: revert processData by:alexm +- perf: simplify if by:alexm +- perf: simplify if (perf_redirectTransition) by:alexm +- refactor: item fixedPrice by:Jon +- refactor: refs #7050 refactorize by:jtubau +- refactor: refs #7050 removed blank spaces by:jtubau +- refactor: refs #7052 move EditTableCellValueForm tests to a new location and enhance test coverage by:jgallego +- refactor: refs #7052 remove unnecessary console logs from EditTableCellValueForm tests by:jgallego +- refactor: refs #7074 move dms constant to global scope by:Jtubau +- refactor: refs #7079 removed useless code by:provira +- refactor: refs #7924 simplify custom inspection icon rendering in ExtraCommunity.vue by:jgallego +- refactor: refs #8004 remove console log from CardSummary component on mount by:pablone +- refactor: refs #8004 remove consoleLogs by:pablone +- refactor: refs #8004 remove unused stateStore import in InvoiceInList.vue by:pablone +- refactor: refs #8004 remove unused travelFilterRef and chip definition in TravelList.vue by:pablone +- refactor: refs #8004 replace VnSelect with VnSelectWorker in CustomerList component by:pablone +- refactor: refs #8201 added onMounted to stablish the value to show icons by:Jon +- refactor: refs #8201 deleted condition by:Jon +- refactor: refs #8201 deleted log by:Jon +- refactor: refs #8201 deleted logs by:Jon +- refactor: refs #8266 8266 change expedition item name by:Jtubau +- refactor: refs #8266 change expedition label by:Jtubau +- refactor: refs #8293 remove redundant attributes by:Jtubau +- refactor: refs #8320 changed folder names from "specs" to "**tests**" by:provira +- refactor: refs #8320 moved front tests to their respective sections by:provira +- refactor: refs #8813 removed unused class property by:provira ### Fixed 🛠️ -- fix: discount class by:PAU ROVIRA ROSALENY -- fix: duplicate transalation after test to dev by:alexm -- fix: fix translations by:carlossa -- fix: redirect to sales when confirming lines by:Jon -- fix: refs #7050 delete import added by mistake by:Jtubau -- fix: refs #7133 handleSalesModelValue function to handle empty input by:jorgep -- fix: refs #7189 update user language on sessionStorage by:jorgep -- fix: refs #7935 remove unused 'companyFk' column from InvoiceInList component by:jorgep -- fix: refs #8004 more list style issues by:pablone -- fix: refs #8004 some style issues on all list by:pablone -- fix: refs #8004 update label for daysOnward in TravelFilter component and add translations by:pablone -- fix: refs #8004 vnTable card with and add permanent labels by:pablone -- fix: refs #8201 added onDataSaved emi to refetch when cahnges are made by:Jon -- fix: refs #8201 use arrayData to fix the error by:Jon -- fix: refs #8314 space between label and value by:jtubau -- fix: refs #8813 fixed ClaimLines format by:provira -- fix: update button sizes in ExtraCommunity.vue for better visibility by:jgallego -- perf: revert processData by:alexm -- refactor: item fixedPrice by:Jon +- fix: discount class by:PAU ROVIRA ROSALENY +- fix: duplicate transalation after test to dev by:alexm +- fix: fix translations by:carlossa +- fix: redirect to sales when confirming lines by:Jon +- fix: refs #7050 delete import added by mistake by:Jtubau +- fix: refs #7133 handleSalesModelValue function to handle empty input by:jorgep +- fix: refs #7189 update user language on sessionStorage by:jorgep +- fix: refs #7935 remove unused 'companyFk' column from InvoiceInList component by:jorgep +- fix: refs #8004 more list style issues by:pablone +- fix: refs #8004 some style issues on all list by:pablone +- fix: refs #8004 update label for daysOnward in TravelFilter component and add translations by:pablone +- fix: refs #8004 vnTable card with and add permanent labels by:pablone +- fix: refs #8201 added onDataSaved emi to refetch when cahnges are made by:Jon +- fix: refs #8201 use arrayData to fix the error by:Jon +- fix: refs #8314 space between label and value by:jtubau +- fix: refs #8813 fixed ClaimLines format by:provira +- fix: update button sizes in ExtraCommunity.vue for better visibility by:jgallego +- perf: revert processData by:alexm +- refactor: item fixedPrice by:Jon # Version 24.52 - 2024-01-07 ### Added 🆕 -- chore: refs #8197 remove console log by:alexm -- chore: refs #8197 replace name by:alexm -- chore: refs #8197 unnecessary file by:alexm -- feat: #8110 apply mixin in quasar components by:Javier Segarra -- feat(Account & AccountRole): refs #8197 add VnCardMain by:alexm -- feat: addDptoLink by:Jtubau -- feat: added restore ticket function in ticket descriptor menu by:Jon -- feat: add support service wip by:jorgep -- feat: focus menu searchbar by:jorgep -- feat: make additional data object by:jorgep -- feat: message to grant access by:jorgep -- feat: refs #6583 add default param by:jorgep -- feat: refs #6583 add destination opt filter by:jorgep -- feat: refs #6583 add icon by:jorgep -- feat: refs #6583 add locale by:jorgep -- feat: refs #7072 added test to computed fn total by:Jtubau -- feat: refs #7235 update invoice out global form to fetch config based on serial type by:jgallego -- feat: refs #7301 add exclude inventory supplier from list by:pablone -- feat: refs #7301 enhance VnDateBadge styling and improve ItemLastEntries component by:pablone -- feat: refs #7882 Added distribution point by:guillermo -- feat: refs #7882 Added longitude & latitude by:guillermo -- feat: refs #7936 add autocomplete on tab fn by:jorgep -- feat: refs #7936 add company filter by:jorgep -- feat: refs #7936 add currency check before fetching by:jorgep -- feat: refs #7936 add dueDated field by:jorgep -- feat: refs #7936 add number validation to VnInputNumber & new daysAgo filter in InvoiceInFilter by:jorgep -- feat: refs #7936 add optionCaption by:jorgep -- feat: refs #7936 add row click navigation to InvoiceInSerial by:jorgep -- feat: refs #7936 add unit tests by:jorgep -- feat: refs #7936 add useAccountShortToStandard composable by:jorgep -- feat: refs #7936 calculate exchange & update taxable base by:jorgep -- feat: refs #7936 enhance downloadFile function to support opening in a new tab by:jorgep -- feat: refs #7936 enhance getTotal fn & add unit tests by:jorgep -- feat: refs #7936 enhance vn-select by:jorgep -- feat: refs #7936 improve optionLabel logic in InvoiceInVat component for better handling of numeric values by:jorgep -- feat: refs #7936 limit decimal places by:jorgep -- feat: refs #7936 make fields required by:jorgep -- feat: refs #7936 show country code & isVies fields by:jorgep -- feat: refs #7936 show id & value by:jorgep -- feat: refs #7936 simplify optionLabel wip by:jorgep -- feat: refs #7936 update 'isVies' label to use global translation key by:jorgep -- feat: refs #7936 update option labels in InvoiceIn components for better clarity by:jorgep -- feat: refs #7936 use default invoice data by:jorgep -- feat: refs #8001 change request by:robert -- feat: refs #8001 ticketExpeditionGrafana by:robert -- feat: refs #8194 created VnSelectWorker component and use it in Lilium by:Jon -- feat: refs #8197 better leftMenu and VnCardMain improvements by:alexm -- feat: refs #8197 default leftMenu by:alexm -- feat: refs #8197 default sectionName by:alexm -- feat: refs #8197 keepData in VnSection by:alexm -- feat: refs #8197 vnTableFilter by:alexm -- feat: refs #8197 working rightMenu by:alexm -- feat: remove re-fetch when add element by:Javier Segarra -- feat: remove search after category by:Javier Segarra -- feat: requested changes in item module by:Jon -- feat: update quantity by:Javier Segarra -- feat(VnPaginate): refs #8197 hold data when change to Card by:alexm +- chore: refs #8197 remove console log by:alexm +- chore: refs #8197 replace name by:alexm +- chore: refs #8197 unnecessary file by:alexm +- feat: #8110 apply mixin in quasar components by:Javier Segarra +- feat(Account & AccountRole): refs #8197 add VnCardMain by:alexm +- feat: addDptoLink by:Jtubau +- feat: added restore ticket function in ticket descriptor menu by:Jon +- feat: add support service wip by:jorgep +- feat: focus menu searchbar by:jorgep +- feat: make additional data object by:jorgep +- feat: message to grant access by:jorgep +- feat: refs #6583 add default param by:jorgep +- feat: refs #6583 add destination opt filter by:jorgep +- feat: refs #6583 add icon by:jorgep +- feat: refs #6583 add locale by:jorgep +- feat: refs #7072 added test to computed fn total by:Jtubau +- feat: refs #7235 update invoice out global form to fetch config based on serial type by:jgallego +- feat: refs #7301 add exclude inventory supplier from list by:pablone +- feat: refs #7301 enhance VnDateBadge styling and improve ItemLastEntries component by:pablone +- feat: refs #7882 Added distribution point by:guillermo +- feat: refs #7882 Added longitude & latitude by:guillermo +- feat: refs #7936 add autocomplete on tab fn by:jorgep +- feat: refs #7936 add company filter by:jorgep +- feat: refs #7936 add currency check before fetching by:jorgep +- feat: refs #7936 add dueDated field by:jorgep +- feat: refs #7936 add number validation to VnInputNumber & new daysAgo filter in InvoiceInFilter by:jorgep +- feat: refs #7936 add optionCaption by:jorgep +- feat: refs #7936 add row click navigation to InvoiceInSerial by:jorgep +- feat: refs #7936 add unit tests by:jorgep +- feat: refs #7936 add useAccountShortToStandard composable by:jorgep +- feat: refs #7936 calculate exchange & update taxable base by:jorgep +- feat: refs #7936 enhance downloadFile function to support opening in a new tab by:jorgep +- feat: refs #7936 enhance getTotal fn & add unit tests by:jorgep +- feat: refs #7936 enhance vn-select by:jorgep +- feat: refs #7936 improve optionLabel logic in InvoiceInVat component for better handling of numeric values by:jorgep +- feat: refs #7936 limit decimal places by:jorgep +- feat: refs #7936 make fields required by:jorgep +- feat: refs #7936 show country code & isVies fields by:jorgep +- feat: refs #7936 show id & value by:jorgep +- feat: refs #7936 simplify optionLabel wip by:jorgep +- feat: refs #7936 update 'isVies' label to use global translation key by:jorgep +- feat: refs #7936 update option labels in InvoiceIn components for better clarity by:jorgep +- feat: refs #7936 use default invoice data by:jorgep +- feat: refs #8001 change request by:robert +- feat: refs #8001 ticketExpeditionGrafana by:robert +- feat: refs #8194 created VnSelectWorker component and use it in Lilium by:Jon +- feat: refs #8197 better leftMenu and VnCardMain improvements by:alexm +- feat: refs #8197 default leftMenu by:alexm +- feat: refs #8197 default sectionName by:alexm +- feat: refs #8197 keepData in VnSection by:alexm +- feat: refs #8197 vnTableFilter by:alexm +- feat: refs #8197 working rightMenu by:alexm +- feat: remove re-fetch when add element by:Javier Segarra +- feat: remove search after category by:Javier Segarra +- feat: requested changes in item module by:Jon +- feat: update quantity by:Javier Segarra +- feat(VnPaginate): refs #8197 hold data when change to Card by:alexm ### Changed 📦 -- perf: #6896 REMOVE COMMENTS by:Javier Segarra -- perf: qFormMixin by:Javier Segarra -- perf: qFormMixin improvement by:Javier Segarra -- perf: refs #8194 select worker component by:Jon -- perf: refs #8197 perf by:alexm -- perf: remove comments by:Javier Segarra -- perf: remove unused variables (origin/warmfix_noUsedVars) by:Javier Segarra -- refactor: added again search emit by:Jon -- refactor: add useCau composable by:jorgep -- refactor: deleted log by:Jon -- refactor: deleted onUnmounted code by:Jon -- refactor: deleted useless hidden tag by:Jon -- refactor: deleted warnings and corrected itemTag by:Jon -- refactor: drop logic by:jorgep -- refactor: ignore params when searching by id on searchbar (origin/VnSearchbar-SearchRemoveParams) by:Jon -- refactor: log error by:Jon -- refactor: refs #7936 locale by:jorgep -- refactor: refs #7936 simplify getTotal fn by:jorgep -- refactor: refs #7936 update label capitalization and replace invoice type options by:jorgep -- refactor: refs #8194 deleted unnecessary label by:Jon -- refactor: refs #8194 modified select worker template by:Jon -- refactor: refs #8194 modified select worker to allow no one filter from monitor ticket by:Jon -- refactor: refs #8194 moved translation to the correct place by:Jon -- refactor: refs #8194 requested changes by:Jon -- refactor: refs #8194 structure changes in component and related files by:Jon -- refactor: refs #8197 adapt AccountAcls to VnCardMain by:alexm -- refactor: refs #8197 adapt AccountAlias by:alexm -- refactor: refs #8197 adapt Ticket to VnCardMain by:alexm -- refactor: refs #8197 backward compatible (8197-VnCardMain_backwardCompatibility) by:alexm -- refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection by:alexm -- refactor: refs #8288 changed invoice out spanish translation by:provira -- refactor: use locale keys by:jorgep -- refactor: use teleport to avoid qdrawer overlapping by:Jon -- refactor: use VnSelectWorker by:Jon +- perf: #6896 REMOVE COMMENTS by:Javier Segarra +- perf: qFormMixin by:Javier Segarra +- perf: qFormMixin improvement by:Javier Segarra +- perf: refs #8194 select worker component by:Jon +- perf: refs #8197 perf by:alexm +- perf: remove comments by:Javier Segarra +- perf: remove unused variables (origin/warmfix_noUsedVars) by:Javier Segarra +- refactor: added again search emit by:Jon +- refactor: add useCau composable by:jorgep +- refactor: deleted log by:Jon +- refactor: deleted onUnmounted code by:Jon +- refactor: deleted useless hidden tag by:Jon +- refactor: deleted warnings and corrected itemTag by:Jon +- refactor: drop logic by:jorgep +- refactor: ignore params when searching by id on searchbar (origin/VnSearchbar-SearchRemoveParams) by:Jon +- refactor: log error by:Jon +- refactor: refs #7936 locale by:jorgep +- refactor: refs #7936 simplify getTotal fn by:jorgep +- refactor: refs #7936 update label capitalization and replace invoice type options by:jorgep +- refactor: refs #8194 deleted unnecessary label by:Jon +- refactor: refs #8194 modified select worker template by:Jon +- refactor: refs #8194 modified select worker to allow no one filter from monitor ticket by:Jon +- refactor: refs #8194 moved translation to the correct place by:Jon +- refactor: refs #8194 requested changes by:Jon +- refactor: refs #8194 structure changes in component and related files by:Jon +- refactor: refs #8197 adapt AccountAcls to VnCardMain by:alexm +- refactor: refs #8197 adapt AccountAlias by:alexm +- refactor: refs #8197 adapt Ticket to VnCardMain by:alexm +- refactor: refs #8197 backward compatible (8197-VnCardMain_backwardCompatibility) by:alexm +- refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection by:alexm +- refactor: refs #8288 changed invoice out spanish translation by:provira +- refactor: use locale keys by:jorgep +- refactor: use teleport to avoid qdrawer overlapping by:Jon +- refactor: use VnSelectWorker by:Jon ### Fixed 🛠️ -- fix: account by:carlossa -- fix: account create by:carlossa -- fix: accountList create by:carlossa -- fix(AccountList): use $refs by:alexm -- fix: add data-key by:alexm -- fix: addLocales by:Jtubau -- fix: dated field by:Jon -- fix: e2e by:jorgep -- fix: fix department filter by:carlossa -- fix: fixed translations by:Javier Segarra -- fix: fixed translations by:provira -- fix: get total from api by:Javier Segarra -- fix: handle non-object options by:jorgep -- fix: monitorPayMethodFilter by:carlossa -- fix: orderBy priority by:Javier Segarra -- fix: prevent null by:jorgep -- fix: redirection vnTable VnTableFilter by:alexm -- fix: refs #6389 fix filter trad by:carlossa -- fix: refs #6389 fix front, filters, itp by:carlossa -- fix: refs #6389 front add packing filter by:carlossa -- fix: refs #6389 front by:carlossa -- fix: refs #6389 front filters by:carlossa -- fix: refs #6389 ipt by:carlossa -- fix: refs #6389 packing by:carlossa -- fix: refs #6583 update checkbox for filtering by destination in TicketAdvanceFilter by:jorgep -- fix: refs #7031 add test e2e by:carlossa -- fix: refs #7031 fix zoneTest by:carlossa -- fix: refs #7301 unnecessary console logs from ItemLastEntries.vue by:pablone -- fix: refs #7936 changes by:jorgep -- fix: refs #7936 decimal places & locale by:jorgep -- fix: refs #7936 descriptor & dueday by:jorgep -- fix: refs #7936 exclude disabled els on tab by:jorgep -- fix: refs #7936 format tax calculation to two decimal places by:jorgep -- fix: refs #7936 improve error handling by:jorgep -- fix: refs #7936 redirection by:jorgep -- fix: refs #7936 rollback by:jorgep -- fix: refs #7936 serial by:jorgep -- fix: refs #7936 tabulation wip by:jorgep -- fix: refs #7936 test by:jorgep -- fix: refs #8114 clean by:carlossa -- fix: refs #8114 fix agencyList by:carlossa -- fix: refs #8114 fix lifeCycle hooks by:carlossa -- fix: refs #8114 fix pr by:carlossa -- fix: refs #8114 fix removeAddress by:carlossa -- fix: refs #8114 orderList by:carlossa -- fix: refs #8114 remove logs by:carlossa -- fix: refs #8197 mapKey (origin/8197-perf_vnTableInside, 8197-perf_vnTableInside) by:alexm -- fix: refs #8197 redirection (8197-perf_redirection) by:alexm -- fix: refs #8197 staticParams and redirect by:alexm -- fix: refs #8197 vnPaginate onFetch emit by:alexm -- fix: refs #8197 vnPaginate when change :id by:alexm -- fix: refs #8197 vnTableFilter in vnTable by:alexm -- fix: refs #8315 ticketBoxing test by:alexm -- fix: remove url by:carlossa -- fix: rollback by:jorgep -- fix: test by:jorgep -- fix(VnDmsList): refs #8197 add mapKey by:alexm -- revert: refs #8197 arrayData changes by:alexm -- test: refs #8197 fix e2e by:alexm -- test: refs #8315 fix claimDevelopment fixtures by:alexm -- test: refs #8315 fix clientList by:alexm -- test: refs #8315 fix VnSelect in e2e by:alexm +- fix: account by:carlossa +- fix: account create by:carlossa +- fix: accountList create by:carlossa +- fix(AccountList): use $refs by:alexm +- fix: add data-key by:alexm +- fix: addLocales by:Jtubau +- fix: dated field by:Jon +- fix: e2e by:jorgep +- fix: fix department filter by:carlossa +- fix: fixed translations by:Javier Segarra +- fix: fixed translations by:provira +- fix: get total from api by:Javier Segarra +- fix: handle non-object options by:jorgep +- fix: monitorPayMethodFilter by:carlossa +- fix: orderBy priority by:Javier Segarra +- fix: prevent null by:jorgep +- fix: redirection vnTable VnTableFilter by:alexm +- fix: refs #6389 fix filter trad by:carlossa +- fix: refs #6389 fix front, filters, itp by:carlossa +- fix: refs #6389 front add packing filter by:carlossa +- fix: refs #6389 front by:carlossa +- fix: refs #6389 front filters by:carlossa +- fix: refs #6389 ipt by:carlossa +- fix: refs #6389 packing by:carlossa +- fix: refs #6583 update checkbox for filtering by destination in TicketAdvanceFilter by:jorgep +- fix: refs #7031 add test e2e by:carlossa +- fix: refs #7031 fix zoneTest by:carlossa +- fix: refs #7301 unnecessary console logs from ItemLastEntries.vue by:pablone +- fix: refs #7936 changes by:jorgep +- fix: refs #7936 decimal places & locale by:jorgep +- fix: refs #7936 descriptor & dueday by:jorgep +- fix: refs #7936 exclude disabled els on tab by:jorgep +- fix: refs #7936 format tax calculation to two decimal places by:jorgep +- fix: refs #7936 improve error handling by:jorgep +- fix: refs #7936 redirection by:jorgep +- fix: refs #7936 rollback by:jorgep +- fix: refs #7936 serial by:jorgep +- fix: refs #7936 tabulation wip by:jorgep +- fix: refs #7936 test by:jorgep +- fix: refs #8114 clean by:carlossa +- fix: refs #8114 fix agencyList by:carlossa +- fix: refs #8114 fix lifeCycle hooks by:carlossa +- fix: refs #8114 fix pr by:carlossa +- fix: refs #8114 fix removeAddress by:carlossa +- fix: refs #8114 orderList by:carlossa +- fix: refs #8114 remove logs by:carlossa +- fix: refs #8197 mapKey (origin/8197-perf_vnTableInside, 8197-perf_vnTableInside) by:alexm +- fix: refs #8197 redirection (8197-perf_redirection) by:alexm +- fix: refs #8197 staticParams and redirect by:alexm +- fix: refs #8197 vnPaginate onFetch emit by:alexm +- fix: refs #8197 vnPaginate when change :id by:alexm +- fix: refs #8197 vnTableFilter in vnTable by:alexm +- fix: refs #8315 ticketBoxing test by:alexm +- fix: remove url by:carlossa +- fix: rollback by:jorgep +- fix: test by:jorgep +- fix(VnDmsList): refs #8197 add mapKey by:alexm +- revert: refs #8197 arrayData changes by:alexm +- test: refs #8197 fix e2e by:alexm +- test: refs #8315 fix claimDevelopment fixtures by:alexm +- test: refs #8315 fix clientList by:alexm +- test: refs #8315 fix VnSelect in e2e by:alexm # Version 24.50 - 2024-12-10 ### Added 🆕 -- feat: add reportFileName option by:Javier Segarra -- feat: all clients just with global series by:jgallego -- feat: improve Merge branch 'test' into dev by:Javier Segarra -- feat: manual invoice in two lines by:jgallego -- feat: manualInvoice with address by:jgallego -- feat: randomize functions and example by:Javier Segarra -- feat: refs #6999 added search when user tabs on a filter with value by:Jon -- feat: refs #6999 added tab to search in VnTable filter by:Jon -- feat: refs #7346 #7346 improve form by:Javier Segarra -- feat: refs #7346 address ordered by:jgallego -- feat: refs #7346 radioButton by:jgallego -- feat: refs #7346 style radioButton by:jgallego -- feat: refs #7346 traducciones en cammelCase (7346-manualInvoice) by:jgallego -- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon -- feat: refs #8061 #8061 updates by:Javier Segarra -- feat: refs #8087 reactive data by:jorgep -- feat: refs #8087 refs#8087 Redadas en travel by:Carlos Andrés -- feat: refs #8138 add component ticket problems by:pablone -- feat: refs #8163 add max length and more tests by:wbuezas -- feat: refs #8163 add prop by:wbuezas -- feat: refs #8163 add VnInput insert functionality and e2e test by:wbuezas -- feat: refs #8163 limit with maxLength by:Javier Segarra -- feat: refs #8163 maxLength SupplierFD account by:Javier Segarra -- feat: refs #8163 maxLengthVnInput by:Javier Segarra -- feat: refs #8163 use VnAccountNumber in VnAccountNumber by:Javier Segarra -- feat: refs #8166 show notification by:jorgep +- feat: add reportFileName option by:Javier Segarra +- feat: all clients just with global series by:jgallego +- feat: improve Merge branch 'test' into dev by:Javier Segarra +- feat: manual invoice in two lines by:jgallego +- feat: manualInvoice with address by:jgallego +- feat: randomize functions and example by:Javier Segarra +- feat: refs #6999 added search when user tabs on a filter with value by:Jon +- feat: refs #6999 added tab to search in VnTable filter by:Jon +- feat: refs #7346 #7346 improve form by:Javier Segarra +- feat: refs #7346 address ordered by:jgallego +- feat: refs #7346 radioButton by:jgallego +- feat: refs #7346 style radioButton by:jgallego +- feat: refs #7346 traducciones en cammelCase (7346-manualInvoice) by:jgallego +- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon +- feat: refs #8061 #8061 updates by:Javier Segarra +- feat: refs #8087 reactive data by:jorgep +- feat: refs #8087 refs#8087 Redadas en travel by:Carlos Andrés +- feat: refs #8138 add component ticket problems by:pablone +- feat: refs #8163 add max length and more tests by:wbuezas +- feat: refs #8163 add prop by:wbuezas +- feat: refs #8163 add VnInput insert functionality and e2e test by:wbuezas +- feat: refs #8163 limit with maxLength by:Javier Segarra +- feat: refs #8163 maxLength SupplierFD account by:Javier Segarra +- feat: refs #8163 maxLengthVnInput by:Javier Segarra +- feat: refs #8163 use VnAccountNumber in VnAccountNumber by:Javier Segarra +- feat: refs #8166 show notification by:jorgep ### Changed 📦 -- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon -- perf: add dataCy by:Javier Segarra -- perf: refs #7346 #7346 Imrpove interface dialog by:Javier Segarra -- perf: refs #7346 #7346 use v-show instead v-if by:Javier Segarra -- perf: refs #8036 currentFilter by:alexm -- perf: refs #8061 filter autonomy by:Javier Segarra -- perf: refs #8061 solve conflicts and random posCode it by:Javier Segarra -- perf: refs #8061 use opts from VnSelect by:Javier Segarra -- perf: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra -- perf: remove console by:Javier Segarra -- perf: remove timeout by:Javier Segarra -- perf: test command fillInForm by:Javier Segarra -- refactor: refs #8162 remove comment by:wbuezas -- refactor: remove unnecesary things by:wbuezas +- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon +- perf: add dataCy by:Javier Segarra +- perf: refs #7346 #7346 Imrpove interface dialog by:Javier Segarra +- perf: refs #7346 #7346 use v-show instead v-if by:Javier Segarra +- perf: refs #8036 currentFilter by:alexm +- perf: refs #8061 filter autonomy by:Javier Segarra +- perf: refs #8061 solve conflicts and random posCode it by:Javier Segarra +- perf: refs #8061 use opts from VnSelect by:Javier Segarra +- perf: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra +- perf: remove console by:Javier Segarra +- perf: remove timeout by:Javier Segarra +- perf: test command fillInForm by:Javier Segarra +- refactor: refs #8162 remove comment by:wbuezas +- refactor: remove unnecesary things by:wbuezas ### Fixed 🛠️ -- fix: #8016 fetching data by:Javier Segarra -- fix: icons by:jgallego -- fix: refs #7229 download file by:jorgep -- fix: refs #7229 remove catch by:jorgep -- fix: refs #7229 set url by:jorgep -- fix: refs #7229 test by:jorgep -- fix: refs #7229 url by:jorgep -- fix: refs #7229 url + test by:jorgep -- fix: refs #7304 7304 clean warning by:carlossa -- fix: refs #7304 fix list by:carlossa -- fix: refs #7304 fix warning by:carlossa -- fix: refs #7346 traslations by:jgallego -- fix: refs #7529 add save by:carlossa -- fix: refs #7529 fix e2e by:carlossa -- fix: refs #7529 fix front by:carlossa -- fix: refs #7529 fix scss by:carlossa -- fix: refs #7529 fix te2e by:carlossa -- fix: refs #7529 fix workerPit e2e by:carlossa -- fix: refs #7529 front by:carlossa -- fix: refs #8036 apply exprBuilder after save filters by:alexm -- fix: refs #8036 only add where when required by:alexm -- fix: refs #8038 solve conflicts by:Jon -- fix: refs #8061 improve code dependencies (origin/8061_improve_newCP) by:Javier Segarra -- fix: refs #8138 move component from ui folder by:pablone -- fix: refs #8138 sme minor issues by:pablone -- fix: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra -- fix: refs #8163 minor problem when keypress by:Javier Segarra -- fix: refs #8166 show zone error by:jorgep -- fix: removed selectedClient by:jgallego -- refs #7529 fix workerPit by:carlossa -- revert: refs #8061 test #8061 updates by:Javier Segarra -- test: fix own test by:Javier Segarra -- test: refs #8162 #8162 fix TicketList spec by:Javier Segarra +- fix: #8016 fetching data by:Javier Segarra +- fix: icons by:jgallego +- fix: refs #7229 download file by:jorgep +- fix: refs #7229 remove catch by:jorgep +- fix: refs #7229 set url by:jorgep +- fix: refs #7229 test by:jorgep +- fix: refs #7229 url by:jorgep +- fix: refs #7229 url + test by:jorgep +- fix: refs #7304 7304 clean warning by:carlossa +- fix: refs #7304 fix list by:carlossa +- fix: refs #7304 fix warning by:carlossa +- fix: refs #7346 traslations by:jgallego +- fix: refs #7529 add save by:carlossa +- fix: refs #7529 fix e2e by:carlossa +- fix: refs #7529 fix front by:carlossa +- fix: refs #7529 fix scss by:carlossa +- fix: refs #7529 fix te2e by:carlossa +- fix: refs #7529 fix workerPit e2e by:carlossa +- fix: refs #7529 front by:carlossa +- fix: refs #8036 apply exprBuilder after save filters by:alexm +- fix: refs #8036 only add where when required by:alexm +- fix: refs #8038 solve conflicts by:Jon +- fix: refs #8061 improve code dependencies (origin/8061_improve_newCP) by:Javier Segarra +- fix: refs #8138 move component from ui folder by:pablone +- fix: refs #8138 sme minor issues by:pablone +- fix: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra +- fix: refs #8163 minor problem when keypress by:Javier Segarra +- fix: refs #8166 show zone error by:jorgep +- fix: removed selectedClient by:jgallego +- refs #7529 fix workerPit by:carlossa +- revert: refs #8061 test #8061 updates by:Javier Segarra +- test: fix own test by:Javier Segarra +- test: refs #8162 #8162 fix TicketList spec by:Javier Segarra # 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 +- 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 +- 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 +- 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 🆕 -- chore: refs #4074 admit several acls by:jorgep -- chore: refs #4074 drop workerCreate by:jorgep -- chore: refs #4074 fix tests by:jorgep -- chore: refs #4074 wip replace useRole for useAcl by:jorgep -- chore: refs #7155 remove console.log by:alexm -- chore: refs #7155 typo by:alexm -- chore: refs #7663 add test by:jorgep -- chore: refs #7663 create test wip by:jorgep -- chore: refs #7663 drop useless code (origin/7663-setWeight) by:jorgep -- chore: refs #7828 fix e2e by:jorgep -- feat(AccountBasicData): add twoFactorFk by:alexm -- feat: add max rule by:Javier Segarra -- feat: add shortcut add event in some subSections by:Javier Segarra -- feat: add shortcut more buttons (origin/add_shortcut_add_subSections) by:Javier Segarra -- feat: add tooltip CustomerNewCustomAgent by:Javier Segarra -- feat: apply color when today by:Javier Segarra -- feat: change label because its more natural by:Javier Segarra -- feat: change order by:Javier Segarra -- feat: change QBadge color by:Javier Segarra -- feat: change url CustomerList by:Javier Segarra -- feat: copy customer countryFk by:Javier Segarra -- feat: create VnSelectEnum and add in AccountBasicData and ClaimBasicData by:alexm -- feat: CustomerBalance by:Javier Segarra -- feat: CustomerConsumptionFilter by:Javier Segarra -- feat: customer consumption (origin/7830-customerDesplegables, 7830-customerDesplegables) by:alexm -- feat: CustomerCreateTicket by:Javier Segarra -- feat: CustomerCredit section by:Javier Segarra -- feat: CustomerGreuges by:Javier Segarra -- feat: CustomerSample to VnTable by:Javier Segarra -- feat: global handler (origin/fix_global_handler, fix_global_handler) by:alexm -- feat: goToSupplier by:Javier Segarra -- feat: handle newValue by:Javier Segarra -- feat: handle same multiple CP by:Javier Segarra -- feat: hide menus on small view (origin/hideMenu) by:jorgep -- feat: minor changes by:Javier Segarra -- feat: orderCreateDialog by:Javier Segarra -- feat: refs #4074 drop useless code by:jorgep -- feat: refs #4074 useAcl in vnSelectDialog by:jorgep -- feat: refs #6346 new wagon type section by:Jon -- feat: refs #7404 add m3 and fix detail by:pablone -- feat: refs #7404 add some style to the form and reorganize fields by:pablone -- feat: refs #7404 add travel m3 to reserves form by:pablone -- feat: refs #7404 style dynamic text color by:pablone -- feat: refs #7404 travel m3 form by:pablone -- feat: refs #7500 added VnImg to show files by:Jon -- feat: refs #7663 add setWeight menu opt (wip) by:jorgep -- feat: refs #7663 fine tunning by:jorgep -- feat: refs #7828 create axios instance which no manage errors (origin/7828-makeCorrectCalls) by:jorgep -- feat: refs #7828 useAcl & cherry pick mail data worker by:jorgep -- feat: remove cli warnings by:Javier Segarra -- feat: show preparation field by:Javier Segarra -- feat: stateGroupedFilter by:Javier Segarra -- feat: translations fixed by:jgallego -- feat(TravelList): add daysOnward by:alexm -- feat: travel m3 by:pablone -- feat: use disableInifiniteScroll property by:Javier Segarra -- feat: VnImg draggable by:Javier Segarra -- feat: vnLocation changes by:Javier Segarra -- feat: vnSelect exprBuilder by:Javier Segarra -- fix: refs #7404 remove some style by:pablone -- fix: refs #7404 style non center pop up (origin/7404-fixFront) by:pablone -- fix: refs #7404 translates and some minor style fixes by:pablone -- fix: styles by:Javier Segarra -- perf: improve style by:Javier Segarra +- chore: refs #4074 admit several acls by:jorgep +- chore: refs #4074 drop workerCreate by:jorgep +- chore: refs #4074 fix tests by:jorgep +- chore: refs #4074 wip replace useRole for useAcl by:jorgep +- chore: refs #7155 remove console.log by:alexm +- chore: refs #7155 typo by:alexm +- chore: refs #7663 add test by:jorgep +- chore: refs #7663 create test wip by:jorgep +- chore: refs #7663 drop useless code (origin/7663-setWeight) by:jorgep +- chore: refs #7828 fix e2e by:jorgep +- feat(AccountBasicData): add twoFactorFk by:alexm +- feat: add max rule by:Javier Segarra +- feat: add shortcut add event in some subSections by:Javier Segarra +- feat: add shortcut more buttons (origin/add_shortcut_add_subSections) by:Javier Segarra +- feat: add tooltip CustomerNewCustomAgent by:Javier Segarra +- feat: apply color when today by:Javier Segarra +- feat: change label because its more natural by:Javier Segarra +- feat: change order by:Javier Segarra +- feat: change QBadge color by:Javier Segarra +- feat: change url CustomerList by:Javier Segarra +- feat: copy customer countryFk by:Javier Segarra +- feat: create VnSelectEnum and add in AccountBasicData and ClaimBasicData by:alexm +- feat: CustomerBalance by:Javier Segarra +- feat: CustomerConsumptionFilter by:Javier Segarra +- feat: customer consumption (origin/7830-customerDesplegables, 7830-customerDesplegables) by:alexm +- feat: CustomerCreateTicket by:Javier Segarra +- feat: CustomerCredit section by:Javier Segarra +- feat: CustomerGreuges by:Javier Segarra +- feat: CustomerSample to VnTable by:Javier Segarra +- feat: global handler (origin/fix_global_handler, fix_global_handler) by:alexm +- feat: goToSupplier by:Javier Segarra +- feat: handle newValue by:Javier Segarra +- feat: handle same multiple CP by:Javier Segarra +- feat: hide menus on small view (origin/hideMenu) by:jorgep +- feat: minor changes by:Javier Segarra +- feat: orderCreateDialog by:Javier Segarra +- feat: refs #4074 drop useless code by:jorgep +- feat: refs #4074 useAcl in vnSelectDialog by:jorgep +- feat: refs #6346 new wagon type section by:Jon +- feat: refs #7404 add m3 and fix detail by:pablone +- feat: refs #7404 add some style to the form and reorganize fields by:pablone +- feat: refs #7404 add travel m3 to reserves form by:pablone +- feat: refs #7404 style dynamic text color by:pablone +- feat: refs #7404 travel m3 form by:pablone +- feat: refs #7500 added VnImg to show files by:Jon +- feat: refs #7663 add setWeight menu opt (wip) by:jorgep +- feat: refs #7663 fine tunning by:jorgep +- feat: refs #7828 create axios instance which no manage errors (origin/7828-makeCorrectCalls) by:jorgep +- feat: refs #7828 useAcl & cherry pick mail data worker by:jorgep +- feat: remove cli warnings by:Javier Segarra +- feat: show preparation field by:Javier Segarra +- feat: stateGroupedFilter by:Javier Segarra +- feat: translations fixed by:jgallego +- feat(TravelList): add daysOnward by:alexm +- feat: travel m3 by:pablone +- feat: use disableInifiniteScroll property by:Javier Segarra +- feat: VnImg draggable by:Javier Segarra +- feat: vnLocation changes by:Javier Segarra +- feat: vnSelect exprBuilder by:Javier Segarra +- fix: refs #7404 remove some style by:pablone +- fix: refs #7404 style non center pop up (origin/7404-fixFront) by:pablone +- fix: refs #7404 translates and some minor style fixes by:pablone +- fix: styles by:Javier Segarra +- perf: improve style by:Javier Segarra ### Changed 📦 -- perf: CustomerBalance by:Javier Segarra -- perf: CustomerBasicData by:Javier Segarra -- perf: CustomerBasicData.salesPersonFk by:Javier Segarra -- perf: CustomerSummary by:Javier Segarra -- perf: customerSummaryTable by:Javier Segarra -- perf: disable card option by:Javier Segarra -- perf: i18n by:Javier Segarra -- perf: improve by:Javier Segarra -- perf: improve style by:Javier Segarra -- perf: imrpove exprBuilder by:Javier Segarra -- perf: minor comments by:Javier Segarra -- perf: refs #6346 previous changes by:Jon -- perf: sendEmail customerConsumption by:Javier Segarra -- perf: solve reload CardSummary component by:Javier Segarra -- perf: update CustommerDescriptor by:Javier Segarra -- refactor: refs #4074 accept array by:jorgep -- refactor: refs #4074 rollback by:jorgep -- refactor: refs #4074 use acl & drop useless roles by:jorgep -- refactor: refs #4074 useAcl in navigationStore & router by:jorgep -- refactor: refs #4074 use fn (origin/4074-useAcls) by:jorgep -- refactor: refs #4074 use VnTitle by:jorgep -- refactor: refs #6346 deleted front error checking by:Jon -- refactor: refs #6346 requested changes by:Jon -- refactor: refs #6346 wagons to VnTable by:Jon -- refactor: refs #7500 deleted useless code by:Jon -- refactor: refs #7500 refactor vnimg when storage is dms by:Jon -- refactor: refs #7828 wip by:jorgep +- perf: CustomerBalance by:Javier Segarra +- perf: CustomerBasicData by:Javier Segarra +- perf: CustomerBasicData.salesPersonFk by:Javier Segarra +- perf: CustomerSummary by:Javier Segarra +- perf: customerSummaryTable by:Javier Segarra +- perf: disable card option by:Javier Segarra +- perf: i18n by:Javier Segarra +- perf: improve by:Javier Segarra +- perf: improve style by:Javier Segarra +- perf: imrpove exprBuilder by:Javier Segarra +- perf: minor comments by:Javier Segarra +- perf: refs #6346 previous changes by:Jon +- perf: sendEmail customerConsumption by:Javier Segarra +- perf: solve reload CardSummary component by:Javier Segarra +- perf: update CustommerDescriptor by:Javier Segarra +- refactor: refs #4074 accept array by:jorgep +- refactor: refs #4074 rollback by:jorgep +- refactor: refs #4074 use acl & drop useless roles by:jorgep +- refactor: refs #4074 useAcl in navigationStore & router by:jorgep +- refactor: refs #4074 use fn (origin/4074-useAcls) by:jorgep +- refactor: refs #4074 use VnTitle by:jorgep +- refactor: refs #6346 deleted front error checking by:Jon +- refactor: refs #6346 requested changes by:Jon +- refactor: refs #6346 wagons to VnTable by:Jon +- refactor: refs #7500 deleted useless code by:Jon +- refactor: refs #7500 refactor vnimg when storage is dms by:Jon +- refactor: refs #7828 wip by:jorgep ### Fixed 🛠️ -- chore: refs #4074 fix tests by:jorgep -- chore: refs #7828 fix e2e by:jorgep -- feat: refs #7404 add m3 and fix detail by:pablone -- feat: translations fixed by:jgallego -- fix: #5938 grouped filter by:Javier Segarra -- fix: #6943 fix customerSummaryTable by:Javier Segarra -- fix: #6943 show nickname salesPerson by:Javier Segarra -- fix: address-create i18n by:Javier Segarra -- fix: comments (origin/6943_fix_customer_module, 6943_fix_customer_module) by:Javier Segarra -- fix: CusomerSummary to Address by:Javier Segarra -- fix: CustomerAddress mobile by:Javier Segarra -- fix: CustomerBillingData by:Javier Segarra -- fix: Customerconsumption by:Javier Segarra -- fix: customer credit opinion by:alexm -- fix: CustomerCreditOpinion workerDescriptor by:Javier Segarra -- fix: CustomerDescriptorAccount by:Javier Segarra -- fix: CustomerDescriptor.bussinessTypeFk by:Javier Segarra -- fix: CustomerFilter by:Javier Segarra -- fix: CustomerGreuges by:Javier Segarra -- fix: CustomerMandates by:Javier Segarra -- fix: Customer module find salesPersons out of first get by:Javier Segarra -- fix: CustomerRecovery transalate label by:Javier Segarra -- fix: CustomerSamples by:Javier Segarra -- fix: customerSummaryToTicketList button by:Javier Segarra -- fix: CustomerWebPayment by:Javier Segarra -- fix: CustommerSummaryTable Proxy by:Javier Segarra -- fix: deleted code by:Jon -- fix: duplicate code by:alexm -- fix: emit:updateModelValue by:Javier Segarra -- fix: fixed wagon tests by:Jon -- fix: fix wagon list reload by:Jon -- fix: i18n en preparation label by:Javier Segarra -- fix: infiniteScroll by:Javier Segarra -- fix: isFullMovable checkbox by:Javier Segarra -- fix: merge conflicts by:Javier Segarra -- fix: merge in dev by:alexm -- fix: missing code by:Jon -- fix: Options VnSelect properties by:Javier Segarra -- fix: refs #4074 await to watch by:jorgep -- fix: refs #4074 drop wrong acl by:jorgep -- fix: refs #4074 workerCard data-key by:jorgep -- fix: refs #6346 fix list and create by:pablone -- fix: refs #6943 prevent null (origin/6943-warmfix-preventNull) by:jorgep -- fix: refs #7155 remove userParams in watcher (7155-travel_daysOnward) by:alexm -- fix: refs #7155 use chip-locale (origin/7155-travel_daysOnward_2, 7155-travel_daysOnward_2) by:alexm -- fix: refs #7404 remove console.log by:pablone -- fix: refs #7404 remove from test by:pablone -- fix: refs #7404 remove some style by:pablone -- fix: refs #7404 revert commit prevent production access by:pablone -- fix: refs #7404 style non center pop up (origin/7404-fixFront) by:pablone -- fix: refs #7404 translates and some minor style fixes by:pablone -- fix: refs #7500 fixed e2e test by:Jon -- fix: refs #7500 fixed showing images wrongly by:Jon -- fix: refs #7830 customer credit by:pablone -- fix: refs #7830 remove console.log by:pablone -- fix: remove console.log by:pablone -- fix: remove FetchData by:Javier Segarra -- fix: remove FIXME (origin/6943_fix_customerSummaryTable) by:Javier Segarra -- fix: remove print variable by:Javier Segarra -- fix: remove promise execution by:Javier Segarra -- fix: reset VnTable scroll properties by:Javier Segarra -- fix: rule by:Javier Segarra -- fix: solve conflicts from master to test by:Javier Segarra -- fix: split params (origin/warmfix-addSearchUrl) by:jorgep -- fix: state cell by:Javier Segarra -- fix: stop call back event hasMoreData by:Javier Segarra -- fix: styles by:Javier Segarra -- fix: SupplierFiscalData VnLocation (origin/fix_supplierFD_location) by:Javier Segarra -- fix: VnLocation test by:Javier Segarra -- fix(VnTable): header background-color by:alexm -- fix(VnTable): sanitizer value is defined by:carlossa -- fix: wagon reload (origin/FixWagonRedirect) by:Jon -- fix: workerDms filter workerFk by:alexm -- fix(WorkerList): add type email by:alexm -- Merge remote-tracking branch 'origin/7830-customerDesplegables' into 6943_fix_customerSummaryTable by:Javier Segarra -- refs #7155 scopeDays fix (origin/7155-scopeDays) by:carlossa -- revert: vnUSerLink change by:Javier Segarra -- test: fix test (7677_vnLocation_perf) by:Javier Segarra +- chore: refs #4074 fix tests by:jorgep +- chore: refs #7828 fix e2e by:jorgep +- feat: refs #7404 add m3 and fix detail by:pablone +- feat: translations fixed by:jgallego +- fix: #5938 grouped filter by:Javier Segarra +- fix: #6943 fix customerSummaryTable by:Javier Segarra +- fix: #6943 show nickname salesPerson by:Javier Segarra +- fix: address-create i18n by:Javier Segarra +- fix: comments (origin/6943_fix_customer_module, 6943_fix_customer_module) by:Javier Segarra +- fix: CusomerSummary to Address by:Javier Segarra +- fix: CustomerAddress mobile by:Javier Segarra +- fix: CustomerBillingData by:Javier Segarra +- fix: Customerconsumption by:Javier Segarra +- fix: customer credit opinion by:alexm +- fix: CustomerCreditOpinion workerDescriptor by:Javier Segarra +- fix: CustomerDescriptorAccount by:Javier Segarra +- fix: CustomerDescriptor.bussinessTypeFk by:Javier Segarra +- fix: CustomerFilter by:Javier Segarra +- fix: CustomerGreuges by:Javier Segarra +- fix: CustomerMandates by:Javier Segarra +- fix: Customer module find salesPersons out of first get by:Javier Segarra +- fix: CustomerRecovery transalate label by:Javier Segarra +- fix: CustomerSamples by:Javier Segarra +- fix: customerSummaryToTicketList button by:Javier Segarra +- fix: CustomerWebPayment by:Javier Segarra +- fix: CustommerSummaryTable Proxy by:Javier Segarra +- fix: deleted code by:Jon +- fix: duplicate code by:alexm +- fix: emit:updateModelValue by:Javier Segarra +- fix: fixed wagon tests by:Jon +- fix: fix wagon list reload by:Jon +- fix: i18n en preparation label by:Javier Segarra +- fix: infiniteScroll by:Javier Segarra +- fix: isFullMovable checkbox by:Javier Segarra +- fix: merge conflicts by:Javier Segarra +- fix: merge in dev by:alexm +- fix: missing code by:Jon +- fix: Options VnSelect properties by:Javier Segarra +- fix: refs #4074 await to watch by:jorgep +- fix: refs #4074 drop wrong acl by:jorgep +- fix: refs #4074 workerCard data-key by:jorgep +- fix: refs #6346 fix list and create by:pablone +- fix: refs #6943 prevent null (origin/6943-warmfix-preventNull) by:jorgep +- fix: refs #7155 remove userParams in watcher (7155-travel_daysOnward) by:alexm +- fix: refs #7155 use chip-locale (origin/7155-travel_daysOnward_2, 7155-travel_daysOnward_2) by:alexm +- fix: refs #7404 remove console.log by:pablone +- fix: refs #7404 remove from test by:pablone +- fix: refs #7404 remove some style by:pablone +- fix: refs #7404 revert commit prevent production access by:pablone +- fix: refs #7404 style non center pop up (origin/7404-fixFront) by:pablone +- fix: refs #7404 translates and some minor style fixes by:pablone +- fix: refs #7500 fixed e2e test by:Jon +- fix: refs #7500 fixed showing images wrongly by:Jon +- fix: refs #7830 customer credit by:pablone +- fix: refs #7830 remove console.log by:pablone +- fix: remove console.log by:pablone +- fix: remove FetchData by:Javier Segarra +- fix: remove FIXME (origin/6943_fix_customerSummaryTable) by:Javier Segarra +- fix: remove print variable by:Javier Segarra +- fix: remove promise execution by:Javier Segarra +- fix: reset VnTable scroll properties by:Javier Segarra +- fix: rule by:Javier Segarra +- fix: solve conflicts from master to test by:Javier Segarra +- fix: split params (origin/warmfix-addSearchUrl) by:jorgep +- fix: state cell by:Javier Segarra +- fix: stop call back event hasMoreData by:Javier Segarra +- fix: styles by:Javier Segarra +- fix: SupplierFiscalData VnLocation (origin/fix_supplierFD_location) by:Javier Segarra +- fix: VnLocation test by:Javier Segarra +- fix(VnTable): header background-color by:alexm +- fix(VnTable): sanitizer value is defined by:carlossa +- fix: wagon reload (origin/FixWagonRedirect) by:Jon +- fix: workerDms filter workerFk by:alexm +- fix(WorkerList): add type email by:alexm +- Merge remote-tracking branch 'origin/7830-customerDesplegables' into 6943_fix_customerSummaryTable by:Javier Segarra +- refs #7155 scopeDays fix (origin/7155-scopeDays) by:carlossa +- revert: vnUSerLink change by:Javier Segarra +- test: fix test (7677_vnLocation_perf) by:Javier Segarra # Version 24.38 - 2024-09-17 ### Added 🆕 -- chore: refs #6772 fix e2e (origin/6772-warmfix-fixE2e) by:jorgep -- chore: refs #7323 worker changes by:jorgep -- chore: refs #7353 fix warnings by:jorgep -- chore: refs #7353 use Vue component nomenclature by:jorgep -- chore: refs #7356 fix type by:jorgep -- feat(AccountConnections): use VnToken by:alexm -- feat: add key to routerView by:Javier Segarra -- feat: add plus shortcut in VnTable by:Javier Segarra -- feat: add row by:Javier Segarra -- feat: addRow withour dialog by:Javier Segarra -- feat: apply mixin by:Javier Segarra -- feat by:Javier Segarra -- feat: change navBar buttons by:Javier Segarra -- feat: dense rows by:Javier Segarra -- feat: fields with wrong name by:jgallego -- feat: fix bugs and filters by:Javier Segarra -- feat: fix refund parameters by:jgallego -- feat: handle create row by:Javier Segarra -- feat: handle dates by:Javier Segarra -- feat: handle qCheckbox 3rd state by:Javier Segarra -- feat: imrpove VnInputTime to set cursor at start by:Javier Segarra -- feat: keyShortcut directive by:Javier Segarra -- feat: minor fixes by:jgallego -- feat: only filter by isDestiny by:Javier Segarra -- feat: refs #211153 businessDataLinkGrafana by:robert -- feat: refs #7129 add km start and end on create form by:pablone -- feat: refs #7353 add filter & fix customTags by:jorgep -- feat: refs #7353 add locale by:jorgep -- feat: refs #7353 add no one opt by:jorgep -- feat: refs #7353 add right icons by:jorgep -- feat: refs #7353 imporve toDateFormat by:jorgep -- feat: refs #7353 salesPerson nickname & id by:jorgep -- feat: refs #7353 split sections by:jorgep -- feat: refs #7847 remove reload btn by:jorgep -- feat: refs #7847 remove reload fn by:jorgep -- feat: refs #7889 added shortcuts to modules by:Jon -- feat: refs #7911 added shortcut to modules by:Jon -- feat: refuncInvoiceForm component by:jgallego -- feat: remove duplicity by:Javier Segarra -- feat: remove future itemFixedPrices by:Javier Segarra -- feat: replace stickyButtons by subtoolbar by:Javier Segarra -- feat: required validation by:Javier Segarra -- feat: show bad dates by:Javier Segarra -- feat: showdate icons by:Javier Segarra -- feat: solve ItemFixedFilterPanel by:Javier Segarra -- feat: transfer an invoice by:jgallego -- feat: try to fix ItemFixedFilterPanel by:Javier Segarra -- feat: unnecessary changes by:Javier Segarra -- feat: update changelog (origin/7896_down_devToTest_2436) by:Javier Segarra -- feat: updates by:Javier Segarra -- feat: update version and changelog by:Javier Segarra -- feat: vnInput\* by:Javier Segarra -- feat: with VnTable by:Javier Segarra -- refs #6772 feat: fix approach by:Javier Segarra -- refs #6772 feat: refresh shelving.basic-data by:Javier Segarra -- style: show subName value by:Javier Segarra +- chore: refs #6772 fix e2e (origin/6772-warmfix-fixE2e) by:jorgep +- chore: refs #7323 worker changes by:jorgep +- chore: refs #7353 fix warnings by:jorgep +- chore: refs #7353 use Vue component nomenclature by:jorgep +- chore: refs #7356 fix type by:jorgep +- feat(AccountConnections): use VnToken by:alexm +- feat: add key to routerView by:Javier Segarra +- feat: add plus shortcut in VnTable by:Javier Segarra +- feat: add row by:Javier Segarra +- feat: addRow withour dialog by:Javier Segarra +- feat: apply mixin by:Javier Segarra +- feat by:Javier Segarra +- feat: change navBar buttons by:Javier Segarra +- feat: dense rows by:Javier Segarra +- feat: fields with wrong name by:jgallego +- feat: fix bugs and filters by:Javier Segarra +- feat: fix refund parameters by:jgallego +- feat: handle create row by:Javier Segarra +- feat: handle dates by:Javier Segarra +- feat: handle qCheckbox 3rd state by:Javier Segarra +- feat: imrpove VnInputTime to set cursor at start by:Javier Segarra +- feat: keyShortcut directive by:Javier Segarra +- feat: minor fixes by:jgallego +- feat: only filter by isDestiny by:Javier Segarra +- feat: refs #211153 businessDataLinkGrafana by:robert +- feat: refs #7129 add km start and end on create form by:pablone +- feat: refs #7353 add filter & fix customTags by:jorgep +- feat: refs #7353 add locale by:jorgep +- feat: refs #7353 add no one opt by:jorgep +- feat: refs #7353 add right icons by:jorgep +- feat: refs #7353 imporve toDateFormat by:jorgep +- feat: refs #7353 salesPerson nickname & id by:jorgep +- feat: refs #7353 split sections by:jorgep +- feat: refs #7847 remove reload btn by:jorgep +- feat: refs #7847 remove reload fn by:jorgep +- feat: refs #7889 added shortcuts to modules by:Jon +- feat: refs #7911 added shortcut to modules by:Jon +- feat: refuncInvoiceForm component by:jgallego +- feat: remove duplicity by:Javier Segarra +- feat: remove future itemFixedPrices by:Javier Segarra +- feat: replace stickyButtons by subtoolbar by:Javier Segarra +- feat: required validation by:Javier Segarra +- feat: show bad dates by:Javier Segarra +- feat: showdate icons by:Javier Segarra +- feat: solve ItemFixedFilterPanel by:Javier Segarra +- feat: transfer an invoice by:jgallego +- feat: try to fix ItemFixedFilterPanel by:Javier Segarra +- feat: unnecessary changes by:Javier Segarra +- feat: update changelog (origin/7896_down_devToTest_2436) by:Javier Segarra +- feat: updates by:Javier Segarra +- feat: update version and changelog by:Javier Segarra +- feat: vnInput\* by:Javier Segarra +- feat: with VnTable by:Javier Segarra +- refs #6772 feat: fix approach by:Javier Segarra +- refs #6772 feat: refresh shelving.basic-data by:Javier Segarra +- style: show subName value by:Javier Segarra ### Changed 📦 -- perf: add v-shortcut in VnCard by:Javier Segarra -- perf: approach by:Javier Segarra -- perf: change directive location by:Javier Segarra -- perf: change slots order by:Javier Segarra -- perf: examples by:Javier Segarra -- perf: hide icon for VnInputDate by:Javier Segarra -- perf: improve ItemFixedPricefilterPanel by:Javier Segarra -- perf: improve mainShrotcutMixin by:Javier Segarra -- perf: minor clean code by:Javier Segarra -- perf: onRowchange by:Javier Segarra -- perf: order by by:Javier Segarra -- perf: order components by:Javier Segarra -- perf: refs #7889 perf shortcut test by:Jon -- perf: remove console.log by:Javier Segarra -- perf: remove icons in header slot by:Javier Segarra -- perf: remove print variables by:Javier Segarra -- perf: restore CustomerBasicData by:Javier Segarra -- refactor: deleted useless prop by:Jon -- refactor: deleted useless prop in FetchedTags by:Jon -- refactor: refs #7323 drop useless code by:jorgep -- refactor: refs #7353 clients correction by:jorgep -- refactor: refs #7353 clients correction wip by:jorgep -- refactor: refs #7353 ease logic by:jorgep -- refactor: refs #7353 order correction by:jorgep -- refactor: refs #7353 simplify code by:jorgep -- refactor: refs #7353 tickets correction by:jorgep -- refactor: refs #7353 use global locales by:jorgep -- refactor: refs #7354 changed descriptor menu options by:Jon -- refactor: refs #7354 changed icon color in table and notification when deleting a zone by:Jon -- refactor: refs #7354 fix tableFilters by:Jon -- refactor: refs #7354 modified VnInputTime by:Jon -- refactor: refs #7354 refactor deliveryPanel by:Jon -- refactor: refs #7354 refactor zones section and fixed e2e tests by:Jon -- refactor: refs #7354 requested changes by:Jon -- refactor: refs #7354 reverse deliveryPanel changes by:Jon -- refactor: refs #7354 Zone migration changes by:Jon -- refactor: refs #7889 deleted subtitle attr and use keyBinding instead by:Jon -- refactor: refs #7889 modified shortcut and dashboard, and added tootlip in LeftMenu by:Jon -- refs #6722 perf: not fetch when id not exists by:Javier Segarra -- refs #6772 perf: change variable name by:JAVIER SEGARRA MARTINEZ -- refs #6772 perf: use ArrayData (6772_reload_sections) by:Javier Segarra -- refs #7283 refactor fix ItemDescriptor by:carlossa -- refs #7283 refactor ItexDescriptor by:carlossa +- perf: add v-shortcut in VnCard by:Javier Segarra +- perf: approach by:Javier Segarra +- perf: change directive location by:Javier Segarra +- perf: change slots order by:Javier Segarra +- perf: examples by:Javier Segarra +- perf: hide icon for VnInputDate by:Javier Segarra +- perf: improve ItemFixedPricefilterPanel by:Javier Segarra +- perf: improve mainShrotcutMixin by:Javier Segarra +- perf: minor clean code by:Javier Segarra +- perf: onRowchange by:Javier Segarra +- perf: order by by:Javier Segarra +- perf: order components by:Javier Segarra +- perf: refs #7889 perf shortcut test by:Jon +- perf: remove console.log by:Javier Segarra +- perf: remove icons in header slot by:Javier Segarra +- perf: remove print variables by:Javier Segarra +- perf: restore CustomerBasicData by:Javier Segarra +- refactor: deleted useless prop by:Jon +- refactor: deleted useless prop in FetchedTags by:Jon +- refactor: refs #7323 drop useless code by:jorgep +- refactor: refs #7353 clients correction by:jorgep +- refactor: refs #7353 clients correction wip by:jorgep +- refactor: refs #7353 ease logic by:jorgep +- refactor: refs #7353 order correction by:jorgep +- refactor: refs #7353 simplify code by:jorgep +- refactor: refs #7353 tickets correction by:jorgep +- refactor: refs #7353 use global locales by:jorgep +- refactor: refs #7354 changed descriptor menu options by:Jon +- refactor: refs #7354 changed icon color in table and notification when deleting a zone by:Jon +- refactor: refs #7354 fix tableFilters by:Jon +- refactor: refs #7354 modified VnInputTime by:Jon +- refactor: refs #7354 refactor deliveryPanel by:Jon +- refactor: refs #7354 refactor zones section and fixed e2e tests by:Jon +- refactor: refs #7354 requested changes by:Jon +- refactor: refs #7354 reverse deliveryPanel changes by:Jon +- refactor: refs #7354 Zone migration changes by:Jon +- refactor: refs #7889 deleted subtitle attr and use keyBinding instead by:Jon +- refactor: refs #7889 modified shortcut and dashboard, and added tootlip in LeftMenu by:Jon +- refs #6722 perf: not fetch when id not exists by:Javier Segarra +- refs #6772 perf: change variable name by:JAVIER SEGARRA MARTINEZ +- refs #6772 perf: use ArrayData (6772_reload_sections) by:Javier Segarra +- refs #7283 refactor fix ItemDescriptor by:carlossa +- refs #7283 refactor ItexDescriptor by:carlossa ### Fixed 🛠️ -- chore: refs #6772 fix e2e (origin/6772-warmfix-fixE2e) by:jorgep -- chore: refs #7353 fix warnings by:jorgep -- chore: refs #7356 fix type by:jorgep -- feat: fix bugs and filters by:Javier Segarra -- feat: fix refund parameters by:jgallego -- feat: minor fixes by:jgallego -- feat: refs #7353 add filter & fix customTags by:jorgep -- feat: try to fix ItemFixedFilterPanel by:Javier Segarra -- fix: add border-top by:Javier Segarra -- fix: added missing descriptors and small details by:Jon -- fix branch by:carlossa -- fix: call upsert when crudModel haschanges by:Javier Segarra -- fix(ClaimList): fix summary by:alexm -- fix: cli warnings by:Javier Segarra -- fix: editTableOptions by:Javier Segarra -- fix events and descriptor menu by:Jon -- fix: InvoiceIn sections (origin/6772_reload_sections) by:Javier Segarra -- fix: minor changes by:Javier Segarra -- fix: minor error whit dates by:Javier Segarra -- fix: module icon by:Javier Segarra -- fix: options QDate by:Javier Segarra -- fix: refs #6900 e2e error by:jorgep -- fix: refs #6900 rollback by:jorgep -- fix: refs #7353 css by:jorgep -- fix: refs #7353 hide search param (origin/7353-warmfix-fixSearchbar) by:jorgep -- fix: refs #7353 iron out filter by:jorgep -- fix: refs #7353 iron out ticket table by:jorgep -- fix: refs #7353 padding by:jorgep -- fix: refs #7353 salesClientTable by:jorgep -- fix: refs #7353 salesorderTable by:jorgep -- fix: refs #7353 saleTicketMonitors by:jorgep -- fix: refs #7353 use same datakey by:jorgep -- fix: refs #7353 vnTable colors by:jorgep -- fix: refs #7354 e2e tests by:Jon -- fix: refs #7354 fix delivery days by:Jon -- fix: refs #7354 fix list searchbar and filters by:Jon -- fix: refs #7354 fix VnSearchbar search for zone section & finished basic tests by:Jon -- fix: refs #7354 fix VnTable filters and agency field by:Jon -- fix: refs #7354 fix zoneSearchbar by:Jon -- fix: refs #7354 requested changes by:Jon -- fix: refs #7356 colors by:jorgep -- fix: refs #7356 create claim dialog by:jorgep -- fix: refs #7889 fixed shortcut test by:Jon -- fix: refs #7903 fixed ticket's search bar and keybinding tooltip by:Jon -- fix: refs #7911 fixed shortcut and related files by:Jon -- fix: remove condition duplicated by:Javier Segarra -- fix: remove property by:Javier Segarra -- fix tootltip by:carlossa -- fix traduction by:carlossa -- fix(VnSectionMain): add QPage by:alexm -- fix(zone): zoneLocation and the others searchbar by:alexm -- refactor: refs #7354 fix tableFilters by:Jon -- refactor: refs #7354 refactor zones section and fixed e2e tests by:Jon -- refs #6772 feat: fix approach by:Javier Segarra -- refs #6772 fix: claimPhoto reload by:Javier Segarra -- refs #6896 fix searchbar by:carlossa -- refs #6897 fix entry by:carlossa -- refs #6899 fix invoiceFix by:carlossa -- refs #6899 fix order by:carlossa -- refs #7283 fix by:carlossa -- refs #7283 fix ItemDescriptor warehouse by:carlossa -- refs #7283 refactor fix ItemDescriptor by:carlossa -- refs #7355 #7366 fix account, summary, list, travelList, tooltip by:carlossa -- refs #7355 fix accountPrivileges by:carlossa -- refs #7355 fix accounts, vnTable by:carlossa -- refs #7355 fix privileges by:carlossa -- refs #7355 fix roles filters by:carlossa -- refs #7355 fix total by:carlossa -- refs #7355 fix views summarys, entryList, travelList refact by:carlossa -- refs #7366 fix travel hours by:carlossa -- test: fix e2e by:Javier Segarra +- chore: refs #6772 fix e2e (origin/6772-warmfix-fixE2e) by:jorgep +- chore: refs #7353 fix warnings by:jorgep +- chore: refs #7356 fix type by:jorgep +- feat: fix bugs and filters by:Javier Segarra +- feat: fix refund parameters by:jgallego +- feat: minor fixes by:jgallego +- feat: refs #7353 add filter & fix customTags by:jorgep +- feat: try to fix ItemFixedFilterPanel by:Javier Segarra +- fix: add border-top by:Javier Segarra +- fix: added missing descriptors and small details by:Jon +- fix branch by:carlossa +- fix: call upsert when crudModel haschanges by:Javier Segarra +- fix(ClaimList): fix summary by:alexm +- fix: cli warnings by:Javier Segarra +- fix: editTableOptions by:Javier Segarra +- fix events and descriptor menu by:Jon +- fix: InvoiceIn sections (origin/6772_reload_sections) by:Javier Segarra +- fix: minor changes by:Javier Segarra +- fix: minor error whit dates by:Javier Segarra +- fix: module icon by:Javier Segarra +- fix: options QDate by:Javier Segarra +- fix: refs #6900 e2e error by:jorgep +- fix: refs #6900 rollback by:jorgep +- fix: refs #7353 css by:jorgep +- fix: refs #7353 hide search param (origin/7353-warmfix-fixSearchbar) by:jorgep +- fix: refs #7353 iron out filter by:jorgep +- fix: refs #7353 iron out ticket table by:jorgep +- fix: refs #7353 padding by:jorgep +- fix: refs #7353 salesClientTable by:jorgep +- fix: refs #7353 salesorderTable by:jorgep +- fix: refs #7353 saleTicketMonitors by:jorgep +- fix: refs #7353 use same datakey by:jorgep +- fix: refs #7353 vnTable colors by:jorgep +- fix: refs #7354 e2e tests by:Jon +- fix: refs #7354 fix delivery days by:Jon +- fix: refs #7354 fix list searchbar and filters by:Jon +- fix: refs #7354 fix VnSearchbar search for zone section & finished basic tests by:Jon +- fix: refs #7354 fix VnTable filters and agency field by:Jon +- fix: refs #7354 fix zoneSearchbar by:Jon +- fix: refs #7354 requested changes by:Jon +- fix: refs #7356 colors by:jorgep +- fix: refs #7356 create claim dialog by:jorgep +- fix: refs #7889 fixed shortcut test by:Jon +- fix: refs #7903 fixed ticket's search bar and keybinding tooltip by:Jon +- fix: refs #7911 fixed shortcut and related files by:Jon +- fix: remove condition duplicated by:Javier Segarra +- fix: remove property by:Javier Segarra +- fix tootltip by:carlossa +- fix traduction by:carlossa +- fix(VnSectionMain): add QPage by:alexm +- fix(zone): zoneLocation and the others searchbar by:alexm +- refactor: refs #7354 fix tableFilters by:Jon +- refactor: refs #7354 refactor zones section and fixed e2e tests by:Jon +- refs #6772 feat: fix approach by:Javier Segarra +- refs #6772 fix: claimPhoto reload by:Javier Segarra +- refs #6896 fix searchbar by:carlossa +- refs #6897 fix entry by:carlossa +- refs #6899 fix invoiceFix by:carlossa +- refs #6899 fix order by:carlossa +- refs #7283 fix by:carlossa +- refs #7283 fix ItemDescriptor warehouse by:carlossa +- refs #7283 refactor fix ItemDescriptor by:carlossa +- refs #7355 #7366 fix account, summary, list, travelList, tooltip by:carlossa +- refs #7355 fix accountPrivileges by:carlossa +- refs #7355 fix accounts, vnTable by:carlossa +- refs #7355 fix privileges by:carlossa +- refs #7355 fix roles filters by:carlossa +- refs #7355 fix total by:carlossa +- refs #7355 fix views summarys, entryList, travelList refact by:carlossa +- refs #7366 fix travel hours by:carlossa +- test: fix e2e by:Javier Segarra # Version 24.36 - 2024-08-27 ### Added 🆕 -- feat(FormModel): trim data by default by:alexm -- feat(orderBasicData): add notes by:alexm -- feat(orderList): correct create order by:alexm -- feat(orderList): use orderFilter and fixed this by:alexm -- feat: #7323 handle workerPhoto (origin/7323_workerPhoto, 7323_workerPhoto) by:Javier Segarra -- feat: add recover password and reset password by:alexm -- feat: refs #7346 add seriaType option by:jgallego -- feat: refs #7346 elimino === by:jgallego -- feat: refs #7346 formdata uses serialType by:jgallego -- feat: refs #7346 refactor by:jgallego -- feat: refs #7346 sonarLint warnings (origin/7346-invoiceOutMultilple, 7346-invoiceOutMultilple) by:jgallego -- feat: refs #7710 uses cloneAll by:jgallego -- fix: refs #7717 fix OrderList table filters' and summary table style by:Jon +- feat(FormModel): trim data by default by:alexm +- feat(orderBasicData): add notes by:alexm +- feat(orderList): correct create order by:alexm +- feat(orderList): use orderFilter and fixed this by:alexm +- feat: #7323 handle workerPhoto (origin/7323_workerPhoto, 7323_workerPhoto) by:Javier Segarra +- feat: add recover password and reset password by:alexm +- feat: refs #7346 add seriaType option by:jgallego +- feat: refs #7346 elimino === by:jgallego +- feat: refs #7346 formdata uses serialType by:jgallego +- feat: refs #7346 refactor by:jgallego +- feat: refs #7346 sonarLint warnings (origin/7346-invoiceOutMultilple, 7346-invoiceOutMultilple) by:jgallego +- feat: refs #7710 uses cloneAll by:jgallego +- fix: refs #7717 fix OrderList table filters' and summary table style by:Jon ### Changed 📦 -- feat: refs #7346 refactor by:jgallego -- perf: date fields (mindshore/feature/TicketFutureFilter, feature/TicketFutureFilter) by:Javier Segarra -- perf: refs #7717 right menu filter by:Jon -- perf: use ref at component start by:Javier Segarra -- refactor: refs #7717 delete useless function and import by:Jon -- refactor: refs #7717 deleted useless code by:Jon +- feat: refs #7346 refactor by:jgallego +- perf: date fields (mindshore/feature/TicketFutureFilter, feature/TicketFutureFilter) by:Javier Segarra +- perf: refs #7717 right menu filter by:Jon +- perf: use ref at component start by:Javier Segarra +- refactor: refs #7717 delete useless function and import by:Jon +- refactor: refs #7717 deleted useless code by:Jon ### Fixed 🛠️ -- feat(orderList): use orderFilter and fixed this by:alexm -- fix(VnTable): orderBy v-model by:alexm -- fix(account_card): redirection by:carlossa -- fix(orderLines): reload when delete and redirect when confirm by:alexm -- fix: #6336 ClaimListStates by:Javier Segarra -- fix: account subsections cards by:carlossa -- fix: duplicate key by:Jon -- fix: order description to vnTable by:alexm -- fix: orderCatalogFilter order by:alexm -- fix: quasar build warnings (6336_claim_fix_states) by:Javier Segarra -- fix: refs #7717 fix OrderList table filters' and summary table style by:Jon -- fix: refs #7717 fix basic data form & minor errors by:Jon -- fix: refs #7717 fix catalog filter, searchbar redirect and search by:Jon -- fix: refs #7717 fix catalog searchbar and worker tests(refs #7323) by:Jon -- fix: refs #7717 fix order sections by:Jon -- fix: refs #7717 fix volume and lines redirect by:Jon -- fix: refs #7717 fixed searchbar filter with rightmenu filters' applied by:Jon -- fix: test by:alexm -- fix: ticketDescriptorMenu by:Javier Segarra -- refs #7355 account fixes by:carlossa +- feat(orderList): use orderFilter and fixed this by:alexm +- fix(VnTable): orderBy v-model by:alexm +- fix(account_card): redirection by:carlossa +- fix(orderLines): reload when delete and redirect when confirm by:alexm +- fix: #6336 ClaimListStates by:Javier Segarra +- fix: account subsections cards by:carlossa +- fix: duplicate key by:Jon +- fix: order description to vnTable by:alexm +- fix: orderCatalogFilter order by:alexm +- fix: quasar build warnings (6336_claim_fix_states) by:Javier Segarra +- fix: refs #7717 fix OrderList table filters' and summary table style by:Jon +- fix: refs #7717 fix basic data form & minor errors by:Jon +- fix: refs #7717 fix catalog filter, searchbar redirect and search by:Jon +- fix: refs #7717 fix catalog searchbar and worker tests(refs #7323) by:Jon +- fix: refs #7717 fix order sections by:Jon +- fix: refs #7717 fix volume and lines redirect by:Jon +- fix: refs #7717 fixed searchbar filter with rightmenu filters' applied by:Jon +- fix: test by:alexm +- fix: ticketDescriptorMenu by:Javier Segarra +- refs #7355 account fixes by:carlossa # Version 24.34 - 2024-08-20 ### Added 🆕 -- chore: #6900 order params by:jorgep -- chore: refs #6900 drop console log by:jorgep -- chore: refs #6900 drop vnCurrency by:jorgep -- chore: refs #6900 fix e2e tests by:jorgep -- chore: refs #6900 mv rectificative logic by:jorgep -- chore: refs #6900 responsive code by:jorgep -- chore: refs #7283 drop array types by:jorgep -- chore: refs #7283 drop import by:jorgep -- chore: refs #7283 fix e2e logout by:jorgep -- chore: refs #7283 update VnAvatar title handling by:jorgep -- chore: refs #7323 fix test by:jorgep -- chore: refs #7323 remove unused import by:jorgep -- chore: refs #7323drop commented code by:jorgep -- feat(VnCard): use props searchbar by:alexm -- feat(customer): improve basicData to balance by:alexm -- feat(customer_balance): refs #6943 add functionality from salix by:alexm -- feat(customer_balance): refs #6943 translations by:alexm -- feat: refs #6130 husky commitLint config by:pablone -- feat: refs #6130 husky hooks by:pablone -- feat: refs #6900 add InvoiceInSerial by:jorgep -- feat: refs #6900 add locale by:jorgep -- feat: refs #6900 use VnTable & sort filter fields by:jorgep -- feat: refs #7323 add flex-wrap by:jorgep -- feat: refs #7323 add my account" btn & fix models log selectable by:jorgep -- feat: refs #7323 improve test by:jorgep +- chore: #6900 order params by:jorgep +- chore: refs #6900 drop console log by:jorgep +- chore: refs #6900 drop vnCurrency by:jorgep +- chore: refs #6900 fix e2e tests by:jorgep +- chore: refs #6900 mv rectificative logic by:jorgep +- chore: refs #6900 responsive code by:jorgep +- chore: refs #7283 drop array types by:jorgep +- chore: refs #7283 drop import by:jorgep +- chore: refs #7283 fix e2e logout by:jorgep +- chore: refs #7283 update VnAvatar title handling by:jorgep +- chore: refs #7323 fix test by:jorgep +- chore: refs #7323 remove unused import by:jorgep +- chore: refs #7323drop commented code by:jorgep +- feat(VnCard): use props searchbar by:alexm +- feat(customer): improve basicData to balance by:alexm +- feat(customer_balance): refs #6943 add functionality from salix by:alexm +- feat(customer_balance): refs #6943 translations by:alexm +- feat: refs #6130 husky commitLint config by:pablone +- feat: refs #6130 husky hooks by:pablone +- feat: refs #6900 add InvoiceInSerial by:jorgep +- feat: refs #6900 add locale by:jorgep +- feat: refs #6900 use VnTable & sort filter fields by:jorgep +- feat: refs #7323 add flex-wrap by:jorgep +- feat: refs #7323 add my account" btn & fix models log selectable by:jorgep +- feat: refs #7323 improve test by:jorgep ### Changed 📦 -- refactor(customer_log: use VnLog by:alexm -- refactor(customer_recovery): to vnTable by:alexm -- refactor(customer_webAccess): FormModel by:alexm -- refactor: refs #7283 update avatar size and color by:jorgep +- refactor(customer_log: use VnLog by:alexm +- refactor(customer_recovery): to vnTable by:alexm +- refactor(customer_webAccess): FormModel by:alexm +- refactor: refs #7283 update avatar size and color by:jorgep ### Fixed 🛠️ -- chore: refs #6900 fix e2e tests by:jorgep -- chore: refs #7283 fix e2e logout by:jorgep -- chore: refs #7323 fix test by:jorgep -- feat: refs #7323 add my account" btn & fix models log selectable by:jorgep -- fix #7355 fix acls list by:carlossa -- fix(VnFilterPanel): emit userParams better by:alexm -- fix(claim_summary): url links (HEAD -> 7864_testToMaster_2434, origin/test, origin/7864_testToMaster_2434, test) by:alexm -- fix(customer_sms: fix reload by:alexm -- fix(twoFactor): unify code login and twoFactor by:alexm -- fix: VnCard VnSearchbar props by:alexm -- fix: accountMailAlias by:alexm -- fix: refs #6130 add commit lint modules by:pablone -- fix: refs #6130 pnpm-lock.yml by:pablone -- fix: refs #6900 improve loading by:jorgep -- fix: refs #6900 improve logic (origin/6900-addSerial) by:jorgep -- fix: refs #6900 improve logic by:jorgep -- fix: refs #6900 rectificative btn reactivity by:jorgep -- fix: refs #6900 use type number by:jorgep -- fix: refs #6900 vat & dueday by:jorgep -- fix: refs #6900 vat, dueday & intrastat by:jorgep -- fix: refs #6989 show entity name & default time from config table by:jorgep -- fix: refs #7283 basicData locale by:jorgep -- fix: refs #7283 itemLastEntries filter by:jorgep -- fix: refs #7283 itemTags & VnImg by:jorgep -- fix: refs #7283 locale by:jorgep -- fix: refs #7283 min-width vnImg by:jorgep -- fix: refs #7283 use vnAvatar & add optional zoom by:jorgep -- fix: refs #7283 userPanel pic by:jorgep -- fix: refs #7323 add department popup by:jorgep -- fix: refs #7323 add locale by:jorgep -- fix: refs #7323 css righ menu by:jorgep -- fix: refs #7323 data-key & add select by:jorgep -- fix: refs #7323 load all opts by:jorgep -- fix: refs #7323 righ menu bug by:jorgep -- fix: refs #7323 use global locale by:jorgep -- fix: refs #7323 use workerFilter (origin/7323-warmfix-fixErrors) by:jorgep -- fix: refs #7323 vnsubtoolbar css by:jorgep -- fix: refs #7323 wrong css by:jorgep -- refs #7355 fix Rol, alias by:carlossa -- refs #7355 fix accountAlias by:carlossa -- refs #7355 fix alias summary by:carlossa -- refs #7355 fix conflicts by:carlossa -- refs #7355 fix create Rol by:carlossa -- refs #7355 fix list by:carlossa -- refs #7355 fix lists redirects summary by:carlossa -- refs #7355 fix roles by:carlossa -- refs #7355 fix search exprBuilder by:carlossa -- refs #7355 fix vnTable by:carlossa +- chore: refs #6900 fix e2e tests by:jorgep +- chore: refs #7283 fix e2e logout by:jorgep +- chore: refs #7323 fix test by:jorgep +- feat: refs #7323 add my account" btn & fix models log selectable by:jorgep +- fix #7355 fix acls list by:carlossa +- fix(VnFilterPanel): emit userParams better by:alexm +- fix(claim_summary): url links (HEAD -> 7864_testToMaster_2434, origin/test, origin/7864_testToMaster_2434, test) by:alexm +- fix(customer_sms: fix reload by:alexm +- fix(twoFactor): unify code login and twoFactor by:alexm +- fix: VnCard VnSearchbar props by:alexm +- fix: accountMailAlias by:alexm +- fix: refs #6130 add commit lint modules by:pablone +- fix: refs #6130 pnpm-lock.yml by:pablone +- fix: refs #6900 improve loading by:jorgep +- fix: refs #6900 improve logic (origin/6900-addSerial) by:jorgep +- fix: refs #6900 improve logic by:jorgep +- fix: refs #6900 rectificative btn reactivity by:jorgep +- fix: refs #6900 use type number by:jorgep +- fix: refs #6900 vat & dueday by:jorgep +- fix: refs #6900 vat, dueday & intrastat by:jorgep +- fix: refs #6989 show entity name & default time from config table by:jorgep +- fix: refs #7283 basicData locale by:jorgep +- fix: refs #7283 itemLastEntries filter by:jorgep +- fix: refs #7283 itemTags & VnImg by:jorgep +- fix: refs #7283 locale by:jorgep +- fix: refs #7283 min-width vnImg by:jorgep +- fix: refs #7283 use vnAvatar & add optional zoom by:jorgep +- fix: refs #7283 userPanel pic by:jorgep +- fix: refs #7323 add department popup by:jorgep +- fix: refs #7323 add locale by:jorgep +- fix: refs #7323 css righ menu by:jorgep +- fix: refs #7323 data-key & add select by:jorgep +- fix: refs #7323 load all opts by:jorgep +- fix: refs #7323 righ menu bug by:jorgep +- fix: refs #7323 use global locale by:jorgep +- fix: refs #7323 use workerFilter (origin/7323-warmfix-fixErrors) by:jorgep +- fix: refs #7323 vnsubtoolbar css by:jorgep +- fix: refs #7323 wrong css by:jorgep +- refs #7355 fix Rol, alias by:carlossa +- refs #7355 fix accountAlias by:carlossa +- refs #7355 fix alias summary by:carlossa +- refs #7355 fix conflicts by:carlossa +- refs #7355 fix create Rol by:carlossa +- refs #7355 fix list by:carlossa +- refs #7355 fix lists redirects summary by:carlossa +- refs #7355 fix roles by:carlossa +- refs #7355 fix search exprBuilder by:carlossa +- refs #7355 fix vnTable by:carlossa # Version 24.32 - 2024-08-06 ### Added 🆕 -- chore: refs #7197 drop space by:jorgep -- chore: refs #7197 drop useless attr by:jorgep -- chore: refs #7197 fix test by:jorgep -- chore: refs #7197 fix tests by:jorgep -- chore: refs #7197 fix unit tests by:jorgep -- chore: refs #7197 idrop useless class by:jorgep -- chore: refs #7197 improve form filling in Cypress tests by:jorgep -- chore: refs #7197 remove unused import by:jorgep -- feat: customerPayments card view by:alexm -- feat: refs #6943 lock grid mode by:jorgep -- feat: refs #6943 wip consumption filter by:jorgep -- feat: refs #7197 add correcting filter by:jorgep -- feat: refs #7197 add supplier activities filter option by:jorgep -- feat: refs #7197 summary responsive by:jorgep -- feat: refs #7323 fix descriptors, added VnTable and minor changes by:Jon -- feat: refs #7323 fixed tests, changed calendar styles and fix workerCreate by:Jon -- feat: refs #7356 list & weekly to VnTable and style fixes by:Jon -- feat: refs #7401 add menu options by:pablone -- feat: SalesClientTable by:Javier Segarra -- feat: salesOrderTable by:Javier Segarra -- feat: salesTicketTable by:Javier Segarra -- feat: VnTable SalesTicketTable by:Javier Segarra -- fix: columns style by:alexm +- chore: refs #7197 drop space by:jorgep +- chore: refs #7197 drop useless attr by:jorgep +- chore: refs #7197 fix test by:jorgep +- chore: refs #7197 fix tests by:jorgep +- chore: refs #7197 fix unit tests by:jorgep +- chore: refs #7197 idrop useless class by:jorgep +- chore: refs #7197 improve form filling in Cypress tests by:jorgep +- chore: refs #7197 remove unused import by:jorgep +- feat: customerPayments card view by:alexm +- feat: refs #6943 lock grid mode by:jorgep +- feat: refs #6943 wip consumption filter by:jorgep +- feat: refs #7197 add correcting filter by:jorgep +- feat: refs #7197 add supplier activities filter option by:jorgep +- feat: refs #7197 summary responsive by:jorgep +- feat: refs #7323 fix descriptors, added VnTable and minor changes by:Jon +- feat: refs #7323 fixed tests, changed calendar styles and fix workerCreate by:Jon +- feat: refs #7356 list & weekly to VnTable and style fixes by:Jon +- feat: refs #7401 add menu options by:pablone +- feat: SalesClientTable by:Javier Segarra +- feat: salesOrderTable by:Javier Segarra +- feat: salesTicketTable by:Javier Segarra +- feat: VnTable SalesTicketTable by:Javier Segarra +- fix: columns style by:alexm ### Changed 📦 -- perf: LeftMenu show/hide by:Javier Segarra -- perf: refs #7356 TicketList state column by:Jon -- perf: VnFilterPanel (origin/7323_WorkerMigration_End) by:Javier Segarra -- perf: width SalesTicketsTable by:Javier Segarra -- refactor: #6943 wip use vnTable CustomerCredits by:jorgep -- refactor: CustomerNotifications use VnTable by:alexm -- refactor: CustomerPayments use VnTable by:alexm -- refactor: refs #7014 deleted main files and changed route files by:Jon -- refactor: refs #7014 improved route.js & deleted RouteMain by:Jon -- refactor: refs #7014 refactor <module>Main.vue by:Jon -- refactor: refs #7014 refactor ZoneCard, deleted ZoneMain & created basic tests for functionality by:Jon -- refactor: refs #7197 use invoiceInSearchbar & queryParams by:jorgep -- refactor: refs #7323 hidden column filter proposal by:Jon -- refactor: refs #7356 fixed VnTable filters by:Jon -- refactor: refs #7356 requested changes by:Jon -- refactor: wip use vnTable CustomerCredits by:jorgep +- perf: LeftMenu show/hide by:Javier Segarra +- perf: refs #7356 TicketList state column by:Jon +- perf: VnFilterPanel (origin/7323_WorkerMigration_End) by:Javier Segarra +- perf: width SalesTicketsTable by:Javier Segarra +- refactor: #6943 wip use vnTable CustomerCredits by:jorgep +- refactor: CustomerNotifications use VnTable by:alexm +- refactor: CustomerPayments use VnTable by:alexm +- refactor: refs #7014 deleted main files and changed route files by:Jon +- refactor: refs #7014 improved route.js & deleted RouteMain by:Jon +- refactor: refs #7014 refactor <module>Main.vue by:Jon +- refactor: refs #7014 refactor ZoneCard, deleted ZoneMain & created basic tests for functionality by:Jon +- refactor: refs #7197 use invoiceInSearchbar & queryParams by:jorgep +- refactor: refs #7323 hidden column filter proposal by:Jon +- refactor: refs #7356 fixed VnTable filters by:Jon +- refactor: refs #7356 requested changes by:Jon +- refactor: wip use vnTable CustomerCredits by:jorgep ### Fixed 🛠️ -- chore: refs #7197 fix test by:jorgep -- chore: refs #7197 fix tests by:jorgep -- chore: refs #7197 fix unit tests by:jorgep -- feat: refs #7323 fix descriptors, added VnTable and minor changes by:Jon -- feat: refs #7323 fixed tests, changed calendar styles and fix workerCreate by:Jon -- feat: refs #7356 list & weekly to VnTable and style fixes by:Jon -- fix(claim): small details (6336-claim-v6) by:alexm -- fix: columns style by:alexm -- fix: customer defaulter add amount order (6943-fixCustomer) by:alexm -- fix: customerDefaulter correct functionality by:alexm -- fix: customerNotifications filter by:alexm -- fix: fix conflicts by:Jon -- fix: refs #6101 fix TicketList by:Jon -- fix: refs #6891 worker tests by:jorgep -- fix: refs #6943 drop padding-left checkbox & create wrap mode vnRow by:jorgep -- fix: refs #6943 prevent undefined by:jorgep -- fix: refs #7014 fix tests by:Jon -- fix: refs #7014 fix wagon module by:Jon -- fix: refs #7197 add url InvoiceInSearchbar by:jorgep -- fix: refs #7197 amount reactivity by:jorgep -- fix: refs #7197 drop character by:jorgep -- fix: refs #7197 reactivity invoiceCorrection by:jorgep -- fix: refs #7197 responsive summary layout by:jorgep -- fix: refs #7197 rollback by:jorgep -- fix: refs #7197 rollback crudModel by:jorgep -- fix: refs #7197 setInvoiceInCorrecition by:jorgep -- fix: refs #7197 vat, intrastat, filter and list sections by:jorgep -- fix: refs #7323 fix department & email table filter by:Jon -- fix: refs #7323 fixed left filter by:Jon -- fix: refs #7323 fix workerTimeControl form by:Jon -- fix: refs #7401 fix routeForm by:pablone -- fix: refs #7401 remove console.log by:pablone -- fix: refs CAU 207504 fix itemDiary and logs by:Jon -- fix: workerCreate form street field to be always upperCase by:Jon -- hotfix: refs CAU #207614 fix sale.concept field by:Jon -- refactor: refs #7356 fixed VnTable filters by:Jon -- refs #6898 fix by:carlossa -- Ticket expedition initial load fix by:wbuezas +- chore: refs #7197 fix test by:jorgep +- chore: refs #7197 fix tests by:jorgep +- chore: refs #7197 fix unit tests by:jorgep +- feat: refs #7323 fix descriptors, added VnTable and minor changes by:Jon +- feat: refs #7323 fixed tests, changed calendar styles and fix workerCreate by:Jon +- feat: refs #7356 list & weekly to VnTable and style fixes by:Jon +- fix(claim): small details (6336-claim-v6) by:alexm +- fix: columns style by:alexm +- fix: customer defaulter add amount order (6943-fixCustomer) by:alexm +- fix: customerDefaulter correct functionality by:alexm +- fix: customerNotifications filter by:alexm +- fix: fix conflicts by:Jon +- fix: refs #6101 fix TicketList by:Jon +- fix: refs #6891 worker tests by:jorgep +- fix: refs #6943 drop padding-left checkbox & create wrap mode vnRow by:jorgep +- fix: refs #6943 prevent undefined by:jorgep +- fix: refs #7014 fix tests by:Jon +- fix: refs #7014 fix wagon module by:Jon +- fix: refs #7197 add url InvoiceInSearchbar by:jorgep +- fix: refs #7197 amount reactivity by:jorgep +- fix: refs #7197 drop character by:jorgep +- fix: refs #7197 reactivity invoiceCorrection by:jorgep +- fix: refs #7197 responsive summary layout by:jorgep +- fix: refs #7197 rollback by:jorgep +- fix: refs #7197 rollback crudModel by:jorgep +- fix: refs #7197 setInvoiceInCorrecition by:jorgep +- fix: refs #7197 vat, intrastat, filter and list sections by:jorgep +- fix: refs #7323 fix department & email table filter by:Jon +- fix: refs #7323 fixed left filter by:Jon +- fix: refs #7323 fix workerTimeControl form by:Jon +- fix: refs #7401 fix routeForm by:pablone +- fix: refs #7401 remove console.log by:pablone +- fix: refs CAU 207504 fix itemDiary and logs by:Jon +- fix: workerCreate form street field to be always upperCase by:Jon +- hotfix: refs CAU #207614 fix sale.concept field by:Jon +- refactor: refs #7356 fixed VnTable filters by:Jon +- refs #6898 fix by:carlossa +- Ticket expedition initial load fix by:wbuezas # Version 24.28 - 2024-07-09 ### Added 🆕 -- Change header titles style by:wbuezas -- chore: refs #7436 fix e2e (origin/7436-showQCheckbox) by:jorgep -- feat: #7196 eslint (origin/7196-cjsToEsm) by:jgallego -- feat: adapt tu VnTable → CrudModel by:alexm -- feat(CustomerFIlter): use correct table by:alexm -- feat(customerList): add searchbar by:alexm -- feat: customerList is customerExtendedList by:alexm -- feat: fixes #7196 by:jgallego -- feat: refs #6739 transferInvoice new checkbox and functionality by:Jon -- feat: refs #6825 create vnTable and add in CustomerExtendedList by:alexm -- feat: refs #6825 create vnTableColumn, cardActions by:alexm -- feat: refs #6825 fix modes by:alexm -- feat: refs #6825 qchip color by:alexm -- feat: refs #6825 right filter panel (6825-vnTable) by:alexm -- feat: refs #6825 scroll for table mode by:alexm -- feat: refs #6825 share filters, create popup by:alexm -- feat: refs #6825 VnComponent mix component and attrs Form to create new row by:alexm -- feat: refs #6825 VnTableFilter and VnPanelFilter init by:alexm -- feat: refs #6826 added rol summary link by:Jon -- feat: refs #6896 created VnImg and added to order module by:Jon -- feat: refs #6896 new filters by:Jon -- feat: refs #7129 fix some code and add order by:pablone -- feat: refs #7436 show checkbox by:jorgep -- feat: refs #7545 Deleted hasIncoterms client column (origin/7545-hasIncoterms) by:guillermo -- feat(TicketService): use correct format by:alexm -- feat(url): sepate filters by:alexm -- feat(VnFilter): merge objects by:alexm -- feat(VnTable): is-editable and use-model. fix: checkbox by:alexm -- feat(VnTable): refs #6825 actions sticky by:alexm -- feat(VnTable): refs #6825 addInWhere by:alexm -- feat(VnTable): refs #6825 dinamic columns by:alexm -- feat(VnTable): refs #6825 execute function when create by:alexm -- feat(VnTable): refs #6825 fix ellipsis and add titles by:alexm -- feat(VnTable): refs #6825 merge where's by:alexm -- feat(VnTable): refs #6825 move to folder. fix checkboxs by:alexm -- feat(VnTable): refs #6825 remove field prop. Add actions in table by:alexm -- feat(VnTable): refs #6825 use checkbox if startsWith 'is' or 'has' by:alexm -- feat(VnTable): refs #6825 VnTableChip component by:alexm -- feat(vnTable): reload data when change url by:alexm -- feat(WorkerFormation): add columnFilter by:alexm -- feat(WorkerFormation): is-editable and use-model by:alexm -- fix: notify icon style by:Javier Segarra -- refactor: refs #6896 fixed styles by:Jon -- Revert "feat: fixes #7196" by:alexm -- style: refs #6464 changed checkbox and qbtn styles by:Jon +- Change header titles style by:wbuezas +- chore: refs #7436 fix e2e (origin/7436-showQCheckbox) by:jorgep +- feat: #7196 eslint (origin/7196-cjsToEsm) by:jgallego +- feat: adapt tu VnTable → CrudModel by:alexm +- feat(CustomerFIlter): use correct table by:alexm +- feat(customerList): add searchbar by:alexm +- feat: customerList is customerExtendedList by:alexm +- feat: fixes #7196 by:jgallego +- feat: refs #6739 transferInvoice new checkbox and functionality by:Jon +- feat: refs #6825 create vnTable and add in CustomerExtendedList by:alexm +- feat: refs #6825 create vnTableColumn, cardActions by:alexm +- feat: refs #6825 fix modes by:alexm +- feat: refs #6825 qchip color by:alexm +- feat: refs #6825 right filter panel (6825-vnTable) by:alexm +- feat: refs #6825 scroll for table mode by:alexm +- feat: refs #6825 share filters, create popup by:alexm +- feat: refs #6825 VnComponent mix component and attrs Form to create new row by:alexm +- feat: refs #6825 VnTableFilter and VnPanelFilter init by:alexm +- feat: refs #6826 added rol summary link by:Jon +- feat: refs #6896 created VnImg and added to order module by:Jon +- feat: refs #6896 new filters by:Jon +- feat: refs #7129 fix some code and add order by:pablone +- feat: refs #7436 show checkbox by:jorgep +- feat: refs #7545 Deleted hasIncoterms client column (origin/7545-hasIncoterms) by:guillermo +- feat(TicketService): use correct format by:alexm +- feat(url): sepate filters by:alexm +- feat(VnFilter): merge objects by:alexm +- feat(VnTable): is-editable and use-model. fix: checkbox by:alexm +- feat(VnTable): refs #6825 actions sticky by:alexm +- feat(VnTable): refs #6825 addInWhere by:alexm +- feat(VnTable): refs #6825 dinamic columns by:alexm +- feat(VnTable): refs #6825 execute function when create by:alexm +- feat(VnTable): refs #6825 fix ellipsis and add titles by:alexm +- feat(VnTable): refs #6825 merge where's by:alexm +- feat(VnTable): refs #6825 move to folder. fix checkboxs by:alexm +- feat(VnTable): refs #6825 remove field prop. Add actions in table by:alexm +- feat(VnTable): refs #6825 use checkbox if startsWith 'is' or 'has' by:alexm +- feat(VnTable): refs #6825 VnTableChip component by:alexm +- feat(vnTable): reload data when change url by:alexm +- feat(WorkerFormation): add columnFilter by:alexm +- feat(WorkerFormation): is-editable and use-model by:alexm +- fix: notify icon style by:Javier Segarra +- refactor: refs #6896 fixed styles by:Jon +- Revert "feat: fixes #7196" by:alexm +- style: refs #6464 changed checkbox and qbtn styles by:Jon ### Changed 📦 -- perf: Remove div.col by:Javier Segarra -- perf: remove ItemPicture by:Javier Segarra -- perf: replace ItemPicture in favour of VnImg by:Javier Segarra -- refactor by:wbuezas -- refactor: refs #5447 changed warehouse filter by:Jon -- refactor: refs #5447 changed warehouse out filter behavior by:Jon -- refactor: refs #5447 fixed filter if continent not selected by:Jon -- refactor: refs #5447 fix request by:Jon -- refactor: refs #5447 refactor filters by:Jon -- refactor: refs #6739 changed invoice functions' name by:Jon -- refactor: refs #6739 changed router.push by:Jon -- refactor: refs #6739 deleted useless const by:Jon -- refactor: refs #6739 fix redirect transferInvoice by:Jon -- refactor: refs #6739 new confirmation window by:Jon -- refactor: refs #6739 requested changes by:Jon -- refactor: refs #6739 updated transferInvoice function by:Jon -- refactor: refs #6896 changes requested in PR by:Jon -- refactor: refs #6896 end migration orders by:Jon -- refactor: refs #6896 fixed styles by:Jon -- refactor: refs #6896 fix qdrawer by:Jon -- refactor: refs #6896 refactor VnImg by:Jon -- refactor: refs #6896 requested changes by:Jon -- refactor: refs #6977 fix VnImg props (origin/6977-ClonedURL) by:Jon -- refactor: refs #6977 refactor VnImg by:Jon -- refactor: refs #6977 use VnImg by:Jon -- refactors by:alexm +- perf: Remove div.col by:Javier Segarra +- perf: remove ItemPicture by:Javier Segarra +- perf: replace ItemPicture in favour of VnImg by:Javier Segarra +- refactor by:wbuezas +- refactor: refs #5447 changed warehouse filter by:Jon +- refactor: refs #5447 changed warehouse out filter behavior by:Jon +- refactor: refs #5447 fixed filter if continent not selected by:Jon +- refactor: refs #5447 fix request by:Jon +- refactor: refs #5447 refactor filters by:Jon +- refactor: refs #6739 changed invoice functions' name by:Jon +- refactor: refs #6739 changed router.push by:Jon +- refactor: refs #6739 deleted useless const by:Jon +- refactor: refs #6739 fix redirect transferInvoice by:Jon +- refactor: refs #6739 new confirmation window by:Jon +- refactor: refs #6739 requested changes by:Jon +- refactor: refs #6739 updated transferInvoice function by:Jon +- refactor: refs #6896 changes requested in PR by:Jon +- refactor: refs #6896 end migration orders by:Jon +- refactor: refs #6896 fixed styles by:Jon +- refactor: refs #6896 fix qdrawer by:Jon +- refactor: refs #6896 refactor VnImg by:Jon +- refactor: refs #6896 requested changes by:Jon +- refactor: refs #6977 fix VnImg props (origin/6977-ClonedURL) by:Jon +- refactor: refs #6977 refactor VnImg by:Jon +- refactor: refs #6977 use VnImg by:Jon +- refactors by:alexm ### Fixed 🛠️ -- chore: refs #7436 fix e2e (origin/7436-showQCheckbox) by:jorgep -- feat: fixes #7196 by:jgallego -- feat: refs #6825 fix modes by:alexm -- feat: refs #7129 fix some code and add order by:pablone -- feat(VnTable): is-editable and use-model. fix: checkbox by:alexm -- feat(VnTable): refs #6825 fix ellipsis and add titles by:alexm -- feat(VnTable): refs #6825 move to folder. fix checkboxs by:alexm -- fix(ArrayData): refs #6825 router.replace and use filter.where by:alexm -- fix: bug replace by:alexm -- fix: column hidden v-if by:Javier Segarra -- fix: comment 4 by:Javier Segarra -- fix: comments by:Javier Segarra -- fix: cypress.config to mjs by:alexm -- fix(EntryBuys): fix VnSubtoolbar by:alexm -- fixes: fix vnFilter params and redirect by:alexm -- fix: fix warnings by:alexm -- fix: invoiceDueDay test by:alexm -- fix log view not refreshing when changing id param by:wbuezas -- fix: map selected by:Javier Segarra -- fix: merge dev by:Javier Segarra -- fix: notify icon style by:Javier Segarra -- fix: point 1 by:Javier Segarra -- fix: point 3 by:Javier Segarra -- fix: refs #5447 deleted console.log by:Jon -- fix: refs 6464 deleted useless class in checkbox by:Jon -- fix: refs #6464 fix error isLoading by:Jon -- fix: refs #6739 changed checkbox field by:Jon -- fix: refs #6825 css by:carlossa -- fix: refs #6826 fix redirect by:Jon -- fix: refs #6826 fix roleDescriptor by:Jon -- fix: refs #7129 fix e2e by:pablone -- fix: refs #7129 fix module routes by:pablone -- fix: refs #7129 fix some issues on load and tools by:pablone -- fix: refs #7129 remove consoleLog by:pablone -- fix: refs #7129 remove fix from claim lines by:pablone -- fix: refs #7274 fix duplicate rows by:jorgep -- fix: refs #7433 skeleton by:jorgep -- fix: refs #7623 bugs & tests by:jorgep -- fix: refs #7623 disable router update by:jorgep -- fix: refs #7623 redirect by:jorgep -- fix: refs #7623 test by:jorgep -- fix: refs #7623 update add updateRoute prop in VnPaginate by:jorgep -- fix: refs #7623 updating skip param by:jorgep -- fix: revert cypress mjs by:alexm -- fix: SkeletonTable by:alexm -- fix: state translations by:Javier Segarra -- fix: ticket order by:Javier Segarra -- fix(ticket router): typo by:alexm -- fix(TicketService): pay use selected by:alexm -- fix: TravelLog by:Javier Segarra -- fix(url): filter by:alexm -- fix(url): redirect by:alexm -- fix(VnFilter): filter with params by:alexm -- fix(VnFilterPanel): remove key by:alexm -- fix(VnTable): create scss by:alexm -- fix(VnTable): duplicate fetch by:alexm -- fix(VnTable): Qtable v-bind by:alexm -- fix(VnTable): refs #6825 checkbox align and color by:alexm -- fix(VnTable): refs #6825 fix click sticky column by:alexm -- fix(VnTable): refs #6825 fix events and css by:alexm -- fix(VnTable): refs #6825 VnInputDate by:alexm -- fix(VnTable): showLabel by:alexm -- fix(VnTable): warns by:alexm -- fix: WorkerNotificationsManager test by:alexm -- fix: WorkerSelect option format by:Javier Segarra -- refactor: refs #5447 fixed filter if continent not selected by:Jon -- refactor: refs #5447 fix request by:Jon -- refactor: refs #6739 fix redirect transferInvoice by:Jon -- refactor: refs #6896 fixed styles by:Jon -- refactor: refs #6896 fix qdrawer by:Jon -- refactor: refs #6977 fix VnImg props (origin/6977-ClonedURL) by:Jon -- refs #6504 fix formModel claimFilter claimCard (origin/6504-fixCardClaim) by:carlossa -- refs #7406 fix components by:carlossa -- refs #7406 fix pr by:carlossa -- refs #7406 fix props by:carlossa -- refs #7406 fix Tb components create by:carlossa -- refs #7406 fix trad by:carlossa -- refs #7406 fix url by:carlossa -- refs #7406 fix VnTable columns by:carlossa -- refs #7409 fix balance and formation by:carlossa -- refs #7409 fix trad by:carlossa -- Revert "feat: fixes #7196" by:alexm -- test: fix intermitent e2e by:alexm -- test: fix vnSearchbar adapt to vnTable (origin/7648_dev_customerEntries) by:alexm +- chore: refs #7436 fix e2e (origin/7436-showQCheckbox) by:jorgep +- feat: fixes #7196 by:jgallego +- feat: refs #6825 fix modes by:alexm +- feat: refs #7129 fix some code and add order by:pablone +- feat(VnTable): is-editable and use-model. fix: checkbox by:alexm +- feat(VnTable): refs #6825 fix ellipsis and add titles by:alexm +- feat(VnTable): refs #6825 move to folder. fix checkboxs by:alexm +- fix(ArrayData): refs #6825 router.replace and use filter.where by:alexm +- fix: bug replace by:alexm +- fix: column hidden v-if by:Javier Segarra +- fix: comment 4 by:Javier Segarra +- fix: comments by:Javier Segarra +- fix: cypress.config to mjs by:alexm +- fix(EntryBuys): fix VnSubtoolbar by:alexm +- fixes: fix vnFilter params and redirect by:alexm +- fix: fix warnings by:alexm +- fix: invoiceDueDay test by:alexm +- fix log view not refreshing when changing id param by:wbuezas +- fix: map selected by:Javier Segarra +- fix: merge dev by:Javier Segarra +- fix: notify icon style by:Javier Segarra +- fix: point 1 by:Javier Segarra +- fix: point 3 by:Javier Segarra +- fix: refs #5447 deleted console.log by:Jon +- fix: refs 6464 deleted useless class in checkbox by:Jon +- fix: refs #6464 fix error isLoading by:Jon +- fix: refs #6739 changed checkbox field by:Jon +- fix: refs #6825 css by:carlossa +- fix: refs #6826 fix redirect by:Jon +- fix: refs #6826 fix roleDescriptor by:Jon +- fix: refs #7129 fix e2e by:pablone +- fix: refs #7129 fix module routes by:pablone +- fix: refs #7129 fix some issues on load and tools by:pablone +- fix: refs #7129 remove consoleLog by:pablone +- fix: refs #7129 remove fix from claim lines by:pablone +- fix: refs #7274 fix duplicate rows by:jorgep +- fix: refs #7433 skeleton by:jorgep +- fix: refs #7623 bugs & tests by:jorgep +- fix: refs #7623 disable router update by:jorgep +- fix: refs #7623 redirect by:jorgep +- fix: refs #7623 test by:jorgep +- fix: refs #7623 update add updateRoute prop in VnPaginate by:jorgep +- fix: refs #7623 updating skip param by:jorgep +- fix: revert cypress mjs by:alexm +- fix: SkeletonTable by:alexm +- fix: state translations by:Javier Segarra +- fix: ticket order by:Javier Segarra +- fix(ticket router): typo by:alexm +- fix(TicketService): pay use selected by:alexm +- fix: TravelLog by:Javier Segarra +- fix(url): filter by:alexm +- fix(url): redirect by:alexm +- fix(VnFilter): filter with params by:alexm +- fix(VnFilterPanel): remove key by:alexm +- fix(VnTable): create scss by:alexm +- fix(VnTable): duplicate fetch by:alexm +- fix(VnTable): Qtable v-bind by:alexm +- fix(VnTable): refs #6825 checkbox align and color by:alexm +- fix(VnTable): refs #6825 fix click sticky column by:alexm +- fix(VnTable): refs #6825 fix events and css by:alexm +- fix(VnTable): refs #6825 VnInputDate by:alexm +- fix(VnTable): showLabel by:alexm +- fix(VnTable): warns by:alexm +- fix: WorkerNotificationsManager test by:alexm +- fix: WorkerSelect option format by:Javier Segarra +- refactor: refs #5447 fixed filter if continent not selected by:Jon +- refactor: refs #5447 fix request by:Jon +- refactor: refs #6739 fix redirect transferInvoice by:Jon +- refactor: refs #6896 fixed styles by:Jon +- refactor: refs #6896 fix qdrawer by:Jon +- refactor: refs #6977 fix VnImg props (origin/6977-ClonedURL) by:Jon +- refs #6504 fix formModel claimFilter claimCard (origin/6504-fixCardClaim) by:carlossa +- refs #7406 fix components by:carlossa +- refs #7406 fix pr by:carlossa +- refs #7406 fix props by:carlossa +- refs #7406 fix Tb components create by:carlossa +- refs #7406 fix trad by:carlossa +- refs #7406 fix url by:carlossa +- refs #7406 fix VnTable columns by:carlossa +- refs #7409 fix balance and formation by:carlossa +- refs #7409 fix trad by:carlossa +- Revert "feat: fixes #7196" by:alexm +- test: fix intermitent e2e by:alexm +- test: fix vnSearchbar adapt to vnTable (origin/7648_dev_customerEntries) by:alexm # Version 24.24 - 2024-06-11 ### Added 🆕 -- feat: 6942 hashtag in key : value summary by:jgallego -- feat: #6957: Rename FetchedTags instance tag by:Javier Segarra -- feat: refactor template by:Javier Segarra -- feat: refs #6600 Add option to add comment for photo motivation by:jorgep -- feat: refs #6942 test e2e tobook & toUnbook by:jorgep -- feat: refs #6942 to book summary button & reactive value by:jorgep -- feat: refs #6942 to unbook by:jorgep -- feat: refs #6942 url update by:jorgep -- feat: refs #6942 use correct currency in InvoiceIn components by:jorgep -- feat: refs #6942 vat rate total by:jorgep -- feat: refs #7494 new icons (7494-icons) by:alexm -- feat: refs #7494 new icons by:alexm -- feat: refs #7542 drop space by:jorgep -- feat: refs #7542 empty by:jorgep -- fix: refs #6942 changes and new features by:jorgep -- fix: style by:Javier Segarra -- style: color transparent when is fetive by:Javier Segarra -- style: fix color when is empty by:Javier Segarra -- style: reset poc style (6957_refactorFetechedTags) by:Javier Segarra -- style: reset poc style by:Javier Segarra -- style updates by:Javier Segarra +- feat: 6942 hashtag in key : value summary by:jgallego +- feat: #6957: Rename FetchedTags instance tag by:Javier Segarra +- feat: refactor template by:Javier Segarra +- feat: refs #6600 Add option to add comment for photo motivation by:jorgep +- feat: refs #6942 test e2e tobook & toUnbook by:jorgep +- feat: refs #6942 to book summary button & reactive value by:jorgep +- feat: refs #6942 to unbook by:jorgep +- feat: refs #6942 url update by:jorgep +- feat: refs #6942 use correct currency in InvoiceIn components by:jorgep +- feat: refs #6942 vat rate total by:jorgep +- feat: refs #7494 new icons (7494-icons) by:alexm +- feat: refs #7494 new icons by:alexm +- feat: refs #7542 drop space by:jorgep +- feat: refs #7542 empty by:jorgep +- fix: refs #6942 changes and new features by:jorgep +- fix: style by:Javier Segarra +- style: color transparent when is fetive by:Javier Segarra +- style: fix color when is empty by:Javier Segarra +- style: reset poc style (6957_refactorFetechedTags) by:Javier Segarra +- style: reset poc style by:Javier Segarra +- style updates by:Javier Segarra ### Changed 📦 -- feat: refactor template by:Javier Segarra -- perf: 6957 add color as new shared variable by:Javier Segarra -- perf: 6957 change fetchedTags color by:Javier Segarra -- perf: remove local tree variable by:Javier Segarra -- refactor: add flat by:alexm -- refactor: refs #6600 replace QInput to VnInput by:jorgep -- refactor: refs #6652 improved defaulter section by:Jon -- refactor: refs #6942 Fix getTotalAmount function to correctly calculate the total amount in InvoiceInDueDay.vue by:jorgep -- refactor: refs #6942 new summary layout by:jorgep -- refactor: refs #6942 store key & actions by:jorgep -- refactor: refs #6942 summary by:jorgep -- refactor: refs #6942 use router hook by:jorgep -- refactor: refs #6942 WIP summary layout by:jorgep +- feat: refactor template by:Javier Segarra +- perf: 6957 add color as new shared variable by:Javier Segarra +- perf: 6957 change fetchedTags color by:Javier Segarra +- perf: remove local tree variable by:Javier Segarra +- refactor: add flat by:alexm +- refactor: refs #6600 replace QInput to VnInput by:jorgep +- refactor: refs #6652 improved defaulter section by:Jon +- refactor: refs #6942 Fix getTotalAmount function to correctly calculate the total amount in InvoiceInDueDay.vue by:jorgep +- refactor: refs #6942 new summary layout by:jorgep +- refactor: refs #6942 store key & actions by:jorgep +- refactor: refs #6942 summary by:jorgep +- refactor: refs #6942 use router hook by:jorgep +- refactor: refs #6942 WIP summary layout by:jorgep ### Fixed 🛠️ -- fix: 9-12 by:Javier Segarra -- fix: defaulter icon by:alexm -- fix: refs #5186 validation by:jorgep -- fix: refs #6095 add reFfk null on search by:pablone -- fix: refs #6942 cardDescriptor use store if its popup or different source data by:jorgep -- fix: refs #6942 changes and new features by:jorgep -- fix: refs #6942 drop comments by:jorgep -- fix: refs #6942 drop console by:jorgep -- fix: refs #6942 drop console.log by:jorgep -- fix: refs #6942 e2e test (origin/6942-warmfix-fixFormModel) by:jorgep -- fix: refs #6942 e2e tests by:jorgep -- fix: refs #6942 e2e tests by:jorgep -- fix: refs #6942 fix emit on data saved by:jorgep -- fix: refs #6942 fix emit on reset by:jorgep -- fix: refs #6942 fix vncard by:jorgep -- fix: refs #6942 formModel & CardDescriptor by:jorgep -- fix: refs #6942 formModel watch changes & invoiceInCreate by:jorgep -- fix: refs #6942 import by:jorgep -- fix: refs #6942 reloading by:jorgep -- fix: refs #6942 rollback by:jorgep -- fix: refs #6942 selectable expense by:jorgep -- fix: refs #6942 skip e2e tests by:jorgep -- fix: refs #6942 table bottom highlight & drop isBooked field by:jorgep -- fix: refs #6942 tests e2e by:jorgep -- fix: refs #6942 tests & summary table spacing by:jorgep -- fix: refs #6942 unit tests by:jorgep -- fix: refs #6942 vnLocation by:jorgep -- fix: refs #6942 wip: formModel by:jorgep -- fix: refs #7542 use right panel by:jorgep -- fix: searchbar redirect by:alexm -- fix: style by:Javier Segarra -- fix: WorkerCalendarItem by:Javier Segarra -- mini fix by:wbuezas -- refs #6111 clean code fix changes by:carlossa -- refs #6111 fix merge, fix column by:carlossa -- refs #6111 fix qtable, actions, scroll by:carlossa -- refs #6111 fix routeList by:carlossa -- refs #6111 fix sticky by:carlossa -- refs #6111 fix trad remove logs by:carlossa -- refs #6111 fix visibleColumns by:carlossa -- refs #6111 routeList fix by:carlossa -- refs #6332 fix calendar by:carlossa -- refs #6332 fix colors by:carlossa -- refs #6332 fix festive by:carlossa -- refs #6820 fix BasicData Tickets by:carlossa -- refs #6820 fix error front by:carlossa -- refs #6820 fix traduction by:carlossa -- refs #7391 fix textarea by:carlossa -- refs #7396 fix summary by:carlossa -- Search childs fix by:wbuezas -- small fix by:wbuezas -- style: fix color when is empty by:Javier Segarra +- fix: 9-12 by:Javier Segarra +- fix: defaulter icon by:alexm +- fix: refs #5186 validation by:jorgep +- fix: refs #6095 add reFfk null on search by:pablone +- fix: refs #6942 cardDescriptor use store if its popup or different source data by:jorgep +- fix: refs #6942 changes and new features by:jorgep +- fix: refs #6942 drop comments by:jorgep +- fix: refs #6942 drop console by:jorgep +- fix: refs #6942 drop console.log by:jorgep +- fix: refs #6942 e2e test (origin/6942-warmfix-fixFormModel) by:jorgep +- fix: refs #6942 e2e tests by:jorgep +- fix: refs #6942 e2e tests by:jorgep +- fix: refs #6942 fix emit on data saved by:jorgep +- fix: refs #6942 fix emit on reset by:jorgep +- fix: refs #6942 fix vncard by:jorgep +- fix: refs #6942 formModel & CardDescriptor by:jorgep +- fix: refs #6942 formModel watch changes & invoiceInCreate by:jorgep +- fix: refs #6942 import by:jorgep +- fix: refs #6942 reloading by:jorgep +- fix: refs #6942 rollback by:jorgep +- fix: refs #6942 selectable expense by:jorgep +- fix: refs #6942 skip e2e tests by:jorgep +- fix: refs #6942 table bottom highlight & drop isBooked field by:jorgep +- fix: refs #6942 tests e2e by:jorgep +- fix: refs #6942 tests & summary table spacing by:jorgep +- fix: refs #6942 unit tests by:jorgep +- fix: refs #6942 vnLocation by:jorgep +- fix: refs #6942 wip: formModel by:jorgep +- fix: refs #7542 use right panel by:jorgep +- fix: searchbar redirect by:alexm +- fix: style by:Javier Segarra +- fix: WorkerCalendarItem by:Javier Segarra +- mini fix by:wbuezas +- refs #6111 clean code fix changes by:carlossa +- refs #6111 fix merge, fix column by:carlossa +- refs #6111 fix qtable, actions, scroll by:carlossa +- refs #6111 fix routeList by:carlossa +- refs #6111 fix sticky by:carlossa +- refs #6111 fix trad remove logs by:carlossa +- refs #6111 fix visibleColumns by:carlossa +- refs #6111 routeList fix by:carlossa +- refs #6332 fix calendar by:carlossa +- refs #6332 fix colors by:carlossa +- refs #6332 fix festive by:carlossa +- refs #6820 fix BasicData Tickets by:carlossa +- refs #6820 fix error front by:carlossa +- refs #6820 fix traduction by:carlossa +- refs #7391 fix textarea by:carlossa +- refs #7396 fix summary by:carlossa +- Search childs fix by:wbuezas +- small fix by:wbuezas +- style: fix color when is empty by:Javier Segarra # Changelog @@ -1526,9 +1811,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto -- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa -- (Route) => Ahora se muestran todos los cmrs +- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto +- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa +- (Route) => Ahora se muestran todos los cmrs ## [2418.01] @@ -1536,27 +1821,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- (Worker) => Se crea la sección Taquilla -- (General) => Se mantiene el filtro lateral en cualquier parte de la seccíon. +- (Worker) => Se crea la sección Taquilla +- (General) => Se mantiene el filtro lateral en cualquier parte de la seccíon. ### Fixed -- (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro +- (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro ## [2414.01] - 2024-04-04 ### Added -- (Tickets) => Se añade la opción de clonar ticket. #6951 -- (Parking) => Se añade la sección Parking. #5186 +- (Tickets) => Se añade la opción de clonar ticket. #6951 +- (Parking) => Se añade la sección Parking. #5186 -- (Rutas) => Se añade el campo "servida" a la tabla y se añade también a los filtros. #7130 +- (Rutas) => Se añade el campo "servida" a la tabla y se añade también a los filtros. #7130 ### Changed ### Fixed -- (General) => Se corrige la redirección cuando hay 1 solo registro y cuando se aplica un filtro diferente al id al hacer una búsqueda general. #6893 +- (General) => Se corrige la redirección cuando hay 1 solo registro y cuando se aplica un filtro diferente al id al hacer una búsqueda general. #6893 ## [2400.01] - 2024-01-04 @@ -1570,26 +1855,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- (Carros) => Se añade contador de carros. #6545 -- (Reclamaciones) => Se añade la sección para hacer acciones sobre una reclamación. #5654 +- (Carros) => Se añade contador de carros. #6545 +- (Reclamaciones) => Se añade la sección para hacer acciones sobre una reclamación. #5654 ### Changed ### Fixed -- (Reclamaciones) => Se corrige el color de la barra según el tema y el evento de actualziar cantidades #6334 +- (Reclamaciones) => Se corrige el color de la barra según el tema y el evento de actualziar cantidades #6334 ## [2253.01] - 2023-01-05 ### Added -- (Clientes) => Añadida nueva sección "Pagos Web" para gestionar los pagos de todos los clientes -- (Tickets) => Añadida opción en el menú desplegable del ticket para enviar SMS al cliente -- (Reclamaciones) => Añadida nueva sección "Registros de auditoría" -- (Trabajadores) => Añadido módulo de trabajadores -- (General) => Añadida barra de búsqueda general en los listados principales -- (Vagones) => Añadido módulo de vagones +- (Clientes) => Añadida nueva sección "Pagos Web" para gestionar los pagos de todos los clientes +- (Tickets) => Añadida opción en el menú desplegable del ticket para enviar SMS al cliente +- (Reclamaciones) => Añadida nueva sección "Registros de auditoría" +- (Trabajadores) => Añadido módulo de trabajadores +- (General) => Añadida barra de búsqueda general en los listados principales +- (Vagones) => Añadido módulo de vagones ### Changed -- Changed... +- Changed... diff --git a/cypress.config.js b/cypress.config.js index 1924144f6..26b7725a5 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -14,8 +14,8 @@ export default defineConfig({ downloadsFolder: 'test/cypress/downloads', video: false, specPattern: 'test/cypress/integration/**/*.spec.js', - experimentalRunAllSpecs: true, - watchForFileChanges: true, + experimentalRunAllSpecs: false, + watchForFileChanges: false, reporter: 'cypress-mochawesome-reporter', reporterOptions: { charts: true, @@ -34,6 +34,17 @@ export default defineConfig({ const plugin = await import('cypress-mochawesome-reporter/plugin'); plugin.default(on); + const fs = await import('fs'); + on('task', { + deleteFile(filePath) { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + return true; + } + return false; + }, + }); + return config; }, viewportWidth: 1280, diff --git a/package.json b/package.json index d23ed0ced..e78b0cf3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "25.08.0", + "version": "25.10.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -71,4 +71,4 @@ "vite": "^6.0.11", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/quasar.config.js b/quasar.config.js index 6d545c026..9467c92af 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -30,7 +30,6 @@ export default configure(function (/* ctx */) { // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'], - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 97d80c670..182c51e47 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -9,19 +9,19 @@ export default { if (!form) return; try { const inputsFormCard = form.querySelectorAll( - `input:not([disabled]):not([type="checkbox"])` + `input:not([disabled]):not([type="checkbox"])`, ); if (inputsFormCard.length) { focusFirstInput(inputsFormCard[0]); } const textareas = document.querySelectorAll( - 'textarea:not([disabled]), [contenteditable]:not([disabled])' + 'textarea:not([disabled]), [contenteditable]:not([disabled])', ); if (textareas.length) { focusFirstInput(textareas[textareas.length - 1]); } const inputs = document.querySelectorAll( - 'form#formModel input:not([disabled]):not([type="checkbox"])' + 'form#formModel input:not([disabled]):not([type="checkbox"])', ); const input = inputs[0]; if (!input) return; @@ -30,22 +30,5 @@ export default { } catch (error) { console.error(error); } - form.addEventListener('keyup', function (evt) { - if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) { - const input = evt.target; - if (input.type == 'textarea' && evt.shiftKey) { - evt.preventDefault(); - let { selectionStart, selectionEnd } = input; - input.value = - input.value.substring(0, selectionStart) + - '\n' + - input.value.substring(selectionEnd); - selectionStart = selectionEnd = selectionStart + 1; - return; - } - evt.preventDefault(); - that.onSubmit(); - } - }); }, }; diff --git a/src/boot/quasar.js b/src/boot/quasar.js index 547517682..a8c397b83 100644 --- a/src/boot/quasar.js +++ b/src/boot/quasar.js @@ -51,4 +51,5 @@ export default boot(({ app }) => { await useCau(response, message); }; + app.provide('app', app); }); diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue index 39ebfe540..a57e2c01c 100644 --- a/src/components/CreateNewPostcodeForm.vue +++ b/src/components/CreateNewPostcodeForm.vue @@ -2,7 +2,6 @@ import { reactive, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import FetchData from 'components/FetchData.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectProvince from 'src/components/VnSelectProvince.vue'; @@ -21,14 +20,11 @@ const postcodeFormData = reactive({ provinceFk: null, townFk: null, }); -const townsFetchDataRef = ref(false); const townFilter = ref({}); const countriesRef = ref(false); const provincesOptions = ref([]); -const townsOptions = ref([]); const town = ref({}); -const countryFilter = ref({}); function onDataSaved(formData) { const newPostcode = { @@ -51,7 +47,6 @@ async function setCountry(countryFk, data) { data.townFk = null; data.provinceFk = null; data.countryFk = countryFk; - await fetchTowns(); } // Province @@ -60,22 +55,11 @@ async function setProvince(id, data) { const newProvince = provincesOptions.value.find((province) => province.id == id); if (newProvince) data.countryFk = newProvince.countryFk; postcodeFormData.provinceFk = id; - await fetchTowns(); } async function onProvinceCreated(data) { postcodeFormData.provinceFk = data.id; } -function provinceByCountry(countryFk = postcodeFormData.countryFk) { - return provincesOptions.value - .filter((province) => province.countryFk === countryFk) - .map(({ id }) => id); -} - -// Town -async function handleTowns(data) { - townsOptions.value = data; -} function setTown(newTown, data) { town.value = newTown; data.provinceFk = newTown?.provinceFk ?? newTown; @@ -88,18 +72,6 @@ async function onCityCreated(newTown, formData) { formData.townFk = newTown; setTown(newTown, formData); } -async function fetchTowns(countryFk = postcodeFormData.countryFk) { - if (!countryFk) return; - const provinces = postcodeFormData.provinceFk - ? [postcodeFormData.provinceFk] - : provinceByCountry(); - townFilter.value.where = { - provinceFk: { - inq: provinces, - }, - }; - await townsFetchDataRef.value?.fetch(); -} async function filterTowns(name) { if (name !== '') { @@ -108,22 +80,11 @@ async function filterTowns(name) { like: `%${name}%`, }, }; - await townsFetchDataRef.value?.fetch(); } } </script> <template> - <FetchData - ref="townsFetchDataRef" - :sort-by="['name ASC']" - :limit="30" - :filter="townFilter" - @on-fetch="handleTowns" - auto-load - url="Towns/location" - /> - <FormModelPopup url-create="postcodes" model="postcode" @@ -149,14 +110,13 @@ async function filterTowns(name) { @filter="filterTowns" :tooltip="t('Create city')" v-model="data.townFk" - :options="townsOptions" - option-label="name" - option-value="id" + url="Towns/location" :rules="validate('postcode.city')" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]" :emit-value="false" required data-cy="locationTown" + sort-by="name ASC" > <template #option="{ itemProps, opt }"> <QItem v-bind="itemProps"> @@ -197,16 +157,12 @@ async function filterTowns(name) { /> <VnSelect ref="countriesRef" - :limit="30" - :filter="countryFilter" :sort-by="['name ASC']" auto-load url="Countries" required :label="t('Country')" hide-selected - option-label="name" - option-value="id" v-model="data.countryFk" :rules="validate('postcode.countryFk')" @update:model-value="(value) => setCountry(value, data)" diff --git a/src/components/CreateNewProvinceForm.vue b/src/components/CreateNewProvinceForm.vue index d35690eeb..1fc0c1f7a 100644 --- a/src/components/CreateNewProvinceForm.vue +++ b/src/components/CreateNewProvinceForm.vue @@ -62,12 +62,9 @@ const where = computed(() => { auto-load :where="where" url="Autonomies/location" - :sort-by="['name ASC']" - :limit="30" + sort-by="name ASC" :label="t('Autonomy')" hide-selected - option-label="name" - option-value="id" v-model="data.autonomyFk" :rules="validate('province.autonomyFk')" > diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index d569dfda1..93a2ac96a 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -64,6 +64,10 @@ const $props = defineProps({ type: Function, default: null, }, + beforeSaveFn: { + type: Function, + default: null, + }, goTo: { type: String, default: '', @@ -176,7 +180,11 @@ async function saveChanges(data) { hasChanges.value = false; return; } - const changes = data || getChanges(); + let changes = data || getChanges(); + if ($props.beforeSaveFn) { + changes = await $props.beforeSaveFn(changes, getChanges); + } + try { await axios.post($props.saveUrl || $props.url + '/crud', changes); } finally { @@ -229,12 +237,12 @@ async function remove(data) { componentProps: { title: t('globals.confirmDeletion'), message: t('globals.confirmDeletionMessage'), - newData, + data: { deletes: ids }, ids, + promise: saveChanges, }, }) .onOk(async () => { - await saveChanges({ deletes: ids }); newData = newData.filter((form) => !ids.some((id) => id == form[pk])); fetch(newData); }); @@ -374,6 +382,8 @@ watch(formUrl, async () => { @click="onSubmit" :disable="!hasChanges" :title="t('globals.save')" + v-shortcut="'s'" + shortcut="s" data-cy="crudModelDefaultSaveBtn" /> <slot name="moreAfterActions" /> diff --git a/src/components/FilterItemForm.vue b/src/components/FilterItemForm.vue index 34968ccef..cacfde1b3 100644 --- a/src/components/FilterItemForm.vue +++ b/src/components/FilterItemForm.vue @@ -42,7 +42,6 @@ const itemFilter = { const itemFilterParams = reactive({}); const closeButton = ref(null); const isLoading = ref(false); -const producersOptions = ref([]); const ItemTypesOptions = ref([]); const InksOptions = ref([]); const tableRows = ref([]); @@ -121,23 +120,17 @@ const selectItem = ({ id }) => { </script> <template> - <FetchData - url="Producers" - @on-fetch="(data) => (producersOptions = data)" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" - auto-load - /> <FetchData url="ItemTypes" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" - order="name" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" + order="name ASC" @on-fetch="(data) => (ItemTypesOptions = data)" auto-load /> <FetchData url="Inks" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" - order="name" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" + order="name ASC" @on-fetch="(data) => (InksOptions = data)" auto-load /> @@ -152,11 +145,11 @@ const selectItem = ({ id }) => { <VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" /> <VnSelect :label="t('globals.producer')" - :options="producersOptions" hide-selected - option-label="name" - option-value="id" v-model="itemFilterParams.producerFk" + url="Producers" + :fields="['id', 'name']" + sort-by="name ASC" /> <VnSelect :label="t('globals.type')" diff --git a/src/components/FilterTravelForm.vue b/src/components/FilterTravelForm.vue index 9fc91457a..765d97763 100644 --- a/src/components/FilterTravelForm.vue +++ b/src/components/FilterTravelForm.vue @@ -124,7 +124,7 @@ const selectTravel = ({ id }) => { <FetchData url="AgencyModes" @on-fetch="(data) => (agenciesOptions = data)" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" auto-load /> <FetchData @@ -181,6 +181,7 @@ const selectTravel = ({ id }) => { color="primary" :disabled="isLoading" :loading="isLoading" + data-cy="save-filter-travel-form" /> </div> <QTable @@ -191,9 +192,10 @@ const selectTravel = ({ id }) => { :no-data-label="t('Enter a new search')" class="q-mt-lg" @row-click="(_, row) => selectTravel(row)" + data-cy="table-filter-travel-form" > <template #body-cell-id="{ row }"> - <QTd auto-width @click.stop> + <QTd auto-width @click.stop data-cy="travelFk-travel-form"> <QBtn flat color="blue">{{ row.id }}</QBtn> <TravelDescriptorProxy :id="row.id" /> </QTd> diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index 59141d374..04ef13d45 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue'; +import { onMounted, onUnmounted, computed, ref, watch, nextTick, useAttrs } from 'vue'; import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -22,6 +22,7 @@ const { validate } = useValidator(); const { notify } = useNotify(); const route = useRoute(); const myForm = ref(null); +const attrs = useAttrs(); const $props = defineProps({ url: { type: String, @@ -106,14 +107,14 @@ const isLoading = ref(false); const isResetting = ref(false); const hasChanges = ref(!$props.observeFormChanges); const originalData = computed(() => state.get(modelValue)); -const formData = ref({}); +const formData = ref(); const defaultButtons = computed(() => ({ save: { dataCy: 'saveDefaultBtn', color: 'primary', icon: 'save', label: 'globals.save', - click: () => myForm.value.submit(), + click: async () => await save(), type: 'submit', }, reset: { @@ -134,7 +135,8 @@ onMounted(async () => { if (!$props.formInitialData) { if ($props.autoLoad && $props.url) await fetch(); - else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data); + else if (arrayData.store.data) + updateAndEmit('onFetch', { val: arrayData.store.data }); } if ($props.observeFormChanges) { watch( @@ -154,7 +156,7 @@ onMounted(async () => { if (!$props.url) watch( () => arrayData.store.data, - (val) => updateAndEmit('onFetch', val), + (val) => updateAndEmit('onFetch', { val }), ); watch( @@ -200,7 +202,7 @@ async function fetch() { }); if (Array.isArray(data)) data = data[0] ?? {}; - updateAndEmit('onFetch', data); + updateAndEmit('onFetch', { val: data }); } catch (e) { state.set(modelValue, {}); throw e; @@ -227,7 +229,11 @@ async function save() { if ($props.urlCreate) notify('globals.dataCreated', 'positive'); - updateAndEmit('onDataSaved', formData.value, response?.data); + updateAndEmit('onDataSaved', { + val: formData.value, + res: response?.data, + old: originalData.value, + }); if ($props.reload) await arrayData.fetch({}); hasChanges.value = false; } finally { @@ -242,7 +248,7 @@ async function saveAndGo() { function reset() { formData.value = JSON.parse(JSON.stringify(originalData.value)); - updateAndEmit('onFetch', originalData.value); + updateAndEmit('onFetch', { val: originalData.value }); if ($props.observeFormChanges) { hasChanges.value = false; isResetting.value = true; @@ -264,11 +270,11 @@ function filter(value, update, filterOptions) { ); } -function updateAndEmit(evt, val, res) { +function updateAndEmit(evt, { val, res, old } = { val: null, res: null, old: null }) { state.set(modelValue, val); if (!$props.url) arrayData.store.data = val; - emit(evt, state.get(modelValue), res); + emit(evt, state.get(modelValue), res, old); } function trimData(data) { @@ -279,6 +285,22 @@ function trimData(data) { return data; } +async function onKeyup(evt) { + if (evt.key === 'Enter' && !('prevent-submit' in attrs)) { + const input = evt.target; + if (input.type == 'textarea' && evt.shiftKey) { + let { selectionStart, selectionEnd } = input; + input.value = + input.value.substring(0, selectionStart) + + '\n' + + input.value.substring(selectionEnd); + selectionStart = selectionEnd = selectionStart + 1; + return; + } + await save(); + } +} + defineExpose({ save, isLoading, @@ -293,12 +315,12 @@ defineExpose({ <QForm ref="myForm" v-if="formData" - @submit="save" + @submit.prevent + @keyup.prevent="onKeyup" @reset="reset" class="q-pa-md" :style="maxWidth ? 'max-width: ' + maxWidth : ''" id="formModel" - :prevent-submit="$attrs['prevent-submit']" > <QCard> <slot diff --git a/src/components/FormModelPopup.vue b/src/components/FormModelPopup.vue index afdc6efca..672eeff7a 100644 --- a/src/components/FormModelPopup.vue +++ b/src/components/FormModelPopup.vue @@ -15,23 +15,35 @@ defineProps({ type: String, default: '', }, + showSaveAndContinueBtn: { + type: Boolean, + default: false, + }, }); const { t } = useI18n(); const formModelRef = ref(null); const closeButton = ref(null); - +const isSaveAndContinue = ref(false); const onDataSaved = (formData, requestResponse) => { - if (closeButton.value) closeButton.value.click(); + if (closeButton.value && !isSaveAndContinue.value) closeButton.value.click(); emit('onDataSaved', formData, requestResponse); }; +const onClick = async (saveAndContinue) => { + isSaveAndContinue.value = saveAndContinue; + await formModelRef.value.save(); +}; + const isLoading = computed(() => formModelRef.value?.isLoading); +const reset = computed(() => formModelRef.value?.reset); defineExpose({ isLoading, onDataSaved, + isSaveAndContinue, + reset, }); </script> @@ -59,15 +71,19 @@ defineExpose({ flat :disabled="isLoading" :loading="isLoading" - @click="emit('onDataCanceled')" - v-close-popup data-cy="FormModelPopup_cancel" + v-close-popup z-max + @click=" + isSaveAndContinue = false; + emit('onDataCanceled'); + " /> <QBtn + :flat="showSaveAndContinueBtn" :label="t('globals.save')" :title="t('globals.save')" - type="submit" + @click="onClick(false)" color="primary" class="q-ml-sm" :disabled="isLoading" @@ -75,6 +91,18 @@ defineExpose({ data-cy="FormModelPopup_save" z-max /> + <QBtn + v-if="showSaveAndContinueBtn" + :label="t('globals.isSaveAndContinue')" + :title="t('globals.isSaveAndContinue')" + color="primary" + class="q-ml-sm" + :disabled="isLoading" + :loading="isLoading" + data-cy="FormModelPopup_isSaveAndContinue" + z-max + @click="onClick(true)" + /> </div> </template> </FormModel> diff --git a/src/components/ItemsFilterPanel.vue b/src/components/ItemsFilterPanel.vue index 48f607a30..f73753a6b 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -121,23 +121,25 @@ const removeTag = (index, params, search) => { applyTags(params, search); }; const setCategoryList = (data) => { - categoryList.value = (data || []) - .filter((category) => category.display) - .map((category) => ({ - ...category, - icon: `vn:${(category.icon || '').split('-')[1]}`, - })); + categoryList.value = (data || []).map((category) => ({ + ...category, + icon: `vn:${(category.icon || '').split('-')[1]}`, + })); fetchItemTypes(); }; </script> <template> - <FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" /> + <FetchData + url="ItemCategories" + auto-load + @on-fetch="setCategoryList" + :where="{ display: { neq: 0 } }" + /> <FetchData url="Tags" :filter="{ fields: ['id', 'name', 'isFree'] }" auto-load - limit="30" @on-fetch="(data) => (tagOptions = data)" /> <VnFilterPanel @@ -195,8 +197,6 @@ const setCategoryList = (data) => { :label="t('components.itemsFilterPanel.typeFk')" v-model="params.typeFk" :options="itemTypesOptions" - option-value="id" - option-label="name" dense outlined rounded @@ -234,7 +234,6 @@ const setCategoryList = (data) => { :label="t('globals.tag')" v-model="value.selectedTag" :options="tagOptions" - option-label="name" dense outlined rounded @@ -328,7 +327,6 @@ en: active: Is active visible: Is visible floramondo: Is floramondo - salesPersonFk: Buyer categoryFk: Category es: @@ -339,7 +337,6 @@ es: active: Activo visible: Visible floramondo: Floramondo - salesPersonFk: Comprador categoryFk: Categoría Plant: Planta natural Flower: Flor fresca diff --git a/src/components/LeftMenuItem.vue b/src/components/LeftMenuItem.vue index a3112b17f..c0cee44fe 100644 --- a/src/components/LeftMenuItem.vue +++ b/src/components/LeftMenuItem.vue @@ -26,6 +26,7 @@ const itemComputed = computed(() => { :to="{ name: itemComputed.name }" clickable v-ripple + :data-cy="`${itemComputed.name}-menu-item`" > <QItemSection avatar v-if="itemComputed.icon"> <QIcon :name="itemComputed.icon" /> diff --git a/src/components/RefundInvoiceForm.vue b/src/components/RefundInvoiceForm.vue index 590acede0..6dcb8b390 100644 --- a/src/components/RefundInvoiceForm.vue +++ b/src/components/RefundInvoiceForm.vue @@ -9,6 +9,7 @@ import VnSelect from 'components/common/VnSelect.vue'; import FormPopup from './FormPopup.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const $props = defineProps({ invoiceOutData: { @@ -131,15 +132,11 @@ const refund = async () => { :required="true" /> </VnRow ><VnRow> - <div> - <QCheckbox - :label="t('Inherit warehouse')" - v-model="invoiceParams.inheritWarehouse" - /> - <QIcon name="info" class="cursor-info q-ml-sm" size="sm"> - <QTooltip>{{ t('Inherit warehouse tooltip') }}</QTooltip> - </QIcon> - </div> + <VnCheckbox + v-model="invoiceParams.inheritWarehouse" + :label="t('Inherit warehouse')" + :info="t('Inherit warehouse tooltip')" + /> </VnRow> </template> </FormPopup> diff --git a/src/components/TicketProblems.vue b/src/components/TicketProblems.vue index a24735a5f..783f2556f 100644 --- a/src/components/TicketProblems.vue +++ b/src/components/TicketProblems.vue @@ -1,8 +1,22 @@ <script setup> +import { toCurrency } from 'src/filters'; + defineProps({ row: { type: Object, required: true } }); </script> <template> <span class="q-gutter-x-xs"> + <router-link + v-if="row.claim?.claimFk" + :to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }" + class="link" + > + <QIcon name="vn:claims" size="xs"> + <QTooltip> + {{ t('ticketSale.claim') }}: + {{ row.claim?.claimFk }} + </QTooltip> + </QIcon> + </router-link> <QIcon v-if="row?.risk" name="vn:risk" @@ -10,7 +24,8 @@ defineProps({ row: { type: Object, required: true } }); size="xs" > <QTooltip> - {{ $t('salesTicketsTable.risk') }}: {{ row.risk - row.credit }} + {{ $t('salesTicketsTable.risk') }}: + {{ toCurrency(row.risk - row.credit) }} </QTooltip> </QIcon> <QIcon @@ -53,7 +68,7 @@ defineProps({ row: { type: Object, required: true } }); <QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip> </QIcon> <QIcon - v-if="!row?.isTaxDataChecked === 0" + v-if="row?.isTaxDataChecked !== 0" name="vn:no036" color="primary" size="xs" diff --git a/src/components/TransferInvoiceForm.vue b/src/components/TransferInvoiceForm.vue index aa71070d6..c4ef1454a 100644 --- a/src/components/TransferInvoiceForm.vue +++ b/src/components/TransferInvoiceForm.vue @@ -10,6 +10,7 @@ import VnSelect from 'components/common/VnSelect.vue'; import FormPopup from './FormPopup.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import VnCheckbox from './common/VnCheckbox.vue'; const $props = defineProps({ invoiceOutData: { @@ -186,15 +187,11 @@ const makeInvoice = async () => { /> </VnRow> <VnRow> - <div> - <QCheckbox - :label="t('Bill destination client')" - v-model="checked" - /> - <QIcon name="info" class="cursor-info q-ml-sm" size="sm"> - <QTooltip>{{ t('transferInvoiceInfo') }}</QTooltip> - </QIcon> - </div> + <VnCheckbox + v-model="checked" + :label="t('Bill destination client')" + :info="t('transferInvoiceInfo')" + /> </VnRow> </template> </FormPopup> diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index 9e9bfad69..d0e245388 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -1,9 +1,8 @@ <script setup> import { markRaw, computed } from 'vue'; -import { QIcon, QCheckbox } from 'quasar'; +import { QIcon, QToggle } from 'quasar'; import { dashIfEmpty } from 'src/filters'; -/* basic input */ import VnSelect from 'components/common/VnSelect.vue'; import VnSelectCache from 'components/common/VnSelectCache.vue'; import VnInput from 'components/common/VnInput.vue'; @@ -12,8 +11,11 @@ import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputTime from 'components/common/VnInputTime.vue'; import VnComponent from 'components/common/VnComponent.vue'; import VnUserLink from 'components/ui/VnUserLink.vue'; +import VnSelectEnum from '../common/VnSelectEnum.vue'; +import VnCheckbox from '../common/VnCheckbox.vue'; const model = defineModel(undefined, { required: true }); +const emit = defineEmits(['blur']); const $props = defineProps({ column: { type: Object, @@ -39,10 +41,18 @@ const $props = defineProps({ type: Object, default: null, }, + autofocus: { + type: Boolean, + default: false, + }, showLabel: { type: Boolean, default: null, }, + eventHandlers: { + type: Object, + default: null, + }, }); const defaultSelect = { @@ -99,7 +109,8 @@ const defaultComponents = { }, }, checkbox: { - component: markRaw(QCheckbox), + ref: 'checkbox', + component: markRaw(VnCheckbox), attrs: ({ model }) => { const defaultAttrs = { disable: !$props.isEditable, @@ -115,6 +126,10 @@ const defaultComponents = { }, forceAttrs: { label: $props.showLabel && $props.column.label, + autofocus: true, + }, + events: { + blur: () => emit('blur'), }, }, select: { @@ -125,12 +140,19 @@ const defaultComponents = { component: markRaw(VnSelect), ...defaultSelect, }, + selectEnum: { + component: markRaw(VnSelectEnum), + ...defaultSelect, + }, icon: { component: markRaw(QIcon), }, userLink: { component: markRaw(VnUserLink), }, + toggle: { + component: markRaw(QToggle), + }, }; const value = computed(() => { @@ -160,7 +182,28 @@ const col = computed(() => { return newColumn; }); -const components = computed(() => $props.components ?? defaultComponents); +const components = computed(() => { + const sourceComponents = $props.components ?? defaultComponents; + + return Object.keys(sourceComponents).reduce((acc, key) => { + const component = sourceComponents[key]; + + if (!component || typeof component !== 'object') { + acc[key] = component; + return acc; + } + + acc[key] = { + ...component, + attrs: { + ...(component.attrs || {}), + autofocus: $props.autofocus, + }, + event: { ...component?.event, ...$props?.eventHandlers }, + }; + return acc; + }, {}); +}); </script> <template> <div class="row no-wrap"> diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 426f5c716..0de3834ea 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -1,14 +1,12 @@ <script setup> import { markRaw, computed } from 'vue'; -import { QCheckbox } from 'quasar'; +import { QCheckbox, QToggle } from 'quasar'; import { useArrayData } from 'composables/useArrayData'; - -/* basic input */ import VnSelect from 'components/common/VnSelect.vue'; import VnInput from 'components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputTime from 'components/common/VnInputTime.vue'; -import VnTableColumn from 'components/VnTable/VnColumn.vue'; +import VnColumn from 'components/VnTable/VnColumn.vue'; const $props = defineProps({ column: { @@ -27,6 +25,10 @@ const $props = defineProps({ type: String, default: 'table', }, + customClass: { + type: String, + default: '', + }, }); defineExpose({ addFilter, props: $props }); @@ -34,7 +36,7 @@ defineExpose({ addFilter, props: $props }); const model = defineModel(undefined, { required: true }); const arrayData = useArrayData( $props.dataKey, - $props.searchUrl ? { searchUrl: $props.searchUrl } : null + $props.searchUrl ? { searchUrl: $props.searchUrl } : null, ); const columnFilter = computed(() => $props.column?.columnFilter); @@ -46,19 +48,18 @@ const enterEvent = { const defaultAttrs = { filled: !$props.showTitle, - class: 'q-px-xs q-pb-xs q-pt-none fit', dense: true, }; const forceAttrs = { - label: $props.showTitle ? '' : columnFilter.value?.label ?? $props.column.label, + label: $props.showTitle ? '' : (columnFilter.value?.label ?? $props.column.label), }; const selectComponent = { component: markRaw(VnSelect), event: updateEvent, attrs: { - class: 'q-px-sm q-pb-xs q-pt-none fit', + class: `q-pt-none fit ${$props.customClass}`, dense: true, filled: !$props.showTitle, }, @@ -109,14 +110,24 @@ const components = { component: markRaw(QCheckbox), event: updateEvent, attrs: { - dense: true, - class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs fit', + class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit', 'toggle-indeterminate': true, + size: 'sm', }, forceAttrs, }, select: selectComponent, rawSelect: selectComponent, + toggle: { + component: markRaw(QToggle), + event: updateEvent, + attrs: { + class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit', + 'toggle-indeterminate': true, + size: 'sm', + }, + forceAttrs, + }, }; async function addFilter(value, name) { @@ -132,19 +143,8 @@ async function addFilter(value, name) { await arrayData.addFilter({ params: { [field]: value } }); } -function alignRow() { - switch ($props.column.align) { - case 'left': - return 'justify-start items-start'; - case 'right': - return 'justify-end items-end'; - default: - return 'flex-center'; - } -} - const showFilter = computed( - () => $props.column?.columnFilter !== false && $props.column.name != 'tableActions' + () => $props.column?.columnFilter !== false && $props.column.name != 'tableActions', ); const onTabPressed = async () => { @@ -152,13 +152,8 @@ const onTabPressed = async () => { }; </script> <template> - <div - v-if="showFilter" - class="full-width" - :class="alignRow()" - style="max-height: 45px; overflow: hidden" - > - <VnTableColumn + <div v-if="showFilter" class="full-width" style="overflow: hidden"> + <VnColumn :column="$props.column" default="input" v-model="model" @@ -168,3 +163,8 @@ const onTabPressed = async () => { /> </div> </template> +<style lang="scss" scoped> +label.vn-label-padding > .q-field__inner > .q-field__control { + padding: inherit !important; +} +</style> diff --git a/src/components/VnTable/VnOrder.vue b/src/components/VnTable/VnOrder.vue index 8ffdfe2bc..47ed9acf4 100644 --- a/src/components/VnTable/VnOrder.vue +++ b/src/components/VnTable/VnOrder.vue @@ -23,6 +23,10 @@ const $props = defineProps({ type: Boolean, default: false, }, + align: { + type: String, + default: 'end', + }, }); const hover = ref(); const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); @@ -41,55 +45,78 @@ async function orderBy(name, direction) { break; } if (!direction) return await arrayData.deleteOrder(name); + await arrayData.addOrder(name, direction); } defineExpose({ orderBy }); + +function textAlignToFlex(textAlign) { + return `justify-content: ${ + { + 'text-center': 'center', + 'text-left': 'start', + 'text-right': 'end', + }[textAlign] || 'start' + };`; +} </script> <template> <div @mouseenter="hover = true" @mouseleave="hover = false" @click="orderBy(name, model?.direction)" - class="row items-center no-wrap cursor-pointer" + class="items-center no-wrap cursor-pointer title" + :style="textAlignToFlex(align)" > <span :title="label">{{ label }}</span> - <QChip - v-if="name" - :label="!vertical ? model?.index : ''" - :icon=" - (model?.index || hover) && !vertical - ? model?.direction == 'DESC' - ? 'arrow_downward' - : 'arrow_upward' - : undefined - " - :size="vertical ? '' : 'sm'" - :class="[ - model?.index ? 'color-vn-text' : 'bg-transparent', - vertical ? 'q-px-none' : '', - ]" - class="no-box-shadow" - :clickable="true" - style="min-width: 40px" - > - <div - class="column flex-center" - v-if="vertical" - :style="!model?.index && 'color: #5d5d5d'" + <div v-if="name && model?.index"> + <QChip + :label="!vertical ? model?.index : ''" + :icon=" + (model?.index || hover) && !vertical + ? model?.direction == 'DESC' + ? 'arrow_downward' + : 'arrow_upward' + : undefined + " + :size="vertical ? '' : 'sm'" + :class="[ + model?.index ? 'color-vn-text' : 'bg-transparent', + vertical ? 'q-px-none' : '', + ]" + class="no-box-shadow" + :clickable="true" + style="min-width: 40px; max-height: 30px" > - {{ model?.index }} - <QIcon - :name=" - model?.index - ? model?.direction == 'DESC' - ? 'arrow_downward' - : 'arrow_upward' - : 'swap_vert' - " - size="xs" - /> - </div> - </QChip> + <div + class="column flex-center" + v-if="vertical" + :style="!model?.index && 'color: #5d5d5d'" + > + {{ model?.index }} + <QIcon + :name=" + model?.index + ? model?.direction == 'DESC' + ? 'arrow_downward' + : 'arrow_upward' + : 'swap_vert' + " + size="xs" + /> + </div> + </QChip> + </div> </div> </template> +<style lang="scss" scoped> +.title { + display: flex; + align-items: center; + height: 30px; + width: 100%; + color: var(--vn-label-color); + white-space: nowrap; +} +</style> diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 04b7c0a46..2cce5d05c 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,22 +1,38 @@ <script setup> -import { ref, onBeforeMount, onMounted, computed, watch, useAttrs } from 'vue'; +import { + ref, + onBeforeMount, + onMounted, + onUnmounted, + computed, + watch, + h, + render, + inject, + useAttrs, + nextTick, +} from 'vue'; +import { useArrayData } from 'src/composables/useArrayData'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; -import { useQuasar } from 'quasar'; +import { useQuasar, date } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; import { useFilterParams } from 'src/composables/useFilterParams'; +import { dashIfEmpty, toDate } from 'src/filters'; import CrudModel from 'src/components/CrudModel.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; -import VnTableColumn from 'components/VnTable/VnColumn.vue'; +import VnColumn from 'components/VnTable/VnColumn.vue'; import VnFilter from 'components/VnTable/VnFilter.vue'; import VnTableChip from 'components/VnTable/VnChip.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; import VnTableFilter from './VnTableFilter.vue'; +import { getColAlign } from 'src/composables/getColAlign'; +const arrayData = useArrayData(useAttrs()['data-key']); const $props = defineProps({ columns: { type: Array, @@ -42,10 +58,6 @@ const $props = defineProps({ type: [Function, Boolean], default: null, }, - rowCtrlClick: { - type: [Function, Boolean], - default: null, - }, redirect: { type: String, default: null, @@ -114,7 +126,19 @@ const $props = defineProps({ type: Boolean, default: false, }, + withFilters: { + type: Boolean, + default: true, + }, + overlay: { + type: Boolean, + default: false, + }, + createComplement: { + type: Object, + }, }); + const { t } = useI18n(); const stateStore = useStateStore(); const route = useRoute(); @@ -132,10 +156,17 @@ const showForm = ref(false); const splittedColumns = ref({ columns: [] }); const columnsVisibilitySkipped = ref(); const createForm = ref(); +const createRef = ref(null); const tableRef = ref(); const params = ref(useFilterParams($attrs['data-key']).params); const orders = ref(useFilterParams($attrs['data-key']).orders); +const app = inject('app'); +const editingRow = ref(null); +const editingField = ref(null); +const isTableMode = computed(() => mode.value == TABLE_MODE); +const selectRegex = /select/; +const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); const tableModes = [ { icon: 'view_column', @@ -156,7 +187,8 @@ onBeforeMount(() => { hasParams.value = urlParams && Object.keys(urlParams).length !== 0; }); -onMounted(() => { +onMounted(async () => { + if ($props.isEditable) document.addEventListener('click', clickHandler); mode.value = quasar.platform.is.mobile && !$props.disableOption?.card ? CARD_MODE @@ -178,14 +210,25 @@ onMounted(() => { } }); +onUnmounted(async () => { + if ($props.isEditable) document.removeEventListener('click', clickHandler); +}); + watch( () => $props.columns, (value) => splitColumns(value), { immediate: true }, ); -const isTableMode = computed(() => mode.value == TABLE_MODE); -const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); +defineExpose({ + create: createForm, + reload, + redirect: redirectFn, + selected, + CrudModelRef, + params, + tableRef, +}); function splitColumns(columns) { splittedColumns.value = { @@ -231,16 +274,6 @@ const rowClickFunction = computed(() => { return () => {}; }); -const rowCtrlClickFunction = computed(() => { - if ($props.rowCtrlClick != undefined) return $props.rowCtrlClick; - if ($props.redirect) - return (evt, { id }) => { - stopEventPropagation(evt); - window.open(`/#/${$props.redirect}/${id}`, '_blank'); - }; - return () => {}; -}); - function redirectFn(id) { router.push({ path: `/${$props.redirect}/${id}` }); } @@ -262,21 +295,6 @@ function columnName(col) { return name; } -function getColAlign(col) { - return 'text-' + (col.align ?? 'left'); -} - -const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); -defineExpose({ - create: createForm, - reload, - redirect: redirectFn, - selected, - CrudModelRef, - params, - tableRef, -}); - function handleOnDataSaved(_) { if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value }); else $props.create.onDataSaved(_); @@ -304,6 +322,241 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { } } } + +function isEditableColumn(column) { + const isEditableCol = column?.isEditable ?? true; + const isVisible = column?.visible ?? true; + const hasComponent = column?.component; + + return $props.isEditable && isVisible && hasComponent && isEditableCol; +} + +function hasEditableFormat(column) { + if (isEditableColumn(column)) return 'editable-text'; +} + +const clickHandler = async (event) => { + const clickedElement = event.target.closest('td'); + + const isDateElement = event.target.closest('.q-date'); + const isTimeElement = event.target.closest('.q-time'); + const isQselectDropDown = event.target.closest('.q-select__dropdown-icon'); + + if (isDateElement || isTimeElement || isQselectDropDown) return; + + if (clickedElement === null) { + await destroyInput(editingRow.value, editingField.value); + return; + } + const rowIndex = clickedElement.getAttribute('data-row-index'); + const colField = clickedElement.getAttribute('data-col-field'); + const column = $props.columns.find((col) => col.name === colField); + + if (editingRow.value !== null && editingField.value !== null) { + if (editingRow.value == rowIndex && editingField.value == colField) return; + + await destroyInput(editingRow.value, editingField.value); + } + + if (isEditableColumn(column)) { + await renderInput(Number(rowIndex), colField, clickedElement); + } +}; + +async function handleTabKey(event, rowIndex, colField) { + if (editingRow.value == rowIndex && editingField.value == colField) + await destroyInput(editingRow.value, editingField.value); + + const direction = event.shiftKey ? -1 : 1; + const { nextRowIndex, nextColumnName } = await handleTabNavigation( + rowIndex, + colField, + direction, + ); + + if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return; + + event.preventDefault(); + await renderInput(nextRowIndex, nextColumnName, null); +} + +async function renderInput(rowId, field, clickedElement) { + editingField.value = field; + editingRow.value = rowId; + + const originalColumn = $props.columns.find((col) => col.name === field); + const column = { ...originalColumn, ...{ label: '' } }; + const row = CrudModelRef.value.formData[rowId]; + const oldValue = CrudModelRef.value.formData[rowId][column?.name]; + + if (!clickedElement) + clickedElement = document.querySelector( + `[data-row-index="${rowId}"][data-col-field="${field}"]`, + ); + + Array.from(clickedElement.childNodes).forEach((child) => { + child.style.visibility = 'hidden'; + child.style.position = 'relative'; + }); + + const isSelect = selectRegex.test(column?.component); + if (isSelect) column.attrs = { ...column.attrs, 'emit-value': false }; + + const node = h(VnColumn, { + row: row, + class: 'temp-input', + column: column, + modelValue: row[column.name], + componentProp: 'columnField', + autofocus: true, + focusOnMount: true, + eventHandlers: { + 'update:modelValue': async (value) => { + if (isSelect && value) { + row[column.name] = value[column.attrs?.optionValue ?? 'id']; + row[column?.name + 'TextValue'] = + value[column.attrs?.optionLabel ?? 'name']; + await column?.cellEvent?.['update:modelValue']?.( + value, + oldValue, + row, + ); + } else row[column.name] = value; + await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row); + }, + keyup: async (event) => { + if (event.key === 'Enter') + await destroyInput(rowIndex, field, clickedElement); + }, + keydown: async (event) => { + switch (event.key) { + case 'Tab': + await handleTabKey(event, rowId, field); + event.stopPropagation(); + break; + case 'Escape': + await destroyInput(rowId, field, clickedElement); + break; + default: + break; + } + }, + click: (event) => { + column?.cellEvent?.['click']?.(event, row); + }, + }, + }); + + node.appContext = app._context; + render(node, clickedElement); + + if (['toggle'].includes(column?.component)) + node.el?.querySelector('span > div').focus(); + + if (['checkbox', undefined].includes(column?.component)) + node.el?.querySelector('span > div > div').focus(); +} + +async function destroyInput(rowIndex, field, clickedElement) { + if (!clickedElement) + clickedElement = document.querySelector( + `[data-row-index="${rowIndex}"][data-col-field="${field}"]`, + ); + if (clickedElement) { + await nextTick(); + render(null, clickedElement); + Array.from(clickedElement.childNodes).forEach((child) => { + child.style.visibility = 'visible'; + child.style.position = ''; + }); + } + if (editingRow.value !== rowIndex || editingField.value !== field) return; + editingRow.value = null; + editingField.value = null; +} + +async function handleTabNavigation(rowIndex, colName, direction) { + const columns = $props.columns; + const totalColumns = columns.length; + let currentColumnIndex = columns.findIndex((col) => col.name === colName); + + let iterations = 0; + let newColumnIndex = currentColumnIndex; + + do { + iterations++; + newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns; + + if (isEditableColumn(columns[newColumnIndex])) break; + } while (iterations < totalColumns); + + if (iterations >= totalColumns + 1) return; + + if (direction === 1 && newColumnIndex <= currentColumnIndex) { + rowIndex++; + } else if (direction === -1 && newColumnIndex >= currentColumnIndex) { + rowIndex--; + } + return { nextRowIndex: rowIndex, nextColumnName: columns[newColumnIndex].name }; +} + +function getCheckboxIcon(value) { + switch (typeof value) { + case 'boolean': + return value ? 'check' : 'close'; + case 'number': + return value === 0 ? 'close' : 'check'; + case 'undefined': + return 'indeterminate_check_box'; + default: + return 'indeterminate_check_box'; + } +} + +function getToggleIcon(value) { + if (value === null) return 'help_outline'; + return value ? 'toggle_on' : 'toggle_off'; +} + +function formatColumnValue(col, row, dashIfEmpty) { + if (col?.format) { + if (selectRegex.test(col?.component) && row[col?.name + 'TextValue']) { + return dashIfEmpty(row[col?.name + 'TextValue']); + } else { + return col.format(row, dashIfEmpty); + } + } + + if (col?.component === 'date') return dashIfEmpty(toDate(row[col?.name])); + + if (col?.component === 'time') + return row[col?.name] >= 5 + ? dashIfEmpty(date.formatDate(new Date(row[col?.name]), 'HH:mm')) + : row[col?.name]; + + if (selectRegex.test(col?.component) && $props.isEditable) { + const { find, url } = col.attrs; + const urlRelation = url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1); + + if (col?.attrs.options) { + const find = col?.attrs.options.find((option) => option.id === row[col.name]); + if (!col.attrs?.optionLabel || !find) return dashIfEmpty(row[col?.name]); + return dashIfEmpty(find[col.attrs?.optionLabel ?? 'name']); + } + + if (typeof row[urlRelation] == 'object') { + if (typeof find == 'object') + return dashIfEmpty(row[urlRelation][find?.label ?? 'name']); + + return dashIfEmpty(row[urlRelation][col?.attrs.optionLabel ?? 'name']); + } + if (typeof row[urlRelation] == 'string') return dashIfEmpty(row[urlRelation]); + } + return dashIfEmpty(row[col?.name]); +} +function cardClick(_, row) { + if ($props.redirect) router.push({ path: `/${$props.redirect}/${row.id}` }); +} </script> <template> <QDrawer @@ -311,7 +564,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { v-model="stateStore.rightDrawer" side="right" :width="256" - show-if-above + :overlay="$props.overlay" > <QScrollArea class="fit"> <VnTableFilter @@ -332,7 +585,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { <CrudModel v-bind="$attrs" :class="$attrs['class'] ?? 'q-px-md'" - :limit="$attrs['limit'] ?? 20" + :limit="$attrs['limit'] ?? 100" ref="CrudModelRef" @on-fetch="(...args) => emit('onFetch', ...args)" :search-url="searchUrl" @@ -348,8 +601,12 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { <QTable ref="tableRef" v-bind="table" - class="vnTable" - :class="{ 'last-row-sticky': $props.footer }" + :class="[ + 'vnTable', + table ? 'selection-cell' : '', + $props.footer ? 'last-row-sticky' : '', + ]" + wrap-cells :columns="splittedColumns.columns" :rows="rows" v-model:selected="selected" @@ -363,11 +620,13 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { @row-click="(_, row) => rowClickFunction && rowClickFunction(row)" @update:selected="emit('update:selected', $event)" @selection="(details) => handleSelection(details, rows)" + :hide-selected-banner="true" > <template #top-left v-if="!$props.withoutHeader"> - <slot name="top-left"></slot> + <slot name="top-left"> </slot> </template> <template #top-right v-if="!$props.withoutHeader"> + <slot name="top-right"></slot> <VnVisibleColumn v-if="isTableMode" v-model="splittedColumns.columns" @@ -381,43 +640,43 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { dense :options="tableModes.filter((mode) => !mode.disable)" /> - <QBtn - v-if="showRightIcon" - icon="filter_alt" - class="bg-vn-section-color q-ml-sm" - dense - @click="stateStore.toggleRightDrawer()" - /> </template> <template #header-cell="{ col }"> <QTh v-if="col.visible ?? true" - :style="col.headerStyle" - :class="col.headerClass" + v-bind:class="col.headerClass" + class="body-cell" + :style="col?.width ? `max-width: ${col?.width}` : ''" > <div - class="column ellipsis" - :class="`text-${col?.align ?? 'left'}`" - :style="$props.columnSearch ? 'height: 75px' : ''" + class="no-padding" + :style="[ + withFilters && $props.columnSearch ? 'height: 75px' : '', + ]" > - <div class="row items-center no-wrap" style="height: 30px"> + <div style="height: 30px"> <QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip> <VnTableOrder v-model="orders[col.orderBy ?? col.name]" :name="col.orderBy ?? col.name" - :label="col?.label" + :label="col?.labelAbbreviation ?? col?.label" :data-key="$attrs['data-key']" :search-url="searchUrl" + :align="getColAlign(col)" /> </div> <VnFilter - v-if="$props.columnSearch" + v-if=" + $props.columnSearch && + col.columnSearch !== false && + withFilters + " :column="col" :show-title="true" :data-key="$attrs['data-key']" v-model="params[columnName(col)]" :search-url="searchUrl" - class="full-width" + customClass="header-filter" /> </div> </QTh> @@ -435,32 +694,67 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { </QTd> </template> <template #body-cell="{ col, row, rowIndex }"> - <!-- Columns --> <QTd - auto-width - class="no-margin" - :class="[getColAlign(col), col.columnClass]" - :style="col.style" + class="no-margin q-px-xs" v-if="col.visible ?? true" - @click.ctrl=" - ($event) => - rowCtrlClickFunction && rowCtrlClickFunction($event, row) - " + :style="{ + 'max-width': col?.width ?? false, + position: 'relative', + }" + :class="[ + col.columnClass, + 'body-cell no-margin no-padding', + getColAlign(col), + ]" + :data-row-index="rowIndex" + :data-col-field="col?.name" > - <slot - :name="`column-${col.name}`" - :col="col" - :row="row" - :row-index="rowIndex" + <div + class="no-padding no-margin peter" + style=" + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + " > - <VnTableColumn - :column="col" + <slot + :name="`column-${col.name}`" + :col="col" :row="row" - :is-editable="col.isEditable ?? isEditable" - v-model="row[col.name]" - component-prop="columnField" - /> - </slot> + :row-index="rowIndex" + > + <QIcon + v-if="col?.component === 'toggle'" + :name=" + col?.getIcon + ? col.getIcon(row[col?.name]) + : getToggleIcon(row[col?.name]) + " + style="color: var(--vn-text-color)" + :class="hasEditableFormat(col)" + size="14px" + /> + <QIcon + v-else-if="col?.component === 'checkbox'" + :name="getCheckboxIcon(row[col?.name])" + style="color: var(--vn-text-color)" + :class="hasEditableFormat(col)" + size="14px" + /> + <span + v-else + :class="hasEditableFormat(col)" + :style=" + typeof col?.style == 'function' + ? col.style(row) + : col?.style + " + style="bottom: 0" + > + {{ formatColumnValue(col, row, dashIfEmpty) }} + </span> + </slot> + </div> </QTd> </template> <template #body-cell-tableActions="{ col, row }"> @@ -481,7 +775,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { flat dense :class=" - btn.isPrimary ? 'text-primary-light' : 'color-vn-text ' + btn.isPrimary ? 'text-primary-light' : 'color-vn-label' " :style="`visibility: ${ ((btn.show && btn.show(row)) ?? true) @@ -489,23 +783,19 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { : 'hidden' }`" @click="btn.action(row)" + :data-cy="btn?.name ?? `tableAction-${index}`" /> </QTd> </template> <template #item="{ row, colsMap }"> <component - :is="$props.redirect ? 'router-link' : 'span'" - :to="`/${$props.redirect}/` + row.id" + v-bind:is="'div'" + @click="(event) => cardClick(event, row)" > <QCard bordered flat class="row no-wrap justify-between cursor-pointer q-pa-sm" - @click=" - (_, row) => { - $props.rowClick && $props.rowClick(row); - } - " style="height: 100%" > <QCardSection @@ -542,7 +832,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { </QCardSection> <!-- Fields --> <QCardSection - class="q-pl-sm q-pr-lg q-py-xs" + class="q-pl-sm q-py-xs" :class="$props.cardClass" > <div @@ -563,7 +853,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { :row="row" :row-index="index" > - <VnTableColumn + <VnColumn :column="col" :row="row" :is-editable="false" @@ -589,13 +879,14 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { :key="index" :title="btn.title" :icon="btn.icon" + data-cy="cardBtn" class="q-pa-xs" - flat :class=" btn.isPrimary ? 'text-primary-light' - : 'color-vn-text ' + : 'color-vn-label' " + flat @click="btn.action(row)" /> </QCardSection> @@ -603,14 +894,17 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { </component> </template> <template #bottom-row="{ cols }" v-if="$props.footer"> - <QTr v-if="rows.length" style="height: 30px"> + <QTr v-if="rows.length" style="height: 45px"> + <QTh v-if="table.selection" /> <QTh v-for="col of cols.filter((cols) => cols.visible ?? true)" :key="col?.id" - class="text-center" :class="getColAlign(col)" > - <slot :name="`column-footer-${col.name}`" /> + <slot + :name="`column-footer-${col.name}`" + :isEditableColumn="isEditableColumn(col)" + /> </QTh> </QTr> </template> @@ -654,32 +948,53 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { {{ createForm?.title }} </QTooltip> </QPageSticky> - <QDialog v-model="showForm" transition-show="scale" transition-hide="scale"> + <QDialog + v-model="showForm" + transition-show="scale" + transition-hide="scale" + :full-width="createComplement?.isFullWidth ?? false" + @before-hide=" + () => { + if (createRef.isSaveAndContinue) { + showForm = true; + createForm.formInitialData = { ...create.formInitialData }; + } + } + " + data-cy="vn-table-create-dialog" + > <FormModelPopup + ref="createRef" v-bind="createForm" :model="$attrs['data-key'] + 'Create'" @on-data-saved="(_, res) => createForm.onDataSaved(res)" > <template #form-inputs="{ data }"> - <div class="grid-create"> - <slot - v-for="column of splittedColumns.create" - :key="column.name" - :name="`column-create-${column.name}`" - :data="data" - :column-name="column.name" - :label="column.label" - > - <VnTableColumn - :column="column" - :row="{}" - default="input" - v-model="data[column.name]" - :show-label="true" - component-prop="columnCreate" - /> - </slot> - <slot name="more-create-dialog" :data="data" /> + <div :style="createComplement?.containerStyle"> + <div> + <slot name="previous-create-dialog" :data="data" /> + </div> + <div class="grid-create" :style="createComplement?.columnGridStyle"> + <slot + v-for="column of splittedColumns.create" + :key="column.name" + :name="`column-create-${column.name}`" + :data="data" + :column-name="column.name" + :label="column.label" + > + <VnColumn + :column="column" + :row="{}" + default="input" + v-model="data[column.name]" + :show-label="true" + component-prop="columnCreate" + :data-cy="`${column.name}-create-popup`" + /> + </slot> + <slot name="more-create-dialog" :data="data" /> + </div> </div> </template> </FormModelPopup> @@ -697,6 +1012,42 @@ es: </i18n> <style lang="scss"> +.selection-cell { + table td:first-child { + padding: 0px; + } +} +.side-padding { + padding-left: 1px; + padding-right: 1px; +} +.editable-text:hover { + border-bottom: 1px dashed var(--q-primary); + @extend .side-padding; +} +.editable-text { + border-bottom: 1px dashed var(--vn-label-color); + @extend .side-padding; +} +.cell-input { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding-top: 0px !important; +} +.q-field--labeled .q-field__native, +.q-field--labeled .q-field__prefix, +.q-field--labeled .q-field__suffix { + padding-top: 20px; +} + +.body-cell { + padding-left: 4px !important; + padding-right: 4px !important; + position: relative; +} .bg-chip-secondary { background-color: var(--vn-page-color); color: var(--vn-text-color); @@ -713,8 +1064,8 @@ es: .grid-three { display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, max-content)); - max-width: 100%; + grid-template-columns: repeat(auto-fit, minmax(300px, max-content)); + width: 100%; grid-gap: 20px; margin: 0 auto; } @@ -722,7 +1073,6 @@ es: .grid-create { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); - max-width: 100%; grid-gap: 20px; margin: 0 auto; } @@ -738,7 +1088,9 @@ es: } } } - +.q-table tbody tr td { + position: relative; +} .q-table { th { padding: 0; @@ -787,6 +1139,7 @@ es: .vn-label-value { display: flex; flex-direction: row; + align-items: center; color: var(--vn-text-color); .value { overflow: hidden; @@ -838,4 +1191,15 @@ es: .q-table__middle.q-virtual-scroll.q-virtual-scroll--vertical.scroll { background-color: var(--vn-section-color); } +.temp-input { + top: 0; + position: absolute; + width: 100%; + height: 100%; + display: flex; +} + +label.header-filter > .q-field__inner > .q-field__control { + padding: inherit; +} </style> diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue index 63b84cd59..79b903e54 100644 --- a/src/components/VnTable/VnTableFilter.vue +++ b/src/components/VnTable/VnTableFilter.vue @@ -29,25 +29,29 @@ function columnName(col) { <VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true"> <template #body="{ params, orders, searchFn }"> <div - class="row no-wrap flex-center" + class="container" v-for="col of columns.filter((c) => c.columnFilter ?? true)" :key="col.id" > - <VnFilter - ref="tableFilterRef" - :column="col" - :data-key="$attrs['data-key']" - v-model="params[columnName(col)]" - :search-url="searchUrl" - /> - <VnTableOrder - v-if="col?.columnFilter !== false && col?.name !== 'tableActions'" - v-model="orders[col.orderBy ?? col.name]" - :name="col.orderBy ?? col.name" - :data-key="$attrs['data-key']" - :search-url="searchUrl" - :vertical="true" - /> + <div class="filter"> + <VnFilter + ref="tableFilterRef" + :column="col" + :data-key="$attrs['data-key']" + v-model="params[columnName(col)]" + :search-url="searchUrl" + /> + </div> + <div class="order"> + <VnTableOrder + v-if="col?.columnFilter !== false && col?.name !== 'tableActions'" + v-model="orders[col.orderBy ?? col.name]" + :name="col.orderBy ?? col.name" + :data-key="$attrs['data-key']" + :search-url="searchUrl" + :vertical="true" + /> + </div> </div> <slot name="moreFilterPanel" @@ -68,3 +72,21 @@ function columnName(col) { </template> </VnFilterPanel> </template> +<style lang="scss" scoped> +.container { + display: flex; + justify-content: center; + align-items: center; + height: 45px; + gap: 10px; +} + +.filter { + width: 70%; + height: 40px; + text-align: center; +} +.order { + width: 10%; +} +</style> diff --git a/src/components/VnTable/VnVisibleColumn.vue b/src/components/VnTable/VnVisibleColumn.vue index dad950d73..6d15c585e 100644 --- a/src/components/VnTable/VnVisibleColumn.vue +++ b/src/components/VnTable/VnVisibleColumn.vue @@ -32,16 +32,21 @@ const areAllChecksMarked = computed(() => { function setUserConfigViewData(data, isLocal) { if (!data) return; - // Importante: El name de las columnas de la tabla debe conincidir con el name de las variables que devuelve la view config if (!isLocal) localColumns.value = []; - // Array to Object + const skippeds = $props.skip.reduce((a, v) => ({ ...a, [v]: v }), {}); for (let column of columns.value) { - const { label, name } = column; + const { label, name, labelAbbreviation } = column; if (skippeds[name]) continue; column.visible = data[name] ?? true; - if (!isLocal) localColumns.value.push({ name, label, visible: column.visible }); + if (!isLocal) + localColumns.value.push({ + name, + label, + labelAbbreviation, + visible: column.visible, + }); } } @@ -152,7 +157,11 @@ onMounted(async () => { <QCheckbox v-for="col in localColumns" :key="col.name" - :label="col.label ?? col.name" + :label=" + col?.labelAbbreviation + ? col.labelAbbreviation + ` (${col.label ?? col.name})` + : (col.label ?? col.name) + " v-model="col.visible" /> </div> diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 17812f146..3dce04374 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -57,6 +57,7 @@ describe('FormModel', () => { vm.state.set(model, formInitialData); expect(vm.hasChanges).toBe(false); + await vm.$nextTick(); vm.formData.mockKey = 'newVal'; await vm.$nextTick(); expect(vm.hasChanges).toBe(true); @@ -94,8 +95,12 @@ describe('FormModel', () => { it('should call axios.patch with the right data', async () => { const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); const { vm } = mount({ propsData: { url, model } }); - vm.formData.mockKey = 'newVal'; + + vm.formData = {}; await vm.$nextTick(); + vm.formData = { mockKey: 'newVal' }; + await vm.$nextTick(); + await vm.save(); expect(spy).toHaveBeenCalled(); vm.formData.mockKey = 'mockVal'; diff --git a/src/components/__tests__/UserPanel.spec.js b/src/components/__tests__/UserPanel.spec.js index ac20f911e..9e449745a 100644 --- a/src/components/__tests__/UserPanel.spec.js +++ b/src/components/__tests__/UserPanel.spec.js @@ -1,61 +1,65 @@ -import { vi, describe, expect, it, beforeEach, beforeAll, afterEach } from 'vitest'; +import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import UserPanel from 'src/components/UserPanel.vue'; import axios from 'axios'; import { useState } from 'src/composables/useState'; +vi.mock('src/utils/quasarLang', () => ({ + default: vi.fn(), +})); + describe('UserPanel', () => { - let wrapper; - let vm; - let state; + let wrapper; + let vm; + let state; - beforeEach(() => { - wrapper = createWrapper(UserPanel, {}); - state = useState(); - state.setUser({ - id: 115, - name: 'itmanagement', - nickname: 'itManagementNick', - lang: 'en', - darkMode: false, - companyFk: 442, - warehouseFk: 1, - }); - wrapper = wrapper.wrapper; - vm = wrapper.vm; + beforeEach(() => { + wrapper = createWrapper(UserPanel, {}); + state = useState(); + state.setUser({ + id: 115, + name: 'itmanagement', + nickname: 'itManagementNick', + lang: 'en', + darkMode: false, + companyFk: 442, + warehouseFk: 1, }); + wrapper = wrapper.wrapper; + vm = wrapper.vm; + }); - afterEach(() => { - vi.clearAllMocks(); - }); + afterEach(() => { + vi.clearAllMocks(); + }); - it('should fetch warehouses data on mounted', async () => { - const fetchData = wrapper.findComponent({ name: 'FetchData' }); - expect(fetchData.props('url')).toBe('Warehouses'); - expect(fetchData.props('autoLoad')).toBe(true); - }); + it('should fetch warehouses data on mounted', async () => { + const fetchData = wrapper.findComponent({ name: 'FetchData' }); + expect(fetchData.props('url')).toBe('Warehouses'); + expect(fetchData.props('autoLoad')).toBe(true); + }); - it('should toggle dark mode correctly and update preferences', async () => { - await vm.saveDarkMode(true); - expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true }); - expect(vm.user.darkMode).toBe(true); - vm.updatePreferences(); - expect(vm.darkMode).toBe(true); - }); + it('should toggle dark mode correctly and update preferences', async () => { + await vm.saveDarkMode(true); + expect(axios.patch).toHaveBeenCalledWith('/UserConfigs/115', { darkMode: true }); + expect(vm.user.darkMode).toBe(true); + await vm.updatePreferences(); + expect(vm.darkMode).toBe(true); + }); - it('should change user language and update preferences', async () => { - const userLanguage = 'es'; - await vm.saveLanguage(userLanguage); - expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage }); - expect(vm.user.lang).toBe(userLanguage); - vm.updatePreferences(); - expect(vm.locale).toBe(userLanguage); - }); + it('should change user language and update preferences', async () => { + const userLanguage = 'es'; + await vm.saveLanguage(userLanguage); + expect(axios.patch).toHaveBeenCalledWith('/VnUsers/115', { lang: userLanguage }); + expect(vm.user.lang).toBe(userLanguage); + await vm.updatePreferences(); + expect(vm.locale).toBe(userLanguage); + }); - it('should update user data', async () => { - const key = 'name'; - const value = 'itboss'; - await vm.saveUserData(key, value); - expect(axios.post).toHaveBeenCalledWith('UserConfigs/setUserConfig', { [key]: value }); - }); -}); + it('should update user data', async () => { + const key = 'name'; + const value = 'itboss'; + await vm.saveUserData(key, value); + expect(axios.post).toHaveBeenCalledWith('UserConfigs/setUserConfig', { [key]: value }); + }); +}); \ No newline at end of file diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue index d2bed6257..7c82316dc 100644 --- a/src/components/common/VnCardBeta.vue +++ b/src/components/common/VnCardBeta.vue @@ -39,6 +39,13 @@ onBeforeMount(async () => { }); onBeforeRouteUpdate(async (to, from) => { + if (hasRouteParam(to.params)) { + const { matched } = router.currentRoute.value; + const { name } = matched.at(-3); + if (name) { + router.push({ name, params: to.params }); + } + } const id = to.params.id; if (id !== from.params.id) await fetch(id, true); }); @@ -50,6 +57,9 @@ async function fetch(id, append = false) { else arrayData.store.url = props.url.replace(regex, `/${id}`); await arrayData.fetch({ append, updateRouter: false }); } +function hasRouteParam(params, valueToCheck = ':addressId') { + return Object.values(params).includes(valueToCheck); +} </script> <template> <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> diff --git a/src/components/common/VnCheckbox.vue b/src/components/common/VnCheckbox.vue new file mode 100644 index 000000000..27131d45e --- /dev/null +++ b/src/components/common/VnCheckbox.vue @@ -0,0 +1,43 @@ +<script setup> +import { computed } from 'vue'; + +const model = defineModel({ type: [Number, Boolean] }); +const $props = defineProps({ + info: { + type: String, + default: null, + }, +}); + +const checkboxModel = computed({ + get() { + if (typeof model.value === 'number') { + return model.value !== 0; + } + return model.value; + }, + set(value) { + if (typeof model.value === 'number') { + model.value = value ? 1 : 0; + } else { + model.value = value; + } + }, +}); +</script> +<template> + <div> + <QCheckbox v-bind="$attrs" v-on="$attrs" v-model="checkboxModel" /> + <QIcon + v-if="info" + v-bind="$attrs" + class="cursor-info q-ml-sm" + name="info" + size="sm" + > + <QTooltip> + {{ info }} + </QTooltip> + </QIcon> + </div> +</template> diff --git a/src/components/common/VnColor.vue b/src/components/common/VnColor.vue new file mode 100644 index 000000000..8a5a787b0 --- /dev/null +++ b/src/components/common/VnColor.vue @@ -0,0 +1,32 @@ +<script setup> +const $props = defineProps({ + colors: { + type: String, + default: '{"value": []}', + }, +}); + +const colorArray = JSON.parse($props.colors)?.value; +const maxHeight = 30; +const colorHeight = maxHeight / colorArray?.length; +</script> +<template> + <div v-if="colors" class="color-div" :style="{ height: `${maxHeight}px` }"> + <div + v-for="(color, index) in colorArray" + :key="index" + :style="{ + backgroundColor: `#${color}`, + height: `${colorHeight}px`, + }" + > + + </div> + </div> +</template> +<style scoped> +.color-div { + display: flex; + flex-direction: column; +} +</style> diff --git a/src/components/common/VnComponent.vue b/src/components/common/VnComponent.vue index 580bcf348..a9e1c8cff 100644 --- a/src/components/common/VnComponent.vue +++ b/src/components/common/VnComponent.vue @@ -17,6 +17,8 @@ const $props = defineProps({ }, }); +const emit = defineEmits(['blur']); + const componentArray = computed(() => { if (typeof $props.prop === 'object') return [$props.prop]; return $props.prop; @@ -46,7 +48,8 @@ function toValueAttrs(attrs) { <span v-for="toComponent of componentArray" :key="toComponent.name" - class="column flex-center fit" + class="column fit" + :class="toComponent?.component == 'checkbox' ? 'flex-center' : ''" > <component v-if="toComponent?.component" @@ -54,6 +57,7 @@ function toValueAttrs(attrs) { v-bind="mix(toComponent).attrs" v-on="mix(toComponent).event ?? {}" v-model="model" + @blur="emit('blur')" /> </span> </template> diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 78f08a479..aeb4a31fd 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -11,6 +11,7 @@ const emit = defineEmits([ 'update:options', 'keyup.enter', 'remove', + 'blur', ]); const $props = defineProps({ @@ -136,6 +137,7 @@ const handleUppercase = () => { :type="$attrs.type" :class="{ required: isRequired }" @keyup.enter="emit('keyup.enter')" + @blur="emit('blur')" @keydown="handleKeydown" :clearable="false" :rules="mixinRules" @@ -143,7 +145,7 @@ const handleUppercase = () => { hide-bottom-space :data-cy="$attrs.dataCy ?? $attrs.label + '_input'" > - <template #prepend> + <template #prepend v-if="$slots.prepend"> <slot name="prepend" /> </template> <template #append> @@ -168,11 +170,11 @@ const handleUppercase = () => { } " ></QIcon> - + <QIcon name="match_case" size="xs" - v-if="!$attrs.disabled && !($attrs.readonly) && $props.uppercase" + v-if="!$attrs.disabled && !$attrs.readonly && $props.uppercase" @click="handleUppercase" class="uppercase-icon" > @@ -180,7 +182,7 @@ const handleUppercase = () => { {{ t('Convert to uppercase') }} </QTooltip> </QIcon> - + <slot name="append" v-if="$slots.append && !$attrs.disabled" /> <QIcon v-if="info" name="info"> <QTooltip max-width="350px"> @@ -194,13 +196,15 @@ const handleUppercase = () => { <style> .uppercase-icon { - transition: color 0.3s, transform 0.2s; - cursor: pointer; + transition: + color 0.3s, + transform 0.2s; + cursor: pointer; } .uppercase-icon:hover { - color: #ed9937; - transform: scale(1.2); + color: #ed9937; + transform: scale(1.2); } </style> <i18n> @@ -214,4 +218,4 @@ const handleUppercase = () => { maxLength: El valor excede los {value} carácteres inputMax: Debe ser menor a {value} Convert to uppercase: Convertir a mayúsculas -</i18n> \ No newline at end of file +</i18n> diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index a8888aad8..1f4705faa 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -42,7 +42,7 @@ const formattedDate = computed({ if (value.at(2) == '/') value = value.split('/').reverse().join('/'); value = date.formatDate( new Date(value).toISOString(), - 'YYYY-MM-DDTHH:mm:ss.SSSZ' + 'YYYY-MM-DDTHH:mm:ss.SSSZ', ); } const [year, month, day] = value.split('-').map((e) => parseInt(e)); @@ -55,7 +55,7 @@ const formattedDate = computed({ orgDate.getHours(), orgDate.getMinutes(), orgDate.getSeconds(), - orgDate.getMilliseconds() + orgDate.getMilliseconds(), ); } } @@ -64,7 +64,7 @@ const formattedDate = computed({ }); const popupDate = computed(() => - model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value + model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value, ); onMounted(() => { // fix quasar bug @@ -73,7 +73,7 @@ onMounted(() => { watch( () => model.value, (val) => (formattedDate.value = val), - { immediate: true } + { immediate: true }, ); const styleAttrs = computed(() => { @@ -107,6 +107,7 @@ const manageDate = (date) => { @click="isPopupOpen = !isPopupOpen" @keydown="isPopupOpen = false" hide-bottom-space + :data-cy="$attrs.dataCy ?? $attrs.label + '_inputDate'" > <template #append> <QIcon diff --git a/src/components/common/VnInputNumber.vue b/src/components/common/VnInputNumber.vue index 165cfae3d..274f78b21 100644 --- a/src/components/common/VnInputNumber.vue +++ b/src/components/common/VnInputNumber.vue @@ -8,6 +8,7 @@ defineProps({ }); const model = defineModel({ type: [Number, String] }); +const emit = defineEmits(['blur']); </script> <template> <VnInput @@ -24,5 +25,6 @@ const model = defineModel({ type: [Number, String] }); model = parseFloat(val).toFixed(decimalPlaces); } " + @blur="emit('blur')" /> </template> diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index 3ede24274..5028e876d 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -85,6 +85,7 @@ const handleModelValue = (data) => { :tooltip="t('Create new location')" :rules="mixinRules" :lazy-rules="true" + required > <template #form> <CreateNewPostcode diff --git a/src/components/common/VnPopupProxy.vue b/src/components/common/VnPopupProxy.vue new file mode 100644 index 000000000..f386bfff8 --- /dev/null +++ b/src/components/common/VnPopupProxy.vue @@ -0,0 +1,38 @@ +<script setup> +import { ref } from 'vue'; + +defineProps({ + label: { + type: String, + default: '', + }, + icon: { + type: String, + required: true, + default: null, + }, + color: { + type: String, + default: 'primary', + }, + tooltip: { + type: String, + default: null, + }, +}); +const popupProxyRef = ref(null); +</script> + +<template> + <QBtn :color="$props.color" :icon="$props.icon" :label="$t($props.label)"> + <template #default> + <slot name="extraIcon"></slot> + <QPopupProxy ref="popupProxyRef" style="max-width: none"> + <QCard> + <slot :popup="popupProxyRef"></slot> + </QCard> + </QPopupProxy> + <QTooltip>{{ $t($props.tooltip) }}</QTooltip> + </template> + </QBtn> +</template> diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index c850f2e53..339f90e0e 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -171,7 +171,8 @@ onMounted(() => { }); const arrayDataKey = - $props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label); + $props.dataKey ?? + ($props.url?.length > 0 ? $props.url : ($attrs.name ?? $attrs.label)); const arrayData = useArrayData(arrayDataKey, { url: $props.url, @@ -220,7 +221,7 @@ async function fetchFilter(val) { optionFilterValue.value ?? (new RegExp(/\d/g).test(val) ? optionValue.value - : optionFilter.value ?? optionLabel.value); + : (optionFilter.value ?? optionLabel.value)); let defaultWhere = {}; if ($props.filterOptions.length) { @@ -239,7 +240,7 @@ async function fetchFilter(val) { const { data } = await arrayData.applyFilter( { filter: filterOptions }, - { updateRouter: false } + { updateRouter: false }, ); setOptions(data); return data; @@ -272,7 +273,7 @@ async function filterHandler(val, update) { ref.setOptionIndex(-1); ref.moveOptionSelection(1, true); } - } + }, ); } @@ -308,7 +309,7 @@ function handleKeyDown(event) { if (inputValue) { const matchingOption = myOptions.value.find( (option) => - option[optionLabel.value].toLowerCase() === inputValue.toLowerCase() + option[optionLabel.value].toLowerCase() === inputValue.toLowerCase(), ); if (matchingOption) { @@ -320,11 +321,11 @@ function handleKeyDown(event) { } const focusableElements = document.querySelectorAll( - 'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])' + 'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])', ); const currentIndex = Array.prototype.indexOf.call( focusableElements, - event.target + event.target, ); if (currentIndex >= 0 && currentIndex < focusableElements.length - 1) { focusableElements[currentIndex + 1].focus(); diff --git a/src/components/common/VnSelectCache.vue b/src/components/common/VnSelectCache.vue index 29cf22dc5..f0f3357f6 100644 --- a/src/components/common/VnSelectCache.vue +++ b/src/components/common/VnSelectCache.vue @@ -14,7 +14,7 @@ const $props = defineProps({ }, }); const options = ref([]); - +const emit = defineEmits(['blur']); onBeforeMount(async () => { const { url, optionValue, optionLabel } = useAttrs(); const findBy = $props.find ?? url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1); @@ -35,5 +35,5 @@ onBeforeMount(async () => { }); </script> <template> - <VnSelect v-bind="$attrs" :options="$attrs.options ?? options" /> + <VnSelect v-bind="$attrs" :options="$attrs.options ?? options" @blur="emit('blur')" /> </template> diff --git a/src/components/common/VnSelectDialog.vue b/src/components/common/VnSelectDialog.vue index a4cd0011d..41730b217 100644 --- a/src/components/common/VnSelectDialog.vue +++ b/src/components/common/VnSelectDialog.vue @@ -37,7 +37,6 @@ const isAllowedToCreate = computed(() => { defineExpose({ vnSelectDialogRef: select }); </script> - <template> <VnSelect ref="select" @@ -67,7 +66,6 @@ defineExpose({ vnSelectDialogRef: select }); </template> </VnSelect> </template> - <style lang="scss" scoped> .default-icon { cursor: pointer; diff --git a/src/components/common/VnSelectSupplier.vue b/src/components/common/VnSelectSupplier.vue index f86db4f2d..5b52ae75b 100644 --- a/src/components/common/VnSelectSupplier.vue +++ b/src/components/common/VnSelectSupplier.vue @@ -1,9 +1,7 @@ <script setup> -import { computed } from 'vue'; import VnSelect from 'components/common/VnSelect.vue'; const model = defineModel({ type: [String, Number, Object] }); -const url = 'Suppliers'; </script> <template> @@ -11,11 +9,13 @@ const url = 'Suppliers'; :label="$t('globals.supplier')" v-bind="$attrs" v-model="model" - :url="url" + url="Suppliers" option-value="id" option-label="nickname" :fields="['id', 'name', 'nickname', 'nif']" + :filter-options="['id', 'name', 'nickname', 'nif']" sort-by="name ASC" + data-cy="vnSupplierSelect" > <template #option="scope"> <QItem v-bind="scope.itemProps"> diff --git a/src/components/common/VnSelectTravelExtended.vue b/src/components/common/VnSelectTravelExtended.vue new file mode 100644 index 000000000..46538f5f9 --- /dev/null +++ b/src/components/common/VnSelectTravelExtended.vue @@ -0,0 +1,50 @@ +<script setup> +import VnSelectDialog from './VnSelectDialog.vue'; +import FilterTravelForm from 'src/components/FilterTravelForm.vue'; +import { useI18n } from 'vue-i18n'; +import { toDate } from 'src/filters'; +const { t } = useI18n(); + +const $props = defineProps({ + data: { + type: Object, + required: true, + }, + onFilterTravelSelected: { + type: Function, + required: true, + }, +}); +</script> +<template> + <VnSelectDialog + :label="t('entry.basicData.travel')" + v-bind="$attrs" + url="Travels/filter" + :fields="['id', 'warehouseInName']" + option-value="id" + option-label="warehouseInName" + map-options + hide-selected + :required="true" + action-icon="filter_alt" + :roles-allowed-to-create="['buyer']" + > + <template #form> + <FilterTravelForm @travel-selected="onFilterTravelSelected(data, $event)" /> + </template> + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt?.agencyModeName }} - + {{ scope.opt?.warehouseInName }} + ({{ toDate(scope.opt?.shipped) }}) → + {{ scope.opt?.warehouseOutName }} + ({{ toDate(scope.opt?.landed) }}) + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelectDialog> +</template> diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 275d919d6..6f122ecd2 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -6,6 +6,7 @@ import { useArrayData } from 'composables/useArrayData'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useState } from 'src/composables/useState'; import { useRoute } from 'vue-router'; +import { useClipboard } from 'src/composables/useClipboard'; import VnMoreOptions from './VnMoreOptions.vue'; const $props = defineProps({ @@ -29,10 +30,6 @@ const $props = defineProps({ type: String, default: null, }, - module: { - type: String, - default: null, - }, summary: { type: Object, default: null, @@ -46,6 +43,7 @@ const $props = defineProps({ const state = useState(); const route = useRoute(); const { t } = useI18n(); +const { copyText } = useClipboard(); const { viewSummary } = useSummaryDialog(); let arrayData; let store; @@ -57,7 +55,7 @@ defineExpose({ getData }); onBeforeMount(async () => { arrayData = useArrayData($props.dataKey, { url: $props.url, - filter: $props.filter, + userFilter: $props.filter, skip: 0, oneRecord: true, }); @@ -103,6 +101,14 @@ function getValueFromPath(path) { return current; } +function copyIdText(id) { + copyText(id, { + component: { + copyValue: id, + }, + }); +} + const emit = defineEmits(['onFetch']); const iconModule = computed(() => route.matched[1].meta.icon); @@ -148,7 +154,9 @@ const toModule = computed(() => {{ t('components.smartCard.openSummary') }} </QTooltip> </QBtn> - <RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }"> + <RouterLink + :to="{ name: `${dataKey}Summary`, params: { id: entity.id } }" + > <QBtn class="link" color="white" @@ -184,10 +192,25 @@ const toModule = computed(() => </slot> </div> </QItemLabel> - <QItem dense> - <QItemLabel class="subtitle" caption> + <QItem> + <QItemLabel class="subtitle"> #{{ getValueFromPath(subtitle) ?? entity.id }} </QItemLabel> + + <QBtn + round + flat + dense + size="sm" + icon="content_copy" + color="primary" + @click.stop="copyIdText(entity.id)" + > + <QTooltip> + {{ t('globals.copyId') }} + </QTooltip> + </QBtn> + <!-- </QItemLabel> --> </QItem> </QList> <div class="list-box q-mt-xs"> @@ -294,3 +317,11 @@ const toModule = computed(() => } } </style> +<i18n> + en: + globals: + copyId: Copy ID + es: + globals: + copyId: Copiar ID +</i18n> diff --git a/src/components/ui/SkeletonDescriptor.vue b/src/components/ui/SkeletonDescriptor.vue index 9679751f5..f9188221a 100644 --- a/src/components/ui/SkeletonDescriptor.vue +++ b/src/components/ui/SkeletonDescriptor.vue @@ -1,53 +1,32 @@ +<script setup> +defineProps({ + hasImage: { + type: Boolean, + default: false, + }, +}); +</script> <template> - <div id="descriptor-skeleton"> + <div id="descriptor-skeleton" class="bg-vn-page"> <div class="row justify-between q-pa-sm"> - <QSkeleton square size="40px" /> - <QSkeleton square size="40px" /> - <QSkeleton square height="40px" width="20px" /> + <QSkeleton square size="30px" v-for="i in 3" :key="i" /> </div> - <div class="col justify-between q-pa-sm q-gutter-y-xs"> - <QSkeleton square height="40px" width="150px" /> - <QSkeleton square height="30px" width="70px" /> + <div class="q-pa-xs" v-if="hasImage"> + <QSkeleton square height="200px" width="100%" /> </div> - <div class="col q-pl-sm q-pa-sm q-mb-md"> - <div class="row justify-between"> - <QSkeleton type="text" square height="30px" width="20%" /> - <QSkeleton type="text" square height="30px" width="60%" /> - </div> - <div class="row justify-between"> - <QSkeleton type="text" square height="30px" width="20%" /> - <QSkeleton type="text" square height="30px" width="60%" /> - </div> - <div class="row justify-between"> - <QSkeleton type="text" square height="30px" width="20%" /> - <QSkeleton type="text" square height="30px" width="60%" /> - </div> - <div class="row justify-between"> - <QSkeleton type="text" square height="30px" width="20%" /> - <QSkeleton type="text" square height="30px" width="60%" /> - </div> - <div class="row justify-between"> - <QSkeleton type="text" square height="30px" width="20%" /> - <QSkeleton type="text" square height="30px" width="60%" /> - </div> - <div class="row justify-between"> - <QSkeleton type="text" square height="30px" width="20%" /> - <QSkeleton type="text" square height="30px" width="60%" /> + <div class="col justify-between q-pa-md q-gutter-y-xs"> + <QSkeleton square height="25px" width="150px" /> + <QSkeleton square height="15px" width="70px" /> + </div> + <div class="q-pl-sm q-pa-sm q-mb-md"> + <div class="row q-gutter-x-sm q-pa-none q-ma-none" v-for="i in 5" :key="i"> + <QSkeleton type="text" square height="20px" width="30%" /> + <QSkeleton type="text" square height="20px" width="60%" /> </div> </div> - <QCardActions> - <QSkeleton size="40px" /> - <QSkeleton size="40px" /> - <QSkeleton size="40px" /> - <QSkeleton size="40px" /> - <QSkeleton size="40px" /> + <QCardActions class="q-gutter-x-sm justify-between"> + <QSkeleton size="40px" v-for="i in 5" :key="i" /> </QCardActions> </div> </template> - -<style lang="scss" scoped> -#descriptor-skeleton .q-card__actions { - justify-content: space-between; -} -</style> diff --git a/src/components/ui/VnConfirm.vue b/src/components/ui/VnConfirm.vue index a02b56bdb..c6f539879 100644 --- a/src/components/ui/VnConfirm.vue +++ b/src/components/ui/VnConfirm.vue @@ -82,7 +82,7 @@ function cancel() { @click="cancel()" /> </QCardSection> - <QCardSection class="q-pb-none"> + <QCardSection class="q-pb-none" data-cy="VnConfirm_message"> <span v-if="message !== false" v-html="message" /> </QCardSection> <QCardSection class="row items-center q-pt-none"> @@ -95,6 +95,7 @@ function cancel() { :disable="isLoading" flat @click="cancel()" + data-cy="VnConfirm_cancel" /> <QBtn :label="t('globals.confirm')" diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 80128018a..d6b525dc8 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -293,6 +293,9 @@ const getLocale = (label) => { /> </template> <style scoped lang="scss"> +.q-field__label.no-pointer-events.absolute.ellipsis { + margin-left: 6px !important; +} .list { width: 256px; } diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue index 39e84be2b..8a1c7a0f2 100644 --- a/src/components/ui/VnMoreOptions.vue +++ b/src/components/ui/VnMoreOptions.vue @@ -11,7 +11,7 @@ <QTooltip> {{ $t('components.cardDescriptor.moreOptions') }} </QTooltip> - <QMenu ref="menuRef"> + <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> <QList> <slot name="menu" :menu-ref="$refs.menuRef" /> </QList> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index 5b1d6e726..ec6289a67 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -18,7 +18,12 @@ import VnInput from 'components/common/VnInput.vue'; const emit = defineEmits(['onFetch']); -const $attrs = useAttrs(); +const originalAttrs = useAttrs(); + +const $attrs = computed(() => { + const { style, ...rest } = originalAttrs; + return rest; +}); const isRequired = computed(() => { return Object.keys($attrs).includes('required') diff --git a/src/components/ui/VnStockValueDisplay.vue b/src/components/ui/VnStockValueDisplay.vue new file mode 100644 index 000000000..d8f43323b --- /dev/null +++ b/src/components/ui/VnStockValueDisplay.vue @@ -0,0 +1,41 @@ +<script setup> +import { toPercentage } from 'filters/index'; + +import { computed } from 'vue'; + +const props = defineProps({ + value: { + type: Number, + required: true, + }, +}); + +const valueClass = computed(() => + props.value === 0 ? 'neutral' : props.value > 0 ? 'positive' : 'negative', +); +const iconName = computed(() => + props.value === 0 ? 'equal' : props.value > 0 ? 'arrow_upward' : 'arrow_downward', +); +const formattedValue = computed(() => props.value); +</script> +<template> + <span :class="valueClass"> + <QIcon :name="iconName" size="sm" class="value-icon" /> + {{ toPercentage(formattedValue) }} + </span> +</template> + +<style lang="scss" scoped> +.positive { + color: $secondary; +} +.negative { + color: $negative; +} +.neutral { + color: $primary; +} +.value-icon { + margin-right: 4px; +} +</style> diff --git a/src/components/ui/VnUsesMana.vue b/src/components/ui/VnUsesMana.vue index 1ad4a706e..cb066b235 100644 --- a/src/components/ui/VnUsesMana.vue +++ b/src/components/ui/VnUsesMana.vue @@ -53,3 +53,8 @@ const manaCode = ref(props.manaCode); /> </div> </template> +<i18n> + es: + Promotion mana: Maná promoción + Claim mana: Maná reclamación +</i18n> diff --git a/src/composables/__tests__/useRequired.spec.js b/src/composables/__tests__/useRequired.spec.js new file mode 100644 index 000000000..e035a80c6 --- /dev/null +++ b/src/composables/__tests__/useRequired.spec.js @@ -0,0 +1,66 @@ +import { describe, it, expect, vi } from 'vitest'; +import { useRequired } from '../useRequired'; + +vi.mock('../useValidator', () => ({ + useValidator: () => ({ + validations: () => ({ + required: vi.fn((isRequired, val) => { + if (!isRequired) return true; + return val !== null && val !== undefined && val !== ''; + }), + }), + }), +})); + +describe('useRequired', () => { + it('should detect required when attr is boolean true', () => { + const attrs = { required: true }; + const { isRequired } = useRequired(attrs); + expect(isRequired).toBe(true); + }); + + it('should detect required when attr is boolean false', () => { + const attrs = { required: false }; + const { isRequired } = useRequired(attrs); + expect(isRequired).toBe(false); + }); + + it('should detect required when attr exists without value', () => { + const attrs = { required: '' }; + const { isRequired } = useRequired(attrs); + expect(isRequired).toBe(true); + }); + + it('should return false when required attr does not exist', () => { + const attrs = { someOtherAttr: 'value' }; + const { isRequired } = useRequired(attrs); + expect(isRequired).toBe(false); + }); + + describe('requiredFieldRule', () => { + it('should validate required field with value', () => { + const attrs = { required: true }; + const { requiredFieldRule } = useRequired(attrs); + expect(requiredFieldRule('some value')).toBe(true); + }); + + it('should validate required field with empty value', () => { + const attrs = { required: true }; + const { requiredFieldRule } = useRequired(attrs); + expect(requiredFieldRule('')).toBe(false); + }); + + it('should pass validation when field is not required', () => { + const attrs = { required: false }; + const { requiredFieldRule } = useRequired(attrs); + expect(requiredFieldRule('')).toBe(true); + }); + + it('should handle null and undefined values', () => { + const attrs = { required: true }; + const { requiredFieldRule } = useRequired(attrs); + expect(requiredFieldRule(null)).toBe(false); + expect(requiredFieldRule(undefined)).toBe(false); + }); + }); +}); diff --git a/src/composables/checkEntryLock.js b/src/composables/checkEntryLock.js new file mode 100644 index 000000000..f964dea27 --- /dev/null +++ b/src/composables/checkEntryLock.js @@ -0,0 +1,65 @@ +import { useQuasar } from 'quasar'; +import { useI18n } from 'vue-i18n'; +import { useRouter } from 'vue-router'; +import axios from 'axios'; +import VnConfirm from 'components/ui/VnConfirm.vue'; + +export async function checkEntryLock(entryFk, userFk) { + const { t } = useI18n(); + const quasar = useQuasar(); + const { push } = useRouter(); + const { data } = await axios.get(`Entries/${entryFk}`, { + params: { + filter: JSON.stringify({ + fields: ['id', 'locked', 'lockerUserFk'], + include: { relation: 'user', scope: { fields: ['id', 'nickname'] } }, + }), + }, + }); + const entryConfig = await axios.get('EntryConfigs/findOne'); + + if (data?.lockerUserFk && data?.locked) { + const now = new Date(Date.vnNow()).getTime(); + const lockedTime = new Date(data.locked).getTime(); + const timeDiff = (now - lockedTime) / 1000; + const isMaxTimeLockExceeded = entryConfig.data.maxLockTime > timeDiff; + + if (data?.lockerUserFk !== userFk && isMaxTimeLockExceeded) { + quasar + .dialog({ + component: VnConfirm, + componentProps: { + 'data-cy': 'entry-lock-confirm', + title: t('entry.lock.title'), + message: t('entry.lock.message', { + userName: data?.user?.nickname, + time: timeDiff / 60, + }), + }, + }) + .onOk( + async () => + await axios.patch(`Entries/${entryFk}`, { + locked: Date.vnNow(), + lockerUserFk: userFk, + }), + ) + .onCancel(() => { + push({ path: `summary` }); + }); + } + } else { + await axios + .patch(`Entries/${entryFk}`, { + locked: Date.vnNow(), + lockerUserFk: userFk, + }) + .then( + quasar.notify({ + message: t('entry.lock.success'), + color: 'positive', + group: false, + }), + ); + } +} diff --git a/src/composables/getColAlign.js b/src/composables/getColAlign.js new file mode 100644 index 000000000..a930fd7d8 --- /dev/null +++ b/src/composables/getColAlign.js @@ -0,0 +1,22 @@ +export function getColAlign(col) { + let align; + switch (col.component) { + case 'time': + case 'date': + case 'select': + align = 'left'; + break; + case 'number': + align = 'right'; + break; + case 'checkbox': + align = 'center'; + break; + default: + align = col?.align; + } + + if (/^is[A-Z]/.test(col.name) || /^has[A-Z]/.test(col.name)) align = 'center'; + + return 'text-' + (align ?? 'center'); +} diff --git a/src/composables/useCau.js b/src/composables/useCau.js index 29319bd9a..43bfc5180 100644 --- a/src/composables/useCau.js +++ b/src/composables/useCau.js @@ -11,6 +11,7 @@ export async function useCau(res, message) { const { config, headers, request, status, statusText, data } = res || {}; const { params, url, method, signal, headers: confHeaders } = config || {}; const { message: resMessage, code, name } = data?.error || {}; + delete confHeaders?.Authorization; const additionalData = { path: location.hash, @@ -40,7 +41,7 @@ export async function useCau(res, message) { handler: async () => { const locale = i18n.global.t; const reason = ref( - code == 'ACCESS_DENIED' ? locale('cau.askPrivileges') : '' + code == 'ACCESS_DENIED' ? locale('cau.askPrivileges') : '', ); openConfirmationModal( locale('cau.title'), @@ -59,10 +60,9 @@ export async function useCau(res, message) { 'onUpdate:modelValue': (val) => (reason.value = val), label: locale('cau.inputLabel'), class: 'full-width', - required: true, autofocus: true, }, - } + }, ); }, }, diff --git a/src/composables/useRequired.js b/src/composables/useRequired.js index d211b96b4..4e84b9e48 100644 --- a/src/composables/useRequired.js +++ b/src/composables/useRequired.js @@ -2,14 +2,10 @@ import { useValidator } from 'src/composables/useValidator'; export function useRequired($attrs) { const { validations } = useValidator(); - const hasRequired = Object.keys($attrs).includes('required'); - let isRequired = false; - if (hasRequired) { - const required = $attrs['required']; - if (typeof required === 'boolean') { - isRequired = required; - } - } + const isRequired = + typeof $attrs['required'] === 'boolean' + ? $attrs['required'] + : Object.keys($attrs).includes('required'); const requiredFieldRule = (val) => validations().required(isRequired, val); return { diff --git a/src/composables/useRole.js b/src/composables/useRole.js index 3ec65dd0a..ff54b409c 100644 --- a/src/composables/useRole.js +++ b/src/composables/useRole.js @@ -27,6 +27,15 @@ export function useRole() { return false; } + function likeAny(roles) { + const roleStore = state.getRoles(); + for (const role of roles) { + if (!roleStore.value.findIndex((rs) => rs.startsWith(role)) !== -1) + return true; + } + + return false; + } function isEmployee() { return hasAny(['employee']); } @@ -35,6 +44,7 @@ export function useRole() { isEmployee, fetch, hasAny, + likeAny, state, }; } diff --git a/src/css/app.scss b/src/css/app.scss index 59e945f05..994ae7ff1 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -21,7 +21,10 @@ body.body--light { .q-header .q-toolbar { color: var(--vn-text-color); } + + --vn-color-negative: $negative; } + body.body--dark { --vn-header-color: #5d5d5d; --vn-page-color: #222; @@ -37,6 +40,8 @@ body.body--dark { --vn-text-color-contrast: black; background-color: var(--vn-page-color); + + --vn-color-negative: $negative; } a { @@ -75,7 +80,6 @@ a { text-decoration: underline; } -// Removes chrome autofill background input:-webkit-autofill, select:-webkit-autofill { color: var(--vn-text-color); @@ -149,11 +153,6 @@ select:-webkit-autofill { cursor: pointer; } -.vn-table-separation-row { - height: 16px !important; - background-color: var(--vn-section-color) !important; -} - /* Estilo para el asterisco en campos requeridos */ .q-field.required .q-field__label:after { content: ' *'; @@ -230,10 +229,12 @@ input::-webkit-inner-spin-button { max-width: 100%; } -.q-table__container { - /* ===== Scrollbar CSS ===== / - / Firefox */ +.remove-bg { + filter: brightness(1.1); + mix-blend-mode: multiply; +} +.q-table__container { * { scrollbar-width: auto; scrollbar-color: var(--vn-label-color) transparent; @@ -274,8 +275,6 @@ input::-webkit-inner-spin-button { font-size: 11pt; } td { - font-size: 11pt; - border-top: 1px solid var(--vn-page-color); border-collapse: collapse; } } @@ -319,9 +318,6 @@ input::-webkit-inner-spin-button { max-width: fit-content; } -.row > .column:has(.q-checkbox) { - max-width: fit-content; -} .q-field__inner { .q-field__control { min-height: auto !important; @@ -339,3 +335,7 @@ input::-webkit-inner-spin-button { border: 1px solid; box-shadow: 0 4px 6px #00000000; } + +.containerShrinked { + width: 80%; +} diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index d6e992437..22c6d2b56 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -13,7 +13,7 @@ // Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: to add new colors https://quasar.dev/style/color-palette/#adding-your-own-colors $primary: #ec8916; -$secondary: $primary; +$secondary: #89be34; $positive: #c8e484; $negative: #fb5252; $info: #84d0e2; @@ -30,7 +30,9 @@ $color-spacer: #7979794d; $border-thin-light: 1px solid $color-spacer-light; $primary-light: #f5b351; $dark-shadow-color: black; -$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d; +$layout-shadow-dark: + 0 0 10px 2px #00000033, + 0 0px 10px #0000003d; $spacing-md: 16px; $color-font-secondary: #777; $width-xs: 400px; diff --git a/src/filters/toDate.js b/src/filters/toDate.js index 8fe8f3836..002797af5 100644 --- a/src/filters/toDate.js +++ b/src/filters/toDate.js @@ -3,6 +3,8 @@ import { useI18n } from 'vue-i18n'; export default function (value, options = {}) { if (!value) return; + if (!isValidDate(value)) return null; + if (!options.dateStyle && !options.timeStyle) { options.day = '2-digit'; options.month = '2-digit'; @@ -10,7 +12,12 @@ export default function (value, options = {}) { } const { locale } = useI18n(); - const date = new Date(value); + const newDate = new Date(value); - return new Intl.DateTimeFormat(locale.value, options).format(date); + return new Intl.DateTimeFormat(locale.value, options).format(newDate); +} +// handle 0000-00-00 +function isValidDate(date) { + const parsedDate = new Date(date); + return parsedDate instanceof Date && !isNaN(parsedDate.getTime()); } diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index d615eef4c..9a60e9da1 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -33,6 +33,7 @@ globals: reset: Reset close: Close cancel: Cancel + isSaveAndContinue: Save and continue clone: Clone confirm: Confirm assign: Assign @@ -48,6 +49,7 @@ globals: rowRemoved: Row removed pleaseWait: Please wait... noPinnedModules: You don't have any pinned modules + enterToConfirm: Press Enter to confirm summary: basicData: Basic data daysOnward: Days onward @@ -155,6 +157,7 @@ globals: changeState: Change state raid: 'Raid {daysInForward} days' isVies: Vies + noData: No data available pageTitles: logIn: Login addressEdit: Update address @@ -167,6 +170,7 @@ globals: workCenters: Work centers modes: Modes zones: Zones + negative: Negative zonesList: List deliveryDays: Delivery days upcomingDeliveries: Upcoming deliveries @@ -174,6 +178,7 @@ globals: alias: Alias aliasUsers: Users subRoles: Subroles + myAccount: Mi cuenta inheritedRoles: Inherited Roles customers: Customers customerCreate: New customer @@ -406,6 +411,106 @@ cau: subtitle: By sending this ticket, all the data related to the error, the section, the user, etc., are already sent. inputLabel: Explain why this error should not appear askPrivileges: Ask for privileges +entry: + list: + newEntry: New entry + tableVisibleColumns: + isExcludedFromAvailable: Exclude from inventory + isOrdered: Ordered + isConfirmed: Ready to label + isReceived: Received + isRaid: Raid + landed: Date + supplierFk: Supplier + reference: Ref/Alb/Guide + invoiceNumber: Invoice + agencyModeId: Agency + isBooked: Booked + companyFk: Company + evaNotes: Notes + warehouseOutFk: Origin + warehouseInFk: Destiny + entryTypeDescription: Entry type + invoiceAmount: Import + travelFk: Travel + summary: + invoiceAmount: Amount + commission: Commission + currency: Currency + invoiceNumber: Invoice number + ordered: Ordered + booked: Booked + excludedFromAvailable: Inventory + travelReference: Reference + travelAgency: Agency + travelShipped: Shipped + travelDelivered: Delivered + travelLanded: Landed + travelReceived: Received + buys: Buys + stickers: Stickers + package: Package + packing: Pack. + grouping: Group. + buyingValue: Buying value + import: Import + pvp: PVP + basicData: + travel: Travel + currency: Currency + commission: Commission + observation: Observation + booked: Booked + excludedFromAvailable: Inventory + buys: + observations: Observations + packagingFk: Box + color: Color + printedStickers: Printed stickers + notes: + observationType: Observation type + latestBuys: + tableVisibleColumns: + image: Picture + itemFk: Item ID + weightByPiece: Weight/Piece + isActive: Active + family: Family + entryFk: Entry + freightValue: Freight value + comissionValue: Commission value + packageValue: Package value + isIgnored: Is ignored + price2: Grouping + price3: Packing + minPrice: Min + ektFk: Ekt + packingOut: Package out + landing: Landing + isExcludedFromAvailable: Exclude from inventory + isRaid: Raid + invoiceNumber: Invoice + reference: Ref/Alb/Guide + params: + isExcludedFromAvailable: Excluir del inventario + isOrdered: Pedida + isConfirmed: Lista para etiquetar + isReceived: Recibida + isRaid: Redada + landed: Fecha + supplierFk: Proveedor + invoiceNumber: Nº Factura + reference: Ref/Alb/Guía + agencyModeId: Agencia + isBooked: Asentado + companyFk: Empresa + travelFk: Envio + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeDescription: Tipo entrada + invoiceAmount: Importe + dated: Fecha ticket: params: ticketFk: Ticket ID @@ -726,6 +831,8 @@ travel: CloneTravelAndEntries: Clone travel and his entries deleteTravel: Delete travel AddEntry: Add entry + availabled: Availabled + availabledHour: Availabled hour thermographs: Thermographs hb: HB basicData: diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index ea5fa9e41..846c442ea 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -33,9 +33,11 @@ globals: reset: Restaurar close: Cerrar cancel: Cancelar + isSaveAndContinue: Guardar y continuar clone: Clonar confirm: Confirmar assign: Asignar + replace: Sustituir back: Volver yes: Si no: No @@ -48,6 +50,8 @@ globals: rowRemoved: Fila eliminada pleaseWait: Por favor espera... noPinnedModules: No has fijado ningún módulo + split: Split + enterToConfirm: Pulsa Enter para confirmar summary: basicData: Datos básicos daysOnward: Días adelante @@ -55,8 +59,8 @@ globals: today: Hoy yesterday: Ayer dateFormat: es-ES - microsip: Abrir en MicroSIP noSelectedRows: No tienes ninguna línea seleccionada + microsip: Abrir en MicroSIP downloadCSVSuccess: Descarga de CSV exitosa reference: Referencia agency: Agencia @@ -76,8 +80,10 @@ globals: requiredField: Campo obligatorio class: clase type: Tipo - reason: motivo + reason: Motivo + removeSelection: Eliminar selección noResults: Sin resultados + results: resultados system: Sistema notificationSent: Notificación enviada warehouse: Almacén @@ -155,6 +161,7 @@ globals: changeState: Cambiar estado raid: 'Redada {daysInForward} días' isVies: Vies + noData: Datos no disponibles pageTitles: logIn: Inicio de sesión addressEdit: Modificar consignatario @@ -166,6 +173,7 @@ globals: agency: Agencia workCenters: Centros de trabajo modes: Modos + negative: Tickets negativos zones: Zonas zonesList: Listado deliveryDays: Días de entrega @@ -286,9 +294,9 @@ globals: buyRequest: Peticiones de compra wasteBreakdown: Deglose de mermas itemCreate: Nuevo artículo - tax: 'IVA' - botanical: 'Botánico' - barcode: 'Código de barras' + tax: IVA + botanical: Botánico + barcode: Código de barras itemTypeCreate: Nueva familia family: Familia lastEntries: Últimas entradas @@ -352,7 +360,7 @@ globals: from: Desde to: Hasta supplierFk: Proveedor - supplierRef: Ref. proveedor + supplierRef: Nº factura serial: Serie amount: Importe awbCode: AWB @@ -397,6 +405,87 @@ cau: subtitle: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc inputLabel: Explique el motivo por el que no deberia aparecer este fallo askPrivileges: Solicitar permisos +entry: + list: + newEntry: Nueva entrada + tableVisibleColumns: + isExcludedFromAvailable: Excluir del inventario + isOrdered: Pedida + isConfirmed: Lista para etiquetar + isReceived: Recibida + isRaid: Redada + landed: Fecha + supplierFk: Proveedor + invoiceNumber: Nº Factura + reference: Ref/Alb/Guía + agencyModeId: Agencia + isBooked: Asentado + companyFk: Empresa + travelFk: Envio + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeDescription: Tipo entrada + invoiceAmount: Importe + summary: + invoiceAmount: Importe + commission: Comisión + currency: Moneda + invoiceNumber: Núm. factura + ordered: Pedida + booked: Contabilizada + excludedFromAvailable: Inventario + travelReference: Referencia + travelAgency: Agencia + travelShipped: F. envio + travelWarehouseOut: Alm. salida + travelDelivered: Enviada + travelLanded: F. entrega + travelReceived: Recibida + buys: Compras + stickers: Etiquetas + package: Embalaje + packing: Pack. + grouping: Group. + buyingValue: Coste + import: Importe + pvp: PVP + basicData: + travel: Envío + currency: Moneda + observation: Observación + commission: Comisión + booked: Asentado + excludedFromAvailable: Inventario + buys: + observations: Observaciónes + packagingFk: Embalaje + color: Color + printedStickers: Etiquetas impresas + notes: + observationType: Tipo de observación + latestBuys: + tableVisibleColumns: + image: Foto + itemFk: Id Artículo + weightByPiece: Peso (gramos)/tallo + isActive: Activo + family: Familia + entryFk: Entrada + freightValue: Porte + comissionValue: Comisión + packageValue: Embalaje + isIgnored: Ignorado + price2: Grouping + price3: Packing + minPrice: Min + ektFk: Ekt + packingOut: Embalaje envíos + landing: Llegada + isExcludedFromAvailable: Excluir del inventario + isRaid: Redada + invoiceNumber: Nº Factura + reference: Ref/Alb/Guía ticket: params: ticketFk: ID de ticket @@ -410,6 +499,38 @@ ticket: freightItemName: Nombre packageItemName: Embalaje longName: Descripción + 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 + negative: Tickets negativos + 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: Bultos + list: + nickname: Alias + state: Estado + shipped: Enviado + landed: Entregado + salesPerson: Comercial + total: Total card: customerId: ID cliente customerCard: Ficha del cliente @@ -456,15 +577,11 @@ ticket: consigneeStreet: Dirección create: address: Dirección -order: - field: - salesPersonFk: Comercial - form: - clientFk: Cliente - addressFk: Dirección - agencyModeFk: Agencia - list: - newOrder: Nuevo Pedido +invoiceOut: + card: + issued: Fecha emisión + customerCard: Ficha del cliente + ticketList: Listado de tickets summary: issued: Fecha dued: Fecha límite @@ -475,6 +592,71 @@ order: fee: Cuota tickets: Tickets totalWithVat: Importe + globalInvoices: + errors: + chooseValidClient: Selecciona un cliente válido + chooseValidCompany: Selecciona una empresa válida + chooseValidPrinter: Selecciona una impresora válida + chooseValidSerialType: Selecciona una tipo de serie válida + fillDates: La fecha de la factura y la fecha máxima deben estar completas + invoiceDateLessThanMaxDate: La fecha de la factura no puede ser menor que la fecha máxima + invoiceWithFutureDate: Existe una factura con una fecha futura + noTicketsToInvoice: No existen tickets para facturar + criticalInvoiceError: Error crítico en la facturación proceso detenido + invalidSerialTypeForAll: El tipo de serie debe ser global cuando se facturan todos los clientes + table: + addressId: Id dirección + streetAddress: Dirección fiscal + statusCard: + percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}' + pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs' + negativeBases: + clientId: Id cliente + base: Base + active: Activo + hasToInvoice: Facturar + verifiedData: Datos comprobados + comercial: Comercial + errors: + downloadCsvFailed: Error al descargar CSV +order: + field: + salesPersonFk: Comercial + form: + clientFk: Cliente + addressFk: Dirección + agencyModeFk: Agencia + list: + newOrder: Nuevo Pedido + summary: + basket: Cesta + notConfirmed: No confirmada + created: Creado + createdFrom: Creado desde + address: Dirección + total: Total + vat: IVA + state: Estado + alias: Alias + items: Artículos + orderTicketList: Tickets del pedido + amount: Monto + confirm: Confirmar + confirmLines: Confirmar lineas +shelving: + list: + parking: Parking + priority: Prioridad + newShelving: Nuevo Carro + summary: + recyclable: Reciclable +parking: + pickingOrder: Orden de recogida + row: Fila + column: Columna + searchBar: + info: Puedes buscar por código de parking + label: Buscar parking... department: chat: Chat bossDepartment: Jefe de departamento @@ -635,8 +817,8 @@ wagon: volumeNotEmpty: El volumen no puede estar vacío typeNotEmpty: El tipo no puede estar vacío maxTrays: Has alcanzado el número máximo de bandejas - minHeightBetweenTrays: 'La distancia mínima entre bandejas es ' - maxWagonHeight: 'La altura máxima del vagón es ' + minHeightBetweenTrays: La distancia mínima entre bandejas es + maxWagonHeight: La altura máxima del vagón es uncompleteTrays: Hay bandejas sin completar params: label: Etiqueta @@ -653,12 +835,12 @@ supplier: tableVisibleColumns: nif: NIF/CIF account: Cuenta - summary: responsible: Responsable verified: Verificado isActive: Está activo billingData: Forma de pago + financialData: Datos financieros payDeadline: Plazo de pago payDay: Día de pago account: Cuenta @@ -736,6 +918,8 @@ travel: deleteTravel: Eliminar envío AddEntry: Añadir entrada thermographs: Termógrafos + availabled: F. Disponible + availabledHour: Hora Disponible hb: HB basicData: daysInForward: Desplazamiento automatico (redada) @@ -784,7 +968,7 @@ components: cardDescriptor: mainList: Listado principal summary: Resumen - moreOptions: 'Más opciones' + moreOptions: Más opciones leftMenu: addToPinned: Añadir a fijados removeFromPinned: Eliminar de fijados diff --git a/src/pages/Account/Alias/Card/AliasDescriptor.vue b/src/pages/Account/Alias/Card/AliasDescriptor.vue index a5793407e..671ef7fbc 100644 --- a/src/pages/Account/Alias/Card/AliasDescriptor.vue +++ b/src/pages/Account/Alias/Card/AliasDescriptor.vue @@ -51,7 +51,6 @@ const removeAlias = () => { <CardDescriptor ref="descriptor" :url="`MailAliases/${entityId}`" - module="Alias" data-key="Alias" title="alias" > diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index e354f694c..49328fe87 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -23,8 +23,7 @@ onMounted(async () => { <CardDescriptor ref="descriptor" :url="`VnUsers/preview`" - :filter="filter" - module="Account" + :filter="{ ...filter, where: { id: entityId } }" data-key="Account" title="nickname" > diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index ab16e07ff..30584c61f 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -12,6 +12,7 @@ import VnInputPassword from 'src/components/common/VnInputPassword.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import { useQuasar } from 'quasar'; import { useRouter } from 'vue-router'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const $props = defineProps({ hasAccount: { @@ -34,6 +35,12 @@ account.value.hasAccount = hasAccount.value; const entityId = computed(() => +route.params.id); const hasitManagementAccess = ref(); const hasSysadminAccess = ref(); +const isHimself = computed(() => user.value.id === account.value.id); +const url = computed(() => + isHimself.value + ? 'Accounts/change-password' + : `Accounts/${entityId.value}/setPassword` +); async function updateStatusAccount(active) { if (active) { @@ -106,11 +113,8 @@ onMounted(() => { :ask-old-pass="askOldPass" :submit-fn=" async (newPassword, oldPassword) => { - await axios.patch(`Accounts/change-password`, { - userId: entityId, - newPassword, - oldPassword, - }); + const body = isHimself ? { userId: entityId, oldPassword } : {}; + await axios.patch(url, { ...body, newPassword }); } " /> @@ -121,18 +125,14 @@ onMounted(() => { :promise="sync" > <template #customHTML> - {{ shouldSyncPassword }} - <QCheckbox - :label="t('account.card.actions.sync.checkbox')" + <VnCheckbox v-model="shouldSyncPassword" - class="full-width" + :label="t('account.card.actions.sync.checkbox')" + :info="t('account.card.actions.sync.tooltip')" clearable clear-icon="close" - > - <QIcon style="padding-left: 10px" color="primary" name="info" size="sm"> - <QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip> - </QIcon></QCheckbox - > + color="primary" + /> <VnInputPassword v-if="shouldSyncPassword" :label="t('login.password')" @@ -158,16 +158,10 @@ onMounted(() => { > <QItemSection>{{ t('globals.delete') }}</QItemSection> </QItem> - <QItem - v-if="hasSysadminAccess" - v-ripple - clickable - @click="user.id === account.id ? onChangePass(true) : onChangePass(false)" - > - <QItemSection v-if="user.id === account.id"> - {{ t('globals.changePass') }} + <QItem v-if="hasSysadminAccess || isHimself" v-ripple clickable> + <QItemSection @click="onChangePass(isHimself)"> + {{ isHimself ? t('globals.changePass') : t('globals.setPass') }} </QItemSection> - <QItemSection v-else>{{ t('globals.setPass') }}</QItemSection> </QItem> <QItem v-if="!account.hasAccount && hasSysadminAccess" diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index dfcc8efc8..517517af0 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -35,7 +35,6 @@ const removeRole = async () => { <CardDescriptor url="VnRoles" :filter="{ where: { id: entityId } }" - module="Role" data-key="Role" :summary="$props.summary" > diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index f55b0c48b..4551c58fe 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -46,7 +46,6 @@ onMounted(async () => { <CardDescriptor :url="`Claims/${entityId}`" :filter="filter" - module="Claim" title="client.name" data-key="Claim" > @@ -86,7 +85,7 @@ onMounted(async () => { /> </template> </VnLv> - <VnLv :label="t('claim.zone')"> + <VnLv v-if="entity.ticket?.zone?.id" :label="t('claim.zone')"> <template #value> <span class="link"> {{ entity.ticket?.zone?.name }} @@ -98,11 +97,10 @@ onMounted(async () => { :label="t('claim.province')" :value="entity.ticket?.address?.province?.name" /> - <VnLv :label="t('claim.ticketId')"> + <VnLv v-if="entity.ticketFk" :label="t('claim.ticketId')"> <template #value> <span class="link"> {{ entity.ticketFk }} - <TicketDescriptorProxy :id="entity.ticketFk" /> </span> </template> diff --git a/src/pages/Claim/Card/ClaimLines.vue b/src/pages/Claim/Card/ClaimLines.vue index 27d614049..dee03b95d 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -190,7 +190,7 @@ async function saveWhenHasChanges() { ref="claimLinesForm" :url="`Claims/${route.params.id}/lines`" save-url="ClaimBeginnings/crud" - :filter="linesFilter" + :user-filter="linesFilter" @on-fetch="onFetch" v-model:selected="selected" :default-save="false" diff --git a/src/pages/Claim/Card/ClaimNotes.vue b/src/pages/Claim/Card/ClaimNotes.vue index 134ee33ab..cc6e33779 100644 --- a/src/pages/Claim/Card/ClaimNotes.vue +++ b/src/pages/Claim/Card/ClaimNotes.vue @@ -1,5 +1,5 @@ <script setup> -import { computed } from 'vue'; +import { computed, useAttrs } from 'vue'; import { useRoute } from 'vue-router'; import { useState } from 'src/composables/useState'; import VnNotes from 'src/components/ui/VnNotes.vue'; @@ -7,6 +7,7 @@ import VnNotes from 'src/components/ui/VnNotes.vue'; const route = useRoute(); const state = useState(); const user = state.getUser(); +const $attrs = useAttrs(); const $props = defineProps({ id: { type: [Number, String], default: null }, diff --git a/src/pages/Claim/Card/ClaimPhoto.vue b/src/pages/Claim/Card/ClaimPhoto.vue index fb2f818c1..d4acc9bbe 100644 --- a/src/pages/Claim/Card/ClaimPhoto.vue +++ b/src/pages/Claim/Card/ClaimPhoto.vue @@ -156,7 +156,6 @@ function onDrag() { url="Claims" :filter="claimDmsFilter" @on-fetch="([data]) => setClaimDms(data)" - limit="20" auto-load ref="claimDmsRef" /> diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index b4dd4ee1b..6c941f59e 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -1,8 +1,6 @@ <script setup> -import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; @@ -14,15 +12,14 @@ const props = defineProps({ type: String, required: true, }, + states: { + type: Array, + default: () => [], + }, }); - -const states = ref([]); - -defineExpose({ states }); </script> <template> - <FetchData url="ClaimStates" @on-fetch="(data) => (states = data)" auto-load /> <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index ba74ba212..41d0c5598 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -10,12 +10,13 @@ import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnTable from 'src/components/VnTable/VnTable.vue'; import ZoneDescriptorProxy from '../Zone/Card/ZoneDescriptorProxy.vue'; import VnSection from 'src/components/common/VnSection.vue'; +import FetchData from 'src/components/FetchData.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const dataKey = 'ClaimList'; -const claimFilterRef = ref(); +const states = ref([]); const columns = computed(() => [ { align: 'left', @@ -81,8 +82,7 @@ const columns = computed(() => [ align: 'left', label: t('claim.state'), format: ({ stateCode }) => - claimFilterRef.value?.states.find(({ code }) => code === stateCode) - ?.description, + states.value?.find(({ code }) => code === stateCode)?.description, name: 'stateCode', chip: { condition: () => true, @@ -92,7 +92,7 @@ const columns = computed(() => [ name: 'claimStateFk', component: 'select', attrs: { - options: claimFilterRef.value?.states, + options: states.value, optionLabel: 'description', }, }, @@ -125,17 +125,18 @@ const STATE_COLOR = { </script> <template> + <FetchData url="ClaimStates" @on-fetch="(data) => (states = data)" auto-load /> <VnSection :data-key="dataKey" :columns="columns" prefix="claim" :array-data-props="{ url: 'Claims/filter', - order: ['cs.priority ASC', 'created ASC'], + order: 'cs.priority ASC, created ASC', }" > <template #advanced-menu> - <ClaimFilter data-key="ClaimList" ref="claimFilterRef" /> + <ClaimFilter :data-key ref="claimFilterRef" :states /> </template> <template #body> <VnTable diff --git a/src/pages/Customer/Card/CustomerAddress.vue b/src/pages/Customer/Card/CustomerAddress.vue index d8a1543cd..f1799d0cc 100644 --- a/src/pages/Customer/Card/CustomerAddress.vue +++ b/src/pages/Customer/Card/CustomerAddress.vue @@ -117,7 +117,7 @@ const toCustomerAddressEdit = (addressId) => { data-key="CustomerAddresses" order="id DESC" ref="vnPaginateRef" - :user-filter="addressFilter" + :filter="addressFilter" :url="`Clients/${route.params.id}/addresses`" /> <div class="full-width flex justify-center"> @@ -189,11 +189,11 @@ const toCustomerAddressEdit = (addressId) => { <QSeparator class="q-mx-lg" - v-if="item.observations.length" + v-if="item?.observations?.length" vertical /> - <div v-if="item.observations.length"> + <div v-if="item?.observations?.length"> <div :key="obIndex" class="flex q-mb-sm" diff --git a/src/pages/Customer/Card/CustomerBillingData.vue b/src/pages/Customer/Card/CustomerBillingData.vue index cfea8b070..cc894d01e 100644 --- a/src/pages/Customer/Card/CustomerBillingData.vue +++ b/src/pages/Customer/Card/CustomerBillingData.vue @@ -17,8 +17,7 @@ const bankEntitiesRef = ref(null); const filter = { fields: ['id', 'bic', 'name'], - order: 'bic ASC', - limit: 30, + order: 'bic ASC' }; const getBankEntities = (data, formData) => { diff --git a/src/pages/Customer/Card/CustomerConsumption.vue b/src/pages/Customer/Card/CustomerConsumption.vue index 50750cf12..f3949bb32 100644 --- a/src/pages/Customer/Card/CustomerConsumption.vue +++ b/src/pages/Customer/Card/CustomerConsumption.vue @@ -61,6 +61,23 @@ const columns = computed(() => [ columnFilter: false, cardVisible: true, }, + { + align: 'left', + name: 'buyerId', + label: t('customer.params.buyerId'), + component: 'select', + attrs: { + url: 'TicketRequests/getItemTypeWorker', + optionLabel: 'nickname', + optionValue: 'id', + + fields: ['id', 'nickname'], + sortBy: ['nickname ASC'], + optionFilter: 'firstName', + }, + cardVisible: false, + visible: false, + }, { name: 'description', align: 'left', @@ -74,6 +91,7 @@ const columns = computed(() => [ name: 'quantity', label: t('globals.quantity'), cardVisible: true, + visible: true, columnFilter: { inWhere: true, }, @@ -138,11 +156,11 @@ const updateDateParams = (value, params) => { const campaign = campaignList.value.find((c) => c.id === value); if (!campaign) return; - const { dated, previousDays, scopeDays } = campaign; - const _date = new Date(dated); - const [from, to] = dateRange(_date); - params.from = new Date(from.setDate(from.getDate() - previousDays)).toISOString(); - params.to = new Date(to.setDate(to.getDate() + scopeDays)).toISOString(); + const { dated, scopeDays } = campaign; + const from = new Date(dated); + from.setDate(from.getDate() - scopeDays); + params.from = from; + params.to = dated; return params; }; </script> @@ -200,29 +218,60 @@ const updateDateParams = (value, params) => { <div v-if="row.subName" class="subName"> {{ row.subName }} </div> - <FetchedTags :item="row" :max-length="3" /> + <FetchedTags :item="row" /> </template> <template #moreFilterPanel="{ params }"> <div class="column no-wrap flex-center q-gutter-y-md q-mt-xs q-pr-xl"> + <VnSelect + :filled="true" + class="q-px-sm q-pt-none fit" + url="ItemTypes" + v-model="params.typeId" + :label="t('item.list.typeName')" + :fields="['id', 'name', 'categoryFk']" + :include="'category'" + :sortBy="'name ASC'" + dense + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ scope.opt?.name }}</QItemLabel> + <QItemLabel caption>{{ + scope.opt?.category?.name + }}</QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + <VnSelect + :filled="true" + class="q-px-sm q-pt-none fit" + url="ItemCategories" + v-model="params.categoryId" + :label="t('item.list.category')" + :fields="['id', 'name']" + :sortBy="'name ASC'" + dense + /> <VnSelect v-model="params.campaign" :options="campaignList" :label="t('globals.campaign')" :filled="true" class="q-px-sm q-pt-none fit" - dense - option-label="code" + :option-label="(opt) => t(opt.code)" + :fields="['id', 'code', 'dated', 'scopeDays']" @update:model-value="(data) => updateDateParams(data, params)" + dense > <template #option="scope"> <QItem v-bind="scope.itemProps"> <QItemSection> - <QItemLabel> - {{ scope.opt?.code }} - {{ - new Date(scope.opt?.dated).getFullYear() - }}</QItemLabel - > + <QItemLabel> {{ t(scope.opt?.code) }} </QItemLabel> + <QItemLabel caption> + {{ new Date(scope.opt?.dated).getFullYear() }} + </QItemLabel> </QItemSection> </QItem> </template> @@ -247,7 +296,21 @@ const updateDateParams = (value, params) => { </template> <i18n> +en: + + valentinesDay: Valentine's Day + mothersDay: Mother's Day + allSaints: All Saints' Day + frenchMothersDay: Mother's Day in France es: Enter a new search: Introduce una nueva búsqueda Group by items: Agrupar por artículos + valentinesDay: Día de San Valentín + mothersDay: Día de la Madre + allSaints: Día de Todos los Santos + frenchMothersDay: (Francia) Día de la Madre + Campaign consumption: Consumo campaña + Campaign: Campaña + From: Desde + To: Hasta </i18n> diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index a646ad299..89f9d9449 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -55,7 +55,6 @@ const debtWarning = computed(() => { <template> <CardDescriptor - module="Customer" :url="`Clients/${entityId}/getCard`" :summary="$props.summary" data-key="Customer" @@ -110,7 +109,21 @@ const debtWarning = computed(() => { > <QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip> </QIcon> - <QIcon v-if="entity.isFreezed" name="vn:frozen" size="xs" color="primary"> + + <QIcon + v-if="entity?.substitutionAllowed" + name="help" + size="xs" + color="primary" + > + <QTooltip>{{ t('Allowed substitution') }}</QTooltip> + </QIcon> + <QIcon + v-if="customer?.isFreezed" + name="vn:frozen" + size="xs" + color="primary" + > <QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip> </QIcon> <QIcon diff --git a/src/pages/Customer/Card/CustomerDescriptorMenu.vue b/src/pages/Customer/Card/CustomerDescriptorMenu.vue index fb78eab69..aea45721c 100644 --- a/src/pages/Customer/Card/CustomerDescriptorMenu.vue +++ b/src/pages/Customer/Card/CustomerDescriptorMenu.vue @@ -61,6 +61,16 @@ const openCreateForm = (type) => { .join('&'); useOpenURL(`/#/${type}/list?${params}`); }; +const updateSubstitutionAllowed = async () => { + try { + await axios.patch(`Clients/${route.params.id}`, { + substitutionAllowed: !$props.customer.substitutionAllowed, + }); + notify('globals.notificationSent', 'positive'); + } catch (error) { + notify(error.message, 'positive'); + } +}; </script> <template> @@ -69,6 +79,13 @@ const openCreateForm = (type) => { {{ t('globals.pageTitles.createTicket') }} </QItemSection> </QItem> + <QItem v-ripple clickable> + <QItemSection @click="updateSubstitutionAllowed()">{{ + $props.customer.substitutionAllowed + ? t('Disable substitution') + : t('Allow substitution') + }}</QItemSection> + </QItem> <QItem v-ripple clickable> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> </QItem> diff --git a/src/pages/Customer/Card/CustomerFiscalData.vue b/src/pages/Customer/Card/CustomerFiscalData.vue index b256c001a..93909eb9c 100644 --- a/src/pages/Customer/Card/CustomerFiscalData.vue +++ b/src/pages/Customer/Card/CustomerFiscalData.vue @@ -2,16 +2,24 @@ import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; +import { useQuasar } from 'quasar'; +import axios from 'axios'; +import useNotify from 'src/composables/useNotify.js'; import FetchData from 'components/FetchData.vue'; import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnLocation from 'src/components/common/VnLocation.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; +import { getDifferences, getUpdatedValues } from 'src/filters'; +import VnConfirm from 'src/components/ui/VnConfirm.vue'; +const quasar = useQuasar(); const { t } = useI18n(); const route = useRoute(); +const { notify } = useNotify(); const typesTaxes = ref([]); const typesTransactions = ref([]); @@ -23,6 +31,37 @@ function handleLocation(data, location) { data.provinceFk = provinceFk; data.countryFk = countryFk; } +function onBeforeSave(formData, originalData) { + return getUpdatedValues( + Object.keys(getDifferences(formData, originalData)), + formData, + ); +} + +async function checkEtChanges(data, _, originalData) { + const equalizatedHasChanged = originalData.isEqualizated != data.isEqualizated; + const hasToInvoiceByAddress = + originalData.hasToInvoiceByAddress || data.hasToInvoiceByAddress; + if (equalizatedHasChanged && hasToInvoiceByAddress) { + quasar.dialog({ + component: VnConfirm, + componentProps: { + title: t('You changed the equalization tax'), + message: t('Do you want to spread the change?'), + promise: () => acceptPropagate(data), + }, + }); + } else if (equalizatedHasChanged) { + await acceptPropagate(data); + } +} + +async function acceptPropagate({ isEqualizated }) { + await axios.patch(`Clients/${route.params.id}/addressesPropagateRe`, { + isEqualizated, + }); + notify(t('Equivalent tax spreaded'), 'warning'); +} </script> <template> @@ -36,6 +75,9 @@ function handleLocation(data, location) { :url-update="`Clients/${route.params.id}/updateFiscalData`" auto-load model="Customer" + :mapper="onBeforeSave" + observe-form-changes + @on-data-saved="checkEtChanges" > <template #form="{ data, validate }"> <VnRow> @@ -110,14 +152,11 @@ function handleLocation(data, location) { </VnRow> <VnRow> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> - <div> - <QCheckbox :label="t('globals.isVies')" v-model="data.isVies" /> - <QIcon name="info" class="cursor-info q-ml-sm" size="sm"> - <QTooltip> - {{ t('whenActivatingIt') }} - </QTooltip> - </QIcon> - </div> + <VnCheckbox + v-model="data.isVies" + :label="t('globals.isVies')" + :info="t('whenActivatingIt')" + /> </VnRow> <VnRow> @@ -129,17 +168,11 @@ function handleLocation(data, location) { </VnRow> <VnRow> - <div> - <QCheckbox - :label="t('Is equalizated')" - v-model="data.isEqualizated" - /> - <QIcon class="cursor-info q-ml-sm" name="info" size="sm"> - <QTooltip> - {{ t('inOrderToInvoice') }} - </QTooltip> - </QIcon> - </div> + <VnCheckbox + v-model="data.isEqualizated" + :label="t('Is equalizated')" + :info="t('inOrderToInvoice')" + /> <QCheckbox :label="t('Daily invoice')" v-model="data.hasDailyInvoice" /> </VnRow> @@ -180,6 +213,9 @@ es: whenActivatingIt: Al activarlo, no informar el código del país en el campo nif inOrderToInvoice: Para facturar no se consulta este campo, sino el RE de consignatario. Al modificar este campo si no esta marcada la casilla Facturar por consignatario, se propagará automaticamente el cambio a todos lo consignatarios, en caso contrario preguntará al usuario si quiere o no propagar Daily invoice: Facturación diaria + Equivalent tax spreaded: Recargo de equivalencia propagado + You changed the equalization tax: Has cambiado el recargo de equivalencia + Do you want to spread the change?: ¿Deseas propagar el cambio a sus consignatarios? en: onlyLetters: Only letters, numbers and spaces can be used whenActivatingIt: When activating it, do not enter the country code in the ID field diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index d2eb125d7..324da0771 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -270,7 +270,7 @@ const sumRisk = ({ clientRisks }) => { <VnTitle target="_blank" :url="`${grafanaUrl}/d/40buzE4Vk/comportamiento-pagos-clientes?orgId=1&var-clientFk=${entityId}`" - :text="t('customer.summary.payMethodFk')" + :text="t('customer.summary.financialData')" icon="vn:grafana" /> <VnLv diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index eae97d1be..21de8fa9b 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -1,4 +1,3 @@ - <script setup> import { useI18n } from 'vue-i18n'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; @@ -52,11 +51,7 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput - :label="t('globals.name')" - v-model="params.name" - is-outlined - /> + <VnInput :label="t('Name')" v-model="params.name" is-outlined /> </QItemSection> </QItem> <QItem class="q-mb-sm"> diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 3c638b612..b8c1a743e 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -264,6 +264,7 @@ const columns = computed(() => [ align: 'left', name: 'isActive', label: t('customer.summary.isActive'), + component: 'checkbox', chip: { color: null, condition: (value) => !value, @@ -302,6 +303,7 @@ const columns = computed(() => [ align: 'left', name: 'isFreezed', label: t('customer.extendedList.tableVisibleColumns.isFreezed'), + component: 'checkbox', chip: { color: null, condition: (value) => value, @@ -419,7 +421,7 @@ function handleLocation(data, location) { <VnTable ref="tableRef" :data-key="dataKey" - url="Clients/filter" + url="Clients/extendedListFilter" :create="{ urlCreate: 'Clients/createWithUser', title: t('globals.pageTitles.customerCreate'), diff --git a/src/pages/Customer/Defaulter/CustomerDefaulter.vue b/src/pages/Customer/Defaulter/CustomerDefaulter.vue index eca2ad596..dc4ac9162 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulter.vue @@ -9,7 +9,7 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnInput from 'src/components/common/VnInput.vue'; import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue'; -import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import { useArrayData } from 'src/composables/useArrayData'; diff --git a/src/pages/Customer/Notifications/CustomerNotificationsCampaignConsumption.vue b/src/pages/Customer/Notifications/CustomerNotificationsCampaignConsumption.vue index 6952379ca..f637c7e0a 100644 --- a/src/pages/Customer/Notifications/CustomerNotificationsCampaignConsumption.vue +++ b/src/pages/Customer/Notifications/CustomerNotificationsCampaignConsumption.vue @@ -87,7 +87,7 @@ onMounted(async () => { <FetchData url="Campaigns/latest" @on-fetch="(data) => (campaignsOptions = data)" - :filter="{ fields: ['id', 'code', 'dated'], order: 'code ASC', limit: 30 }" + :filter="{ fields: ['id', 'code', 'dated'], order: 'code ASC' }" auto-load /> <FetchData diff --git a/src/pages/Customer/components/CustomerAddressCreate.vue b/src/pages/Customer/components/CustomerAddressCreate.vue index 32b4078db..e1be6b150 100644 --- a/src/pages/Customer/components/CustomerAddressCreate.vue +++ b/src/pages/Customer/components/CustomerAddressCreate.vue @@ -98,7 +98,6 @@ function onAgentCreated({ id, fiscalName }, data) { :rules="validate('Worker.postcode')" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]" v-model="data.location" - :required="true" @update:model-value="(location) => handleLocation(data, location)" /> diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index 5b36650f7..f852c160a 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -96,11 +96,11 @@ const updateObservations = async (payload) => { await axios.post('AddressObservations/crud', payload); notes.value = []; deletes.value = []; - toCustomerAddress(); }; async function updateAll({ data, payload }) { await updateObservations(payload); await updateAddress(data); + toCustomerAddress(); } function getPayload() { return { @@ -137,15 +137,12 @@ async function handleDialog(data) { .onOk(async () => { await updateAddressTicket(); await updateAll(body); - toCustomerAddress(); }) .onCancel(async () => { await updateAll(body); - toCustomerAddress(); }); } else { - updateAll(body); - toCustomerAddress(); + await updateAll(body); } } @@ -236,7 +233,7 @@ function handleLocation(data, location) { postcode: data.postalCode, city: data.city, province: data.province, - country: data.province.country, + country: data.province?.country, }" @update:model-value="(location) => handleLocation(data, location)" ></VnLocation> diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index c2c38b55a..8f61bac89 100644 --- a/src/pages/Customer/components/CustomerNewPayment.vue +++ b/src/pages/Customer/components/CustomerNewPayment.vue @@ -84,7 +84,7 @@ function setPaymentType(accounting) { viewReceipt.value = isCash.value; if (accountingType.value.daysInFuture) initialData.payed.setDate( - initialData.payed.getDate() + accountingType.value.daysInFuture + initialData.payed.getDate() + accountingType.value.daysInFuture, ); maxAmount.value = accountingType.value && accountingType.value.maxAmount; @@ -114,7 +114,7 @@ function onBeforeSave(data) { if (isCash.value && shouldSendEmail.value && !data.email) return notify(t('There is no assigned email for this client'), 'negative'); - data.bankFk = data.bankFk.id; + data.bankFk = data.bankFk?.id; return data; } @@ -189,7 +189,7 @@ async function getAmountPaid() { :url-create="urlCreate" :mapper="onBeforeSave" @on-data-saved="onDataSaved" - :prevent-submit="true" + prevent-submit > <template #form="{ data, validate }"> <span ref="closeButton" class="row justify-end close-icon" v-close-popup> diff --git a/src/pages/Customer/locale/en.yml b/src/pages/Customer/locale/en.yml index 118f04a31..b6d495335 100644 --- a/src/pages/Customer/locale/en.yml +++ b/src/pages/Customer/locale/en.yml @@ -107,6 +107,9 @@ customer: defaulterSinced: Defaulted Since hasRecovery: Has Recovery socialName: Social name + typeId: Type + buyerId: Buyer + categoryId: Category city: City phone: Phone postcode: Postcode diff --git a/src/pages/Customer/locale/es.yml b/src/pages/Customer/locale/es.yml index 7c33ffee8..f50d049da 100644 --- a/src/pages/Customer/locale/es.yml +++ b/src/pages/Customer/locale/es.yml @@ -108,6 +108,9 @@ customer: hasRecovery: Tiene recobro socialName: Razón social campaign: Campaña + typeId: Familia + buyerId: Comprador + categoryId: Reino city: Ciudad phone: Teléfono postcode: Código postal diff --git a/src/pages/Entry/Card/EntryBasicData.vue b/src/pages/Entry/Card/EntryBasicData.vue index 689eea686..6462ed24a 100644 --- a/src/pages/Entry/Card/EntryBasicData.vue +++ b/src/pages/Entry/Card/EntryBasicData.vue @@ -1,30 +1,32 @@ <script setup> -import { ref } from 'vue'; +import { onMounted, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useRole } from 'src/composables/useRole'; +import { useState } from 'src/composables/useState'; +import { checkEntryLock } from 'src/composables/checkEntryLock'; import FetchData from 'components/FetchData.vue'; import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; -import FilterTravelForm from 'src/components/FilterTravelForm.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; -import { toDate } from 'src/filters'; +import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; const route = useRoute(); const { t } = useI18n(); const { hasAny } = useRole(); const isAdministrative = () => hasAny(['administrative']); +const state = useState(); +const user = state.getUser().fn(); const companiesOptions = ref([]); const currenciesOptions = ref([]); -const onFilterTravelSelected = (formData, id) => { - formData.travelFk = id; -}; +onMounted(() => { + checkEntryLock(route.params.id, user.id); +}); </script> <template> @@ -52,46 +54,24 @@ const onFilterTravelSelected = (formData, id) => { > <template #form="{ data }"> <VnRow> + <VnSelectTravelExtended + :data="data" + v-model="data.travelFk" + :onFilterTravelSelected="(data, result) => (data.travelFk = result)" + /> <VnSelectSupplier v-model="data.supplierFk" hide-selected :required="true" - map-options /> - <VnSelectDialog - :label="t('entry.basicData.travel')" - v-model="data.travelFk" - url="Travels/filter" - :fields="['id', 'warehouseInName']" - option-value="id" - option-label="warehouseInName" - map-options - hide-selected - :required="true" - action-icon="filter_alt" - > - <template #form> - <FilterTravelForm - @travel-selected="onFilterTravelSelected(data, $event)" - /> - </template> - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel> - {{ scope.opt?.agencyModeName }} - - {{ scope.opt?.warehouseInName }} - ({{ toDate(scope.opt?.shipped) }}) → - {{ scope.opt?.warehouseOutName }} - ({{ toDate(scope.opt?.landed) }}) - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelectDialog> </VnRow> <VnRow> <VnInput v-model="data.reference" :label="t('globals.reference')" /> + <VnInputNumber + v-model="data.invoiceAmount" + :label="t('entry.summary.invoiceAmount')" + :positive="false" + /> </VnRow> <VnRow> <VnInput @@ -113,8 +93,7 @@ const onFilterTravelSelected = (formData, id) => { <VnInputNumber :label="t('entry.summary.commission')" v-model="data.commission" - step="1" - autofocus + :step="1" :positive="false" /> <VnSelect @@ -161,7 +140,7 @@ const onFilterTravelSelected = (formData, id) => { :label="t('entry.summary.excludedFromAvailable')" /> <QCheckbox - v-if="isAdministrative()" + :disable="!isAdministrative()" v-model="data.isBooked" :label="t('entry.basicData.booked')" /> diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue index 6194ce5b8..81578c609 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -1,478 +1,806 @@ <script setup> -import { ref, computed } from 'vue'; -import { useRoute, useRouter } from 'vue-router'; +import { useStateStore } from 'stores/useStateStore'; +import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { QBtn } from 'quasar'; +import { onMounted, ref } from 'vue'; -import VnPaginate from 'src/components/ui/VnPaginate.vue'; -import VnSelect from 'components/common/VnSelect.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import FetchedTags from 'components/ui/FetchedTags.vue'; -import VnConfirm from 'components/ui/VnConfirm.vue'; +import { useState } from 'src/composables/useState'; + +import FetchData from 'src/components/FetchData.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; - -import { useQuasar } from 'quasar'; -import { toCurrency } from 'src/filters'; +import FetchedTags from 'src/components/ui/FetchedTags.vue'; +import VnColor from 'src/components/common/VnColor.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import ItemDescriptor from 'src/pages/Item/Card/ItemDescriptor.vue'; import axios from 'axios'; -import useNotify from 'src/composables/useNotify.js'; +import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; +import { checkEntryLock } from 'src/composables/checkEntryLock'; -const quasar = useQuasar(); -const route = useRoute(); -const router = useRouter(); -const { t } = useI18n(); -const { notify } = useNotify(); - -const rowsSelected = ref([]); -const entryBuysPaginateRef = ref(null); -const originalRowDataCopy = ref(null); - -const getInputEvents = (colField, props) => { - return colField === 'packagingFk' - ? { 'update:modelValue': () => saveChange(colField, props) } - : { - 'keyup.enter': () => saveChange(colField, props), - blur: () => saveChange(colField, props), - }; -}; - -const tableColumnComponents = computed(() => ({ - item: { - component: QBtn, - props: { - color: 'primary', - flat: true, - }, - event: () => ({}), +const $props = defineProps({ + id: { + type: Number, + default: null, }, - quantity: { - component: VnInput, - props: { - type: 'number', - min: 0, - class: 'input-number', - dense: true, - }, - event: getInputEvents, + editableMode: { + type: Boolean, + default: true, }, - packagingFk: { - component: VnSelect, - props: { - 'option-value': 'id', - 'option-label': 'id', - 'emit-value': true, - 'map-options': true, - 'use-input': true, - 'hide-selected': true, - url: 'Packagings', - fields: ['id'], - where: { freightItemFk: true }, - 'sort-by': 'id ASC', - dense: true, - }, - event: getInputEvents, + tableHeight: { + type: String, + default: null, }, - stickers: { - component: VnInput, - props: { - type: 'number', - min: 0, - class: 'input-number', - dense: true, - }, - event: getInputEvents, - }, - printedStickers: { - component: VnInput, - props: { - type: 'number', - min: 0, - class: 'input-number', - dense: true, - }, - event: getInputEvents, - }, - weight: { - component: VnInput, - props: { - type: 'number', - min: 0, - dense: true, - }, - event: getInputEvents, - }, - packing: { - component: VnInput, - props: { - type: 'number', - min: 0, - dense: true, - }, - event: getInputEvents, - }, - grouping: { - component: VnInput, - props: { - type: 'number', - min: 0, - dense: true, - }, - event: getInputEvents, - }, - buyingValue: { - component: VnInput, - props: { - type: 'number', - min: 0, - dense: true, - }, - event: getInputEvents, - }, - price2: { - component: VnInput, - props: { - type: 'number', - min: 0, - dense: true, - }, - event: getInputEvents, - }, - price3: { - component: VnInput, - props: { - type: 'number', - min: 0, - dense: true, - }, - event: getInputEvents, - }, - import: { - component: 'span', - props: {}, - event: () => ({}), - }, -})); - -const entriesTableColumns = computed(() => { - return [ - { - label: t('globals.item'), - field: 'itemFk', - name: 'item', - align: 'left', - }, - { - label: t('globals.quantity'), - field: 'quantity', - name: 'quantity', - align: 'left', - }, - { - label: t('entry.summary.package'), - field: 'packagingFk', - name: 'packagingFk', - align: 'left', - }, - { - label: t('entry.summary.stickers'), - field: 'stickers', - name: 'stickers', - align: 'left', - }, - { - label: t('entry.buys.printedStickers'), - field: 'printedStickers', - name: 'printedStickers', - align: 'left', - }, - { - label: t('globals.weight'), - field: 'weight', - name: 'weight', - align: 'left', - }, - { - label: t('entry.summary.packing'), - field: 'packing', - name: 'packing', - align: 'left', - }, - { - label: t('entry.summary.grouping'), - field: 'grouping', - name: 'grouping', - align: 'left', - }, - { - label: t('entry.summary.buyingValue'), - field: 'buyingValue', - name: 'buyingValue', - align: 'left', - format: (value) => toCurrency(value), - }, - { - label: t('item.fixedPrice.groupingPrice'), - field: 'price2', - name: 'price2', - align: 'left', - }, - { - label: t('item.fixedPrice.packingPrice'), - field: 'price3', - name: 'price3', - align: 'left', - }, - { - label: t('entry.summary.import'), - name: 'import', - align: 'left', - format: (_, row) => toCurrency(row.buyingValue * row.quantity), - }, - ]; }); -const copyOriginalRowsData = (rows) => { - originalRowDataCopy.value = JSON.parse(JSON.stringify(rows)); -}; - -const saveChange = async (field, { rowIndex, row }) => { - if (originalRowDataCopy.value[rowIndex][field] == row[field]) return; - await axios.patch(`Buys/${row.id}`, row); - originalRowDataCopy.value[rowIndex][field] = row[field]; -}; - -const openRemoveDialog = async () => { - quasar - .dialog({ - component: VnConfirm, - componentProps: { - title: t('Confirm deletion'), - message: t( - `Are you sure you want to delete this buy${ - rowsSelected.value.length > 1 ? 's' : '' - }?` - ), - data: rowsSelected.value, +const state = useState(); +const user = state.getUser().fn(); +const stateStore = useStateStore(); +const { t } = useI18n(); +const route = useRoute(); +const selectedRows = ref([]); +const entityId = ref($props.id ?? route.params.id); +const entryBuysRef = ref(); +const footerFetchDataRef = ref(); +const footer = ref({}); +const columns = [ + { + align: 'center', + labelAbbreviation: 'NV', + label: t('Ignore'), + toolTip: t('Ignored for available'), + name: 'isIgnored', + component: 'checkbox', + attrs: { + toggleIndeterminate: false, + }, + create: true, + width: '25px', + }, + { + label: t('Buyer'), + name: 'workerFk', + component: 'select', + attrs: { + url: 'Workers/search', + fields: ['id', 'nickname'], + optionLabel: 'nickname', + optionValue: 'id', + }, + visible: false, + }, + { + label: t('Family'), + name: 'itemTypeFk', + component: 'select', + attrs: { + url: 'itemTypes', + fields: ['id', 'name'], + optionLabel: 'name', + optionValue: 'id', + }, + visible: false, + }, + { + name: 'id', + isId: true, + visible: false, + isEditable: false, + columnFilter: false, + }, + { + name: 'entryFk', + isId: true, + visible: false, + isEditable: false, + disable: true, + create: true, + columnFilter: false, + }, + { + align: 'center', + label: 'Id', + name: 'itemFk', + component: 'number', + isEditable: false, + width: '35px', + }, + { + labelAbbreviation: '', + label: 'Color', + name: 'hex', + columnSearch: false, + isEditable: false, + width: '9px', + component: 'select', + attrs: { + url: 'Inks', + fields: ['id', 'name'], + }, + }, + { + align: 'center', + label: t('Article'), + name: 'name', + component: 'select', + attrs: { + url: 'Items', + fields: ['id', 'name'], + optionLabel: 'name', + optionValue: 'id', + }, + width: '85px', + isEditable: false, + }, + { + align: 'center', + label: t('Article'), + name: 'itemFk', + visible: false, + create: true, + columnFilter: false, + }, + { + align: 'center', + labelAbbreviation: t('Siz.'), + label: t('Size'), + toolTip: t('Size'), + component: 'number', + name: 'size', + width: '35px', + isEditable: false, + style: () => { + return { color: 'var(--vn-label-color)' }; + }, + }, + { + align: 'center', + labelAbbreviation: t('Sti.'), + label: t('Stickers'), + toolTip: t('Printed Stickers/Stickers'), + name: 'stickers', + component: 'input', + create: true, + attrs: { + positive: false, + }, + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + row['quantity'] = value * row['packing']; + row['amount'] = row['quantity'] * row['buyingValue']; }, - }) - .onOk(async () => { - await deleteBuys(); - const notifyMessage = t( - `Buy${rowsSelected.value.length > 1 ? 's' : ''} deleted` - ); - notify(notifyMessage, 'positive'); - }); -}; + }, + width: '35px', + }, + { + align: 'center', + label: t('Bucket'), + name: 'packagingFk', + component: 'select', + attrs: { + url: 'packagings', + fields: ['id'], + optionLabel: 'id', + optionValue: 'id', + }, + create: true, + width: '40px', + }, + { + align: 'center', + label: 'Kg', + name: 'weight', + component: 'number', + create: true, + width: '35px', + format: (row) => parseFloat(row['weight']).toFixed(1), + }, + { + labelAbbreviation: 'P', + label: 'Packing', + toolTip: 'Packing', + name: 'packing', + component: 'number', + create: true, + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + const oldPacking = oldValue === 1 || oldValue === null ? 1 : oldValue; + row['weight'] = (row['weight'] * value) / oldPacking; + row['quantity'] = row['stickers'] * value; + row['amount'] = row['quantity'] * row['buyingValue']; + }, + }, + width: '30px', + style: (row) => { + if (row.groupingMode === 'grouping') + return { color: 'var(--vn-label-color)' }; + }, + }, + { + align: 'center', + labelAbbreviation: 'GM', + label: t('Grouping selector'), + toolTip: t('Grouping selector'), + name: 'groupingMode', + component: 'toggle', + attrs: { + 'toggle-indeterminate': true, + trueValue: 'grouping', + falseValue: 'packing', + indeterminateValue: null, + }, + size: 'xs', + width: '25px', + create: true, + rightFilter: false, + getIcon: (value) => { + switch (value) { + case 'grouping': + return 'toggle_on'; + case 'packing': + return 'toggle_off'; + default: + return 'minimize'; + } + }, + }, + { + align: 'center', + labelAbbreviation: 'G', + label: 'Grouping', + toolTip: 'Grouping', + name: 'grouping', + component: 'number', + width: '30px', + create: true, + style: (row) => { + if (row.groupingMode === 'packing') return { color: 'var(--vn-label-color)' }; + }, + }, + { + align: 'center', + label: t('Quantity'), + name: 'quantity', + component: 'number', + attrs: { + positive: false, + }, + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + row['amount'] = value * row['buyingValue']; + }, + }, + width: '45px', + create: true, + style: getQuantityStyle, + }, + { + align: 'center', + labelAbbreviation: t('Cost'), + label: t('Buying value'), + toolTip: t('Buying value'), + name: 'buyingValue', + create: true, + component: 'number', + attrs: { + positive: false, + }, + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + row['amount'] = row['quantity'] * value; + }, + }, + width: '45px', + format: (row) => parseFloat(row['buyingValue']).toFixed(3), + }, + { + align: 'center', + label: t('Amount'), + name: 'amount', + width: '45px', + component: 'number', + attrs: { + positive: false, + }, + isEditable: false, + format: (row) => parseFloat(row['amount']).toFixed(2), + style: getAmountStyle, + }, + { + align: 'center', + labelAbbreviation: t('Pack.'), + label: t('Package'), + toolTip: t('Package'), + name: 'price2', + component: 'number', + width: '35px', + create: true, + format: (row) => parseFloat(row['price2']).toFixed(2), + }, + { + align: 'center', + label: t('Box'), + name: 'price3', + component: 'number', + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + row['price2'] = row['price2'] * (value / oldValue); + }, + }, + width: '35px', + create: true, + format: (row) => parseFloat(row['price3']).toFixed(2), + }, + { + align: 'center', + labelAbbreviation: 'CM', + label: t('Check min price'), + toolTip: t('Check min price'), + name: 'hasMinPrice', + attrs: { + toggleIndeterminate: false, + }, + component: 'checkbox', + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + await axios.patch(`Items/${row['itemFk']}`, { + hasMinPrice: value, + }); + }, + }, + width: '25px', + }, + { + align: 'center', + labelAbbreviation: 'Min.', + label: t('Minimum price'), + toolTip: t('Minimum price'), + name: 'minPrice', + component: 'number', + cellEvent: { + 'update:modelValue': async (value, oldValue, row) => { + await axios.patch(`Items/${row['itemFk']}`, { + minPrice: value, + }); + }, + }, + width: '35px', + style: (row) => { + if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' }; + }, + format: (row) => parseFloat(row['minPrice']).toFixed(2), + }, + { + align: 'center', + labelAbbreviation: t('P.Sen'), + label: t('Packing sent'), + toolTip: t('Packing sent'), + name: 'packingOut', + component: 'number', + isEditable: false, + width: '40px', + style: () => { + return { color: 'var(--vn-label-color)' }; + }, + }, + { + align: 'center', + labelAbbreviation: t('Com.'), + label: t('Comment'), + toolTip: t('Comment'), + name: 'comment', + component: 'input', + isEditable: false, + width: '50px', + }, + { + align: 'center', + labelAbbreviation: 'Prod.', + label: t('Producer'), + toolTip: t('Producer'), + name: 'subName', + isEditable: false, + width: '45px', + style: () => { + return { color: 'var(--vn-label-color)' }; + }, + }, + { + align: 'center', + label: t('Tags'), + name: 'tags', + width: '125px', + columnSearch: false, + }, + { + align: 'center', + labelAbbreviation: 'Comp.', + label: t('Company'), + toolTip: t('Company'), + name: 'company_name', + component: 'input', + isEditable: false, + width: '35px', + style: () => { + return { color: 'var(--vn-label-color)' }; + }, + }, +]; -const deleteBuys = async () => { - await axios.post('Buys/deleteBuys', { buys: rowsSelected.value }); - entryBuysPaginateRef.value.fetch(); -}; +function getQuantityStyle(row) { + if (row?.quantity !== row?.stickers * row?.packing) + return { color: 'var(--q-negative)' }; +} +function getAmountStyle(row) { + if (row?.isChecked) return { color: 'var(--q-positive)' }; + return { color: 'var(--vn-label-color)' }; +} -const importBuys = () => { - router.push({ name: 'EntryBuysImport' }); -}; +async function beforeSave(data, getChanges) { + try { + const changes = data.updates; + if (!changes) return data; + const patchPromises = []; -const toggleGroupingMode = async (buy, mode) => { - const groupingMode = mode === 'grouping' ? mode : 'packing'; - const newGroupingMode = buy.groupingMode === groupingMode ? null : groupingMode; - const params = { - groupingMode: newGroupingMode, - }; - await axios.patch(`Buys/${buy.id}`, params); - buy.groupingMode = newGroupingMode; -}; + for (const change of changes) { + let patchData = {}; -const lockIconType = (groupingMode, mode) => { - if (mode === 'packing') { - return groupingMode === 'packing' ? 'lock' : 'lock_open'; - } else { - return groupingMode === 'grouping' ? 'lock' : 'lock_open'; + if ('hasMinPrice' in change.data) { + patchData.hasMinPrice = change.data?.hasMinPrice; + delete change.data.hasMinPrice; + } + if ('minPrice' in change.data) { + patchData.minPrice = change.data?.minPrice; + delete change.data.minPrice; + } + + if (Object.keys(patchData).length > 0) { + const promise = axios + .get('Buys/findOne', { + params: { + filter: { + fields: ['itemFk'], + where: { id: change.where.id }, + }, + }, + }) + .then((buy) => { + return axios.patch(`Items/${buy.data.itemFk}`, patchData); + }) + .catch((error) => { + console.error('Error processing change: ', change, error); + }); + + patchPromises.push(promise); + } + } + + await Promise.all(patchPromises); + + data.updates = changes.filter((change) => Object.keys(change.data).length > 0); + + return data; + } catch (error) { + console.error('Error in beforeSave:', error); + throw error; } -}; +} + +function invertQuantitySign(rows, sign) { + for (const row of rows) { + if (sign > 0) row.quantity = Math.abs(row.quantity); + else if (row.quantity > 0) row.quantity = -row.quantity; + } +} +function setIsChecked(rows, value) { + for (const row of rows) { + row.isChecked = value; + } + footerFetchDataRef.value.fetch(); +} + +async function setBuyUltimate(itemFk, data) { + if (!itemFk) return; + const buyUltimate = await axios.get(`Entries/getBuyUltimate`, { + params: { + itemFk, + warehouseFk: user.warehouseFk, + date: Date.vnNew(), + }, + }); + const buyUltimateData = buyUltimate.data[0]; + + const allowedKeys = columns + .filter((col) => col.create === true) + .map((col) => col.name); + + allowedKeys.forEach((key) => { + if (buyUltimateData.hasOwnProperty(key) && key !== 'entryFk') { + if (!['stickers', 'quantity'].includes(key)) data[key] = buyUltimateData[key]; + } + }); +} + +onMounted(() => { + stateStore.rightDrawer = false; + if ($props.editableMode) checkEntryLock(entityId.value, user.id); +}); </script> - <template> - <VnSubToolbar> - <template #st-actions> - <QBtnGroup push style="column-gap: 10px"> - <slot name="moreBeforeActions" /> - <QBtn - :label="t('globals.remove')" - color="primary" - icon="delete" - flat - @click="openRemoveDialog()" - :disable="!rowsSelected?.length" - :title="t('globals.remove')" - /> - </QBtnGroup> - </template> - </VnSubToolbar> - <VnPaginate - ref="entryBuysPaginateRef" - data-key="EntryBuys" - :url="`Entries/${route.params.id}/getBuys`" - @on-fetch="copyOriginalRowsData($event)" - auto-load - > - <template #body="{ rows }"> - <QTable - :rows="rows" - :columns="entriesTableColumns" - selection="multiple" - row-key="id" - class="full-width q-mt-md" - :grid="$q.screen.lt.md" - v-model:selected="rowsSelected" - :no-data-label="t('globals.noResults')" + <Teleport to="#st-data" v-if="stateStore?.isSubToolbarShown() && editableMode"> + <QBtnGroup push style="column-gap: 1px"> + <QBtnDropdown + label="+/-" + color="primary" + flat + :title="t('Invert quantity value')" + :disable="!selectedRows.length" + data-cy="change-quantity-sign" > - <template #body="props"> - <QTr> - <QTd> - <QCheckbox v-model="props.selected" /> - </QTd> - <QTd - v-for="col in props.cols" - :key="col.name" - style="max-width: 100px" - > - <component - :is="tableColumnComponents[col.name].component" - v-bind="tableColumnComponents[col.name].props" - v-model="props.row[col.field]" - v-on=" - tableColumnComponents[col.name].event( - col.field, - props - ) - " + <QList> + <QItem> + <QItemSection> + <QBtn + flat + @click="invertQuantitySign(selectedRows, -1)" + data-cy="set-negative-quantity" > - <template - v-if=" - col.name === 'grouping' || col.name === 'packing' - " - #append - > - <QBtn - :icon=" - lockIconType(props.row.groupingMode, col.name) - " - @click="toggleGroupingMode(props.row, col.name)" - class="cursor-pointer" - size="sm" - flat - dense - unelevated - push - :style="{ - 'font-variation-settings': `'FILL' ${ - lockIconType( - props.row.groupingMode, - col.name - ) === 'lock' - ? 1 - : 0 - }`, - }" - /> - </template> - <template - v-if="col.name === 'item' || col.name === 'import'" - > - {{ col.value }} - </template> - <ItemDescriptorProxy - v-if="col.name === 'item'" - :id="props.row.item.id" - /> - </component> - </QTd> - </QTr> - <QTr no-hover class="full-width infoRow" style="column-span: all"> - <QTd /> - <QTd cols> - <span>{{ props.row.item.itemType.code }}</span> - </QTd> - <QTd> - <span>{{ props.row.item.size }}</span> - </QTd> - <QTd> - <span>{{ toCurrency(props.row.item.minPrice) }}</span> - </QTd> - <QTd colspan="7"> - <span>{{ props.row.item.concept }}</span> - <span v-if="props.row.item.subName" class="subName"> - {{ props.row.item.subName }} - </span> - <FetchedTags :item="props.row.item" /> - </QTd> - </QTr> - </template> - <template #item="props"> - <div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition"> - <QCard bordered flat> - <QCardSection> - <QCheckbox v-model="props.selected" dense /> - </QCardSection> - <QSeparator /> - <QList dense> - <QItem v-for="col in props.cols" :key="col.name"> - <component - :is="tableColumnComponents[col.name].component" - v-bind="tableColumnComponents[col.name].props" - v-model="props.row[col.field]" - v-on=" - tableColumnComponents[col.name].event( - col.field, - props - ) - " - class="full-width" - > - <template - v-if=" - col.name === 'item' || - col.name === 'import' - " - > - {{ col.label + ': ' + col.value }} - </template> - </component> - </QItem> - </QList> - </QCard> - </div> - </template> - </QTable> + <span style="font-size: large">-</span> + </QBtn> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <QBtn + flat + @click="invertQuantitySign(selectedRows, 1)" + data-cy="set-positive-quantity" + > + <span style="font-size: large">+</span> + </QBtn> + </QItemSection> + </QItem> + </QList> + </QBtnDropdown> + <QBtnDropdown + icon="price_check" + color="primary" + flat + :title="t('Check buy amount')" + :disable="!selectedRows.length" + data-cy="check-buy-amount" + > + <QList> + <QItem> + <QItemSection> + <QBtn + size="sm" + icon="check" + flat + @click="setIsChecked(selectedRows, true)" + data-cy="check-amount" + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <QBtn + size="sm" + icon="close" + flat + @click="setIsChecked(selectedRows, false)" + data-cy="uncheck-amount" + /> + </QItemSection> + </QItem> + </QList> + </QBtnDropdown> + </QBtnGroup> + </Teleport> + <FetchData + ref="footerFetchDataRef" + :url="`Entries/${entityId}/getBuyList`" + :params="{ groupBy: 'GROUP BY b.entryFk' }" + @on-fetch="(data) => (footer = data[0])" + auto-load + /> + <VnTable + ref="entryBuysRef" + data-key="EntryBuys" + :url="`Entries/${entityId}/getBuyList`" + save-url="Buys/crud" + :disable-option="{ card: true }" + v-model:selected="selectedRows" + @on-fetch="() => footerFetchDataRef.fetch()" + :table=" + editableMode + ? { + 'row-key': 'id', + selection: 'multiple', + } + : {} + " + :create=" + editableMode + ? { + urlCreate: 'Buys', + title: t('Create buy'), + onDataSaved: () => { + entryBuysRef.reload(); + }, + formInitialData: { entryFk: entityId, isIgnored: false }, + showSaveAndContinueBtn: true, + } + : null + " + :create-complement="{ + isFullWidth: true, + containerStyle: { + display: 'flex', + 'flex-wrap': 'wrap', + gap: '16px', + position: 'relative', + height: '450px', + }, + columnGridStyle: { + 'max-width': '50%', + flex: 1, + 'margin-right': '30px', + }, + }" + :is-editable="editableMode" + :without-header="!editableMode" + :with-filters="editableMode" + :right-search="true" + :right-search-icon="true" + :row-click="false" + :columns="columns" + :beforeSaveFn="beforeSave" + class="buyList" + :table-height="$props.tableHeight ?? '84vh'" + auto-load + footer + data-cy="entry-buys" + > + <template #column-hex="{ row }"> + <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> </template> - </VnPaginate> - - <QPageSticky :offset="[20, 20]"> - <QBtn fab icon="upload" color="primary" @click="importBuys()" /> - <QTooltip class="text-no-wrap"> - {{ t('Import buys') }} - </QTooltip> - </QPageSticky> + <template #column-name="{ row }"> + <span class="link"> + {{ row?.name }} + <ItemDescriptorProxy :id="row?.itemFk" /> + </span> + </template> + <template #column-tags="{ row }"> + <FetchedTags :item="row" :columns="3" /> + </template> + <template #column-stickers="{ row }"> + <span :class="editableMode ? 'editable-text' : ''"> + <span style="color: var(--vn-label-color)"> + {{ row.printedStickers }} + </span> + <span>/{{ row.stickers }}</span> + </span> + </template> + <template #column-footer-stickers> + <div> + <span style="color: var(--vn-label-color)"> + {{ footer?.printedStickers }}</span + > + <span>/</span> + <span data-cy="footer-stickers">{{ footer?.stickers }}</span> + </div> + </template> + <template #column-footer-weight> + {{ footer?.weight }} + </template> + <template #column-footer-quantity> + <span :style="getQuantityStyle(footer)" data-cy="footer-quantity"> + {{ footer?.quantity }} + </span> + </template> + <template #column-footer-amount> + <span :style="getAmountStyle(footer)" data-cy="footer-amount"> + {{ footer?.amount }} + </span> + </template> + <template #column-create-itemFk="{ data }"> + <VnSelect + url="Items/search" + v-model="data.itemFk" + :label="t('Article')" + :fields="['id', 'name', 'size', 'producerName']" + :filter-options="['id', 'name', 'size', 'producerName']" + option-label="name" + option-value="id" + @update:modelValue=" + async (value) => { + await setBuyUltimate(value, data); + } + " + :required="true" + data-cy="itemFk-create-popup" + sort-by="nickname DESC" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt.name }} + </QItemLabel> + <QItemLabel caption> + #{{ scope.opt.id }}, {{ scope.opt?.size }}, + {{ scope.opt?.producerName }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </template> + <template #column-create-groupingMode="{ data }"> + <VnSelectEnum + :label="t('Grouping mode')" + v-model="data.groupingMode" + schema="vn" + table="buy" + column="groupingMode" + option-value="groupingMode" + option-label="groupingMode" + /> + </template> + <template #previous-create-dialog="{ data }"> + <div + style="position: absolute" + :class="{ 'centered-container': !data.itemFk }" + > + <ItemDescriptor :id="data.itemFk" v-if="data.itemFk" /> + <div v-else> + <span>{{ t('globals.noData') }}</span> + </div> + </div> + </template> + </VnTable> </template> - -<style lang="scss" scoped> -.q-table--horizontal-separator tbody tr:nth-child(odd) > td { - border-bottom-width: 0px; - border-top-width: 2px; - border-color: var(--vn-text-color); -} -.infoRow > td { - color: var(--vn-label-color); -} -</style> - <i18n> es: - Import buys: Importar compras - Buy deleted: Compra eliminada - Buys deleted: Compras eliminadas - Confirm deletion: Confirmar eliminación - Are you sure you want to delete this buy?: Seguro que quieres eliminar esta compra? - Are you sure you want to delete this buys?: Seguro que quieres eliminar estas compras? + Article: Artículo + Siz.: Med. + Size: Medida + Sti.: Eti. + Bucket: Cubo + Quantity: Cantidad + Amount: Importe + Pack.: Paq. + Package: Paquete + Box: Caja + P.Sen: P.Env + Packing sent: Packing envíos + Com.: Ref. + Comment: Referencia + Minimum price: Precio mínimo + Stickers: Etiquetas + Printed Stickers/Stickers: Etiquetas impresas/Etiquetas + Cost: Cost. + Buying value: Coste + Producer: Productor + Company: Compañia + Tags: Etiquetas + Grouping mode: Modo de agrupación + C.min: P.min + Ignore: Ignorar + Ignored for available: Ignorado para disponible + Grouping selector: Selector de grouping + Check min price: Marcar precio mínimo + Create buy: Crear compra + Invert quantity value: Invertir valor de cantidad + Check buy amount: Marcar como correcta la cantidad de compra </i18n> +<style lang="scss" scoped> +.centered-container { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + width: 40%; + height: 100%; +} +</style> diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 19d13e51a..69b300cb2 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -1,12 +1,19 @@ <script setup> import { ref, computed, onMounted } from 'vue'; -import { useRoute } from 'vue-router'; +import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; -import VnLv from 'src/components/ui/VnLv.vue'; import { toDate } from 'src/filters'; import { getUrl } from 'src/composables/getUrl'; -import EntryDescriptorMenu from './EntryDescriptorMenu.vue'; +import { useQuasar } from 'quasar'; +import { usePrintService } from 'composables/usePrintService'; +import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; +import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; +import axios from 'axios'; + +const quasar = useQuasar(); +const { push } = useRouter(); +const { openReport } = usePrintService(); const $props = defineProps({ id: { @@ -83,12 +90,63 @@ const getEntryRedirectionFilter = (entry) => { to, }); }; + +function showEntryReport() { + openReport(`Entries/${entityId.value}/entry-order-pdf`); +} + +function showNotification(type, message) { + quasar.notify({ + type: type, + message: t(message), + }); +} + +async function recalculateRates(entity) { + try { + const entryConfig = await axios.get('EntryConfigs/findOne'); + if (entryConfig.data?.inventorySupplierFk === entity.supplierFk) { + showNotification( + 'negative', + 'Cannot recalculate prices because this is an inventory entry', + ); + return; + } + + await axios.post(`Entries/${entityId.value}/recalcEntryPrices`); + showNotification('positive', 'Entry prices recalculated'); + } catch (error) { + showNotification('negative', 'Failed to recalculate rates'); + console.error(error); + } +} + +async function cloneEntry() { + try { + const response = await axios.post(`Entries/${entityId.value}/cloneEntry`); + push({ path: `/entry/${response.data}` }); + showNotification('positive', 'Entry cloned'); + } catch (error) { + showNotification('negative', 'Failed to clone entry'); + console.error(error); + } +} + +async function deleteEntry() { + try { + await axios.post(`Entries/${entityId.value}/deleteEntry`); + push({ path: `/entry/list` }); + showNotification('positive', 'Entry deleted'); + } catch (error) { + showNotification('negative', 'Failed to delete entry'); + console.error(error); + } +} </script> <template> <CardDescriptor ref="entryDescriptorRef" - module="Entry" :url="`Entries/${entityId}`" :userFilter="entryFilter" title="supplier.nickname" @@ -96,15 +154,56 @@ const getEntryRedirectionFilter = (entry) => { width="lg-width" > <template #menu="{ entity }"> - <EntryDescriptorMenu :id="entity.id" /> + <QItem + v-ripple + clickable + @click="showEntryReport(entity)" + data-cy="show-entry-report" + > + <QItemSection>{{ t('Show entry report') }}</QItemSection> + </QItem> + <QItem + v-ripple + clickable + @click="recalculateRates(entity)" + data-cy="recalculate-rates" + > + <QItemSection>{{ t('Recalculate rates') }}</QItemSection> + </QItem> + <QItem v-ripple clickable @click="cloneEntry(entity)" data-cy="clone-entry"> + <QItemSection>{{ t('Clone') }}</QItemSection> + </QItem> + <QItem v-ripple clickable @click="deleteEntry(entity)" data-cy="delete-entry"> + <QItemSection>{{ t('Delete') }}</QItemSection> + </QItem> </template> <template #body="{ entity }"> - <VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" /> - <VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" /> - <VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" /> + <VnLv :label="t('Travel')"> + <template #value> + <span class="link" v-if="entity?.travelFk"> + {{ entity.travel?.agency?.name }} + {{ entity.travel?.warehouseOut?.code }} → + {{ entity.travel?.warehouseIn?.code }} + <TravelDescriptorProxy :id="entity?.travelFk" /> + </span> + </template> + </VnLv> <VnLv - :label="t('globals.warehouseOut')" - :value="entity.travel?.warehouseOut?.name" + :label="t('entry.summary.travelShipped')" + :value="toDate(entity.travel?.shipped)" + /> + <VnLv + :label="t('entry.summary.travelLanded')" + :value="toDate(entity.travel?.landed)" + /> + <VnLv :label="t('entry.summary.currency')" :value="entity?.currency?.code" /> + <VnLv + :label="t('entry.summary.invoiceAmount')" + :value="entity?.invoiceAmount" + /> + <VnLv + :label="t('entry.summary.entryType')" + :value="entity?.entryType?.description" /> </template> <template #icons="{ entity }"> @@ -131,6 +230,14 @@ const getEntryRedirectionFilter = (entry) => { }}</QTooltip > </QIcon> + <QIcon + v-if="!entity?.travelFk" + name="vn:deletedTicket" + size="xs" + color="primary" + > + <QTooltip>{{ t('This entry is deleted') }}</QTooltip> + </QIcon> </QCardActions> </template> <template #actions="{ entity }"> @@ -143,21 +250,6 @@ const getEntryRedirectionFilter = (entry) => { > <QTooltip>{{ t('Supplier card') }}</QTooltip> </QBtn> - <QBtn - :to="{ - name: 'TravelMain', - query: { - params: JSON.stringify({ - agencyModeFk: entity.travel?.agencyModeFk, - }), - }, - }" - size="md" - icon="local_airport" - color="primary" - > - <QTooltip>{{ t('All travels with current agency') }}</QTooltip> - </QBtn> <QBtn :to="{ name: 'EntryMain', @@ -177,10 +269,24 @@ const getEntryRedirectionFilter = (entry) => { </template> <i18n> es: + Travel: Envío Supplier card: Ficha del proveedor All travels with current agency: Todos los envíos con la agencia actual All entries with current supplier: Todas las entradas con el proveedor actual Show entry report: Ver informe del pedido Inventory entry: Es inventario Virtual entry: Es una redada + shipped: Enviado + landed: Recibido + This entry is deleted: Esta entrada está eliminada + Cannot recalculate prices because this is an inventory entry: No se pueden recalcular los precios porque es una entrada de inventario + Entry deleted: Entrada eliminada + Entry cloned: Entrada clonada + Entry prices recalculated: Precios de la entrada recalculados + Failed to recalculate rates: No se pudieron recalcular las tarifas + Failed to clone entry: No se pudo clonar la entrada + Failed to delete entry: No se pudo eliminar la entrada + Recalculate rates: Recalcular tarifas + Clone: Clonar + Delete: Eliminar </i18n> diff --git a/src/pages/Entry/Card/EntryDescriptorMenu.vue b/src/pages/Entry/Card/EntryDescriptorMenu.vue index 03cd53358..dc759c7a8 100644 --- a/src/pages/Entry/Card/EntryDescriptorMenu.vue +++ b/src/pages/Entry/Card/EntryDescriptorMenu.vue @@ -54,8 +54,8 @@ const transferEntry = async () => { <i18n> en: transferEntryDialog: The entries will be transferred to the next day - transferEntry: Transfer Entry + transferEntry: Partial delay es: transferEntryDialog: Se van a transferir las compras al dia siguiente - transferEntry: Transferir Entrada + transferEntry: Retraso parcial </i18n> diff --git a/src/pages/Entry/Card/EntryFilter.js b/src/pages/Entry/Card/EntryFilter.js index 3ff62cf27..d9fd1c2be 100644 --- a/src/pages/Entry/Card/EntryFilter.js +++ b/src/pages/Entry/Card/EntryFilter.js @@ -9,6 +9,7 @@ export default { 'shipped', 'agencyModeFk', 'warehouseOutFk', + 'warehouseInFk', 'daysInForward', ], include: [ @@ -21,13 +22,13 @@ export default { { relation: 'warehouseOut', scope: { - fields: ['name'], + fields: ['name', 'code'], }, }, { relation: 'warehouseIn', scope: { - fields: ['name'], + fields: ['name', 'code'], }, }, ], @@ -39,5 +40,17 @@ export default { fields: ['id', 'nickname'], }, }, + { + relation: 'currency', + scope: { + fields: ['id', 'code'], + }, + }, + { + relation: 'entryType', + scope: { + fields: ['code', 'description'], + }, + }, ], }; diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index 8c46fb6e6..c40e2ba46 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -2,19 +2,17 @@ import { onMounted, ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; +import { toDate } from 'src/filters'; +import { getUrl } from 'src/composables/getUrl'; +import axios from 'axios'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; - -import { toDate, toCurrency, toCelsius } from 'src/filters'; -import { getUrl } from 'src/composables/getUrl'; -import axios from 'axios'; -import FetchedTags from 'src/components/ui/FetchedTags.vue'; -import VnToSummary from 'src/components/ui/VnToSummary.vue'; -import EntryDescriptorMenu from './EntryDescriptorMenu.vue'; -import VnRow from 'src/components/ui/VnRow.vue'; +import EntryBuys from './EntryBuys.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; +import VnToSummary from 'src/components/ui/VnToSummary.vue'; const route = useRoute(); const { t } = useI18n(); @@ -33,117 +31,6 @@ const entry = ref(); const entryBuys = ref([]); const entryUrl = ref(); -onMounted(async () => { - entryUrl.value = (await getUrl('entry/')) + entityId.value; -}); - -const tableColumnComponents = { - quantity: { - component: () => 'span', - props: () => {}, - }, - stickers: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - packagingFk: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - weight: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - packing: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - grouping: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - buyingValue: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - amount: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, - pvp: { - component: () => 'span', - props: () => {}, - event: () => {}, - }, -}; - -const entriesTableColumns = computed(() => { - return [ - { - label: t('globals.quantity'), - field: 'quantity', - name: 'quantity', - align: 'left', - }, - { - label: t('entry.summary.stickers'), - field: 'stickers', - name: 'stickers', - align: 'left', - }, - { - label: t('entry.summary.package'), - field: 'packagingFk', - name: 'packagingFk', - align: 'left', - }, - { - label: t('globals.weight'), - field: 'weight', - name: 'weight', - align: 'left', - }, - { - label: t('entry.summary.packing'), - field: 'packing', - name: 'packing', - align: 'left', - }, - { - label: t('entry.summary.grouping'), - field: 'grouping', - name: 'grouping', - align: 'left', - }, - { - label: t('entry.summary.buyingValue'), - field: 'buyingValue', - name: 'buyingValue', - align: 'left', - format: (value) => toCurrency(value), - }, - { - label: t('entry.summary.import'), - name: 'amount', - align: 'left', - format: (_, row) => toCurrency(row.buyingValue * row.quantity), - }, - { - label: t('entry.summary.pvp'), - name: 'pvp', - align: 'left', - format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3), - }, - ]; -}); - async function setEntryData(data) { if (data) entry.value = data; await fetchEntryBuys(); @@ -153,14 +40,18 @@ const fetchEntryBuys = async () => { const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`); if (data) entryBuys.value = data; }; -</script> +onMounted(async () => { + entryUrl.value = (await getUrl('entry/')) + entityId.value; +}); +</script> <template> <CardSummary ref="summaryRef" :url="`Entries/${entityId}/getEntry`" @on-fetch="(data) => setEntryData(data)" data-key="EntrySummary" + data-cy="entry-summary" > <template #header-left> <VnToSummary @@ -173,159 +64,154 @@ const fetchEntryBuys = async () => { <template #header> <span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> </template> - <template #menu="{ entity }"> - <EntryDescriptorMenu :id="entity.id" /> - </template> <template #body> <QCard class="vn-one"> <VnTitle :url="`#/entry/${entityId}/basic-data`" :text="t('globals.summary.basicData')" /> - <VnLv :label="t('entry.summary.commission')" :value="entry.commission" /> - <VnLv - :label="t('entry.summary.currency')" - :value="entry.currency?.name" - /> - <VnLv :label="t('globals.company')" :value="entry.company.code" /> - <VnLv :label="t('globals.reference')" :value="entry.reference" /> - <VnLv - :label="t('entry.summary.invoiceNumber')" - :value="entry.invoiceNumber" - /> - <VnLv - :label="t('entry.basicData.initialTemperature')" - :value="toCelsius(entry.initialTemperature)" - /> - <VnLv - :label="t('entry.basicData.finalTemperature')" - :value="toCelsius(entry.finalTemperature)" - /> + <div class="card-group"> + <div class="card-content"> + <VnLv + :label="t('entry.summary.commission')" + :value="entry?.commission" + /> + <VnLv + :label="t('entry.summary.currency')" + :value="entry?.currency?.name" + /> + <VnLv + :label="t('globals.company')" + :value="entry?.company?.code" + /> + <VnLv :label="t('globals.reference')" :value="entry?.reference" /> + <VnLv + :label="t('entry.summary.invoiceNumber')" + :value="entry?.invoiceNumber" + /> + </div> + <div class="card-content"> + <VnCheckbox + :label="t('entry.summary.ordered')" + v-model="entry.isOrdered" + :disable="true" + size="xs" + /> + <VnCheckbox + :label="t('globals.confirmed')" + v-model="entry.isConfirmed" + :disable="true" + size="xs" + /> + <VnCheckbox + :label="t('entry.summary.booked')" + v-model="entry.isBooked" + :disable="true" + size="xs" + /> + <VnCheckbox + :label="t('entry.summary.excludedFromAvailable')" + v-model="entry.isExcludedFromAvailable" + :disable="true" + size="xs" + /> + </div> + </div> </QCard> - <QCard class="vn-one"> + <QCard class="vn-one" v-if="entry?.travelFk"> <VnTitle - :url="`#/entry/${entityId}/basic-data`" - :text="t('globals.summary.basicData')" + :url="`#/travel/${entry.travel.id}/summary`" + :text="t('Travel')" /> - <VnLv :label="t('entry.summary.travelReference')"> - <template #value> - <span class="link"> - {{ entry.travel.ref }} - <TravelDescriptorProxy :id="entry.travel.id" /> - </span> - </template> - </VnLv> - <VnLv - :label="t('entry.summary.travelAgency')" - :value="entry.travel.agency?.name" - /> - <VnLv - :label="t('globals.shipped')" - :value="toDate(entry.travel.shipped)" - /> - <VnLv - :label="t('globals.warehouseOut')" - :value="entry.travel.warehouseOut?.name" - /> - <VnLv - :label="t('entry.summary.travelDelivered')" - :value="entry.travel.isDelivered" - /> - <VnLv :label="t('globals.landed')" :value="toDate(entry.travel.landed)" /> - <VnLv - :label="t('globals.warehouseIn')" - :value="entry.travel.warehouseIn?.name" - /> - <VnLv - :label="t('entry.summary.travelReceived')" - :value="entry.travel.isReceived" - /> - </QCard> - <QCard class="vn-one"> - <VnTitle :url="`#/travel/${entityId}/summary`" :text="t('Travel data')" /> - <VnRow class="block"> - <VnLv :label="t('entry.summary.ordered')" :value="entry.isOrdered" /> - <VnLv :label="t('globals.confirmed')" :value="entry.isConfirmed" /> - <VnLv :label="t('entry.summary.booked')" :value="entry.isBooked" /> - <VnLv - :label="t('entry.summary.excludedFromAvailable')" - :value="entry.isExcludedFromAvailable" - /> - </VnRow> + <div class="card-group"> + <div class="card-content"> + <VnLv :label="t('entry.summary.travelReference')"> + <template #value> + <span class="link"> + {{ entry.travel.ref }} + <TravelDescriptorProxy :id="entry.travel.id" /> + </span> + </template> + </VnLv> + <VnLv + :label="t('entry.summary.travelAgency')" + :value="entry.travel.agency?.name" + /> + <VnLv + :label="t('entry.summary.travelShipped')" + :value="toDate(entry.travel.shipped)" + /> + <VnLv + :label="t('globals.warehouseOut')" + :value="entry.travel.warehouseOut?.name" + /> + <VnLv + :label="t('entry.summary.travelLanded')" + :value="toDate(entry.travel.landed)" + /> + <VnLv + :label="t('globals.warehouseIn')" + :value="entry.travel.warehouseIn?.name" + /> + </div> + <div class="card-content"> + <VnCheckbox + :label="t('entry.summary.travelDelivered')" + v-model="entry.travel.isDelivered" + :disable="true" + size="xs" + /> + <VnCheckbox + :label="t('entry.summary.travelReceived')" + v-model="entry.travel.isReceived" + :disable="true" + size="xs" + /> + </div> + </div> </QCard> <QCard class="vn-max"> <VnTitle :url="`#/entry/${entityId}/buys`" :text="t('entry.summary.buys')" /> - <QTable - :rows="entryBuys" - :columns="entriesTableColumns" - row-key="index" - class="full-width q-mt-md" - :no-data-label="t('globals.noResults')" - > - <template #body="{ cols, row, rowIndex }"> - <QTr no-hover> - <QTd v-for="col in cols" :key="col?.name"> - <component - :is="tableColumnComponents[col?.name].component()" - v-bind="tableColumnComponents[col?.name].props()" - @click="tableColumnComponents[col?.name].event()" - class="col-content" - > - <template - v-if=" - col?.name !== 'observation' && - col?.name !== 'isConfirmed' - " - >{{ col.value }}</template - > - <QTooltip v-if="col.toolTip">{{ - col.toolTip - }}</QTooltip> - </component> - </QTd> - </QTr> - <QTr no-hover> - <QTd> - <span>{{ row.item.itemType.code }}</span> - </QTd> - <QTd> - <span>{{ row.item.id }}</span> - </QTd> - <QTd> - <span>{{ row.item.size }}</span> - </QTd> - <QTd> - <span>{{ toCurrency(row.item.minPrice) }}</span> - </QTd> - <QTd colspan="6"> - <span>{{ row.item.concept }}</span> - <span v-if="row.item.subName" class="subName"> - {{ row.item.subName }} - </span> - <FetchedTags :item="row.item" /> - </QTd> - </QTr> - <!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys --> - <QTr v-if="rowIndex !== entryBuys.length - 1"> - <QTd colspan="10" class="vn-table-separation-row" /> - </QTr> - </template> - </QTable> + <EntryBuys + v-if="entityId" + :id="Number(entityId)" + :editable-mode="false" + table-height="49vh" + /> </QCard> </template> </CardSummary> </template> - <style lang="scss" scoped> -.separation-row { - background-color: var(--vn-section-color) !important; +.card-group { + display: flex; + flex-direction: column; +} + +.card-content { + display: flex; + flex-direction: column; + text-overflow: ellipsis; + > div { + max-height: 24px; + } +} + +@media (min-width: 1010px) { + .card-group { + flex-direction: row; + } + .card-content { + flex: 1; + margin-right: 16px; + } } </style> - <i18n> es: - Travel data: Datos envío + Travel: Envío + InvoiceIn data: Datos factura </i18n> diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index 0f632c0ef..8c60918a8 100644 --- a/src/pages/Entry/EntryFilter.vue +++ b/src/pages/Entry/EntryFilter.vue @@ -19,6 +19,7 @@ const props = defineProps({ const currenciesOptions = ref([]); const companiesOptions = ref([]); +const entryFilterPanel = ref(); </script> <template> @@ -38,7 +39,7 @@ const companiesOptions = ref([]); @on-fetch="(data) => (currenciesOptions = data)" auto-load /> - <VnFilterPanel :data-key="props.dataKey" :search-button="true"> + <VnFilterPanel ref="entryFilterPanel" :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`entryFilter.params.${tag.label}`) }}: </strong> @@ -48,70 +49,65 @@ const companiesOptions = ref([]); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('entryFilter.params.search')" - is-outlined - /> + <QCheckbox + :label="t('params.isExcludedFromAvailable')" + v-model="params.isExcludedFromAvailable" + toggle-indeterminate + > + <QTooltip> + {{ t('params.isExcludedFromAvailable') }} + </QTooltip> + </QCheckbox> + </QItemSection> + <QItemSection> + <QCheckbox + :label="t('params.isOrdered')" + v-model="params.isOrdered" + toggle-indeterminate + > + <QTooltip> + {{ t('entry.list.tableVisibleColumns.isOrdered') }} + </QTooltip> + </QCheckbox> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.reference" - :label="t('entryFilter.params.reference')" - is-outlined - /> + <QCheckbox + :label="t('params.isReceived')" + v-model="params.isReceived" + toggle-indeterminate + > + <QTooltip> + {{ t('entry.list.tableVisibleColumns.isReceived') }} + </QTooltip> + </QCheckbox> + </QItemSection> + <QItemSection> + <QCheckbox + :label="t('entry.list.tableVisibleColumns.isConfirmed')" + v-model="params.isConfirmed" + toggle-indeterminate + > + <QTooltip> + {{ t('entry.list.tableVisibleColumns.isConfirmed') }} + </QTooltip> + </QCheckbox> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.invoiceNumber" - :label="t('entryFilter.params.invoiceNumber')" - is-outlined - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnInput - v-model="params.travelFk" - :label="t('entryFilter.params.travelFk')" - is-outlined - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnSelect - :label="t('entryFilter.params.companyFk')" - v-model="params.companyFk" + <VnInputDate + :label="t('params.landed')" + v-model="params.landed" @update:model-value="searchFn()" - :options="companiesOptions" - option-value="id" - option-label="code" - hide-selected - dense - outlined - rounded + is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnSelect - :label="t('entryFilter.params.currencyFk')" - v-model="params.currencyFk" - @update:model-value="searchFn()" - :options="currenciesOptions" - option-value="id" - option-label="name" - hide-selected - dense - outlined - rounded - /> + <VnInput v-model="params.id" label="Id" is-outlined /> </QItemSection> </QItem> <QItem> @@ -125,62 +121,165 @@ const companiesOptions = ref([]); rounded /> </QItemSection> - </QItem> - <QItem> <QItemSection> - <VnInputDate - :label="t('entryFilter.params.created')" - v-model="params.created" - @update:model-value="searchFn()" + <VnInput + v-model="params.invoiceNumber" + :label="t('params.invoiceNumber')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - :label="t('entryFilter.params.from')" - v-model="params.from" - @update:model-value="searchFn()" + <VnInput + v-model="params.reference" + :label="t('entry.list.tableVisibleColumns.reference')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - :label="t('entryFilter.params.to')" - v-model="params.to" + <VnSelect + :label="t('params.agencyModeId')" + v-model="params.agencyModeId" @update:model-value="searchFn()" + url="AgencyModes" + :fields="['id', 'name']" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.evaNotes" + :label="t('params.evaNotes')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <QCheckbox - :label="t('entryFilter.params.isBooked')" - v-model="params.isBooked" - toggle-indeterminate - /> - </QItemSection> - <QItemSection> - <QCheckbox - :label="t('entryFilter.params.isConfirmed')" - v-model="params.isConfirmed" - toggle-indeterminate + <VnSelect + :label="t('params.warehouseOutFk')" + v-model="params.warehouseOutFk" + @update:model-value="searchFn()" + url="Warehouses" + :fields="['id', 'name']" + hide-selected + dense + outlined + rounded /> </QItemSection> </QItem> <QItem> <QItemSection> - <QCheckbox - :label="t('entryFilter.params.isOrdered')" - v-model="params.isOrdered" - toggle-indeterminate + <VnSelect + :label="t('params.warehouseInFk')" + v-model="params.warehouseInFk" + @update:model-value="searchFn()" + url="Warehouses" + :fields="['id', 'name']" + hide-selected + dense + outlined + rounded + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt?.name }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id} , ${scope.opt?.nickname}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.invoiceNumber" + :label="t('params.invoiceNumber')" + is-outlined + /> + </QItemSection> + </QItem> + + <QItem> + <QItemSection> + <VnSelect + :label="t('params.entryTypeCode')" + v-model="params.entryTypeCode" + @update:model-value="searchFn()" + url="EntryTypes" + :fields="['code', 'description']" + option-value="code" + option-label="description" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.evaNotes" + :label="t('params.evaNotes')" + is-outlined /> </QItemSection> </QItem> </template> </VnFilterPanel> </template> + +<i18n> +en: + params: + isExcludedFromAvailable: Inventory + isOrdered: Ordered + isReceived: Received + isConfirmed: Confirmed + isRaid: Raid + landed: Date + id: Id + supplierFk: Supplier + invoiceNumber: Invoice number + reference: Ref/Alb/Guide + agencyModeId: Agency mode + evaNotes: Notes + warehouseOutFk: Origin + warehouseInFk: Destiny + entryTypeCode: Entry type + hasToShowDeletedEntries: Show deleted entries +es: + params: + isExcludedFromAvailable: Inventario + isOrdered: Pedida + isConfirmed: Confirmado + isReceived: Recibida + isRaid: Raid + landed: Fecha + id: Id + supplierFk: Proveedor + invoiceNumber: Núm. factura + reference: Ref/Alb/Guía + agencyModeId: Modo agencia + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeCode: Tipo de entrada + hasToShowDeletedEntries: Mostrar entradas eliminadas +</i18n> diff --git a/src/pages/Entry/EntryLatestBuysFilter.vue b/src/pages/Entry/EntryLatestBuysFilter.vue index 235f29dfb..658ba3847 100644 --- a/src/pages/Entry/EntryLatestBuysFilter.vue +++ b/src/pages/Entry/EntryLatestBuysFilter.vue @@ -1,8 +1,6 @@ <script setup> import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; - -import FetchData from 'components/FetchData.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; import VnSelect from 'components/common/VnSelect.vue'; @@ -18,18 +16,10 @@ defineProps({ }, }); -const itemTypeWorkersOptions = ref([]); const tagValues = ref([]); </script> <template> - <FetchData - url="TicketRequests/getItemTypeWorker" - limit="30" - auto-load - :filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }" - @on-fetch="(data) => (itemTypeWorkersOptions = data)" - /> <ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']"> <template #body="{ params, searchFn }"> <QItem class="q-my-md"> @@ -37,9 +27,10 @@ const tagValues = ref([]); <VnSelect :label="t('components.itemsFilterPanel.salesPersonFk')" v-model="params.salesPersonFk" - :options="itemTypeWorkersOptions" - option-value="id" + url="TicketRequests/getItemTypeWorker" option-label="nickname" + :fields="['id', 'nickname']" + sort-by="nickname ASC" dense outlined rounded @@ -52,8 +43,9 @@ const tagValues = ref([]); <QItemSection> <VnSelectSupplier v-model="params.supplierFk" - @update:model-value="searchFn()" - hide-selected + url="Suppliers" + :fields="['id', 'name', 'nickname']" + sort-by="name ASC" dense outlined rounded diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index 3172c6d0e..3c96a2302 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -1,21 +1,25 @@ <script setup> +import axios from 'axios'; +import VnSection from 'src/components/common/VnSection.vue'; import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; +import { useState } from 'src/composables/useState'; +import { onBeforeMount } from 'vue'; + import EntryFilter from './EntryFilter.vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import { toCelsius, toDate } from 'src/filters'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import EntrySummary from './Card/EntrySummary.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; -import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; -import VnSection from 'src/components/common/VnSection.vue'; +import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; +import { toDate } from 'src/filters'; const { t } = useI18n(); const tableRef = ref(); +const defaultEntry = ref({}); +const state = useState(); +const user = state.getUser(); const dataKey = 'EntryList'; -const { viewSummary } = useSummaryDialog(); -const entryFilter = { +const entryQueryFilter = { include: [ { relation: 'suppliers', @@ -40,44 +44,58 @@ const entryFilter = { const columns = computed(() => [ { - name: 'status', - columnFilter: false, + labelAbbreviation: 'Ex', + label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), + toolTip: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), + name: 'isExcludedFromAvailable', + component: 'checkbox', + width: '35px', }, { - align: 'left', - label: t('globals.id'), - name: 'id', - isId: true, - chip: { - condition: () => true, - }, + labelAbbreviation: 'Pe', + label: t('entry.list.tableVisibleColumns.isOrdered'), + toolTip: t('entry.list.tableVisibleColumns.isOrdered'), + name: 'isOrdered', + component: 'checkbox', + width: '35px', }, { - align: 'left', - label: t('globals.reference'), - name: 'reference', - isTitle: true, - component: 'input', - columnField: { - component: null, - }, - create: true, - cardVisible: true, + labelAbbreviation: 'LE', + label: t('entry.list.tableVisibleColumns.isConfirmed'), + toolTip: t('entry.list.tableVisibleColumns.isConfirmed'), + name: 'isConfirmed', + component: 'checkbox', + width: '35px', }, { - align: 'left', - label: t('entry.list.tableVisibleColumns.created'), - name: 'created', - create: true, - cardVisible: true, + labelAbbreviation: 'Re', + label: t('entry.list.tableVisibleColumns.isReceived'), + toolTip: t('entry.list.tableVisibleColumns.isReceived'), + name: 'isReceived', + component: 'checkbox', + width: '35px', + }, + { + label: t('entry.list.tableVisibleColumns.landed'), + name: 'landed', component: 'date', columnField: { component: null, }, - format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.created)), + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)), + width: '105px', + }, + { + label: t('globals.id'), + name: 'id', + isId: true, + component: 'number', + chip: { + condition: () => true, + }, + width: '50px', }, { - align: 'left', label: t('entry.list.tableVisibleColumns.supplierFk'), name: 'supplierFk', create: true, @@ -86,165 +104,213 @@ const columns = computed(() => [ attrs: { url: 'suppliers', fields: ['id', 'name'], - }, - columnField: { - component: null, + where: { order: 'name DESC' }, }, format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), + width: '110px', }, { align: 'left', - label: t('entry.list.tableVisibleColumns.isBooked'), - name: 'isBooked', + label: t('entry.list.tableVisibleColumns.invoiceNumber'), + name: 'invoiceNumber', + component: 'input', + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.reference'), + name: 'reference', + isTitle: true, + component: 'input', + columnField: { + component: null, + }, cardVisible: true, - create: true, - component: 'checkbox', }, { align: 'left', - label: t('entry.list.tableVisibleColumns.isConfirmed'), - name: 'isConfirmed', + label: 'AWB', + name: 'awbCode', + component: 'input', + width: '100px', + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.agencyModeId'), + name: 'agencyModeId', cardVisible: true, - create: true, - component: 'checkbox', + component: 'select', + attrs: { + url: 'agencyModes', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.agencyModeName), }, { align: 'left', - label: t('entry.list.tableVisibleColumns.isOrdered'), - name: 'isOrdered', + label: t('entry.list.tableVisibleColumns.evaNotes'), + name: 'evaNotes', + component: 'input', + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.warehouseOutFk'), + name: 'warehouseOutFk', cardVisible: true, - create: true, - component: 'checkbox', + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseOutName), + width: '65px', }, { align: 'left', - label: t('entry.list.tableVisibleColumns.companyFk'), + label: t('entry.list.tableVisibleColumns.warehouseInFk'), + name: 'warehouseInFk', + cardVisible: true, + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseInName), + width: '65px', + }, + { + align: 'left', + labelAbbreviation: t('Type'), + label: t('entry.list.tableVisibleColumns.entryTypeDescription'), + toolTip: t('entry.list.tableVisibleColumns.entryTypeDescription'), + name: 'entryTypeCode', + component: 'select', + attrs: { + url: 'entryTypes', + fields: ['code', 'description'], + optionValue: 'code', + optionLabel: 'description', + }, + width: '65px', + format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription), + }, + { name: 'companyFk', + label: t('entry.list.tableVisibleColumns.companyFk'), + cardVisible: false, + visible: false, + create: true, component: 'select', attrs: { - url: 'companies', - fields: ['id', 'code'], + optionValue: 'id', optionLabel: 'code', - optionValue: 'id', + url: 'Companies', }, - columnField: { - component: null, - }, - create: true, - - format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode), }, { - align: 'left', - label: t('entry.list.tableVisibleColumns.travelFk'), name: 'travelFk', - component: 'select', - attrs: { - url: 'travels', - fields: ['id', 'ref'], - optionLabel: 'ref', - optionValue: 'id', - }, - columnField: { - component: null, - }, + label: t('entry.list.tableVisibleColumns.travelFk'), + cardVisible: false, + visible: false, create: true, - format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), - }, - { - align: 'left', - label: t('entry.list.tableVisibleColumns.invoiceAmount'), - name: 'invoiceAmount', - cardVisible: true, - }, - { - align: 'left', - name: 'initialTemperature', - label: t('entry.basicData.initialTemperature'), - field: 'initialTemperature', - format: (row) => toCelsius(row.initialTemperature), - }, - { - align: 'left', - name: 'finalTemperature', - label: t('entry.basicData.finalTemperature'), - field: 'finalTemperature', - format: (row) => toCelsius(row.finalTemperature), - }, - { - label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), - name: 'isExcludedFromAvailable', - columnFilter: { - inWhere: true, - }, - }, - { - align: 'right', - name: 'tableActions', - actions: [ - { - title: t('components.smartCard.viewSummary'), - icon: 'preview', - action: (row) => viewSummary(row.id, EntrySummary), - isPrimary: true, - }, - ], }, ]); +function getBadgeAttrs(row) { + const date = row.landed; + let today = Date.vnNew(); + today.setHours(0, 0, 0, 0); + let timeTicket = new Date(date); + timeTicket.setHours(0, 0, 0, 0); + + let timeDiff = today - timeTicket; + + if (timeDiff > 0) return { color: 'info', 'text-color': 'black' }; + if (timeDiff < 0) return { color: 'warning', 'text-color': 'black' }; + switch (row.entryTypeCode) { + case 'regularization': + case 'life': + case 'internal': + case 'inventory': + if (!row.isOrdered || !row.isConfirmed) + return { color: 'negative', 'text-color': 'black' }; + break; + case 'product': + case 'packaging': + case 'devaluation': + case 'payment': + case 'transport': + if ( + row.invoiceAmount === null || + (row.invoiceNumber === null && row.reference === null) || + !row.isOrdered || + !row.isConfirmed + ) + return { color: 'negative', 'text-color': 'black' }; + break; + default: + break; + } + return { color: 'transparent' }; +} + +onBeforeMount(async () => { + defaultEntry.value = (await axios.get('EntryConfigs/findOne')).data; +}); </script> <template> <VnSection :data-key="dataKey" - :columns="columns" prefix="entry" url="Entries/filter" :array-data-props="{ url: 'Entries/filter', - order: 'id DESC', - userFilter: entryFilter, + order: 'landed DESC', + userFilter: EntryFilter, }" > <template #advanced-menu> - <EntryFilter data-key="EntryList" /> + <EntryFilter :data-key="dataKey" /> </template> <template #body> <VnTable + v-if="defaultEntry.defaultSupplierFk" ref="tableRef" :data-key="dataKey" + url="Entries/filter" + :filter="entryQueryFilter" + order="landed DESC" :create="{ urlCreate: 'Entries', - title: t('entry.list.newEntry'), + title: t('Create entry'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, + formInitialData: { + supplierFk: defaultEntry.defaultSupplierFk, + dated: Date.vnNew(), + companyFk: user?.companyFk, + }, }" :columns="columns" redirect="entry" :right-search="false" > - <template #column-status="{ row }"> - <div class="row q-gutter-xs"> - <QIcon - v-if="!!row.isExcludedFromAvailable" - name="vn:inventory" - color="primary" - > - <QTooltip>{{ - t( - 'entry.list.tableVisibleColumns.isExcludedFromAvailable', - ) - }}</QTooltip> - </QIcon> - <QIcon v-if="!!row.isRaid" name="vn:net" color="primary"> - <QTooltip> - {{ - t('globals.raid', { - daysInForward: row.daysInForward, - }) - }}</QTooltip - > - </QIcon> - </div> + <template #column-landed="{ row }"> + <QBadge + v-if="row?.travelFk" + v-bind="getBadgeAttrs(row)" + class="q-pa-sm" + style="font-size: 14px" + > + {{ toDate(row.landed) }} + </QBadge> </template> <template #column-supplierFk="{ row }"> <span class="link" @click.stop> @@ -252,13 +318,27 @@ const columns = computed(() => [ <SupplierDescriptorProxy :id="row.supplierFk" /> </span> </template> - <template #column-travelFk="{ row }"> - <span class="link" @click.stop> - {{ row.travelRef }} - <TravelDescriptorProxy :id="row.travelFk" /> - </span> + <template #column-create-travelFk="{ data }"> + <VnSelectTravelExtended + :data="data" + v-model="data.travelFk" + :onFilterTravelSelected=" + (data, result) => (data.travelFk = result) + " + data-cy="entry-travel-select" + /> </template> </VnTable> </template> </VnSection> </template> + +<i18n> +es: + Inventory entry: Es inventario + Virtual entry: Es una redada + Search entries: Buscar entradas + You can search by entry reference: Puedes buscar por referencia de la entrada + Create entry: Crear entrada + Type: Tipo +</i18n> diff --git a/src/pages/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue index fa0bdc12e..4bd0fe640 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -34,18 +34,20 @@ const columns = computed(() => [ label: t('entryStockBought.buyer'), isTitle: true, component: 'select', + isEditable: false, cardVisible: true, create: true, attrs: { url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name'], + fields: ['id', 'name', 'nickname'], where: { role: 'buyer' }, optionFilter: 'firstName', - optionLabel: 'name', + optionLabel: 'nickname', optionValue: 'id', useLike: false, }, columnFilter: false, + width: '70px', }, { align: 'center', @@ -55,6 +57,7 @@ const columns = computed(() => [ create: true, component: 'number', summation: true, + width: '50px', }, { align: 'center', @@ -78,6 +81,7 @@ const columns = computed(() => [ actions: [ { title: t('entryStockBought.viewMoreDetails'), + name: 'searchBtn', icon: 'search', isPrimary: true, action: (row) => { @@ -91,6 +95,7 @@ const columns = computed(() => [ }, }, ], + 'data-cy': 'table-actions', }, ]); @@ -158,7 +163,7 @@ function round(value) { @on-fetch=" (data) => { travel = data.find( - (data) => data.warehouseIn?.code.toLowerCase() === 'vnh' + (data) => data.warehouseIn?.code.toLowerCase() === 'vnh', ); } " @@ -179,6 +184,7 @@ function round(value) { @click="openDialog()" :title="t('entryStockBought.editTravel')" color="primary" + data-cy="edit-travel" /> </div> </VnRow> @@ -239,10 +245,11 @@ function round(value) { table-height="80vh" auto-load :column-search="false" + :without-header="true" > <template #column-workerFk="{ row }"> <span class="link" @click.stop> - {{ row?.worker?.user?.name }} + {{ row?.worker?.user?.nickname }} <WorkerDescriptorProxy :id="row?.workerFk" /> </span> </template> @@ -279,10 +286,11 @@ function round(value) { justify-content: center; } .column { + min-width: 40%; + margin-top: 5%; display: flex; flex-direction: column; align-items: center; - min-width: 35%; } .text-negative { color: $negative !important; diff --git a/src/pages/Entry/EntryStockBoughtDetail.vue b/src/pages/Entry/EntryStockBoughtDetail.vue index 812171825..1a37994d9 100644 --- a/src/pages/Entry/EntryStockBoughtDetail.vue +++ b/src/pages/Entry/EntryStockBoughtDetail.vue @@ -21,7 +21,7 @@ const $props = defineProps({ const customUrl = `StockBoughts/getStockBoughtDetail?workerFk=${$props.workerFk}&dated=${$props.dated}`; const columns = [ { - align: 'left', + align: 'right', label: t('Entry'), name: 'entryFk', isTitle: true, @@ -29,7 +29,7 @@ const columns = [ columnFilter: false, }, { - align: 'left', + align: 'right', name: 'itemFk', label: t('Item'), columnFilter: false, @@ -44,21 +44,21 @@ const columns = [ cardVisible: true, }, { - align: 'left', + align: 'right', name: 'volume', label: t('Volume'), columnFilter: false, cardVisible: true, }, { - align: 'left', + align: 'right', label: t('Packaging'), name: 'packagingFk', columnFilter: false, cardVisible: true, }, { - align: 'left', + align: 'right', label: 'Packing', name: 'packing', columnFilter: false, @@ -73,12 +73,14 @@ const columns = [ ref="tableRef" data-key="StockBoughtsDetail" :url="customUrl" - order="itemName DESC" + order="volume DESC" :columns="columns" :right-search="false" :disable-infinite-scroll="true" :disable-option="{ card: true }" :limit="0" + :without-header="true" + :with-filters="false" auto-load > <template #column-entryFk="{ row }"> @@ -99,16 +101,14 @@ const columns = [ </template> <style lang="css" scoped> .container { - max-width: 50vw; + max-width: 100%; + width: 50%; overflow: auto; justify-content: center; align-items: center; margin: auto; background-color: var(--vn-section-color); - padding: 4px; -} -.container > div > div > .q-table__top.relative-position.row.items-center { - background-color: red !important; + padding: 2%; } </style> <i18n> diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index 80f3491a8..88b16cb03 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -1,21 +1,36 @@ entry: + lock: + title: Lock entry + message: This entry has been locked by {userName} for {time} minutes. Do you want to unlock it? + success: The entry has been locked successfully list: newEntry: New entry tableVisibleColumns: - created: Creation - supplierFk: Supplier - isBooked: Booked - isConfirmed: Confirmed + isExcludedFromAvailable: Exclude from inventory isOrdered: Ordered + isConfirmed: Ready to label + isReceived: Received + isRaid: Raid + landed: Date + supplierFk: Supplier + reference: Ref/Alb/Guide + invoiceNumber: Invoice + agencyModeId: Agency + isBooked: Booked companyFk: Company - travelFk: Travel - isExcludedFromAvailable: Inventory + evaNotes: Notes + warehouseOutFk: Origin + warehouseInFk: Destiny + entryTypeDescription: Entry type invoiceAmount: Import + travelFk: Travel + dated: Dated inventoryEntry: Inventory entry summary: commission: Commission currency: Currency invoiceNumber: Invoice number + invoiceAmount: Invoice amount ordered: Ordered booked: Booked excludedFromAvailable: Inventory @@ -33,6 +48,7 @@ entry: buyingValue: Buying value import: Import pvp: PVP + entryType: Entry type basicData: travel: Travel currency: Currency @@ -69,17 +85,55 @@ entry: landing: Landing isExcludedFromAvailable: Es inventory params: - toShipped: To - fromShipped: From - daysOnward: Days onward - daysAgo: Days ago - warehouseInFk: Warehouse in + isExcludedFromAvailable: Exclude from inventory + isOrdered: Ordered + isConfirmed: Ready to label + isReceived: Received + isIgnored: Ignored + isRaid: Raid + landed: Date + supplierFk: Supplier + reference: Ref/Alb/Guide + invoiceNumber: Invoice + agencyModeId: Agency + isBooked: Booked + companyFk: Company + evaNotes: Notes + warehouseOutFk: Origin + warehouseInFk: Destiny + entryTypeDescription: Entry type + invoiceAmount: Import + travelFk: Travel + dated: Dated + itemFk: Item id + hex: Color + name: Item name + size: Size + stickers: Stickers + packagingFk: Packaging + weight: Kg + groupingMode: Grouping selector + grouping: Grouping + quantity: Quantity + buyingValue: Buying value + price2: Package + price3: Box + minPrice: Minumum price + hasMinPrice: Has minimum price + packingOut: Packing out + comment: Comment + subName: Supplier name + tags: Tags + company_name: Company name + itemTypeFk: Item type + workerFk: Worker id search: Search entries searchInfo: You can search by entry reference descriptorMenu: showEntryReport: Show entry report entryFilter: params: + isExcludedFromAvailable: Exclude from inventory invoiceNumber: Invoice number travelFk: Travel companyFk: Company @@ -91,8 +145,16 @@ entryFilter: isBooked: Booked isConfirmed: Confirmed isOrdered: Ordered + isReceived: Received search: General search reference: Reference + landed: Landed + id: Id + agencyModeId: Agency + evaNotes: Notes + warehouseOutFk: Origin + warehouseInFk: Destiny + entryTypeCode: Entry type myEntries: id: ID landed: Landed diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index a5b968016..3025d64cb 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -1,21 +1,36 @@ entry: + lock: + title: Entrada bloqueada + message: Esta entrada ha sido bloqueada por {userName} hace {time} minutos. ¿Quieres desbloquearla? + success: La entrada ha sido bloqueada correctamente list: newEntry: Nueva entrada tableVisibleColumns: - created: Creación - supplierFk: Proveedor - isBooked: Asentado - isConfirmed: Confirmado + isExcludedFromAvailable: Excluir del inventario isOrdered: Pedida + isConfirmed: Lista para etiquetar + isReceived: Recibida + isRaid: Redada + landed: Fecha + supplierFk: Proveedor + invoiceNumber: Nº Factura + reference: Ref/Alb/Guía + agencyModeId: Agencia + isBooked: Asentado companyFk: Empresa travelFk: Envio - isExcludedFromAvailable: Inventario + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeDescription: Tipo entrada invoiceAmount: Importe + dated: Fecha inventoryEntry: Es inventario summary: commission: Comisión currency: Moneda invoiceNumber: Núm. factura + invoiceAmount: Importe ordered: Pedida booked: Contabilizada excludedFromAvailable: Inventario @@ -34,12 +49,13 @@ entry: buyingValue: Coste import: Importe pvp: PVP + entryType: Tipo entrada basicData: travel: Envío currency: Moneda observation: Observación commission: Comisión - booked: Asentado + booked: Contabilizada excludedFromAvailable: Inventario initialTemperature: Ini °C finalTemperature: Fin °C @@ -69,31 +85,70 @@ entry: packingOut: Embalaje envíos landing: Llegada isExcludedFromAvailable: Es inventario - params: - toShipped: Hasta - fromShipped: Desde - warehouseInFk: Alm. entrada - daysOnward: Días adelante - daysAgo: Días atras - descriptorMenu: - showEntryReport: Ver informe del pedido + search: Buscar entradas searchInfo: Puedes buscar por referencia de entrada + params: + isExcludedFromAvailable: Excluir del inventario + isOrdered: Pedida + isConfirmed: Lista para etiquetar + isReceived: Recibida + isRaid: Redada + isIgnored: Ignorado + landed: Fecha + supplierFk: Proveedor + invoiceNumber: Nº Factura + reference: Ref/Alb/Guía + agencyModeId: Agencia + isBooked: Asentado + companyFk: Empresa + travelFk: Envio + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeDescription: Tipo entrada + invoiceAmount: Importe + dated: Fecha + itemFk: Id artículo + hex: Color + name: Nombre artículo + size: Medida + stickers: Etiquetas + packagingFk: Embalaje + weight: Kg + groupinMode: Selector de grouping + grouping: Grouping + quantity: Quantity + buyingValue: Precio de compra + price2: Paquete + price3: Caja + minPrice: Precio mínimo + hasMinPrice: Tiene precio mínimo + packingOut: Packing out + comment: Referencia + subName: Nombre proveedor + tags: Etiquetas + company_name: Nombre empresa + itemTypeFk: Familia + workerFk: Comprador entryFilter: params: - invoiceNumber: Núm. factura - travelFk: Envío - companyFk: Empresa - currencyFk: Moneda - supplierFk: Proveedor - from: Desde - to: Hasta - created: Fecha creación - isBooked: Asentado - isConfirmed: Confirmado + isExcludedFromAvailable: Inventario isOrdered: Pedida - search: Búsqueda general - reference: Referencia + isConfirmed: Confirmado + isReceived: Recibida + isRaid: Raid + landed: Fecha + id: Id + supplierFk: Proveedor + invoiceNumber: Núm. factura + reference: Ref/Alb/Guía + agencyModeId: Modo agencia + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeCode: Tipo de entrada + hasToShowDeletedEntries: Mostrar entradas eliminadas myEntries: id: ID landed: F. llegada diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index a3beabdb6..905ddebb2 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -125,7 +125,7 @@ function deleteFile(dmsFk) { <VnInput clearable clear-icon="close" - :label="t('Supplier ref')" + :label="t('invoiceIn.supplierRef')" v-model="data.supplierRef" /> </VnRow> @@ -149,6 +149,7 @@ function deleteFile(dmsFk) { option-value="id" option-label="id" :filter-options="['id', 'name']" + data-cy="UnDeductibleVatSelect" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -310,7 +311,6 @@ function deleteFile(dmsFk) { supplierFk: Supplier es: supplierFk: Proveedor - Supplier ref: Ref. proveedor Expedition date: Fecha expedición Operation date: Fecha operación Undeductible VAT: Iva no deducible diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index acd55c0fa..3843f5bf7 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -90,7 +90,6 @@ async function setInvoiceCorrection(id) { <template> <CardDescriptor ref="cardDescriptorRef" - module="InvoiceIn" data-key="InvoiceIn" :url="`InvoiceIns/${entityId}`" :filter="filter" diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue index c3ab635c8..8b039ec27 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue @@ -186,7 +186,7 @@ const createInvoiceInCorrection = async () => { clickable @click="book(entityId)" > - <QItemSection>{{ t('invoiceIn.descriptorMenu.toBook') }}</QItemSection> + <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> </QItem> </template> </InvoiceInToBook> @@ -197,7 +197,7 @@ const createInvoiceInCorrection = async () => { @click="triggerMenu('unbook')" > <QItemSection> - {{ t('invoiceIn.descriptorMenu.toUnbook') }} + {{ t('invoiceIn.descriptorMenu.unbook') }} </QItemSection> </QItem> <QItem diff --git a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue index cb3271dc1..20cc1cc71 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, onBeforeMount } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import axios from 'axios'; @@ -7,11 +7,11 @@ import { toDate } from 'src/filters'; import { useArrayData } from 'src/composables/useArrayData'; import { getTotal } from 'src/composables/getTotal'; import CrudModel from 'src/components/CrudModel.vue'; -import FetchData from 'src/components/FetchData.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import useNotify from 'src/composables/useNotify.js'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import { toCurrency } from 'filters/index'; const route = useRoute(); const { notify } = useNotify(); @@ -21,12 +21,11 @@ const invoiceIn = computed(() => arrayData.store.data); const currency = computed(() => invoiceIn.value?.currency?.code); const rowsSelected = ref([]); -const banks = ref([]); const invoiceInFormRef = ref(); const invoiceId = +route.params.id; const filter = { where: { invoiceInFk: invoiceId } }; const areRows = ref(false); - +const totals = ref(); const columns = computed(() => [ { name: 'duedate', @@ -40,10 +39,9 @@ const columns = computed(() => [ name: 'bank', label: t('Bank'), field: (row) => row.bankFk, - options: banks.value, model: 'bankFk', - optionValue: 'id', optionLabel: 'bank', + url: 'Accountings', sortable: true, tabIndex: 2, align: 'left', @@ -66,6 +64,8 @@ const columns = computed(() => [ }, ]); +const totalAmount = computed(() => getTotal(invoiceInFormRef.value.formData, 'amount')); + const isNotEuro = (code) => code != 'EUR'; async function insert() { @@ -73,14 +73,12 @@ async function insert() { await invoiceInFormRef.value.reload(); notify(t('globals.dataSaved'), 'positive'); } + +onBeforeMount(async () => { + totals.value = (await axios.get(`InvoiceIns/${invoiceId}/getTotals`)).data; +}); </script> <template> - <FetchData - url="Accountings" - auto-load - limit="30" - @on-fetch="(data) => (banks = data)" - /> <CrudModel v-if="invoiceIn" ref="invoiceInFormRef" @@ -110,9 +108,9 @@ async function insert() { <QTd> <VnSelect v-model="row[col.model]" - :options="col.options" - :option-value="col.optionValue" + :url="col.url" :option-label="col.optionLabel" + :option-value="col.optionValue" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -153,7 +151,7 @@ async function insert() { <QTd /> <QTd /> <QTd> - {{ getTotal(rows, 'amount', { currency: 'default' }) }} + {{ toCurrency(totalAmount) }} </QTd> <QTd> <template v-if="isNotEuro(invoiceIn.currency.code)"> @@ -186,8 +184,7 @@ async function insert() { :label="t('Bank')" class="full-width" v-model="props.row['bankFk']" - :options="banks" - option-value="id" + url="Accountings" option-label="bank" > <template #option="scope"> @@ -235,7 +232,16 @@ async function insert() { v-shortcut="'+'" size="lg" round - @click="!areRows ? insert() : invoiceInFormRef.insert()" + @click=" + () => { + if (!areRows) insert(); + else + invoiceInFormRef.insert({ + amount: (totals.totalTaxableBase - totalAmount).toFixed(2), + invoiceInFk: invoiceId, + }); + } + " /> </QPageSticky> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index e546638f2..d358601d3 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -193,7 +193,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <InvoiceIntoBook> <template #content="{ book }"> <QBtn - :label="t('To book')" + :label="t('Book')" color="orange-11" text-color="black" @click="book(entityId)" @@ -224,10 +224,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </span> </template> </VnLv> - <VnLv - :label="t('invoiceIn.list.supplierRef')" - :value="entity.supplierRef" - /> + <VnLv :label="t('invoiceIn.supplierRef')" :value="entity.supplierRef" /> <VnLv :label="t('invoiceIn.summary.currency')" :value="entity.currency?.code" @@ -357,7 +354,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; entity.totals.totalTaxableBaseForeignValue && toCurrency( entity.totals.totalTaxableBaseForeignValue, - currency + currency, ) }}</QTd> </QTr> @@ -392,7 +389,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; entity.totals.totalDueDayForeignValue && toCurrency( entity.totals.totalDueDayForeignValue, - currency + currency, ) }} </QTd> @@ -472,5 +469,5 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; Search invoice: Buscar factura recibida You can search by invoice reference: Puedes buscar por referencia de la factura Totals: Totales - To book: Contabilizar + Book: Contabilizar </i18n> diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index edb43375f..e77453bc0 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, nextTick } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; @@ -25,7 +25,6 @@ const sageTaxTypes = ref([]); const sageTransactionTypes = ref([]); const rowsSelected = ref([]); const invoiceInFormRef = ref(); -const expenseRef = ref(); defineProps({ actionIcon: { @@ -97,6 +96,20 @@ const columns = computed(() => [ }, ]); +const taxableBaseTotal = computed(() => { + return getTotal(invoiceInFormRef.value.formData, 'taxableBase'); +}); + +const taxRateTotal = computed(() => { + return getTotal(invoiceInFormRef.value.formData, null, { + cb: taxRate, + }); +}); + +const combinedTotal = computed(() => { + return +taxableBaseTotal.value + +taxRateTotal.value; +}); + const filter = { fields: [ 'id', @@ -125,7 +138,7 @@ function taxRate(invoiceInTax) { return ((taxTypeSage / 100) * taxableBase).toFixed(2); } -function autocompleteExpense(evt, row, col) { +function autocompleteExpense(evt, row, col, ref) { const val = evt.target.value; if (!val) return; @@ -134,22 +147,17 @@ function autocompleteExpense(evt, row, col) { ({ id }) => id == useAccountShortToStandard(param), ); - expenseRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup); + ref.vnSelectDialogRef.vnSelectRef.toggleOption(lookup); } -const taxableBaseTotal = computed(() => { - return getTotal(invoiceInFormRef.value.formData, 'taxableBase'); -}); - -const taxRateTotal = computed(() => { - return getTotal(invoiceInFormRef.value.formData, null, { - cb: taxRate, +function setCursor(ref) { + nextTick(() => { + const select = ref.vnSelectDialogRef + ? ref.vnSelectDialogRef.vnSelectRef + : ref.vnSelectRef; + select.$el.querySelector('input').setSelectionRange(0, 0); }); -}); - -const combinedTotal = computed(() => { - return +taxableBaseTotal.value + +taxRateTotal.value; -}); +} </script> <template> <FetchData @@ -187,14 +195,24 @@ const combinedTotal = computed(() => { <template #body-cell-expense="{ row, col }"> <QTd> <VnSelectDialog - ref="expenseRef" + :ref="`expenseRef-${row.$index}`" v-model="row[col.model]" :options="col.options" :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" - @keydown.tab="autocompleteExpense($event, row, col)" + @keydown.tab=" + autocompleteExpense( + $event, + row, + col, + $refs[`expenseRef-${row.$index}`], + ) + " + @update:model-value=" + setCursor($refs[`expenseRef-${row.$index}`]) + " > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -210,7 +228,7 @@ const combinedTotal = computed(() => { </QTd> </template> <template #body-cell-taxablebase="{ row }"> - <QTd> + <QTd shrink> <VnInputNumber clear-icon="close" v-model="row.taxableBase" @@ -221,12 +239,16 @@ const combinedTotal = computed(() => { <template #body-cell-sageiva="{ row, col }"> <QTd> <VnSelect + :ref="`sageivaRef-${row.$index}`" v-model="row[col.model]" :options="col.options" :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'vat']" data-cy="vat-sageiva" + @update:model-value=" + setCursor($refs[`sageivaRef-${row.$index}`]) + " > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -244,11 +266,15 @@ const combinedTotal = computed(() => { <template #body-cell-sagetransaction="{ row, col }"> <QTd> <VnSelect + :ref="`sagetransactionRef-${row.$index}`" v-model="row[col.model]" :options="col.options" :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'transaction']" + @update:model-value=" + setCursor($refs[`sagetransactionRef-${row.$index}`]) + " > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -266,7 +292,7 @@ const combinedTotal = computed(() => { </QTd> </template> <template #body-cell-foreignvalue="{ row }"> - <QTd> + <QTd shrink> <VnInputNumber :class="{ 'no-pointer-events': !isNotEuro(currency), diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index e1723e3b1..0960d0d6c 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -29,6 +29,7 @@ const cols = computed(() => [ name: 'isBooked', label: t('invoiceIn.isBooked'), columnFilter: false, + component: 'checkbox', }, { align: 'left', @@ -56,7 +57,7 @@ const cols = computed(() => [ { align: 'left', name: 'supplierRef', - label: t('invoiceIn.list.supplierRef'), + label: t('invoiceIn.supplierRef'), }, { align: 'left', @@ -177,7 +178,7 @@ const cols = computed(() => [ :required="true" /> <VnInput - :label="t('invoiceIn.list.supplierRef')" + :label="t('invoiceIn.supplierRef')" v-model="data.supplierRef" /> <VnSelect diff --git a/src/pages/InvoiceIn/InvoiceInToBook.vue b/src/pages/InvoiceIn/InvoiceInToBook.vue index 95ce8155a..5bdbe197b 100644 --- a/src/pages/InvoiceIn/InvoiceInToBook.vue +++ b/src/pages/InvoiceIn/InvoiceInToBook.vue @@ -4,6 +4,7 @@ import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import { useArrayData } from 'src/composables/useArrayData'; +import qs from 'qs'; const { notify, dialog } = useQuasar(); const { t } = useI18n(); @@ -12,29 +13,51 @@ defineExpose({ checkToBook }); const { store } = useArrayData(); async function checkToBook(id) { - let directBooking = true; + let messages = []; + + const hasProblemWithTax = ( + await axios.get('InvoiceInTaxes/count', { + params: { + where: JSON.stringify({ + invoiceInFk: id, + or: [{ taxTypeSageFk: null }, { transactionTypeSageFk: null }], + }), + }, + }) + ).data?.count; + + if (hasProblemWithTax) + messages.push(t('The VAT and Transaction fields have not been informed')); const { data: totals } = await axios.get(`InvoiceIns/${id}/getTotals`); const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase; const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat; - if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) directBooking = false; + if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) + messages.push(t('The sum of the taxable bases does not match the due dates')); - const { data: dueDaysCount } = await axios.get('InvoiceInDueDays/count', { - where: { - invoiceInFk: id, - dueDated: { gte: Date.vnNew() }, - }, - }); + const dueDaysCount = ( + await axios.get('InvoiceInDueDays/count', { + params: { + where: JSON.stringify({ + invoiceInFk: id, + dueDated: { gte: Date.vnNew() }, + }), + }, + }) + ).data?.count; - if (dueDaysCount) directBooking = false; + if (dueDaysCount) messages.push(t('Some due dates are less than or equal to today')); - if (directBooking) return toBook(id); - - dialog({ - component: VnConfirm, - componentProps: { title: t('Are you sure you want to book this invoice?') }, - }).onOk(async () => await toBook(id)); + if (!messages.length) toBook(id); + else + dialog({ + component: VnConfirm, + componentProps: { + title: t('Are you sure you want to book this invoice?'), + message: messages.reduce((acc, msg) => `${acc}<p>${msg}</p>`, ''), + }, + }).onOk(() => toBook(id)); } async function toBook(id) { @@ -59,4 +82,7 @@ async function toBook(id) { es: Are you sure you want to book this invoice?: ¿Estás seguro de querer asentar esta factura? It was not able to book the invoice: No se pudo contabilizar la factura + Some due dates are less than or equal to today: Algún vencimiento tiene una fecha menor o igual que hoy + The sum of the taxable bases does not match the due dates: La suma de las bases imponibles no coincide con la de los vencimientos + The VAT and Transaction fields have not been informed: No se han informado los campos de iva y/o transacción </i18n> diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 6b21b316b..548e6c201 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -3,10 +3,10 @@ invoiceIn: searchInfo: Search incoming invoices by ID or supplier fiscal name serial: Serial isBooked: Is booked + supplierRef: Invoice nº list: ref: Reference supplier: Supplier - supplierRef: Supplier ref. file: File issued: Issued dueDated: Due dated @@ -19,8 +19,6 @@ invoiceIn: unbook: Unbook delete: Delete clone: Clone - toBook: To book - toUnbook: To unbook deleteInvoice: Delete invoice invoiceDeleted: invoice deleted cloneInvoice: Clone invoice @@ -70,4 +68,3 @@ invoiceIn: isBooked: Is booked account: Ledger account correctingFk: Rectificative - \ No newline at end of file diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 3f27c895c..142d95f92 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -3,10 +3,10 @@ invoiceIn: searchInfo: Buscar facturas recibidas por ID o nombre fiscal del proveedor serial: Serie isBooked: Contabilizada + supplierRef: Nº factura list: ref: Referencia supplier: Proveedor - supplierRef: Ref. proveedor issued: F. emisión dueDated: F. vencimiento file: Fichero @@ -15,12 +15,10 @@ invoiceIn: descriptor: ticketList: Listado de tickets descriptorMenu: - book: Asentar - unbook: Desasentar + book: Contabilizar + unbook: Descontabilizar delete: Eliminar clone: Clonar - toBook: Contabilizar - toUnbook: Descontabilizar deleteInvoice: Eliminar factura invoiceDeleted: Factura eliminada cloneInvoice: Clonar factura @@ -68,4 +66,3 @@ invoiceIn: isBooked: Contabilizada account: Cuenta contable correctingFk: Rectificativa - diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue index de614e9fc..dfaf6c109 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -36,7 +36,6 @@ function ticketFilter(invoice) { <template> <CardDescriptor ref="descriptor" - module="InvoiceOut" :url="`InvoiceOuts/${entityId}`" :filter="filter" title="ref" diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue index eb72563e1..1fd9f3e92 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue @@ -103,7 +103,7 @@ const refundInvoice = async (withWarehouse) => { t('refundInvoiceSuccessMessage', { refundTicket: data[0].id, }), - 'positive' + 'positive', ); }; @@ -124,6 +124,13 @@ const showRefundInvoiceForm = () => { }, }); }; + +const showExportationLetter = () => { + openReport(`InvoiceOuts/${$props.invoiceOutData.ref}/exportation-pdf`, { + recipientId: $props.invoiceOutData.client.id, + refFk: $props.invoiceOutData.ref, + }); +}; </script> <template> @@ -172,7 +179,7 @@ const showRefundInvoiceForm = () => { t('Confirm deletion'), t('Are you sure you want to delete this invoice?'), deleteInvoice, - redirectToInvoiceOutList + redirectToInvoiceOutList, ) " > @@ -185,7 +192,7 @@ const showRefundInvoiceForm = () => { openConfirmationModal( '', t('Are you sure you want to book this invoice?'), - bookInvoice + bookInvoice, ) " > @@ -198,7 +205,7 @@ const showRefundInvoiceForm = () => { openConfirmationModal( t('Generate PDF invoice document'), t('Are you sure you want to generate/regenerate the PDF invoice?'), - generateInvoicePdf + generateInvoicePdf, ) " > @@ -226,6 +233,14 @@ const showRefundInvoiceForm = () => { {{ t('Create a single ticket with all the content of the current invoice') }} </QTooltip> </QItem> + <QItem + v-if="$props.invoiceOutData.serial === 'E'" + v-ripple + clickable + @click="showExportationLetter()" + > + <QItemSection>{{ t('Show CITES letter') }}</QItemSection> + </QItem> </template> <i18n> @@ -255,7 +270,7 @@ es: Create a single ticket with all the content of the current invoice: Crear un ticket único con todo el contenido de la factura actual refundInvoiceSuccessMessage: Se ha creado el siguiente ticket de abono {refundTicket} The email can't be empty: El email no puede estar vacío - + Show CITES letter: Ver carta CITES en: refundInvoiceSuccessMessage: The following refund ticket have been created {refundTicket} </i18n> diff --git a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue index 3ceb447dd..161f2ab45 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue @@ -125,7 +125,7 @@ const ticketsColumns = ref([ :value="toDate(invoiceOut.issued)" /> <VnLv - :label="t('invoiceOut.summary.dued')" + :label="t('invoiceOut.summary.expirationDate')" :value="toDate(invoiceOut.dued)" /> <VnLv :label="t('globals.created')" :value="toDate(invoiceOut.created)" /> diff --git a/src/pages/InvoiceOut/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index cdc9f037a..648b8e4e6 100644 --- a/src/pages/InvoiceOut/InvoiceOutFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutFilter.vue @@ -22,7 +22,7 @@ const states = ref(); <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`invoiceOut.params.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> @@ -84,15 +84,6 @@ const states = ref(); /> </QItemSection> </QItem> - <QItem> - <QItemSection> - <VnInputDate - v-model="params.issued" - :label="t('Issued')" - is-outlined - /> - </QItemSection> - </QItem> <QItem> <QItemSection> <VnInputDate @@ -110,37 +101,3 @@ const states = ref(); </template> </VnFilterPanel> </template> - -<i18n> -en: - params: - search: Contains - clientFk: Customer - fi: FI - amount: Amount - min: Min - max: Max - hasPdf: Has PDF - issued: Issued - created: Created - dued: Dued -es: - params: - search: Contiene - clientFk: Cliente - fi: CIF - amount: Importe - min: Min - max: Max - hasPdf: Tiene PDF - issued: Emitida - created: Creada - dued: Vencida - Customer ID: ID cliente - FI: CIF - Amount: Importe - Has PDF: Tiene PDF - Issued: Fecha emisión - Created: Fecha creación - Dued: Fecha vencimiento -</i18n> diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index 9398ded64..873ab030f 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -71,14 +71,6 @@ const columns = computed(() => [ inWhere: true, }, }, - { - align: 'left', - name: 'issued', - label: t('invoiceOut.summary.issued'), - component: 'date', - format: (row) => toDate(row.issued), - columnField: { component: null }, - }, { align: 'left', name: 'clientFk', @@ -97,12 +89,19 @@ const columns = computed(() => [ }, { align: 'left', - name: 'companyCode', + name: 'companyFk', label: t('globals.company'), cardVisible: true, component: 'select', - attrs: { url: 'Companies', optionLabel: 'code', optionValue: 'id' }, - columnField: { component: null }, + attrs: { + url: 'Companies', + optionLabel: 'code', + optionValue: 'id', + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode), }, { align: 'left', diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue index 135eb9aca..b062678a0 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue @@ -10,6 +10,8 @@ import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vu import TicketDescriptorProxy from '../Ticket/Card/TicketDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; +import InvoiceOutNegativeBasesFilter from './InvoiceOutNegativeBasesFilter.vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; const { t } = useI18n(); const tableRef = ref(); @@ -97,16 +99,19 @@ const columns = computed(() => [ align: 'left', name: 'isActive', label: t('invoiceOut.negativeBases.active'), + component: 'checkbox', }, { align: 'left', name: 'hasToInvoice', label: t('invoiceOut.negativeBases.hasToInvoice'), + component: 'checkbox', }, { align: 'left', - name: 'hasVerifiedData', + name: 'isTaxDataChecked', label: t('invoiceOut.negativeBases.verifiedData'), + component: 'checkbox', }, { align: 'left', @@ -142,7 +147,7 @@ const downloadCSV = async () => { await invoiceOutGlobalStore.getNegativeBasesCsv( userParams.from, userParams.to, - filterParams + filterParams, ); }; </script> @@ -154,6 +159,11 @@ const downloadCSV = async () => { </QBtn> </template> </VnSubToolbar> + <RightMenu> + <template #right-panel> + <InvoiceOutNegativeBasesFilter data-key="negativeFilter" /> + </template> + </RightMenu> <VnTable ref="tableRef" data-key="negativeFilter" @@ -174,6 +184,7 @@ const downloadCSV = async () => { auto-load :is-editable="false" :use-model="true" + :right-search="false" > <template #column-clientId="{ row }"> <span class="link" @click.stop> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue index 6ceec61e4..cd9836bb7 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue @@ -2,9 +2,10 @@ import { useI18n } from 'vue-i18n'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; -import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const props = defineProps({ @@ -24,11 +25,11 @@ const props = defineProps({ > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`invoiceOut.params.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> - <template #body="{ params }"> + <template #body="{ params, searchFn }"> <QItem> <QItemSection> <VnInputDate @@ -49,38 +50,70 @@ const props = defineProps({ </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.company" + <VnSelect + url="Companies" :label="t('globals.company')" - is-outlined - /> + v-model="params.company" + option-label="code" + option-value="code" + dense + outlined + rounded + @update:model-value="searchFn()" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt?.code }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput + <VnSelect + url="Countries" + :label="t('globals.params.countryFk')" v-model="params.country" - :label="t('globals.country')" - is-outlined - /> - </QItemSection> - </QItem> - - <QItem> - <QItemSection> - <VnInput - v-model="params.clientId" - :label="t('invoiceOut.negativeBases.clientId')" - is-outlined - /> + option-label="name" + option-value="name" + outlined + dense + rounded + @update:model-value="searchFn()" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt?.name }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.clientSocialName" + <VnSelect + url="Clients" :label="t('globals.client')" - is-outlined + v-model="params.clientId" + outlined + dense + rounded + @update:model-value="searchFn()" /> </QItemSection> </QItem> @@ -90,15 +123,18 @@ const props = defineProps({ v-model="params.amount" :label="t('globals.amount')" is-outlined + :positive="false" /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.comercialName" + <VnSelectWorker :label="t('invoiceOut.negativeBases.comercial')" + v-model="params.workerName" + option-value="name" is-outlined + @update:model-value="searchFn()" /> </QItemSection> </QItem> diff --git a/src/pages/InvoiceOut/locale/en.yml b/src/pages/InvoiceOut/locale/en.yml index cb0dfdca7..9dd31d186 100644 --- a/src/pages/InvoiceOut/locale/en.yml +++ b/src/pages/InvoiceOut/locale/en.yml @@ -4,7 +4,7 @@ invoiceOut: params: company: Company country: Country - clientId: Client ID + clientId: Client clientSocialName: Client taxableBase: Base ticketFk: Ticket @@ -12,6 +12,18 @@ invoiceOut: hasToInvoice: Has to invoice hasVerifiedData: Verified data workerName: Worker + isTaxDataChecked: Verified data + amount: Amount + clientFk: Client + companyFk: Company + created: Created + dued: Dued + customsAgentFk: Custom Agent + ref: Reference + fi: FI + min: Min + max: Max + hasPdf: Has PDF card: issued: Issued customerCard: Customer card @@ -19,6 +31,7 @@ invoiceOut: summary: issued: Issued dued: Due + expirationDate: Expiration date booked: Booked taxBreakdown: Tax breakdown taxableBase: Taxable base @@ -52,7 +65,7 @@ invoiceOut: active: Active hasToInvoice: Has to Invoice verifiedData: Verified Data - comercial: Commercial + comercial: Sales person errors: downloadCsvFailed: CSV download failed invoiceOutModule: diff --git a/src/pages/InvoiceOut/locale/es.yml b/src/pages/InvoiceOut/locale/es.yml index a35c33c4e..79ceb4aa8 100644 --- a/src/pages/InvoiceOut/locale/es.yml +++ b/src/pages/InvoiceOut/locale/es.yml @@ -4,7 +4,7 @@ invoiceOut: params: company: Empresa country: País - clientId: ID del cliente + clientId: Cliente clientSocialName: Cliente taxableBase: Base ticketFk: Ticket @@ -12,6 +12,18 @@ invoiceOut: hasToInvoice: Debe facturar hasVerifiedData: Datos verificados workerName: Comercial + isTaxDataChecked: Datos comprobados + amount: Importe + clientFk: Cliente + companyFk: Empresa + created: Creada + dued: Vencida + customsAgentFk: Agente aduanas + ref: Referencia + fi: CIF + min: Min + max: Max + hasPdf: Tiene PDF card: issued: Fecha emisión customerCard: Ficha del cliente @@ -19,6 +31,7 @@ invoiceOut: summary: issued: Fecha dued: Fecha límite + expirationDate: Fecha vencimiento booked: Contabilizada taxBreakdown: Desglose impositivo taxableBase: Base imp. diff --git a/src/pages/Item/Card/ItemBasicData.vue b/src/pages/Item/Card/ItemBasicData.vue index 7e5645024..df7e71684 100644 --- a/src/pages/Item/Card/ItemBasicData.vue +++ b/src/pages/Item/Card/ItemBasicData.vue @@ -11,6 +11,7 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import FilterItemForm from 'src/components/FilterItemForm.vue'; import CreateIntrastatForm from './CreateIntrastatForm.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const route = useRoute(); const { t } = useI18n(); @@ -208,30 +209,20 @@ const onIntrastatCreated = (response, formData) => { /> </VnRow> <VnRow class="row q-gutter-md q-mb-md"> - <div> - <QCheckbox - v-model="data.isFragile" - :label="t('item.basicData.isFragile')" - class="q-mr-sm" - /> - <QIcon name="info" class="cursor-pointer" size="xs"> - <QTooltip max-width="300px"> - {{ t('item.basicData.isFragileTooltip') }} - </QTooltip> - </QIcon> - </div> - <div> - <QCheckbox - v-model="data.isPhotoRequested" - :label="t('item.basicData.isPhotoRequested')" - class="q-mr-sm" - /> - <QIcon name="info" class="cursor-pointer" size="xs"> - <QTooltip> - {{ t('item.basicData.isPhotoRequestedTooltip') }} - </QTooltip> - </QIcon> - </div> + <VnCheckbox + v-model="data.isFragile" + :label="t('item.basicData.isFragile')" + :info="t('item.basicData.isFragileTooltip')" + class="q-mr-sm" + size="xs" + /> + <VnCheckbox + v-model="data.isPhotoRequested" + :label="t('item.basicData.isPhotoRequested')" + :info="t('item.basicData.isPhotoRequestedTooltip')" + class="q-mr-sm" + size="xs" + /> </VnRow> <VnRow> <VnInput diff --git a/src/pages/Item/Card/ItemBotanical.vue b/src/pages/Item/Card/ItemBotanical.vue index 4894d94fc..a40d81589 100644 --- a/src/pages/Item/Card/ItemBotanical.vue +++ b/src/pages/Item/Card/ItemBotanical.vue @@ -7,8 +7,8 @@ import FetchData from 'components/FetchData.vue'; import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; -import CreateGenusForm from './CreateGenusForm.vue'; -import CreateSpecieForm from './CreateSpecieForm.vue'; +import CreateGenusForm from '../components/CreateGenusForm.vue'; +import CreateSpecieForm from '../components/CreateSpecieForm.vue'; const route = useRoute(); const { t } = useI18n(); diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue index 7e7057a90..a4c58ef4b 100644 --- a/src/pages/Item/Card/ItemDescriptor.vue +++ b/src/pages/Item/Card/ItemDescriptor.vue @@ -34,6 +34,10 @@ const $props = defineProps({ type: Number, default: null, }, + proxyRender: { + type: Boolean, + default: false, + }, }); const route = useRoute(); @@ -88,7 +92,6 @@ const updateStock = async () => { <template> <CardDescriptor data-key="Item" - module="Item" :summary="$props.summary" :url="`Items/${entityId}/getCard`" @on-fetch="setData" @@ -112,7 +115,7 @@ const updateStock = async () => { <template #value> <span class="link"> {{ entity.itemType?.worker?.user?.name }} - <WorkerDescriptorProxy :id="entity.itemType?.worker?.id" /> + <WorkerDescriptorProxy :id="entity.itemType?.worker?.id ?? NaN" /> </span> </template> </VnLv> @@ -147,7 +150,7 @@ const updateStock = async () => { </QCardActions> </template> <template #actions="{}"> - <QCardActions class="row justify-center"> + <QCardActions class="row justify-center" v-if="proxyRender"> <QBtn :to="{ name: 'ItemDiary', @@ -160,6 +163,16 @@ const updateStock = async () => { > <QTooltip>{{ t('item.descriptor.itemDiary') }}</QTooltip> </QBtn> + <QBtn + :to="{ + name: 'ItemLastEntries', + }" + size="md" + icon="vn:regentry" + color="primary" + > + <QTooltip>{{ t('item.descriptor.itemLastEntries') }}</QTooltip> + </QBtn> </QCardActions> </template> </CardDescriptor> diff --git a/src/pages/Item/Card/ItemDescriptorProxy.vue b/src/pages/Item/Card/ItemDescriptorProxy.vue index 2ffc9080f..f686e8221 100644 --- a/src/pages/Item/Card/ItemDescriptorProxy.vue +++ b/src/pages/Item/Card/ItemDescriptorProxy.vue @@ -4,7 +4,7 @@ import ItemSummary from './ItemSummary.vue'; const $props = defineProps({ id: { - type: Number, + type: [Number, String], required: true, }, dated: { @@ -21,9 +21,8 @@ const $props = defineProps({ }, }); </script> - <template> - <QPopupProxy> + <QPopupProxy style="max-width: 10px"> <ItemDescriptor v-if="$props.id" :id="$props.id" @@ -31,6 +30,7 @@ const $props = defineProps({ :dated="dated" :sale-fk="saleFk" :warehouse-fk="warehouseFk" + :proxy-render="true" /> </QPopupProxy> </template> diff --git a/src/pages/Item/Card/ItemShelving.vue b/src/pages/Item/Card/ItemShelving.vue index 7ad60c9e0..b29e2a2a5 100644 --- a/src/pages/Item/Card/ItemShelving.vue +++ b/src/pages/Item/Card/ItemShelving.vue @@ -110,10 +110,16 @@ const columns = computed(() => [ attrs: { inWhere: true }, align: 'left', }, + { + label: t('globals.visible'), + name: 'stock', + attrs: { inWhere: true }, + align: 'left', + }, ]); const totalLabels = computed(() => - rows.value.reduce((acc, row) => acc + row.stock / row.packing, 0).toFixed(2) + rows.value.reduce((acc, row) => acc + row.stock / row.packing, 0).toFixed(2), ); const removeLines = async () => { @@ -157,7 +163,7 @@ watchEffect(selectedRows); openConfirmationModal( t('shelvings.removeConfirmTitle'), t('shelvings.removeConfirmSubtitle'), - removeLines + removeLines, ) " > diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index 00529bb8d..ad998f35f 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -69,10 +69,19 @@ const columns = computed(() => [ name: 'name', ...defaultColumnAttrs, create: true, + columnFilter: { + component: 'select', + attrs: { + url: 'Items', + fields: ['id', 'name', 'subName'], + optionLabel: 'name', + optionValue: 'name', + uppercase: false, + }, + }, }, { label: t('item.fixedPrice.groupingPrice'), - field: 'rate2', name: 'rate2', ...defaultColumnAttrs, component: 'input', @@ -80,7 +89,6 @@ const columns = computed(() => [ }, { label: t('item.fixedPrice.packingPrice'), - field: 'rate3', name: 'rate3', ...defaultColumnAttrs, component: 'input', @@ -89,7 +97,6 @@ const columns = computed(() => [ { label: t('item.fixedPrice.minPrice'), - field: 'minPrice', name: 'minPrice', ...defaultColumnAttrs, component: 'input', @@ -112,7 +119,6 @@ const columns = computed(() => [ }, { label: t('item.fixedPrice.ended'), - field: 'ended', name: 'ended', ...defaultColumnAttrs, columnField: { @@ -128,7 +134,6 @@ const columns = computed(() => [ { label: t('globals.warehouse'), - field: 'warehouseFk', name: 'warehouseFk', ...defaultColumnAttrs, columnClass: 'shrink', diff --git a/src/pages/Item/ItemFixedPriceFilter.vue b/src/pages/Item/ItemFixedPriceFilter.vue index 531c7e09e..8d92e245d 100644 --- a/src/pages/Item/ItemFixedPriceFilter.vue +++ b/src/pages/Item/ItemFixedPriceFilter.vue @@ -1,8 +1,6 @@ <script setup> -import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import FetchData from 'components/FetchData.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnSelect from 'components/common/VnSelect.vue'; import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue'; @@ -16,17 +14,9 @@ const props = defineProps({ }, }); -const itemTypeWorkersOptions = ref([]); </script> <template> - <FetchData - url="TicketRequests/getItemTypeWorker" - limit="30" - auto-load - :filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }" - @on-fetch="(data) => (itemTypeWorkersOptions = data)" - /> <ItemsFilterPanel :data-key="props.dataKey" :custom-tags="['tags']"> <template #body="{ params, searchFn }"> <QItem class="q-my-md"> @@ -34,14 +24,15 @@ const itemTypeWorkersOptions = ref([]); <VnSelect :label="t('params.buyerFk')" v-model="params.buyerFk" - :options="itemTypeWorkersOptions" - option-value="id" + url="TicketRequests/getItemTypeWorker" + :fields="['id', 'nickname']" option-label="nickname" dense outlined rounded use-input @update:model-value="searchFn()" + sort-by="nickname ASC" /> </QItemSection> </QItem> @@ -50,11 +41,10 @@ const itemTypeWorkersOptions = ref([]); <VnSelect url="Warehouses" auto-load - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" + :fields="['id', 'name']" + sort-by="name ASC" :label="t('params.warehouseFk')" v-model="params.warehouseFk" - option-label="name" - option-value="id" dense outlined rounded diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue index af48f7f5c..c2a63ddd9 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -8,6 +8,7 @@ import VnInput from 'src/components/common/VnInput.vue'; import FetchData from 'components/FetchData.vue'; import { useArrayData } from 'src/composables/useArrayData'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; +import VnInputDate from 'src/components/common/VnInputDate.vue'; const { t } = useI18n(); const props = defineProps({ @@ -52,7 +53,7 @@ onMounted(async () => { name: key, value, selectedField: { name: key, label: t(`params.${key}`) }, - }) + }), ); } exprBuilder('state', arrayData.store?.userParams?.state); @@ -157,6 +158,32 @@ onMounted(async () => { /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnInputDate + v-model="params.from" + :label="t('params.from')" + is-outlined + /> + </QItemSection> + <QItemSection> + <VnInputDate + v-model="params.to" + :label="t('params.to')" + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + :label="t('params.daysOnward')" + v-model="params.daysOnward" + lazy-rules + is-outlined + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <VnSelect @@ -175,11 +202,10 @@ onMounted(async () => { </QItem> <QItem> <QItemSection> - <VnInput - :label="t('params.daysOnward')" - v-model="params.daysOnward" - lazy-rules - is-outlined + <QCheckbox + :label="t('params.mine')" + v-model="params.mine" + :toggle-indeterminate="false" /> </QItemSection> </QItem> diff --git a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue index 0f71ad1f1..725fb30aa 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue @@ -26,7 +26,6 @@ const entityId = computed(() => { </script> <template> <CardDescriptor - module="ItemType" :url="`ItemTypes/${entityId}`" :filter="filter" title="code" diff --git a/src/pages/Item/Card/CreateGenusForm.vue b/src/pages/Item/components/CreateGenusForm.vue similarity index 100% rename from src/pages/Item/Card/CreateGenusForm.vue rename to src/pages/Item/components/CreateGenusForm.vue diff --git a/src/pages/Item/Card/CreateSpecieForm.vue b/src/pages/Item/components/CreateSpecieForm.vue similarity index 100% rename from src/pages/Item/Card/CreateSpecieForm.vue rename to src/pages/Item/components/CreateSpecieForm.vue diff --git a/src/pages/Item/components/ItemProposal.vue b/src/pages/Item/components/ItemProposal.vue new file mode 100644 index 000000000..d2dbea7b3 --- /dev/null +++ b/src/pages/Item/components/ItemProposal.vue @@ -0,0 +1,332 @@ +<script setup> +import { ref, computed } from 'vue'; +import { useI18n } from 'vue-i18n'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import { toCurrency } from 'filters/index'; +import VnStockValueDisplay from 'src/components/ui/VnStockValueDisplay.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; +import axios from 'axios'; +import notifyResults from 'src/utils/notifyResults'; +import FetchData from 'components/FetchData.vue'; + +const MATCH = 'match'; + +const { t } = useI18n(); +const $props = defineProps({ + itemLack: { + type: Object, + required: true, + default: () => {}, + }, + replaceAction: { + type: Boolean, + required: false, + default: false, + }, + sales: { + type: Array, + required: false, + default: () => [], + }, +}); +const proposalSelected = ref([]); +const ticketConfig = ref({}); +const proposalTableRef = ref(null); + +const sale = computed(() => $props.sales[0]); +const saleFk = computed(() => sale.value.saleFk); +const filter = computed(() => ({ + itemFk: $props.itemLack.itemFk, + sales: saleFk.value, +})); + +const defaultColumnAttrs = { + align: 'center', + sortable: false, +}; +const emit = defineEmits(['onDialogClosed', 'itemReplaced']); + +const conditionalValuePrice = (price) => + price > 1 + ticketConfig.value.lackAlertPrice / 100 ? 'match' : 'not-match'; + +const columns = computed(() => [ + { + ...defaultColumnAttrs, + label: t('proposal.available'), + name: 'available', + field: 'available', + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + columnClass: 'shrink', + }, + { + ...defaultColumnAttrs, + label: t('proposal.counter'), + name: 'counter', + field: 'counter', + columnClass: 'shrink', + style: 'max-width: 75px', + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + }, + + { + align: 'left', + sortable: true, + label: t('proposal.longName'), + name: 'longName', + field: 'longName', + columnClass: 'expand', + }, + { + align: 'left', + sortable: true, + label: t('item.list.color'), + name: 'tag5', + field: 'value5', + columnClass: 'expand', + }, + { + align: 'left', + sortable: true, + label: t('item.list.stems'), + name: 'tag6', + field: 'value6', + columnClass: 'expand', + }, + { + align: 'left', + sortable: true, + label: t('item.list.producer'), + name: 'tag7', + field: 'value7', + columnClass: 'expand', + }, + + { + ...defaultColumnAttrs, + label: t('proposal.price2'), + name: 'price2', + style: 'max-width: 75px', + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + }, + { + ...defaultColumnAttrs, + label: t('proposal.minQuantity'), + name: 'minQuantity', + field: 'minQuantity', + style: 'max-width: 75px', + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + }, + { + ...defaultColumnAttrs, + label: t('proposal.located'), + name: 'located', + field: 'located', + }, + { + align: 'right', + label: '', + name: 'tableActions', + actions: [ + { + title: t('Replace'), + icon: 'change_circle', + show: (row) => isSelectionAvailable(row), + action: change, + isPrimary: true, + }, + ], + }, +]); + +function extractMatchValues(obj) { + return Object.keys(obj) + .filter((key) => key.startsWith(MATCH)) + .map((key) => parseInt(key.replace(MATCH, ''), 10)); +} +const gradientStyle = (value) => { + let color = 'white'; + const perc = parseFloat(value); + switch (true) { + case perc >= 0 && perc < 33: + color = 'primary'; + break; + case perc >= 33 && perc < 66: + color = 'warning'; + break; + + default: + color = 'secondary'; + break; + } + return color; +}; +const statusConditionalValue = (row) => { + const matches = extractMatchValues(row); + const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0); + return 100 * (value / matches.length); +}; + +const isSelectionAvailable = (itemProposal) => { + const { price2 } = itemProposal; + const salePrice = sale.value.price; + const byPrice = (100 * price2) / salePrice > ticketConfig.value.lackAlertPrice; + if (byPrice) { + return byPrice; + } + const byQuantity = + (100 * itemProposal.available) / Math.abs($props.itemLack.lack) < + ticketConfig.value.lackAlertPrice; + return byQuantity; +}; + +async function change({ itemFk: substitutionFk }) { + try { + const promises = $props.sales.map(({ saleFk, quantity }) => { + const params = { + saleFk, + substitutionFk, + quantity, + }; + return axios.post('Sales/replaceItem', params); + }); + const results = await Promise.allSettled(promises); + + notifyResults(results, 'saleFk'); + emit('itemReplaced', { + type: 'refresh', + quantity: quantity.value, + itemProposal: proposalSelected.value[0], + }); + proposalSelected.value = []; + } catch (error) { + console.error(error); + } +} + +async function handleTicketConfig(data) { + ticketConfig.value = data[0]; +} +</script> +<template> + <FetchData + url="TicketConfigs" + :filter="{ fields: ['lackAlertPrice'] }" + @on-fetch="handleTicketConfig" + auto-load + /> + + <VnTable + v-if="ticketConfig" + auto-load + data-cy="proposalTable" + ref="proposalTableRef" + data-key="ItemsGetSimilar" + url="Items/getSimilar" + :user-filter="filter" + :columns="columns" + class="full-width q-mt-md" + row-key="id" + :row-click="change" + :is-editable="false" + :right-search="false" + :without-header="true" + :disable-option="{ card: true, table: true }" + > + <template #column-longName="{ row }"> + <QTd + class="flex" + style="max-width: 100%; flex-shrink: 50px; flex-wrap: nowrap" + > + <div + class="middle full-width" + :class="[`proposal-${gradientStyle(statusConditionalValue(row))}`]" + > + <QTooltip> {{ statusConditionalValue(row) }}% </QTooltip> + </div> + <div style="flex: 2 0 100%; align-content: center"> + <div> + <span class="link">{{ row.longName }}</span> + <ItemDescriptorProxy :id="row.id" /> + </div> + </div> + </QTd> + </template> + <template #column-tag5="{ row }"> + <span :class="{ match: !row.match5 }">{{ row.value5 }}</span> + </template> + <template #column-tag6="{ row }"> + <span :class="{ match: !row.match6 }">{{ row.value6 }}</span> + </template> + <template #column-tag7="{ row }"> + <span :class="{ match: !row.match7 }">{{ row.value7 }}</span> + </template> + <template #column-counter="{ row }"> + <span + :class="{ + match: row.counter === 1, + 'not-match': row.counter !== 1, + }" + >{{ row.counter }}</span + > + </template> + <template #column-minQuantity="{ row }"> + {{ row.minQuantity }} + </template> + <template #column-price2="{ row }"> + <div class="flex column items-center content-center"> + <VnStockValueDisplay :value="(sales[0].price - row.price2) / 100" /> + <span :class="[conditionalValuePrice(row.price2)]">{{ + toCurrency(row.price2) + }}</span> + </div> + </template> + </VnTable> +</template> +<style lang="scss" scoped> +@import 'src/css/quasar.variables.scss'; +.middle { + float: left; + margin-right: 2px; + flex: 2 0 5px; +} +.match { + color: $negative; +} +.not-match { + color: inherit; +} +.proposal-warning { + background-color: $warning; +} +.proposal-secondary { + background-color: $secondary; +} +.proposal-primary { + background-color: $primary; +} +.text { + margin: 0.05rem; + padding: 1px; + border: 1px solid var(--vn-label-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: smaller; +} +</style> diff --git a/src/pages/Item/components/ItemProposalProxy.vue b/src/pages/Item/components/ItemProposalProxy.vue new file mode 100644 index 000000000..7da0ce398 --- /dev/null +++ b/src/pages/Item/components/ItemProposalProxy.vue @@ -0,0 +1,56 @@ +<script setup> +import ItemProposal from './ItemProposal.vue'; +import { useDialogPluginComponent } from 'quasar'; + +const $props = defineProps({ + itemLack: { + type: Object, + required: true, + default: () => {}, + }, + replaceAction: { + type: Boolean, + required: false, + default: false, + }, + sales: { + type: Array, + required: false, + default: () => [], + }, +}); +const { dialogRef } = useDialogPluginComponent(); +const emit = defineEmits([ + 'onDialogClosed', + 'itemReplaced', + ...useDialogPluginComponent.emits, +]); +defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() }); +</script> +<template> + <QDialog ref="dialogRef" transition-show="scale" transition-hide="scale"> + <QCard class="dialog-width"> + <QCardSection class="row items-center q-pb-none"> + <span class="text-h6 text-grey">{{ $t('Item proposal') }}</span> + <QSpace /> + <QBtn icon="close" flat round dense v-close-popup /> + </QCardSection> + <QCardSection> + <ItemProposal + v-bind="$props" + @item-replaced=" + (data) => { + emit('itemReplaced', data); + dialogRef.hide(); + } + " + ></ItemProposal + ></QCardSection> + </QCard> + </QDialog> +</template> +<style lang="scss" scoped> +.dialog-width { + max-width: $width-lg; +} +</style> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index bc73abb12..9d27fc96e 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -112,6 +112,7 @@ item: available: Available warehouseText: 'Calculated on the warehouse of { warehouseName }' itemDiary: Item diary + itemLastEntries: Last entries producer: Producer clone: title: All its properties will be copied @@ -130,6 +131,7 @@ item: origin: Orig. userName: Buyer weight: Weight + color: Color weightByPiece: Weight/stem stemMultiplier: Multiplier producer: Producer @@ -215,4 +217,24 @@ item: specie: Specie search: 'Search item' searchInfo: 'You can search by id' - regularizeStock: Regularize stock \ No newline at end of file + regularizeStock: Regularize stock +itemProposal: Items proposal +proposal: + difference: Difference + title: Items proposal + itemFk: Item + longName: Name + subName: Producer + value5: value5 + value6: value6 + value7: value7 + value8: value8 + available: Available + minQuantity: minQuantity + price2: Price + located: Located + counter: Counter + groupingPrice: Grouping Price + itemOldPrice: itemOld Price + status: State + quantityToReplace: Quanity to replace diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index dd5074f5f..935f5160b 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -118,6 +118,7 @@ item: available: Disponible warehouseText: 'Calculado sobre el almacén de { warehouseName }' itemDiary: Registro de compra-venta + itemLastEntries: Últimas entradas producer: Productor clone: title: Todas sus propiedades serán copiadas @@ -135,6 +136,7 @@ item: size: Medida origin: Orig. weight: Peso + color: Color weightByPiece: Peso/tallo userName: Comprador stemMultiplier: Multiplicador @@ -220,5 +222,30 @@ item: achieved: 'Conseguido' concept: 'Concepto' state: 'Estado' - search: 'Buscar artículo' - searchInfo: 'Puedes buscar por id' +itemProposal: Artículos similares +proposal: + substitutionAvailable: Sustitución disponible + notSubstitutionAvailableByPrice: Sustitución no disponible, 30% de diferencia por precio o cantidad + compatibility: Compatibilidad + title: Items de sustitución para los tickets seleccionados + itemFk: Item + longName: Nombre + subName: Productor + value5: value5 + value6: value6 + value7: value7 + value8: value8 + available: Disponible + minQuantity: Min. cantidad + price2: Precio + located: Ubicado + counter: Contador + difference: Diferencial + groupingPrice: Precio Grouping + itemOldPrice: Precio itemOld + status: Estado + quantityToReplace: Cantidad a reemplazar + replace: Sustituir + replaceAndConfirm: Sustituir y confirmar precio +search: 'Buscar artículo' +searchInfo: 'Puedes buscar por id' diff --git a/src/pages/Monitor/MonitorOrders.vue b/src/pages/Monitor/MonitorOrders.vue index 4efab56fb..873f8abb4 100644 --- a/src/pages/Monitor/MonitorOrders.vue +++ b/src/pages/Monitor/MonitorOrders.vue @@ -157,7 +157,7 @@ const openTab = (id) => openConfirmationModal( $t('globals.deleteConfirmTitle'), $t('salesOrdersTable.deleteConfirmMessage'), - removeOrders + removeOrders, ) " > diff --git a/src/pages/Monitor/Ticket/MonitorTickets.vue b/src/pages/Monitor/Ticket/MonitorTickets.vue index 3b5dccb56..2ec862df0 100644 --- a/src/pages/Monitor/Ticket/MonitorTickets.vue +++ b/src/pages/Monitor/Ticket/MonitorTickets.vue @@ -61,6 +61,7 @@ function exprBuilder(param, value) { case 'nickname': return { [`t.nickname`]: { like: `%${value}%` } }; case 'zoneFk': + return { 't.zoneFk': value }; case 'department': return { 'd.name': value }; case 'totalWithVat': diff --git a/src/pages/Order/Card/OrderCatalogItemDialog.vue b/src/pages/Order/Card/OrderCatalogItemDialog.vue index be35750a9..766945e4d 100644 --- a/src/pages/Order/Card/OrderCatalogItemDialog.vue +++ b/src/pages/Order/Card/OrderCatalogItemDialog.vue @@ -39,14 +39,13 @@ const addToOrder = async () => { }); const { data: orderTotal } = await axios.get( - `Orders/${Number(route.params.id)}/getTotal` + `Orders/${Number(route.params.id)}/getTotal`, ); state.set('orderTotal', orderTotal); - const rows = orderData.value.rows.push(...items) || []; state.set('Order', { ...orderData.value, - rows, + items, }); notify(t('globals.dataSaved'), 'positive'); emit('added', -totalQuantity(items)); @@ -57,7 +56,7 @@ const canAddToOrder = () => { if (canAddToOrder) { const excedQuantity = prices.value.reduce( (acc, { quantity }) => acc + quantity, - 0 + 0, ); if (excedQuantity > props.item.available) { canAddToOrder = false; diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue index 1752efe7b..0d18864dc 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -57,7 +57,6 @@ const total = ref(0); ref="descriptor" :url="`Orders/${entityId}`" :filter="filter" - module="Order" title="client.name" @on-fetch="setData" data-key="Order" diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue index 6153b2d3e..1b864de6f 100644 --- a/src/pages/Order/Card/OrderLines.vue +++ b/src/pages/Order/Card/OrderLines.vue @@ -238,7 +238,7 @@ watch( lineFilter.value.where.orderFk = router.currentRoute.value.params.id; tableLinesRef.value.reload(); - } + }, ); </script> diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 21cb5ed7e..40990f329 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -71,8 +71,9 @@ const columns = computed(() => [ format: (row) => row?.name, }, { - align: 'left', + align: 'center', name: 'isConfirmed', + component: 'checkbox', label: t('module.isConfirmed'), }, { @@ -95,7 +96,9 @@ const columns = computed(() => [ columnField: { component: null, }, - style: 'color="positive"', + style: () => { + return { color: 'positive' }; + }, }, { align: 'left', diff --git a/src/pages/Route/Agency/AgencyList.vue b/src/pages/Route/Agency/AgencyList.vue index 4322b9bc8..5c2904bf3 100644 --- a/src/pages/Route/Agency/AgencyList.vue +++ b/src/pages/Route/Agency/AgencyList.vue @@ -51,7 +51,6 @@ const columns = computed(() => [ name: 'isAnyVolumeAllowed', component: 'checkbox', cardVisible: true, - disable: true, }, { align: 'right', @@ -72,7 +71,7 @@ const columns = computed(() => [ :data-key :columns="columns" prefix="agency" - :right-filter="false" + :right-filter="true" :array-data-props="{ url: 'Agencies', order: 'name', @@ -83,6 +82,7 @@ const columns = computed(() => [ <VnTable :data-key :columns="columns" + is-editable="false" :right-search="false" :use-model="true" redirect="route/agency" diff --git a/src/pages/Route/Agency/Card/AgencyDescriptor.vue b/src/pages/Route/Agency/Card/AgencyDescriptor.vue index b9772037c..a0472c6c3 100644 --- a/src/pages/Route/Agency/Card/AgencyDescriptor.vue +++ b/src/pages/Route/Agency/Card/AgencyDescriptor.vue @@ -22,7 +22,6 @@ const card = computed(() => store.data); </script> <template> <CardDescriptor - module="Agency" data-key="Agency" :url="`Agencies/${entityId}`" :title="card?.name" diff --git a/src/pages/Route/Card/RouteAutonomousFilter.vue b/src/pages/Route/Card/RouteAutonomousFilter.vue index 37745594c..3be409ec9 100644 --- a/src/pages/Route/Card/RouteAutonomousFilter.vue +++ b/src/pages/Route/Card/RouteAutonomousFilter.vue @@ -46,7 +46,6 @@ const exprBuilder = (param, value) => { url="AgencyModes" :filter="{ fields: ['id', 'name'] }" sort-by="name ASC" - limit="30" @on-fetch="(data) => (agencyList = data)" auto-load /> @@ -54,7 +53,6 @@ const exprBuilder = (param, value) => { url="Agencies" :filter="{ fields: ['id', 'name'] }" sort-by="name ASC" - limit="30" @on-fetch="(data) => (agencyAgreementList = data)" auto-load /> @@ -120,7 +118,11 @@ const exprBuilder = (param, value) => { <VnSelectSupplier :label="t('Autonomous')" v-model="params.supplierFk" - hide-selected + url="Suppliers" + :fields="['name']" + sort-by="name ASC" + option-value="name" + option-label="name" dense outlined rounded diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index a8c6cc18b..503cd1941 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -1,11 +1,13 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; import { dashIfEmpty, toDate } from 'src/filters'; import RouteDescriptorMenu from 'pages/Route/Card/RouteDescriptorMenu.vue'; import filter from './RouteFilter.js'; +import useCardDescription from 'src/composables/useCardDescription'; +import axios from 'axios'; const $props = defineProps({ id: { @@ -16,14 +18,32 @@ const $props = defineProps({ }); const route = useRoute(); - +const zone = ref(); +const zoneId = ref(); const entityId = computed(() => { return $props.id || route.params.id; }); +const getZone = async () => { + const filter = { + where: { routeFk: $props.id ? $props.id : route.params.id }, + }; + const { data } = await axios.get('Tickets/findOne', { + params: { + filter: JSON.stringify(filter), + }, + }); + zoneId.value = data.zoneFk; + const { data: zoneData } = await axios.get(`Zones/${zoneId.value}`); + zone.value = zoneData.name; +}; +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); +onMounted(async () => { + getZone(); +}); </script> <template> <CardDescriptor - module="Route" :url="`Routes/${entityId}`" :filter="filter" :title="null" @@ -31,9 +51,9 @@ const entityId = computed(() => { width="lg-width" > <template #body="{ entity }"> - <VnLv :label="$t('Date')" :value="toDate(entity?.created)" /> + <VnLv :label="$t('Date')" :value="toDate(entity?.dated)" /> <VnLv :label="$t('Agency')" :value="entity?.agencyMode?.name" /> - <VnLv :label="$t('Zone')" :value="entity?.zone?.name" /> + <VnLv :label="$t('Zone')" :value="zone" /> <VnLv :label="$t('Volume')" :value="`${dashIfEmpty(entity?.m3)} / ${dashIfEmpty( diff --git a/src/pages/Route/Card/RouteFilter.js b/src/pages/Route/Card/RouteFilter.js index 16d200c99..90ee71bf7 100644 --- a/src/pages/Route/Card/RouteFilter.js +++ b/src/pages/Route/Card/RouteFilter.js @@ -14,7 +14,6 @@ export default { 'started', 'finished', 'cost', - 'zoneFk', 'isOk', ], include: [ @@ -23,7 +22,6 @@ export default { relation: 'vehicle', scope: { fields: ['id', 'm3'] }, }, - { relation: 'zone', scope: { fields: ['id', 'name'] } }, { relation: 'worker', scope: { diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue index 30ac54896..667204b15 100644 --- a/src/pages/Route/Card/RouteForm.vue +++ b/src/pages/Route/Card/RouteForm.vue @@ -28,7 +28,6 @@ const defaultInitialData = { isOk: false, }; const maxDistance = ref(); - const onSave = (data, response) => { if (isNew) { axios.post(`Routes/${response?.id}/updateWorkCenter`); diff --git a/src/pages/Route/Roadmap/RoadmapAddStopForm.vue b/src/pages/Route/Roadmap/RoadmapAddStopForm.vue index 6cc21fd4d..dd8ad94cb 100644 --- a/src/pages/Route/Roadmap/RoadmapAddStopForm.vue +++ b/src/pages/Route/Roadmap/RoadmapAddStopForm.vue @@ -48,7 +48,6 @@ const onFetch = (data) => { }, ], }" - limit="30" @on-fetch="onFetch" /> <div :class="[isDialog ? 'column' : 'form-gap', 'full-width flex']"> diff --git a/src/pages/Route/Roadmap/RoadmapBasicData.vue b/src/pages/Route/Roadmap/RoadmapBasicData.vue index 5f8ba3bf5..a9e6059c3 100644 --- a/src/pages/Route/Roadmap/RoadmapBasicData.vue +++ b/src/pages/Route/Roadmap/RoadmapBasicData.vue @@ -18,6 +18,7 @@ const onSave = (data, response) => { <template> <FormModel :update-url="`Roadmaps/${$route.params?.id}`" + :url="`Roadmaps/${$route.params?.id}`" observe-form-changes model="Roadmap" auto-load diff --git a/src/pages/Route/Roadmap/RoadmapDescriptor.vue b/src/pages/Route/Roadmap/RoadmapDescriptor.vue index 1f1e6d6ff..baa864a15 100644 --- a/src/pages/Route/Roadmap/RoadmapDescriptor.vue +++ b/src/pages/Route/Roadmap/RoadmapDescriptor.vue @@ -26,12 +26,7 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor - module="Roadmap" - :url="`Roadmaps/${entityId}`" - :filter="filter" - data-key="Roadmap" - > + <CardDescriptor :url="`Roadmaps/${entityId}`" :filter="filter" data-key="Roadmap"> <template #body="{ entity }"> <VnLv :label="t('Roadmap')" :value="entity?.name" /> <VnLv :label="t('ETD')" :value="toDateHourMin(entity?.etd)" /> diff --git a/src/pages/Route/Roadmap/RoadmapFilter.vue b/src/pages/Route/Roadmap/RoadmapFilter.vue index 0d51a88bb..982f1efba 100644 --- a/src/pages/Route/Roadmap/RoadmapFilter.vue +++ b/src/pages/Route/Roadmap/RoadmapFilter.vue @@ -1,7 +1,5 @@ <script setup> -import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; @@ -65,6 +63,7 @@ const emit = defineEmits(['search']); <QItemSection> <VnSelectSupplier :label="t('Carrier')" + :fields="['id', 'nickname']" v-model="params.supplierFk" dense outlined diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue index 03d081fc8..a8d847711 100644 --- a/src/pages/Route/RouteExtendedList.vue +++ b/src/pages/Route/RouteExtendedList.vue @@ -3,7 +3,7 @@ import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useQuasar } from 'quasar'; -import { toDate } from 'src/filters'; +import { dashIfEmpty, toDate, toHour } from 'src/filters'; import { useRouter } from 'vue-router'; import { usePrintService } from 'src/composables/usePrintService'; @@ -38,7 +38,7 @@ const routeFilter = { }; const columns = computed(() => [ { - align: 'left', + align: 'center', name: 'id', label: 'Id', chip: { @@ -48,7 +48,7 @@ const columns = computed(() => [ columnFilter: false, }, { - align: 'left', + align: 'center', name: 'workerFk', label: t('route.Worker'), create: true, @@ -68,10 +68,10 @@ const columns = computed(() => [ }, useLike: false, cardVisible: true, - format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), + format: (row, dashIfEmpty) => dashIfEmpty(row.workerUserName), }, { - align: 'left', + align: 'center', name: 'agencyModeFk', label: t('route.Agency'), isTitle: true, @@ -87,9 +87,10 @@ const columns = computed(() => [ }, }, columnClass: 'expand', + format: (row, dashIfEmpty) => dashIfEmpty(row.agencyName), }, { - align: 'left', + align: 'center', name: 'vehicleFk', label: t('route.Vehicle'), cardVisible: true, @@ -107,29 +108,31 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + format: (row, dashIfEmpty) => dashIfEmpty(row.vehiclePlateNumber), }, { - align: 'left', + align: 'center', name: 'dated', label: t('route.Date'), columnFilter: false, cardVisible: true, create: true, component: 'date', - format: ({ date }) => toDate(date), + format: ({ dated }, dashIfEmpty) => + dated === '0000-00-00' ? dashIfEmpty(null) : toDate(dated), }, { - align: 'left', + align: 'center', name: 'from', label: t('route.From'), visible: false, cardVisible: true, create: true, component: 'date', - format: ({ date }) => toDate(date), + format: ({ from }) => toDate(from), }, { - align: 'left', + align: 'center', name: 'to', label: t('route.To'), visible: false, @@ -146,18 +149,20 @@ const columns = computed(() => [ columnClass: 'shrink', }, { - align: 'left', + align: 'center', name: 'started', label: t('route.hourStarted'), component: 'time', columnFilter: false, + format: ({ started }) => toHour(started), }, { - align: 'left', + align: 'center', name: 'finished', label: t('route.hourFinished'), component: 'time', columnFilter: false, + format: ({ finished }) => toHour(finished), }, { align: 'center', @@ -176,7 +181,7 @@ const columns = computed(() => [ visible: false, }, { - align: 'left', + align: 'center', name: 'description', label: t('route.Description'), isTitle: true, @@ -185,7 +190,7 @@ const columns = computed(() => [ field: 'description', }, { - align: 'left', + align: 'center', name: 'isOk', label: t('route.Served'), component: 'checkbox', @@ -275,7 +280,7 @@ const openTicketsDialog = (id) => { </QCardSection> <QCardSection class="q-pt-none"> <VnInputDate - :label="t('route.Stating date')" + :label="t('route.Starting date')" v-model="startingDate" autofocus /> @@ -299,60 +304,65 @@ const openTicketsDialog = (id) => { <RouteFilter data-key="RouteList" /> </template> </RightMenu> - <VnTable - class="route-list" - ref="tableRef" - data-key="RouteList" - url="Routes/filter" - :columns="columns" - :right-search="false" - :is-editable="true" - :filter="routeFilter" - redirect="route" - :row-click="false" - :create="{ - urlCreate: 'Routes', - title: t('route.createRoute'), - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, - }" - save-url="Routes/crud" - :disable-option="{ card: true }" - table-height="85vh" - v-model:selected="selectedRows" - :table="{ - 'row-key': 'id', - selection: 'multiple', - }" - > - <template #moreBeforeActions> - <QBtn - icon="vn:clone" - color="primary" - class="q-mr-sm" - :disable="!selectedRows?.length" - @click="confirmationDialog = true" - > - <QTooltip>{{ t('route.Clone Selected Routes') }}</QTooltip> - </QBtn> - <QBtn - icon="cloud_download" - color="primary" - class="q-mr-sm" - :disable="!selectedRows?.length" - @click="showRouteReport" - > - <QTooltip>{{ t('route.Download selected routes as PDF') }}</QTooltip> - </QBtn> - <QBtn - icon="check" - color="primary" - class="q-mr-sm" - :disable="!selectedRows?.length" - @click="markAsServed()" - > - <QTooltip>{{ t('route.Mark as served') }}</QTooltip> - </QBtn> - </template> - </VnTable> + <QPage class="q-px-md"> + <VnTable + class="route-list" + ref="tableRef" + data-key="RouteList" + url="Routes/filter" + :columns="columns" + :right-search="false" + :is-editable="true" + :filter="routeFilter" + redirect="route" + :row-click="false" + :create="{ + urlCreate: 'Routes', + title: t('route.createRoute'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: {}, + }" + save-url="Routes/crud" + :disable-option="{ card: true }" + table-height="85vh" + v-model:selected="selectedRows" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" + > + <template #moreBeforeActions> + <QBtn + icon="vn:clone" + color="primary" + flat + class="q-mr-sm" + :disable="!selectedRows?.length" + @click="confirmationDialog = true" + > + <QTooltip>{{ t('route.Clone Selected Routes') }}</QTooltip> + </QBtn> + <QBtn + icon="cloud_download" + color="primary" + flat + class="q-mr-sm" + :disable="!selectedRows?.length" + @click="showRouteReport" + > + <QTooltip>{{ t('route.Download selected routes as PDF') }}</QTooltip> + </QBtn> + <QBtn + icon="check" + color="primary" + flat + class="q-mr-sm" + :disable="!selectedRows?.length" + @click="markAsServed()" + > + <QTooltip>{{ t('route.Mark as served') }}</QTooltip> + </QBtn> + </template> + </VnTable> + </QPage> </template> diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index bc3227f6c..9dad8ba22 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -38,6 +38,17 @@ const columns = computed(() => [ align: 'left', name: 'workerFk', label: t('route.Worker'), + component: 'select', + attrs: { + url: 'Workers/activeWithInheritedRole', + fields: ['id', 'name'], + useLike: false, + optionFilter: 'firstName', + find: { + value: 'workerFk', + label: 'workerUserName', + }, + }, create: true, cardVisible: true, format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), @@ -48,6 +59,15 @@ const columns = computed(() => [ name: 'agencyName', label: t('route.Agency'), cardVisible: true, + component: 'select', + attrs: { + url: 'agencyModes', + fields: ['id', 'name'], + find: { + value: 'agencyModeFk', + label: 'agencyName', + }, + }, create: true, columnClass: 'expand', columnFilter: false, @@ -57,6 +77,17 @@ const columns = computed(() => [ name: 'vehiclePlateNumber', label: t('route.Vehicle'), cardVisible: true, + component: 'select', + attrs: { + url: 'vehicles', + fields: ['id', 'numberPlate'], + optionLabel: 'numberPlate', + optionFilterValue: 'numberPlate', + find: { + value: 'vehicleFk', + label: 'vehiclePlateNumber', + }, + }, create: true, columnFilter: false, }, diff --git a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue index f31ffe847..d9a2434ab 100644 --- a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue +++ b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue @@ -9,7 +9,6 @@ const { notify } = useNotify(); <template> <CardDescriptor :url="`Vehicles/${$route.params.id}`" - module="Vehicle" data-key="Vehicle" title="numberPlate" :to-module="{ name: 'VehicleList' }" diff --git a/src/pages/Shelving/Card/ShelvingDescriptor.vue b/src/pages/Shelving/Card/ShelvingDescriptor.vue index 9d491e36e..5e618aa7f 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptor.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptor.vue @@ -25,7 +25,6 @@ const entityId = computed(() => { </script> <template> <CardDescriptor - module="Shelving" :url="`Shelvings/${entityId}`" :filter="filter" title="code" diff --git a/src/pages/Parking/Card/ParkingBasicData.vue b/src/pages/Shelving/Parking/Card/ParkingBasicData.vue similarity index 87% rename from src/pages/Parking/Card/ParkingBasicData.vue rename to src/pages/Shelving/Parking/Card/ParkingBasicData.vue index fcc9dbd24..3de358002 100644 --- a/src/pages/Parking/Card/ParkingBasicData.vue +++ b/src/pages/Shelving/Parking/Card/ParkingBasicData.vue @@ -8,6 +8,11 @@ import VnSelect from 'src/components/common/VnSelect.vue'; const sectors = ref([]); const sectorFilter = { fields: ['id', 'description'] }; + +const filter = { + fields: ['sectorFk', 'code', 'pickingOrder'], + include: [{ relation: 'sector', scope: sectorFilter }], +}; </script> <template> <FetchData @@ -26,10 +31,6 @@ const sectorFilter = { fields: ['id', 'description'] }; :label="$t('parking.pickingOrder')" /> </VnRow> - <VnRow> - <VnInput v-model="data.row" :label="$t('parking.row')" /> - <VnInput v-model="data.column" :label="$t('parking.column')" /> - </VnRow> <VnRow> <VnSelect v-model="data.sectorFk" diff --git a/src/pages/Parking/Card/ParkingCard.vue b/src/pages/Shelving/Parking/Card/ParkingCard.vue similarity index 77% rename from src/pages/Parking/Card/ParkingCard.vue rename to src/pages/Shelving/Parking/Card/ParkingCard.vue index 6845aeec1..b32c1b7d3 100644 --- a/src/pages/Parking/Card/ParkingCard.vue +++ b/src/pages/Shelving/Parking/Card/ParkingCard.vue @@ -1,6 +1,6 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; -import ParkingDescriptor from 'pages/Parking/Card/ParkingDescriptor.vue'; +import ParkingDescriptor from 'pages/Shelving/Parking/Card/ParkingDescriptor.vue'; import filter from './ParkingFilter.js'; </script> diff --git a/src/pages/Parking/Card/ParkingDescriptor.vue b/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue similarity index 97% rename from src/pages/Parking/Card/ParkingDescriptor.vue rename to src/pages/Shelving/Parking/Card/ParkingDescriptor.vue index 0b7642c1c..46c9f8ea0 100644 --- a/src/pages/Parking/Card/ParkingDescriptor.vue +++ b/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue @@ -17,7 +17,6 @@ const entityId = computed(() => props.id || route.params.id); </script> <template> <CardDescriptor - module="Parking" data-key="Parking" :url="`Parkings/${entityId}`" title="code" diff --git a/src/pages/Parking/Card/ParkingFilter.js b/src/pages/Shelving/Parking/Card/ParkingFilter.js similarity index 100% rename from src/pages/Parking/Card/ParkingFilter.js rename to src/pages/Shelving/Parking/Card/ParkingFilter.js diff --git a/src/pages/Parking/Card/ParkingLog.vue b/src/pages/Shelving/Parking/Card/ParkingLog.vue similarity index 100% rename from src/pages/Parking/Card/ParkingLog.vue rename to src/pages/Shelving/Parking/Card/ParkingLog.vue diff --git a/src/pages/Parking/Card/ParkingSummary.vue b/src/pages/Shelving/Parking/Card/ParkingSummary.vue similarity index 100% rename from src/pages/Parking/Card/ParkingSummary.vue rename to src/pages/Shelving/Parking/Card/ParkingSummary.vue diff --git a/src/pages/Parking/ParkingExprBuilder.js b/src/pages/Shelving/Parking/ParkingExprBuilder.js similarity index 100% rename from src/pages/Parking/ParkingExprBuilder.js rename to src/pages/Shelving/Parking/ParkingExprBuilder.js diff --git a/src/pages/Parking/ParkingFilter.vue b/src/pages/Shelving/Parking/ParkingFilter.vue similarity index 100% rename from src/pages/Parking/ParkingFilter.vue rename to src/pages/Shelving/Parking/ParkingFilter.vue diff --git a/src/pages/Parking/ParkingList.vue b/src/pages/Shelving/Parking/ParkingList.vue similarity index 100% rename from src/pages/Parking/ParkingList.vue rename to src/pages/Shelving/Parking/ParkingList.vue diff --git a/src/pages/Parking/locale/en.yml b/src/pages/Shelving/Parking/locale/en.yml similarity index 52% rename from src/pages/Parking/locale/en.yml rename to src/pages/Shelving/Parking/locale/en.yml index 72caba408..2076f38b4 100644 --- a/src/pages/Parking/locale/en.yml +++ b/src/pages/Shelving/Parking/locale/en.yml @@ -1,7 +1,5 @@ parking: pickingOrder: Picking order sector: Sector - row: Row - column: Column search: Search parking searchInfo: You can search by parking code \ No newline at end of file diff --git a/src/pages/Parking/locale/es.yml b/src/pages/Shelving/Parking/locale/es.yml similarity index 51% rename from src/pages/Parking/locale/es.yml rename to src/pages/Shelving/Parking/locale/es.yml index ab23182a1..17fe3af53 100644 --- a/src/pages/Parking/locale/es.yml +++ b/src/pages/Shelving/Parking/locale/es.yml @@ -1,7 +1,5 @@ parking: pickingOrder: Orden de recogida - row: Fila sector: Sector - column: Columna search: Buscar parking searchInfo: Puedes buscar por código de parking \ No newline at end of file diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 6a6feb9ef..462bdf853 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -62,7 +62,6 @@ const getEntryQueryParams = (supplier) => { <template> <CardDescriptor - module="Supplier" :url="`Suppliers/${entityId}`" :filter="filter" data-key="Supplier" diff --git a/src/pages/Supplier/Card/SupplierFiscalData.vue b/src/pages/Supplier/Card/SupplierFiscalData.vue index e569eb236..ecee5b76b 100644 --- a/src/pages/Supplier/Card/SupplierFiscalData.vue +++ b/src/pages/Supplier/Card/SupplierFiscalData.vue @@ -10,6 +10,7 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnLocation from 'src/components/common/VnLocation.vue'; import VnAccountNumber from 'src/components/common/VnAccountNumber.vue'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const route = useRoute(); const { t } = useI18n(); @@ -182,18 +183,11 @@ function handleLocation(data, location) { v-model="data.isTrucker" :label="t('supplier.fiscalData.isTrucker')" /> - <div class="row items-center"> - <QCheckbox v-model="data.isVies" :label="t('globals.isVies')" /> - <QIcon name="info" size="xs" class="cursor-pointer q-ml-sm"> - <QTooltip> - {{ - t( - 'When activating it, do not enter the country code in the ID field.' - ) - }} - </QTooltip> - </QIcon> - </div> + <VnCheckbox + v-model="data.isVies" + :label="t('globals.isVies')" + :info="t('whenActivatingIt')" + /> </div> </VnRow> </template> @@ -201,6 +195,8 @@ function handleLocation(data, location) { </template> <i18n> +en: + whenActivatingIt: When activating it, do not enter the country code in the ID field. es: - When activating it, do not enter the country code in the ID field.: Al activarlo, no informar el código del país en el campo nif + whenActivatingIt: Al activarlo, no informar el código del país en el campo nif. </i18n> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue index 44f2bf7fb..055c9a0ff 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -9,6 +9,7 @@ import FetchData from 'components/FetchData.vue'; import { useStateStore } from 'stores/useStateStore'; import { toCurrency } from 'filters/index'; import { useRole } from 'src/composables/useRole'; +import VnCheckbox from 'src/components/common/VnCheckbox.vue'; const haveNegatives = defineModel('have-negatives', { type: Boolean, required: true }); const formData = defineModel({ type: Object, required: true }); @@ -182,22 +183,19 @@ onMounted(async () => { </QCard> <QCard v-if="haveNegatives" - class="q-pa-md q-mb-md q-ma-md color-vn-text" + class="q-pa-xs q-mb-md q-ma-md color-vn-text" bordered flat style="border-color: black" > <QCardSection horizontal class="flex row items-center"> - <QCheckbox - :label="t('basicData.withoutNegatives')" + <VnCheckbox v-model="formData.withoutNegatives" + :label="t('basicData.withoutNegatives')" + :info="t('basicData.withoutNegativesInfo')" :toggle-indeterminate="false" + size="xs" /> - <QIcon name="info" size="xs" class="q-ml-sm"> - <QTooltip max-width="350px"> - {{ t('basicData.withoutNegativesInfo') }} - </QTooltip> - </QIcon> </QCardSection> </QCard> </QDrawer> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue index cf4481537..9d70fea38 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue @@ -260,7 +260,7 @@ async function getZone(options) { auto-load /> <QForm> - <VnRow> + <VnRow class="row q-gutter-md q-mb-md no-wrap"> <VnSelect :label="t('ticketList.client')" v-model="clientId" @@ -296,7 +296,7 @@ async function getZone(options) { :rules="validate('ticketList.warehouse')" /> </VnRow> - <VnRow> + <VnRow class="row q-gutter-md q-mb-md no-wrap"> <VnSelect :label="t('basicData.address')" v-model="addressId" diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index 762db19bf..c5f3233b1 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -44,7 +44,6 @@ function ticketFilter(ticket) { @on-fetch="(data) => ([problems] = data)" /> <CardDescriptor - module="Ticket" :url="`Tickets/${entityId}`" :filter="filter" data-key="Ticket" diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index 693875712..14eec9db9 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -21,6 +21,10 @@ const $props = defineProps({ type: String, default: 'mana', }, + sale: { + type: Object, + default: null, + }, }); const emit = defineEmits(['save', 'cancel']); @@ -29,8 +33,8 @@ const { t } = useI18n(); const QPopupProxyRef = ref(null); const manaCode = ref($props.manaCode); -const save = () => { - emit('save'); +const save = (sale = $props.sale) => { + emit('save', sale); QPopupProxyRef.value.hide(); }; @@ -38,16 +42,20 @@ const cancel = () => { emit('cancel'); QPopupProxyRef.value.hide(); }; +defineExpose({ save }); </script> <template> - <QPopupProxy ref="QPopupProxyRef"> + <QPopupProxy ref="QPopupProxyRef" data-cy="ticketEditManaProxy"> <div class="container"> <QSpinner v-if="!mana" color="primary" size="md" /> <div v-else> <div class="header">Mana: {{ toCurrency(mana) }}</div> <div class="q-pa-md"> - <slot /> + <slot :popup="QPopupProxyRef" /> + <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> + <VnUsesMana :mana-code="manaCode" /> + </div> <div v-if="newPrice" class="column items-center q-mt-lg"> <span class="text-primary">{{ t('New price') }}</span> <span class="text-subtitle1"> @@ -56,9 +64,6 @@ const cancel = () => { </div> </div> </div> - <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> - <VnUsesMana :mana-code="manaCode" /> - </div> <div class="row"> <QBtn color="primary" diff --git a/src/pages/Ticket/Card/TicketPackage.vue b/src/pages/Ticket/Card/TicketPackage.vue index 8bfb73682..5fbf4c800 100644 --- a/src/pages/Ticket/Card/TicketPackage.vue +++ b/src/pages/Ticket/Card/TicketPackage.vue @@ -49,7 +49,7 @@ watch( <FetchData @on-fetch="(data) => (listPackagingsOptions = data)" auto-load - :filter="{ fields: ['packagingFk', 'name'], order: 'name ASC', limit: 30 }" + :filter="{ fields: ['packagingFk', 'name'], order: 'name ASC' }" url="Packagings/listPackaging" /> <div class="flex justify-center"> diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 97d87ccf8..e680fb290 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -14,7 +14,7 @@ import VnImg from 'src/components/ui/VnImg.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import TicketSaleMoreActions from './TicketSaleMoreActions.vue'; -import TicketTransfer from './TicketTransfer.vue'; +import TicketTransferProxy from './TicketTransferProxy.vue'; import { toCurrency, toPercentage } from 'src/filters'; import { useArrayData } from 'composables/useArrayData'; @@ -22,7 +22,6 @@ import { useVnConfirm } from 'composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; import axios from 'axios'; import VnTable from 'src/components/VnTable/VnTable.vue'; -import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; @@ -33,6 +32,7 @@ const { t } = useI18n(); const { notify } = useNotify(); const { openConfirmationModal } = useVnConfirm(); const editPriceProxyRef = ref(null); +const editManaProxyRef = ref(null); const stateBtnDropdownRef = ref(null); const quasar = useQuasar(); const arrayData = useArrayData('Ticket'); @@ -53,7 +53,6 @@ const transfer = ref({ sales: [], }); const tableRef = ref([]); -const canProceed = ref(); watch( () => route.params.id, @@ -133,7 +132,6 @@ const columns = computed(() => [ align: 'left', label: t('globals.amount'), name: 'amount', - format: (row) => parseInt(row.amount * row.quantity), }, { align: 'left', @@ -183,8 +181,6 @@ const resetChanges = async () => { }; const rowToUpdate = ref(null); const changeQuantity = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; if ( !sale.itemFk || sale.quantity == null || @@ -193,11 +189,21 @@ const changeQuantity = async (sale) => { return; if (!sale.id) return addSale(sale); + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateQuantity(sale)); + } else await updateQuantity(sale); +}; + +const updateQuantity = async (sale) => { try { + let { quantity, id } = sale; if (!rowToUpdate.value) return; rowToUpdate.value = null; sale.isNew = false; - await updateQuantity(sale); + const params = { quantity: quantity }; + await axios.post(`Sales/${id}/updateQuantity`, params); + notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); } catch (e) { const { quantity } = tableRef.value.CrudModelRef.originalData.find( (s) => s.id === sale.id, @@ -207,12 +213,6 @@ const changeQuantity = async (sale) => { } }; -const updateQuantity = async ({ quantity, id }) => { - const params = { quantity: quantity }; - await axios.post(`Sales/${id}/updateQuantity`, params); - notify('globals.dataSaved', 'positive'); -}; - const addSale = async (sale) => { const params = { barcode: sale.itemFk, @@ -237,13 +237,17 @@ const addSale = async (sale) => { sale.isNew = false; arrayData.fetch({}); }; +const changeConcept = async (sale) => { + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateConcept(sale)); + } else await updateConcept(sale); +}; const updateConcept = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; const data = { newConcept: sale.concept }; await axios.post(`Sales/${sale.id}/updateConcept`, data); notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); }; const DEFAULT_EDIT = { @@ -295,33 +299,43 @@ const onOpenEditDiscountPopover = async (sale) => { }; } }; - -const updatePrice = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; +const changePrice = async (sale) => { const newPrice = edit.value.price; if (newPrice != null && newPrice != sale.price) { - await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); - sale.price = newPrice; - edit.value = { ...DEFAULT_EDIT }; - notify('globals.dataSaved', 'positive'); + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updatePrice(sale, newPrice)); + } else updatePrice(sale, newPrice); } - await getMana(); }; +const updatePrice = async (sale, newPrice) => { + await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); + sale.price = newPrice; + edit.value = { ...DEFAULT_EDIT }; + notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); +}; const changeDiscount = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; const newDiscount = edit.value.discount; - if (newDiscount != null && newDiscount != sale.discount) updateDiscount([sale]); + if (newDiscount != null && newDiscount != sale.discount) { + if (await isSalePrepared(sale)) + await confirmUpdate(() => updateDiscount([sale], newDiscount)); + else await updateDiscount([sale], newDiscount); + } +}; + +const updateDiscounts = async (sales, newDiscount = null) => { + const salesTracking = await fetchSalesTracking(); + + const someSaleIsPrepared = salesTracking.some((sale) => + matchSale(salesTracking, sale), + ); + if (someSaleIsPrepared) await confirmUpdate(() => updateDiscount(sales, newDiscount)); + else updateDiscount(sales, newDiscount); }; const updateDiscount = async (sales, newDiscount = null) => { - for (const sale of sales) { - const canProceed = await isSalePrepared(sale); - if (!canProceed) return; - } const saleIds = sales.map((sale) => sale.id); const _newDiscount = newDiscount || edit.value.discount; const params = { @@ -331,8 +345,7 @@ const updateDiscount = async (sales, newDiscount = null) => { }; await axios.post(`Tickets/${route.params.id}/updateDiscount`, params); notify('globals.dataSaved', 'positive'); - for (let sale of sales) sale.discount = _newDiscount; - edit.value = { ...DEFAULT_EDIT }; + tableRef.value.reload(); }; const getNewPrice = computed(() => { @@ -425,9 +438,13 @@ onMounted(async () => { const items = ref([]); const newRow = ref({}); +const changeItem = async (sale) => { + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateItem(sale)); + } else await updateItem(sale); +}; + const updateItem = async (row) => { - canProceed.value = await isSalePrepared(row); - if (!canProceed.value) return; const selectedItem = items.value.find((item) => item.id === row.itemFk); if (selectedItem) { row.item = selectedItem; @@ -471,7 +488,18 @@ const endNewRow = (row) => { } }; -async function isSalePrepared(item) { +async function confirmUpdate(cb) { + await quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('Item prepared'), + message: t('This item is already prepared. Do you want to continue?'), + }, + }) + .onOk(cb); +} +async function fetchSalesTracking() { const filter = { params: { where: { ticketFk: route.params.id }, @@ -483,48 +511,37 @@ async function isSalePrepared(item) { filter: JSON.stringify(filter), }, }); - - const matchingSale = data.find((sale) => sale.itemFk === item.itemFk); - if (!matchingSale) { - return true; - } - - if ( - matchingSale.hasSaleGroupDetail || - matchingSale.isControled || - matchingSale.isPrepared || - matchingSale.isPrevious || - matchingSale.isPreviousSelected - ) { - try { - await new Promise((resolve, reject) => { - quasar - .dialog({ - component: VnConfirm, - componentProps: { - title: t('Item prepared'), - message: t( - 'This item is already prepared. Do you want to continue?', - ), - data: item, - }, - }) - .onOk(() => resolve(true)) - .onCancel(() => reject(new Error('cancelled'))); - }); - } catch (error) { - tableRef.value.reload(); - return false; - } - } - return true; + return data; } +async function isSalePrepared(sale) { + const data = await fetchSalesTracking(); + return matchSale(data, sale); +} +function matchSale(data, sale) { + const matchingSale = data.find(({ itemFk }) => itemFk === sale.itemFk); + + if (!matchingSale) { + return false; + } + + return isPrepared(matchingSale); +} +function isPrepared(sale) { + const flagsToCheck = [ + 'hasSaleGroupDetail', + 'isControled', + 'isPrepared', + 'isPrevious', + 'isPreviousSelected', + ]; + return flagsToCheck.some((flag) => sale[flag] === 1); +} watch( () => newRow.value.itemFk, (newItemFk) => { if (newItemFk) { - updateItem(newRow.value); + changeItem(newRow.value); } }, ); @@ -585,7 +602,7 @@ watch( :mana="mana" :ticket-config="ticketConfig" @get-mana="getMana()" - @update-discounts="updateDiscount" + @update-discounts="updateDiscounts" @refresh-table="resetChanges" /> <QBtn @@ -609,8 +626,9 @@ watch( @click="setTransferParams()" data-cy="ticketSaleTransferBtn" > - <QTooltip>{{ t('Transfer lines') }}</QTooltip> - <TicketTransfer + <QTooltip>{{ t('ticketSale.transferLines') }}</QTooltip> + <TicketTransferProxy + class="full-width" :transfer="transfer" :ticket="store.data" @refresh-data="resetChanges()" @@ -715,7 +733,7 @@ watch( option-value="id" v-model="row.itemFk" :use-like="false" - @update:model-value="updateItem(row)" + @update:model-value="changeItem(row)" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -741,16 +759,21 @@ watch( </div> <FetchedTags :item="row" :max-length="6" /> <QPopupProxy v-if="row.id && isTicketEditable"> - <VnInput v-model="row.concept" @change="updateConcept(row)" /> + <VnInput + v-model="row.concept" + @keyup.enter.stop="changeConcept(row)" + :hint="t('globals.enterToConfirm')" + /> </QPopupProxy> </template> <template #column-quantity="{ row }"> <VnInput + data-cy="ticketSaleQuantityInput" v-if="row.isNew || isTicketEditable" type="number" v-model.number="row.quantity" @blur="changeQuantity(row)" - @keyup.enter="changeQuantity(row)" + @keyup.enter.stop="changeQuantity(row)" @update:model-value="() => (rowToUpdate = row)" @focus="edit.oldQuantity = row.quantity" /> @@ -764,10 +787,12 @@ watch( <TicketEditManaProxy ref="editPriceProxyRef" :mana="mana" + :sale="row" :new-price="getNewPrice" - @save="updatePrice(row)" + @save="changePrice" > <VnInput + @keyup.enter.stop="() => editManaProxyRef.save(row)" v-model.number="edit.price" :label="t('basicData.price')" type="number" @@ -781,27 +806,29 @@ watch( <QBtn flat class="link" dense @click="onOpenEditDiscountPopover(row)"> {{ toPercentage(row.discount / 100) }} </QBtn> + <TicketEditManaProxy + ref="editManaProxyRef" :mana="mana" + :sale="row" :new-price="getNewPrice" :uses-mana="usesMana" :mana-code="manaCode" - @save="changeDiscount(row)" + @save="changeDiscount" > <VnInput + autofocus + @keyup.enter.stop="() => editManaProxyRef.save(row)" v-model.number="edit.discount" :label="t('ticketSale.discount')" type="number" /> - <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> - <VnUsesMana :mana-code="manaCode" /> - </div> </TicketEditManaProxy> </template> <span v-else>{{ toPercentage(row.discount / 100) }}</span> </template> <template #column-amount="{ row }"> - {{ toCurrency(row.quantity * row.price) }} + {{ toCurrency(getSaleTotal(row)) }} </template> </VnTable> diff --git a/src/pages/Ticket/Card/TicketSplit.vue b/src/pages/Ticket/Card/TicketSplit.vue new file mode 100644 index 000000000..e79057266 --- /dev/null +++ b/src/pages/Ticket/Card/TicketSplit.vue @@ -0,0 +1,37 @@ +<script setup> +import { ref } from 'vue'; + +import VnInputDate from 'src/components/common/VnInputDate.vue'; +import split from './components/split'; +const emit = defineEmits(['ticketTransfered']); + +const $props = defineProps({ + ticket: { + type: [Array, Object], + default: () => {}, + }, +}); + +const splitDate = ref(Date.vnNew()); + +const splitSelectedRows = async () => { + const tickets = Array.isArray($props.ticket) ? $props.ticket : [$props.ticket]; + await split(tickets, splitDate.value); + emit('ticketTransfered', tickets); +}; +</script> + +<template> + <VnInputDate class="q-mr-sm" :label="$t('New date')" v-model="splitDate" clearable /> + <QBtn class="q-mr-sm" color="primary" label="Split" @click="splitSelectedRows"></QBtn> +</template> +<style lang="scss"> +.q-table__bottom.row.items-center.q-table__bottom--nodata { + border-top: none; +} +</style> +<i18n> +es: + Sales to transfer: Líneas a transferir + Destination ticket: Ticket destinatario +</i18n> diff --git a/src/pages/Ticket/Card/TicketTransfer.vue b/src/pages/Ticket/Card/TicketTransfer.vue index 005d74a0e..ffa964c92 100644 --- a/src/pages/Ticket/Card/TicketTransfer.vue +++ b/src/pages/Ticket/Card/TicketTransfer.vue @@ -1,11 +1,11 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; - import VnInput from 'src/components/common/VnInput.vue'; import TicketTransferForm from './TicketTransferForm.vue'; import { toDateFormat } from 'src/filters/date.js'; +const emit = defineEmits(['ticketTransfered']); const $props = defineProps({ mana: { @@ -21,16 +21,15 @@ const $props = defineProps({ default: () => {}, }, ticket: { - type: Object, + type: [Array, Object], default: () => {}, }, }); +onMounted(() => (_transfer.value = $props.transfer)); const { t } = useI18n(); -const QPopupProxyRef = ref(null); const transferFormRef = ref(null); const _transfer = ref(); - const transferLinesColumns = computed(() => [ { label: t('ticketList.id'), @@ -86,76 +85,74 @@ const handleRowClick = (row) => { transferFormRef.value.transferSales(ticketId); } }; - -onMounted(() => (_transfer.value = $props.transfer)); </script> <template> - <QPopupProxy ref="QPopupProxyRef" data-cy="ticketTransferPopup"> - <QCard class="q-px-md" style="display: flex; width: 80vw"> - <QTable - :rows="transfer.sales" - :columns="transferLinesColumns" - :title="t('Sales to transfer')" - row-key="id" - :pagination="{ rowsPerPage: 0 }" - class="full-width q-mt-md" - :no-data-label="t('globals.noResults')" - > - <template #body-cell-quantity="{ row }"> - <QTd @click.stop> - <VnInput - v-model.number="row.quantity" - :clearable="false" - style="max-width: 60px" - /> - </QTd> - </template> - </QTable> - <QSeparator vertical spaced /> - <QTable - v-if="transfer.lastActiveTickets" - :rows="transfer.lastActiveTickets" - :columns="destinationTicketColumns" - :title="t('Destination ticket')" - row-key="id" - class="full-width q-mt-md" - @row-click="(_, row) => handleRowClick(row)" - > - <template #body-cell-address="{ row }"> - <QTd @click.stop> - <span> - {{ row.nickname }} - {{ row.name }} - {{ row.street }} - {{ row.postalCode }} - {{ row.city }} - </span> - <QTooltip> - {{ row.nickname }} - {{ row.name }} - {{ row.street }} - {{ row.postalCode }} - {{ row.city }} - </QTooltip> - </QTd> - </template> + <QTable + :rows="transfer.sales" + :columns="transferLinesColumns" + :title="t('Sales to transfer')" + row-key="id" + :pagination="{ rowsPerPage: 0 }" + class="full-width q-mt-md" + :no-data-label="t('globals.noResults')" + > + <template #body-cell-quantity="{ row }"> + <QTd @click.stop> + <VnInput + v-model.number="row.quantity" + :clearable="false" + style="max-width: 60px" + /> + </QTd> + </template> + </QTable> + <QSeparator vertical spaced /> + <QTable + v-if="transfer.lastActiveTickets" + :rows="transfer.lastActiveTickets" + :columns="destinationTicketColumns" + :title="t('Destination ticket')" + row-key="id" + class="full-width q-mt-md" + @row-click="(_, row) => handleRowClick(row)" + :no-data-label="t('globals.noResults')" + :pagination="{ rowsPerPage: 0 }" + > + <template #body-cell-address="{ row }"> + <QTd @click.stop> + <span> + {{ row.nickname }} + {{ row.name }} + {{ row.street }} + {{ row.postalCode }} + {{ row.city }} + </span> + <QTooltip> + {{ row.nickname }} + {{ row.name }} + {{ row.street }} + {{ row.postalCode }} + {{ row.city }} + </QTooltip> + </QTd> + </template> - <template #no-data> - <TicketTransferForm ref="transferFormRef" v-bind="$props" /> - </template> - <template #bottom> - <TicketTransferForm ref="transferFormRef" v-bind="$props" /> - </template> - </QTable> - </QCard> - </QPopupProxy> + <template #no-data> + <TicketTransferForm ref="transferFormRef" v-bind="$props" /> + </template> + <template #bottom> + <TicketTransferForm ref="transferFormRef" v-bind="$props" /> + </template> + </QTable> </template> - +<style lang="scss"> +.q-table__bottom.row.items-center.q-table__bottom--nodata { + border-top: none; +} +</style> <i18n> es: Sales to transfer: Líneas a transferir Destination ticket: Ticket destinatario - Transfer to ticket: Transferir a ticket - New ticket: Nuevo ticket </i18n> diff --git a/src/pages/Ticket/Card/TicketTransferProxy.vue b/src/pages/Ticket/Card/TicketTransferProxy.vue new file mode 100644 index 000000000..3f3f018df --- /dev/null +++ b/src/pages/Ticket/Card/TicketTransferProxy.vue @@ -0,0 +1,54 @@ +<script setup> +import { ref } from 'vue'; +import TicketTransfer from './TicketTransfer.vue'; +import Split from './TicketSplit.vue'; +const emit = defineEmits(['ticketTransfered']); + +const $props = defineProps({ + mana: { + type: Number, + default: null, + }, + newPrice: { + type: Number, + default: 0, + }, + transfer: { + type: Object, + default: () => {}, + }, + ticket: { + type: [Array, Object], + default: () => {}, + }, + split: { + type: Boolean, + default: false, + }, +}); + +const popupProxyRef = ref(null); +const splitRef = ref(null); +const transferRef = ref(null); +</script> + +<template> + <QPopupProxy ref="popupProxyRef" data-cy="ticketTransferPopup"> + <div class="flex row items-center q-ma-lg" v-if="$props.split"> + <Split + ref="splitRef" + @splitSelectedRows="splitSelectedRows" + :ticket="$props.ticket" + /> + </div> + + <div v-else> + <TicketTransfer + ref="transferRef" + :ticket="$props.ticket" + :sales="$props.sales" + :transfer="$props.transfer" + /> + </div> + </QPopupProxy> +</template> diff --git a/src/pages/Ticket/Card/components/split.js b/src/pages/Ticket/Card/components/split.js new file mode 100644 index 000000000..afa1d5cd6 --- /dev/null +++ b/src/pages/Ticket/Card/components/split.js @@ -0,0 +1,22 @@ +import axios from 'axios'; +import notifyResults from 'src/utils/notifyResults'; + +export default async function (data, date) { + const reducedData = data.reduce((acc, item) => { + const existing = acc.find(({ ticketFk }) => ticketFk === item.id); + if (existing) { + existing.sales.push(item.saleFk); + } else { + acc.push({ ticketFk: item.id, sales: [item.saleFk], date }); + } + return acc; + }, []); + + const promises = reducedData.map((params) => axios.post(`Tickets/split`, params)); + + const results = await Promise.allSettled(promises); + + notifyResults(results, 'ticketFk'); + + return results; +} diff --git a/src/pages/Ticket/Negative/TicketLackDetail.vue b/src/pages/Ticket/Negative/TicketLackDetail.vue new file mode 100644 index 000000000..dcf835d03 --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackDetail.vue @@ -0,0 +1,198 @@ +<script setup> +import { computed, onMounted, onUnmounted, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import ChangeQuantityDialog from './components/ChangeQuantityDialog.vue'; +import ChangeStateDialog from './components/ChangeStateDialog.vue'; +import ChangeItemDialog from './components/ChangeItemDialog.vue'; +import TicketTransferProxy from '../Card/TicketTransferProxy.vue'; +import FetchData from 'src/components/FetchData.vue'; +import { useStateStore } from 'stores/useStateStore'; +import { useState } from 'src/composables/useState'; + +import { useRoute } from 'vue-router'; +import TicketLackTable from './TicketLackTable.vue'; +import VnPopupProxy from 'src/components/common/VnPopupProxy.vue'; +import ItemProposalProxy from 'src/pages/Item/components/ItemProposalProxy.vue'; + +import { useQuasar } from 'quasar'; +const quasar = useQuasar(); +const { t } = useI18n(); +const editableStates = ref([]); +const stateStore = useStateStore(); +const tableRef = ref(); +const changeItemDialogRef = ref(null); +const changeStateDialogRef = ref(null); +const changeQuantityDialogRef = ref(null); +const showProposalDialog = ref(false); +const showChangeQuantityDialog = ref(false); +const selectedRows = ref([]); +const route = useRoute(); +onMounted(() => { + stateStore.rightDrawer = false; +}); +onUnmounted(() => { + stateStore.rightDrawer = true; +}); + +const entityId = computed(() => route.params.id); +const item = ref({}); + +const itemProposalSelected = ref(null); +const reload = async () => { + tableRef.value.tableRef.reload(); +}; +defineExpose({ reload }); +const filter = computed(() => ({ + scopeDays: route.query.days, + showType: true, + alertLevelCode: 'FREE', + date: Date.vnNew(), + warehouseFk: useState().getUser().value.warehouseFk, +})); +const itemProposalEvt = (data) => { + const { itemProposal } = data; + itemProposalSelected.value = itemProposal; + reload(); +}; + +function onBuysFetched(data) { + Object.assign(item.value, data[0]); +} +const showItemProposal = () => { + quasar + .dialog({ + component: ItemProposalProxy, + componentProps: { + itemLack: tableRef.value.itemLack, + replaceAction: true, + sales: selectedRows.value, + }, + }) + .onOk(itemProposalEvt); +}; +</script> + +<template> + <FetchData + url="States/editableStates" + @on-fetch="(data) => (editableStates = data)" + auto-load + /> + <FetchData + :url="`Items/${entityId}/getCard`" + :fields="['longName']" + @on-fetch="(data) => (item = data)" + auto-load + /> + <FetchData + :url="`Buys/latestBuysFilter`" + :fields="['longName']" + :filter="{ where: { 'i.id': entityId } }" + @on-fetch="onBuysFetched" + auto-load + /> + + <TicketLackTable + ref="tableRef" + :filter="filter" + @update:selection="({ value }, _) => (selectedRows = value)" + > + <template #top-right> + <QBtnGroup push class="q-mr-lg" style="column-gap: 1px"> + <QBtn + data-cy="transferLines" + color="primary" + :disable="!(selectedRows.length === 1)" + > + <template #default> + <QIcon name="vn:splitline" /> + <QIcon name="vn:ticket" /> + + <QTooltip>{{ t('ticketSale.transferLines') }} </QTooltip> + <TicketTransferProxy + ref="transferFormRef" + split="true" + :ticket="selectedRows" + :transfer="{ + sales: selectedRows, + lastActiveTickets: selectedRows.map((row) => row.id), + }" + @ticket-transfered="reload" + ></TicketTransferProxy> + </template> + </QBtn> + <QBtn + color="primary" + @click="showProposalDialog = true" + :disable="selectedRows.length < 1" + data-cy="itemProposal" + > + <QIcon + name="import_export" + class="rotate-90" + @click="showItemProposal" + ></QIcon> + <QTooltip bottom anchor="bottom right"> + {{ t('itemProposal') }} + </QTooltip> + </QBtn> + <VnPopupProxy + data-cy="changeItem" + icon="sync" + :disable="selectedRows.length < 1" + :tooltip="t('negative.detail.modal.changeItem.title')" + > + <template #extraIcon> <QIcon name="vn:item" /> </template> + <template v-slot="{ popup }"> + <ChangeItemDialog + ref="changeItemDialogRef" + @update-item="popup.hide()" + :selected-rows="selectedRows" + /></template> + </VnPopupProxy> + <VnPopupProxy + data-cy="changeState" + icon="sync" + :disable="selectedRows.length < 1" + :tooltip="t('negative.detail.modal.changeState.title')" + > + <template #extraIcon> <QIcon name="vn:eye" /> </template> + <template v-slot="{ popup }"> + <ChangeStateDialog + ref="changeStateDialogRef" + @update-state="popup.hide()" + :selected-rows="selectedRows" + /></template> + </VnPopupProxy> + <VnPopupProxy + data-cy="changeQuantity" + icon="sync" + :disable="selectedRows.length < 1" + :tooltip="t('negative.detail.modal.changeQuantity.title')" + @click="showChangeQuantityDialog = true" + > + <template #extraIcon> <QIcon name="exposure" /> </template> + <template v-slot="{ popup }"> + <ChangeQuantityDialog + ref="changeQuantityDialogRef" + @update-quantity="popup.hide()" + :selected-rows="selectedRows" + /></template> + </VnPopupProxy> </QBtnGroup + ></template> + </TicketLackTable> +</template> +<style lang="scss" scoped> +.list-enter-active, +.list-leave-active { + transition: all 1s ease; +} +.list-enter-from, +.list-leave-to { + opacity: 0; + background-color: $primary; +} +.q-table.q-table__container > div:first-child { + border-radius: unset; +} +</style> diff --git a/src/pages/Ticket/Negative/TicketLackFilter.vue b/src/pages/Ticket/Negative/TicketLackFilter.vue new file mode 100644 index 000000000..3762f453d --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackFilter.vue @@ -0,0 +1,175 @@ +<script setup> +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; + +import FetchData from 'components/FetchData.vue'; +import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +const { t } = useI18n(); +const props = defineProps({ + dataKey: { + type: String, + required: true, + }, +}); + +const to = Date.vnNew(); +to.setDate(to.getDate() + 1); + +const warehouses = ref(); +const categoriesOptions = ref([]); +const itemTypesRef = ref(null); +const itemTypesOptions = ref([]); + +const itemTypesFilter = { + fields: ['id', 'name', 'categoryFk'], + include: 'category', + order: 'name ASC', + where: {}, +}; +const onCategoryChange = async (categoryFk, search) => { + if (!categoryFk) { + itemTypesFilter.where.categoryFk = null; + delete itemTypesFilter.where.categoryFk; + } else { + itemTypesFilter.where.categoryFk = categoryFk; + } + search(); + await itemTypesRef.value.fetch(); +}; +const emit = defineEmits(['set-user-params']); + +const setUserParams = (params) => { + emit('set-user-params', params); +}; +</script> + +<template> + <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> + <FetchData + url="ItemCategories" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" + @on-fetch="(data) => (categoriesOptions = data)" + auto-load + /> + + <FetchData + ref="itemTypesRef" + url="ItemTypes" + :filter="itemTypesFilter" + @on-fetch="(data) => (itemTypesOptions = data)" + auto-load + /> + + <VnFilterPanel + :data-key="props.dataKey" + :search-button="true" + @set-user-params="setUserParams" + > + <template #tags="{ tag, formatFn }"> + <div class="q-gutter-x-xs"> + <strong>{{ t(`negative.${tag.label}`) }}</strong> + <span>{{ formatFn(tag.value) }}</span> + </div> + </template> + <template #body="{ params, searchFn }"> + <QList dense class="q-gutter-y-sm q-mt-sm"> + <QItem> + <QItemSection> + <VnInput + v-model="params.days" + :label="t('negative.days')" + dense + is-outlined + type="number" + @update:model-value=" + (value) => { + setUserParams(params); + } + " + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.id" + :label="t('negative.id')" + dense + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.producer" + :label="t('negative.producer')" + dense + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.origen" + :label="t('negative.origen')" + dense + is-outlined + /> + </QItemSection> </QItem + ><QItem> + <QItemSection v-if="categoriesOptions"> + <VnSelect + :label="t('negative.categoryFk')" + v-model="params.categoryFk" + @update:model-value=" + ($event) => onCategoryChange($event, searchFn) + " + :options="categoriesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + /> </QItemSection + ><QItemSection v-else> + <QSkeleton class="full-width" type="QSelect" /> + </QItemSection> + </QItem> + <QItem> + <QItemSection v-if="itemTypesOptions"> + <VnSelect + :label="t('negative.type')" + v-model="params.typeFk" + @update:model-value="searchFn()" + :options="itemTypesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ scope.opt?.name }}</QItemLabel> + <QItemLabel caption>{{ + scope.opt?.category?.name + }}</QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </QItemSection + ><QItemSection v-else> + <QSkeleton class="full-width" type="QSelect" /> + </QItemSection> + </QItem> + </QList> + </template> + </VnFilterPanel> +</template> diff --git a/src/pages/Ticket/Negative/TicketLackList.vue b/src/pages/Ticket/Negative/TicketLackList.vue new file mode 100644 index 000000000..d1e8b823a --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackList.vue @@ -0,0 +1,227 @@ +<script setup> +import { computed, ref, reactive } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useStateStore } from 'stores/useStateStore'; +import VnTable from 'components/VnTable/VnTable.vue'; +import { onBeforeMount } from 'vue'; +import { dashIfEmpty, toDate, toHour } from 'src/filters'; +import { useRouter } from 'vue-router'; +import { useState } from 'src/composables/useState'; +import { useRole } from 'src/composables/useRole'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; +import TicketLackFilter from './TicketLackFilter.vue'; +onBeforeMount(() => { + stateStore.$state.rightDrawer = true; +}); +const router = useRouter(); +const stateStore = useStateStore(); +const { t } = useI18n(); +const selectedRows = ref([]); +const tableRef = ref(); +const filterParams = ref({}); +const negativeParams = reactive({ + days: useRole().likeAny('buyer') ? 2 : 0, + warehouseFk: useState().getUser().value.warehouseFk, +}); +const redirectToCreateView = ({ itemFk }) => { + router.push({ + name: 'NegativeDetail', + params: { id: itemFk }, + query: { days: filterParams.value.days ?? negativeParams.days }, + }); +}; +const columns = computed(() => [ + { + name: 'date', + align: 'center', + label: t('negative.date'), + format: ({ timed }) => toDate(timed), + sortable: true, + cardVisible: true, + isId: true, + columnFilter: { + component: 'date', + }, + }, + { + columnClass: 'shrink', + name: 'timed', + align: 'center', + label: t('negative.timed'), + format: ({ timed }) => toHour(timed), + sortable: true, + cardVisible: true, + columnFilter: { + component: 'time', + }, + }, + { + name: 'itemFk', + align: 'center', + label: t('negative.id'), + format: ({ itemFk }) => itemFk, + sortable: true, + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + }, + { + name: 'longName', + align: 'center', + label: t('negative.longName'), + field: ({ longName }) => longName, + + sortable: true, + headerStyle: 'width: 350px', + cardVisible: true, + columnClass: 'expand', + }, + { + name: 'producer', + align: 'center', + label: t('negative.supplier'), + field: ({ producer }) => dashIfEmpty(producer), + sortable: true, + columnClass: 'shrink', + }, + { + name: 'inkFk', + align: 'center', + label: t('negative.colour'), + field: ({ inkFk }) => inkFk, + sortable: true, + cardVisible: true, + }, + { + name: 'size', + align: 'center', + label: t('negative.size'), + field: ({ size }) => size, + sortable: true, + cardVisible: true, + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + }, + { + name: 'category', + align: 'center', + label: t('negative.origen'), + field: ({ category }) => dashIfEmpty(category), + sortable: true, + cardVisible: true, + }, + { + name: 'lack', + align: 'center', + label: t('negative.lack'), + field: ({ lack }) => lack, + columnFilter: { + component: 'input', + type: 'number', + columnClass: 'shrink', + }, + sortable: true, + headerStyle: 'padding-center: 33px', + cardVisible: true, + }, + { + name: 'tableActions', + align: 'center', + actions: [ + { + title: t('Open details'), + icon: 'edit', + action: redirectToCreateView, + isPrimary: true, + }, + ], + }, +]); + +const setUserParams = (params) => { + filterParams.value = params; +}; +</script> + +<template> + <RightMenu> + <template #right-panel> + <TicketLackFilter data-key="NegativeList" @set-user-params="setUserParams" /> + </template> + </RightMenu> + {{ filterRef }} + <VnTable + ref="tableRef" + data-key="NegativeList" + :url="`Tickets/itemLack`" + :order="['itemFk DESC, date DESC, timed DESC']" + :user-params="negativeParams" + auto-load + :columns="columns" + default-mode="table" + :right-search="false" + :is-editable="false" + :use-model="true" + :map-key="false" + :row-click="redirectToCreateView" + v-model:selected="selectedRows" + :create="false" + :crud-model="{ + disableInfiniteScroll: true, + }" + :table="{ + 'row-key': 'itemFk', + selection: 'multiple', + }" + > + <template #column-itemFk="{ row }"> + <div + style="display: flex; justify-content: space-around; align-items: center" + > + <span @click.stop>{{ row.itemFk }}</span> + </div> + </template> + <template #column-longName="{ row }"> + <span class="link" @click.stop> + {{ row.longName }} + <ItemDescriptorProxy :id="row.itemFk" /> + </span> + </template> + </VnTable> +</template> + +<style lang="scss" scoped> +.list { + max-height: 100%; + padding: 15px; + width: 100%; +} + +.grid-style-transition { + transition: + transform 0.28s, + background-color 0.28s; +} + +#true { + background-color: $positive; +} + +#false { + background-color: $negative; +} + +div.q-dialog__inner > div { + max-width: fit-content !important; +} +.q-btn-group > .q-btn-item:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +</style> diff --git a/src/pages/Ticket/Negative/TicketLackTable.vue b/src/pages/Ticket/Negative/TicketLackTable.vue new file mode 100644 index 000000000..176e8f7ad --- /dev/null +++ b/src/pages/Ticket/Negative/TicketLackTable.vue @@ -0,0 +1,356 @@ +<script setup> +import FetchedTags from 'components/ui/FetchedTags.vue'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import { computed, ref, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; +import axios from 'axios'; +import FetchData from 'src/components/FetchData.vue'; +import { toDate, toHour } from 'src/filters'; +import useNotify from 'src/composables/useNotify.js'; +import ZoneDescriptorProxy from 'pages/Zone/Card/ZoneDescriptorProxy.vue'; +import { useRoute } from 'vue-router'; +import VnTable from 'src/components/VnTable/VnTable.vue'; +import TicketDescriptorProxy from '../Card/TicketDescriptorProxy.vue'; +import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; + +const $props = defineProps({ + filter: { + type: Object, + default: () => ({}), + }, +}); + +watch( + () => $props.filter, + (v) => { + filterLack.value.where = v; + tableRef.value.reload(filterLack); + }, +); + +const filterLack = ref({ + include: [ + { + relation: 'workers', + scope: { + fields: ['id', 'firstName'], + }, + }, + ], + where: { ...$props.filter }, + order: 'ts.alertLevelCode ASC', +}); + +const selectedRows = ref([]); +const { t } = useI18n(); +const { notify } = useNotify(); +const entityId = computed(() => route.params.id); +const item = ref({}); +const route = useRoute(); +const columns = computed(() => [ + { + name: 'status', + align: 'center', + sortable: false, + columnClass: 'shrink', + columnFilter: false, + }, + { + name: 'ticketFk', + label: t('negative.detail.ticketFk'), + align: 'center', + sortable: true, + columnFilter: { + component: 'input', + type: 'number', + }, + }, + { + name: 'shipped', + label: t('negative.detail.shipped'), + field: 'shipped', + align: 'center', + format: ({ shipped }) => toDate(shipped), + sortable: true, + columnFilter: { + component: 'date', + columnClass: 'shrink', + }, + }, + { + name: 'minTimed', + label: t('negative.detail.theoreticalhour'), + field: 'minTimed', + align: 'center', + sortable: true, + component: 'time', + columnFilter: {}, + }, + { + name: 'alertLevelCode', + label: t('negative.detail.state'), + columnFilter: { + name: 'alertLevelCode', + component: 'select', + attrs: { + url: 'AlertLevels', + fields: ['name', 'code'], + optionLabel: 'code', + optionValue: 'code', + }, + }, + align: 'center', + sortable: true, + }, + { + name: 'zoneName', + label: t('negative.detail.zoneName'), + field: 'zoneName', + align: 'center', + sortable: true, + }, + { + name: 'nickname', + label: t('negative.detail.nickname'), + field: 'nickname', + align: 'center', + sortable: true, + }, + { + name: 'quantity', + label: t('negative.detail.quantity'), + field: 'quantity', + sortable: true, + component: 'input', + type: 'number', + }, +]); + +const emit = defineEmits(['update:selection']); +const itemLack = ref(null); +const fetchItemLack = ref(null); +const tableRef = ref(null); +defineExpose({ tableRef, itemLack }); +watch(selectedRows, () => emit('update:selection', selectedRows)); +const getInputEvents = ({ col, ...rows }) => ({ + 'update:modelValue': () => saveChange(col.name, rows), + 'keyup.enter': () => saveChange(col.name, rows), +}); +const saveChange = async (field, { row }) => { + try { + switch (field) { + case 'alertLevelCode': + await axios.post(`Tickets/state`, { + ticketFk: row.ticketFk, + code: row[field], + }); + break; + + case 'quantity': + await axios.post(`Sales/${row.saleFk}/updateQuantity`, { + quantity: +row.quantity, + }); + break; + } + notify('globals.dataSaved', 'positive'); + fetchItemLack.value.fetch(); + } catch (err) { + console.error('Error saving changes', err); + f; + } +}; + +function onBuysFetched(data) { + Object.assign(item.value, data[0]); +} +</script> + +<template> + <FetchData + ref="fetchItemLack" + :url="`Tickets/itemLack`" + :params="{ id: entityId }" + @on-fetch="(data) => (itemLack = data[0])" + auto-load + /> + <FetchData + :url="`Items/${entityId}/getCard`" + :fields="['longName']" + @on-fetch="(data) => (item = data)" + auto-load + /> + <FetchData + :url="`Buys/latestBuysFilter`" + :fields="['longName']" + :filter="{ where: { 'i.id': entityId } }" + @on-fetch="onBuysFetched" + auto-load + /> + <VnTable + ref="tableRef" + data-key="NegativeItem" + :map-key="false" + :url="`Tickets/itemLack/${entityId}`" + :columns="columns" + auto-load + :create="false" + :create-as-dialog="false" + :use-model="true" + :filter="filterLack" + :order="['ts.alertLevelCode ASC']" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" + dense + :is-editable="true" + :row-click="false" + :right-search="false" + :right-search-icon="false" + v-model:selected="selectedRows" + :disable-option="{ card: true }" + > + <template #top-left> + <div style="display: flex; align-items: center" v-if="itemLack"> + <!-- <VnImg :id="itemLack.itemFk" class="rounded image-wrapper"></VnImg> --> + <div class="flex column" style="align-items: center"> + <QBadge + ref="badgeLackRef" + class="q-ml-xs" + text-color="white" + :color="itemLack.lack === 0 ? 'positive' : 'negative'" + :label="itemLack.lack" + /> + </div> + <div class="flex column left" style="align-items: flex-start"> + <QBtn flat class="link text-blue"> + {{ item?.longName ?? item.name }} + <ItemDescriptorProxy :id="entityId" /> + <FetchedTags class="q-ml-md" :item="item" :columns="7" /> + </QBtn> + </div> + </div> + </template> + <template #top-right> + <slot name="top-right" /> + </template> + + <template #column-status="{ row }"> + <QTd style="min-width: 150px"> + <div class="icon-container"> + <QIcon + v-if="row.isBasket" + name="vn:basket" + color="primary" + class="cursor-pointer" + size="xs" + > + <QTooltip>{{ t('negative.detail.isBasket') }}</QTooltip> + </QIcon> + <QIcon + v-if="row.hasToIgnore" + name="star" + color="primary" + class="cursor-pointer fill-icon" + size="xs" + > + <QTooltip>{{ t('negative.detail.hasToIgnore') }}</QTooltip> + </QIcon> + <QIcon + v-if="row.hasObservation" + name="change_circle" + color="primary" + class="cursor-pointer" + size="xs" + > + <QTooltip>{{ + t('negative.detail.hasObservation') + }}</QTooltip> </QIcon + ><QIcon + v-if="row.isRookie" + name="vn:Person" + size="xs" + color="primary" + class="cursor-pointer" + > + <QTooltip>{{ t('negative.detail.isRookie') }}</QTooltip> + </QIcon> + <QIcon + v-if="row.peticionCompra" + name="vn:buyrequest" + size="xs" + color="primary" + class="cursor-pointer" + > + <QTooltip>{{ t('negative.detail.peticionCompra') }}</QTooltip> + </QIcon> + <QIcon + v-if="row.turno" + name="vn:calendar" + size="xs" + color="primary" + class="cursor-pointer" + > + <QTooltip>{{ t('negative.detail.turno') }}</QTooltip> + </QIcon> + </div></QTd + > + </template> + <template #column-nickname="{ row }"> + <span class="link" @click.stop> + {{ row.nickname }} + <CustomerDescriptorProxy :id="row.customerId" /> + </span> + </template> + <template #column-ticketFk="{ row }"> + <span class="q-pa-sm link"> + {{ row.id }} + <TicketDescriptorProxy :id="row.id" /> + </span> + </template> + <template #column-alertLevelCode="props"> + <VnSelect + url="States/editableStates" + auto-load + hide-selected + option-value="id" + option-label="name" + v-model="props.row.alertLevelCode" + v-on="getInputEvents(props)" + /> + </template> + + <template #column-zoneName="{ row }"> + <span class="link">{{ row.zoneName }}</span> + <ZoneDescriptorProxy :id="row.zoneFk" /> + </template> + <template #column-quantity="props"> + <VnInputNumber + v-model.number="props.row.quantity" + v-on="getInputEvents(props)" + ></VnInputNumber> + </template> + </VnTable> +</template> +<style lang="scss" scoped> +.icon-container { + display: grid; + grid-template-columns: repeat(3, 0.2fr); + row-gap: 5px; /* Ajusta el espacio entre los iconos según sea necesario */ +} +.icon-container > * { + width: 100%; + height: auto; +} +.list-enter-active, +.list-leave-active { + transition: all 1s ease; +} +.list-enter-from, +.list-leave-to { + opacity: 0; + background-color: $primary; +} +</style> diff --git a/src/pages/Ticket/Negative/components/ChangeItemDialog.vue b/src/pages/Ticket/Negative/components/ChangeItemDialog.vue new file mode 100644 index 000000000..e419b85c0 --- /dev/null +++ b/src/pages/Ticket/Negative/components/ChangeItemDialog.vue @@ -0,0 +1,90 @@ +<script setup> +import { ref } from 'vue'; +import axios from 'axios'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import notifyResults from 'src/utils/notifyResults'; +const emit = defineEmits(['update-item']); + +const showChangeItemDialog = ref(false); +const newItem = ref(null); +const $props = defineProps({ + selectedRows: { + type: Array, + default: () => [], + }, +}); + +const updateItem = async () => { + try { + showChangeItemDialog.value = true; + const rowsToUpdate = $props.selectedRows.map(({ saleFk, quantity }) => + axios.post(`Sales/replaceItem`, { + saleFk, + substitutionFk: newItem.value, + quantity, + }), + ); + const result = await Promise.allSettled(rowsToUpdate); + notifyResults(result, 'saleFk'); + emit('update-item', newItem.value); + } catch (err) { + console.error('Error updating item:', err); + return err; + } +}; +</script> + +<template> + <QCard class="q-pa-sm"> + <QCardSection class="row items-center justify-center column items-stretch"> + {{ showChangeItemDialog }} + <span>{{ $t('negative.detail.modal.changeItem.title') }}</span> + <VnSelect + url="Items/WithName" + :fields="['id', 'name']" + :sort-by="['id DESC']" + :options="items" + option-label="name" + option-value="id" + v-model="newItem" + > + </VnSelect> + </QCardSection> + <QCardActions align="right"> + <QBtn :label="$t('globals.cancel')" color="primary" flat v-close-popup /> + <QBtn + :label="$t('globals.confirm')" + color="primary" + :disable="!newItem" + @click="updateItem" + unelevated + autofocus + /> </QCardActions + ></QCard> +</template> + +<style lang="scss" scoped> +.list { + max-height: 100%; + padding: 15px; + width: 100%; +} + +.grid-style-transition { + transition: + transform 0.28s, + background-color 0.28s; +} + +#true { + background-color: $positive; +} + +#false { + background-color: $negative; +} + +div.q-dialog__inner > div { + max-width: fit-content !important; +} +</style> diff --git a/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue b/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue new file mode 100644 index 000000000..2e9aac4f0 --- /dev/null +++ b/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue @@ -0,0 +1,84 @@ +<script setup> +import { ref } from 'vue'; +import axios from 'axios'; +import VnInput from 'src/components/common/VnInput.vue'; +import notifyResults from 'src/utils/notifyResults'; + +const showChangeQuantityDialog = ref(false); +const newQuantity = ref(null); +const $props = defineProps({ + selectedRows: { + type: Array, + default: () => [], + }, +}); +const emit = defineEmits(['update-quantity']); +const updateQuantity = async () => { + try { + showChangeQuantityDialog.value = true; + const rowsToUpdate = $props.selectedRows.map(({ saleFk }) => + axios.post(`Sales/${saleFk}/updateQuantity`, { + saleFk, + quantity: +newQuantity.value, + }), + ); + + const result = await Promise.allSettled(rowsToUpdate); + notifyResults(result, 'saleFk'); + + emit('update-quantity', newQuantity.value); + } catch (err) { + return err; + } +}; +</script> + +<template> + <QCard class="q-pa-sm"> + <QCardSection class="row items-center justify-center column items-stretch"> + <span>{{ $t('negative.detail.modal.changeQuantity.title') }}</span> + <VnInput + type="number" + :min="0" + :label="$t('negative.detail.modal.changeQuantity.placeholder')" + v-model="newQuantity" + /> + </QCardSection> + <QCardActions align="right"> + <QBtn :label="$t('globals.cancel')" color="primary" flat v-close-popup /> + <QBtn + :label="$t('globals.confirm')" + color="primary" + :disable="!newQuantity || newQuantity < 0" + @click="updateQuantity" + unelevated + autofocus + /> </QCardActions + ></QCard> +</template> + +<style lang="scss" scoped> +.list { + max-height: 100%; + padding: 15px; + width: 100%; +} + +.grid-style-transition { + transition: + transform 0.28s, + background-color 0.28s; +} + +#true { + background-color: $positive; +} + +#false { + background-color: $negative; +} + +div.q-dialog__inner > div { + max-width: fit-content !important; +} +</style> diff --git a/src/pages/Ticket/Negative/components/ChangeStateDialog.vue b/src/pages/Ticket/Negative/components/ChangeStateDialog.vue new file mode 100644 index 000000000..1acc7e0ef --- /dev/null +++ b/src/pages/Ticket/Negative/components/ChangeStateDialog.vue @@ -0,0 +1,91 @@ +<script setup> +import { ref } from 'vue'; +import axios from 'axios'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import FetchData from 'components/FetchData.vue'; +import notifyResults from 'src/utils/notifyResults'; + +const emit = defineEmits(['update-state']); +const editableStates = ref([]); +const showChangeStateDialog = ref(false); +const newState = ref(null); +const $props = defineProps({ + selectedRows: { + type: Array, + default: () => [], + }, +}); +const updateState = async () => { + try { + showChangeStateDialog.value = true; + const rowsToUpdate = $props.selectedRows.map(({ id }) => + axios.post(`Tickets/state`, { + ticketFk: id, + code: newState.value, + }), + ); + const result = await Promise.allSettled(rowsToUpdate); + notifyResults(result, 'ticketFk'); + + emit('update-state', newState.value); + } catch (err) { + return err; + } +}; +</script> + +<template> + <FetchData + url="States/editableStates" + @on-fetch="(data) => (editableStates = data)" + auto-load + /> + <QCard class="q-pa-sm"> + <QCardSection class="row items-center justify-center column items-stretch"> + <span>{{ $t('negative.detail.modal.changeState.title') }}</span> + <VnSelect + :label="$t('negative.detail.modal.changeState.placeholder')" + v-model="newState" + :options="editableStates" + option-label="name" + option-value="code" + /> + </QCardSection> + <QCardActions align="right"> + <QBtn :label="$t('globals.cancel')" color="primary" flat v-close-popup /> + <QBtn + :label="$t('globals.confirm')" + color="primary" + :disable="!newState" + @click="updateState" + unelevated + autofocus + /> </QCardActions + ></QCard> +</template> + +<style lang="scss" scoped> +.list { + max-height: 100%; + padding: 15px; + width: 100%; +} + +.grid-style-transition { + transition: + transform 0.28s, + background-color 0.28s; +} + +#true { + background-color: $positive; +} + +#false { + background-color: $negative; +} + +div.q-dialog__inner > div { + max-width: fit-content !important; +} +</style> diff --git a/src/pages/Ticket/TicketFuture.vue b/src/pages/Ticket/TicketFuture.vue index e8e2dd775..92911cd25 100644 --- a/src/pages/Ticket/TicketFuture.vue +++ b/src/pages/Ticket/TicketFuture.vue @@ -1,24 +1,22 @@ <script setup> -import { onMounted, ref, computed, reactive } from 'vue'; +import { ref, computed, reactive, watch } from 'vue'; import { useI18n } from 'vue-i18n'; -import FetchData from 'components/FetchData.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; +import VnTable from 'src/components/VnTable/VnTable.vue'; import TicketFutureFilter from './TicketFutureFilter.vue'; import { dashIfEmpty, toCurrency } from 'src/filters'; import { useVnConfirm } from 'composables/useVnConfirm'; -import { useArrayData } from 'composables/useArrayData'; import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js'; import useNotify from 'src/composables/useNotify.js'; import { useState } from 'src/composables/useState'; import { toDateTimeFormat } from 'src/filters/date.js'; import axios from 'axios'; +import TicketProblems from 'src/components/TicketProblems.vue'; const state = useState(); const { t } = useI18n(); @@ -26,214 +24,126 @@ const { openConfirmationModal } = useVnConfirm(); const { notify } = useNotify(); const user = state.getUser(); -const itemPackingTypesOptions = ref([]); const selectedTickets = ref([]); - -const exprBuilder = (param, value) => { - switch (param) { - case 'id': - return { id: value }; - case 'futureId': - return { futureId: value }; - case 'liters': - return { liters: value }; - case 'lines': - return { lines: value }; - case 'iptColFilter': - return { ipt: { like: `%${value}%` } }; - case 'futureIptColFilter': - return { futureIpt: { like: `%${value}%` } }; - case 'totalWithVat': - return { totalWithVat: value }; - } -}; - +const vnTableRef = ref({}); +const originElRef = ref(null); +const destinationElRef = ref(null); const userParams = reactive({ futureScopeDays: Date.vnNew().toISOString(), originScopeDays: Date.vnNew().toISOString(), warehouseFk: user.value.warehouseFk, }); -const arrayData = useArrayData('FutureTickets', { - url: 'Tickets/getTicketsFuture', - userParams: userParams, - exprBuilder: exprBuilder, -}); -const { store } = arrayData; - -const params = reactive({ - futureScopeDays: Date.vnNew(), - originScopeDays: Date.vnNew(), - warehouseFk: user.value.warehouseFk, -}); - -const applyColumnFilter = async (col) => { - const paramKey = col.columnFilter?.filterParamKey || col.field; - params[paramKey] = col.columnFilter.filterValue; - await arrayData.addFilter({ params }); -}; - -const getInputEvents = (col) => { - return col.columnFilter.type === 'select' - ? { 'update:modelValue': () => applyColumnFilter(col) } - : { - 'keyup.enter': () => applyColumnFilter(col), - }; -}; - -const tickets = computed(() => store.data); - const ticketColumns = computed(() => [ { - label: t('futureTickets.problems'), + label: '', name: 'problems', + headerClass: 'horizontal-separator', align: 'left', - columnFilter: null, + columnFilter: false, }, { label: t('advanceTickets.ticketId'), - name: 'ticketId', + name: 'id', align: 'center', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - filterParamKey: 'id', - event: getInputEvents, - attrs: { - dense: true, - }, - }, + headerClass: 'horizontal-separator', }, { label: t('futureTickets.shipped'), name: 'shipped', align: 'left', - sortable: true, - columnFilter: null, + columnFilter: false, + headerClass: 'horizontal-separator', }, { + align: 'center', + class: 'shrink', label: t('advanceTickets.ipt'), name: 'ipt', - field: 'ipt', - align: 'left', - sortable: true, columnFilter: { - component: VnSelect, - filterParamKey: 'iptColFilter', - type: 'select', - filterValue: null, - event: getInputEvents, + component: 'select', attrs: { - options: itemPackingTypesOptions.value, - 'option-value': 'code', - 'option-label': 'description', - dense: true, + url: 'itemPackingTypes', + fields: ['code', 'description'], + where: { isActive: true }, + optionValue: 'code', + optionLabel: 'description', + inWhere: false, }, }, - format: (val) => dashIfEmpty(val), + format: (row, dashIfEmpty) => dashIfEmpty(row.ipt), + headerClass: 'horizontal-separator', }, { label: t('ticketList.state'), name: 'state', align: 'left', - sortable: true, - columnFilter: null, + columnFilter: false, + headerClass: 'horizontal-separator', }, { label: t('advanceTickets.liters'), name: 'liters', - field: 'liters', align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, + headerClass: 'horizontal-separator', }, { label: t('advanceTickets.import'), - field: 'import', name: 'import', align: 'left', - sortable: true, + headerClass: 'horizontal-separator', + columnFilter: false, + format: (row) => toCurrency(row.totalWithVat), }, { label: t('futureTickets.availableLines'), name: 'lines', field: 'lines', align: 'center', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => dashIfEmpty(val), + headerClass: 'horizontal-separator', + format: (row, dashIfEmpty) => dashIfEmpty(row.lines), }, { label: t('advanceTickets.futureId'), name: 'futureId', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - filterParamKey: 'futureId', - event: getInputEvents, - attrs: { - dense: true, - }, - }, + align: 'center', + headerClass: 'horizontal-separator vertical-separator ', + columnClass: 'vertical-separator', }, { label: t('futureTickets.futureShipped'), name: 'futureShipped', align: 'left', - sortable: true, - columnFilter: null, - format: (val) => dashIfEmpty(val), + headerClass: 'horizontal-separator', + columnFilter: false, + format: (row) => toDateTimeFormat(row.futureShipped), }, - { + align: 'center', label: t('advanceTickets.futureIpt'), + class: 'shrink', name: 'futureIpt', - field: 'futureIpt', - align: 'left', - sortable: true, columnFilter: { - component: VnSelect, - filterParamKey: 'futureIptColFilter', - type: 'select', - filterValue: null, - event: getInputEvents, + component: 'select', attrs: { - options: itemPackingTypesOptions.value, - 'option-value': 'code', - 'option-label': 'description', - dense: true, + url: 'itemPackingTypes', + fields: ['code', 'description'], + where: { isActive: true }, + optionValue: 'code', + optionLabel: 'description', }, }, - format: (val) => dashIfEmpty(val), + headerClass: 'horizontal-separator', + format: (row, dashIfEmpty) => dashIfEmpty(row.futureIpt), }, { label: t('advanceTickets.futureState'), name: 'futureState', align: 'right', - sortable: true, - columnFilter: null, - format: (val) => dashIfEmpty(val), + headerClass: 'horizontal-separator', + class: 'expand', + columnFilter: false, + format: (row, dashIfEmpty) => dashIfEmpty(row.futureState), }, ]); @@ -258,26 +168,51 @@ const moveTicketsFuture = async () => { await axios.post('Tickets/merge', params); notify(t('advanceTickets.moveTicketSuccess'), 'positive'); selectedTickets.value = []; - arrayData.fetch({ append: false }); + vnTableRef.value.reload(); }; -onMounted(async () => { - await arrayData.fetch({ append: false }); -}); + +watch( + () => vnTableRef.value.tableRef?.$el, + ($el) => { + if (!$el) return; + const head = $el.querySelector('thead'); + const firstRow = $el.querySelector('thead > tr'); + + const newRow = document.createElement('tr'); + destinationElRef.value = document.createElement('th'); + originElRef.value = document.createElement('th'); + + newRow.classList.add('bg-header'); + destinationElRef.value.classList.add('text-uppercase', 'color-vn-label'); + originElRef.value.classList.add('text-uppercase', 'color-vn-label'); + + destinationElRef.value.setAttribute('colspan', '7'); + originElRef.value.setAttribute('colspan', '9'); + + originElRef.value.textContent = `${t('advanceTickets.origin')}`; + destinationElRef.value.textContent = `${t('advanceTickets.destination')}`; + + newRow.append(destinationElRef.value, originElRef.value); + head.insertBefore(newRow, firstRow); + }, + { once: true, inmmediate: true }, +); + +watch( + () => vnTableRef.value.params, + () => { + if (originElRef.value && destinationElRef.value) { + destinationElRef.value.textContent = `${t('advanceTickets.origin')}`; + originElRef.value.textContent = `${t('advanceTickets.destination')}`; + } + }, + { deep: true }, +); </script> <template> - <FetchData - url="itemPackingTypes" - :filter="{ - fields: ['code', 'description'], - order: 'description ASC', - where: { isActive: true }, - }" - auto-load - @on-fetch="(data) => (itemPackingTypesOptions = data)" - /> <VnSearchbar - data-key="FutureTickets" + data-key="futureTicket" :label="t('Search ticket')" :info="t('futureTickets.searchInfo')" /> @@ -293,7 +228,7 @@ onMounted(async () => { t(`futureTickets.moveTicketDialogSubtitle`, { selectedTickets: selectedTickets.length, }), - moveTicketsFuture + moveTicketsFuture, ) " > @@ -305,77 +240,29 @@ onMounted(async () => { </VnSubToolbar> <RightMenu> <template #right-panel> - <TicketFutureFilter data-key="FutureTickets" /> + <TicketFutureFilter data-key="futureTickets" /> </template> </RightMenu> <QPage class="column items-center q-pa-md"> - <QTable - :rows="tickets" + <VnTable + data-key="futureTickets" + ref="vnTableRef" + url="Tickets/getTicketsFuture" + search-url="futureTickets" + :user-params="userParams" + :limit="0" :columns="ticketColumns" - row-key="id" - selection="multiple" + :table="{ + 'row-key': '$index', + selection: 'multiple', + }" v-model:selected="selectedTickets" - :pagination="{ rowsPerPage: 0 }" - :no-data-label="t('globals.noResults')" - style="max-width: 99%" + :right-search="false" + auto-load + :disable-option="{ card: true }" > - <template #header="props"> - <QTr> - <QTh class="horizontal-separator" /> - <QTh - class="horizontal-separator text-uppercase color-vn-label" - colspan="8" - translate - > - {{ t('advanceTickets.origin') }} - </QTh> - <QTh - class="horizontal-separator text-uppercase color-vn-label" - colspan="4" - translate - > - {{ t('advanceTickets.destination') }} - </QTh> - </QTr> - <QTr> - <QTh> - <QCheckbox v-model="props.selected" /> - </QTh> - <QTh - v-for="(col, index) in ticketColumns" - :key="index" - :class="{ 'vertical-separator': col.name === 'futureId' }" - > - {{ col.label }} - </QTh> - </QTr> - </template> - <template #top-row="{ cols }"> - <QTr> - <QTd /> - <QTd - v-for="(col, index) in cols" - :key="index" - style="max-width: 100px" - > - <component - :is="col.columnFilter.component" - v-if="col.columnFilter" - v-model="col.columnFilter.filterValue" - v-bind="col.columnFilter.attrs" - v-on="col.columnFilter.event(col)" - dense - /> - </QTd> - </QTr> - </template> - <template #header-cell-availableLines="{ col }"> - <QTh class="vertical-separator"> - {{ col.label }} - </QTh> - </template> - <template #body-cell-problems="{ row }"> - <QTd class="q-gutter-x-xs"> + <template #column-problems="{ row }"> + <span class="q-gutter-x-xs"> <QIcon v-if="row.futureAgencyFk !== row.agencyFk && row.agencyFk" color="primary" @@ -400,164 +287,88 @@ onMounted(async () => { </span> </QTooltip> </QIcon> - <QIcon - v-if="row.isTaxDataChecked === 0" - color="primary" - name="vn:no036" - size="xs" - > - <QTooltip> - {{ t('futureTickets.noVerified') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.hasTicketRequest" - color="primary" - name="vn:buyrequest" - size="xs" - > - <QTooltip> - {{ t('futureTickets.purchaseRequest') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.itemShortage" - color="primary" - name="vn:unavailable" - size="xs" - > - <QTooltip> - {{ t('ticketSale.noVisible') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.isFreezed" - color="primary" - name="vn:frozen" - size="xs" - > - <QTooltip> - {{ t('futureTickets.clientFrozen') }} - </QTooltip> - </QIcon> - <QIcon v-if="row.risk" color="primary" name="vn:risk" size="xs"> - <QTooltip> - {{ t('futureTickets.risk') }}: {{ row.risk }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.hasComponentLack" - color="primary" - name="vn:components" - size="xs" - > - <QTooltip> - {{ t('futureTickets.componentLack') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row.hasRounding" - color="primary" - name="sync_problem" - size="xs" - > - <QTooltip> - {{ t('futureTickets.rounding') }} - </QTooltip> - </QIcon> - </QTd> + <TicketProblems :row /> + </span> </template> - <template #body-cell-ticketId="{ row }"> - <QTd> - <QBtn flat class="link"> - {{ row.id }} - <TicketDescriptorProxy :id="row.id" /> - </QBtn> - </QTd> + <template #column-id="{ row }"> + <QBtn flat class="link" @click.stop dense> + {{ row.id }} + <TicketDescriptorProxy :id="row.id" /> + </QBtn> </template> - <template #body-cell-shipped="{ row }"> - <QTd class="shipped"> - <QBadge - text-color="black" - :color="getDateQBadgeColor(row.shipped)" - class="q-ma-none" - > - {{ toDateTimeFormat(row.shipped) }} - </QBadge> - </QTd> + <template #column-shipped="{ row }"> + <QBadge + text-color="black" + :color="getDateQBadgeColor(row.shipped)" + class="q-ma-none" + > + {{ toDateTimeFormat(row.shipped) }} + </QBadge> </template> - <template #body-cell-state="{ row }"> - <QTd> - <QBadge - text-color="black" - :color="row.classColor" - class="q-ma-none" - dense - > - {{ row.state }} - </QBadge> - </QTd> + <template #column-state="{ row }"> + <QBadge + v-if="row.state" + text-color="black" + :color="row.classColor" + class="q-ma-none" + dense + > + {{ row.state }} + </QBadge> + <span v-else> {{ dashIfEmpty(row.state) }}</span> </template> - <template #body-cell-import="{ row }"> - <QTd> - <QBadge - :text-color=" - totalPriceColor(row.totalWithVat) === 'warning' - ? 'black' - : 'white' - " - :color="totalPriceColor(row.totalWithVat)" - class="q-ma-none" - dense - > - {{ toCurrency(row.totalWithVat || 0) }} - </QBadge> - </QTd> + <template #column-import="{ row }"> + <QBadge + :text-color=" + totalPriceColor(row.totalWithVat) === 'warning' + ? 'black' + : 'white' + " + :color="totalPriceColor(row.totalWithVat)" + class="q-ma-none" + dense + > + {{ toCurrency(row.totalWithVat || 0) }} + </QBadge> </template> - <template #body-cell-futureId="{ row }"> - <QTd class="vertical-separator"> - <QBtn flat class="link" dense> - {{ row.futureId }} - <TicketDescriptorProxy :id="row.futureId" /> - </QBtn> - </QTd> + <template #column-futureId="{ row }"> + <QBtn flat class="link" @click.stop dense> + {{ row.futureId }} + <TicketDescriptorProxy :id="row.futureId" /> + </QBtn> </template> - <template #body-cell-futureShipped="{ row }"> - <QTd class="shipped"> - <QBadge - text-color="black" - :color="getDateQBadgeColor(row.futureShipped)" - class="q-ma-none" - > - {{ toDateTimeFormat(row.futureShipped) }} - </QBadge> - </QTd> + <template #column-futureShipped="{ row }"> + <QBadge + text-color="black" + :color="getDateQBadgeColor(row.futureShipped)" + class="q-ma-none" + > + {{ toDateTimeFormat(row.futureShipped) }} + </QBadge> </template> - <template #body-cell-futureState="{ row }"> - <QTd> - <QBadge - text-color="black" - :color="row.futureClassColor" - class="q-ma-none" - dense - > - {{ row.futureState }} - </QBadge> - </QTd> + <template #column-futureState="{ row }"> + <QBadge + text-color="black" + :color="row.futureClassColor" + class="q-mr-xs" + dense + > + {{ row.futureState }} + </QBadge> </template> - </QTable> + </VnTable> </QPage> </template> <style scoped lang="scss"> -.shipped { - min-width: 132px; -} -.vertical-separator { +:deep(.vertical-separator) { border-left: 4px solid white !important; } -.horizontal-separator { +:deep(.horizontal-separator) { + border-top: 4px solid white !important; +} +:deep(.horizontal-bottom-separator) { border-bottom: 4px solid white !important; } </style> diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue index d28b0af71..64e060a39 100644 --- a/src/pages/Ticket/TicketFutureFilter.vue +++ b/src/pages/Ticket/TicketFutureFilter.vue @@ -12,7 +12,7 @@ import axios from 'axios'; import { onMounted } from 'vue'; const { t } = useI18n(); -const props = defineProps({ +defineProps({ dataKey: { type: String, required: true, @@ -58,7 +58,7 @@ onMounted(async () => { auto-load /> <VnFilterPanel - :data-key="props.dataKey" + :data-key :un-removable-params="['warehouseFk', 'originScopeDays ', 'futureScopeDays']" > <template #tags="{ tag, formatFn }"> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 8df19c0d9..88878076d 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -232,7 +232,7 @@ const columns = computed(() => [ function resetAgenciesSelector(formData) { agenciesOptions.value = []; - if(formData) formData.agencyModeId = null; + if (formData) formData.agencyModeId = null; } function redirectToLines(id) { @@ -240,7 +240,7 @@ function redirectToLines(id) { window.open(url, '_blank'); } -const onClientSelected = async (formData) => { +const onClientSelected = async (formData) => { resetAgenciesSelector(formData); await fetchClient(formData); await fetchAddresses(formData); @@ -248,14 +248,12 @@ const onClientSelected = async (formData) => { const fetchAvailableAgencies = async (formData) => { resetAgenciesSelector(formData); - const response= await getAgencies(formData, selectedClient.value); + const response = await getAgencies(formData, selectedClient.value); if (!response) return; - - const { options, agency } = response - if(options) - agenciesOptions.value = options; - if(agency) - formData.agencyModeId = agency; + + const { options, agency } = response; + if (options) agenciesOptions.value = options; + if (agency) formData.agencyModeId = agency; }; const fetchClient = async (formData) => { @@ -330,7 +328,7 @@ function openBalanceDialog(ticket) { const description = ref([]); const firstTicketClientId = checkedTickets[0].clientFk; const isSameClient = checkedTickets.every( - (ticket) => ticket.clientFk === firstTicketClientId + (ticket) => ticket.clientFk === firstTicketClientId, ); if (!isSameClient) { @@ -369,7 +367,7 @@ async function onSubmit() { description: dialogData.value.value.description, clientFk: dialogData.value.value.clientFk, email: email[0].email, - } + }, ); if (data) notify('globals.dataSaved', 'positive'); @@ -388,32 +386,32 @@ function setReference(data) { switch (data) { case 1: newDescription = `${t( - 'ticketList.creditCard' + 'ticketList.creditCard', )}, ${dialogData.value.value.description.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 2: newDescription = `${t( - 'ticketList.cash' + 'ticketList.cash', )}, ${dialogData.value.value.description.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 3: newDescription = `${newDescription.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 4: newDescription = `${t( - 'ticketList.transfers' + 'ticketList.transfers', )}, ${dialogData.value.value.description.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 3317: diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index d4bfd1103..cdbb22d9b 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -23,6 +23,8 @@ ticketSale: hasComponentLack: Component lack ok: Ok more: More + transferLines: Transfer lines(no basket)/ Split + transferBasket: Some row selected is basket advanceTickets: preparation: Preparation origin: Origin @@ -188,7 +190,6 @@ ticketList: accountPayment: Account payment sendDocuware: Set delivered and send delivery note(s) to the tablet addPayment: Add payment - date: Date company: Company amount: Amount reference: Reference @@ -202,8 +203,6 @@ ticketList: creditCard: Credit card transfers: Transfers province: Province - warehouse: Warehouse - hour: Hour closure: Closure toLines: Go to lines addressNickname: Address nickname @@ -214,3 +213,79 @@ ticketList: notVisible: Not visible clientFrozen: Client frozen componentLack: Component lack +negative: + hour: Hour + id: Id Article + longName: Article + supplier: Supplier + colour: Colour + size: Size + origen: Origin + value: Negative + itemFk: Article + producer: Producer + warehouse: Warehouse + warehouseFk: Warehouse + category: Category + categoryFk: Family + type: Type + typeFk: Type + lack: Negative + inkFk: inkFk + timed: timed + date: Date + minTimed: minTimed + negativeAction: Negative + totalNegative: Total negatives + days: Days + buttonsUpdate: + item: Item + state: State + quantity: Quantity + modalOrigin: + title: Update negatives + question: Select a state to update + modalSplit: + title: Confirm split selected + question: Select a state to update + detail: + saleFk: Sale + itemFk: Article + ticketFk: Ticket + code: Code + nickname: Alias + name: Name + zoneName: Agency name + shipped: Date + theoreticalhour: Theoretical hour + agName: Agency + quantity: Quantity + alertLevelCode: Group state + state: State + peticionCompra: Ticket request + isRookie: Is rookie + turno: Turn line + isBasket: Basket + hasObservation: Has substitution + hasToIgnore: VIP + modal: + changeItem: + title: Update item reference + placeholder: New item + changeState: + title: Update tickets state + placeholder: New state + changeQuantity: + title: Update tickets quantity + placeholder: New quantity + split: + title: Are you sure you want to split selected tickets? + subTitle: Confirm split action + handleSplited: + title: Handle splited tickets + subTitle: Confirm date and agency + split: + ticket: Old ticket + newTicket: New ticket + status: Result + message: Message diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index ff68461fa..75d3c6a2b 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -127,6 +127,8 @@ ticketSale: ok: Ok more: Más address: Consignatario + transferLines: Transferir líneas(no cesta)/ Separar + transferBasket: No disponible para una cesta size: Medida ticketComponents: serie: Serie @@ -213,6 +215,81 @@ ticketList: toLines: Ir a lineas addressNickname: Alias consignatario ref: Referencia +negative: + hour: Hora + id: Id Articulo + longName: Articulo + supplier: Productor + colour: Color + size: Medida + origen: Origen + value: Negativo + warehouseFk: Almacen + producer: Producer + category: Categoría + categoryFk: Familia + typeFk: Familia + warehouse: Almacen + lack: Negativo + inkFk: Color + timed: Hora + date: Fecha + minTimed: Hora + type: Tipo + negativeAction: Negativo + totalNegative: Total negativos + days: Rango de dias + buttonsUpdate: + item: artículo + state: Estado + quantity: Cantidad + modalOrigin: + title: Actualizar negativos + question: Seleccione un estado para guardar + modalSplit: + title: Confirmar acción de split + question: Selecciona un estado + detail: + saleFk: Línea + itemFk: Artículo + ticketFk: Ticket + code: code + nickname: Alias + name: Nombre + zoneName: Agencia + shipped: F. envío + theoreticalhour: Hora teórica + agName: Agencia + quantity: Cantidad + alertLevelCode: Estado agrupado + state: Estado + peticionCompra: Petición compra + isRookie: Cliente nuevo + turno: Linea turno + isBasket: Cesta + hasObservation: Tiene sustitución + hasToIgnore: VIP + modal: + changeItem: + title: Actualizar referencia artículo + placeholder: Nuevo articulo + changeState: + title: Actualizar estado + placeholder: Nuevo estado + changeQuantity: + title: Actualizar cantidad + placeholder: Nueva cantidad + split: + title: ¿Seguro de separar los tickets seleccionados? + subTitle: Confirma separar tickets seleccionados + handleSplited: + title: Gestionar tickets spliteados + subTitle: Confir fecha y agencia + split: + ticket: Ticket viejo + newTicket: Ticket nuevo + status: Estado + message: Mensaje rounding: Redondeo noVerifiedData: Sin datos comprobados purchaseRequest: Petición de compra diff --git a/src/pages/Travel/Card/TravelBasicData.vue b/src/pages/Travel/Card/TravelBasicData.vue index 4b9aa28ed..b1adc8126 100644 --- a/src/pages/Travel/Card/TravelBasicData.vue +++ b/src/pages/Travel/Card/TravelBasicData.vue @@ -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 VnInputDate from 'components/common/VnInputDate.vue'; +import VnInputTime from 'components/common/VnInputTime.vue'; const route = useRoute(); const { t } = useI18n(); @@ -53,7 +54,16 @@ const warehousesOptionsIn = ref([]); <VnInputDate v-model="data.shipped" :label="t('globals.shipped')" /> <VnInputDate v-model="data.landed" :label="t('globals.landed')" /> </VnRow> - + <VnRow> + <VnInputDate + v-model="data.availabled" + :label="t('travel.summary.availabled')" + /> + <VnInputTime + v-model="data.availabled" + :label="t('travel.summary.availabledHour')" + /> + </VnRow> <VnRow> <VnSelect :label="t('globals.warehouseOut')" @@ -101,10 +111,3 @@ const warehousesOptionsIn = ref([]); </template> </FormModel> </template> - -<i18n> -es: - raidDays: El travel se desplaza automáticamente cada día para estar desde hoy al número de días indicado. Si se deja vacio no se moverá -en: - raidDays: The travel adjusts itself daily to match the number of days set, starting from today. If left blank, it won’t move -</i18n> diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue index 72acf91b8..922f89f33 100644 --- a/src/pages/Travel/Card/TravelDescriptor.vue +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -32,7 +32,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. <template> <CardDescriptor - module="Travel" :url="`Travels/${entityId}`" :title="data.title" :subtitle="data.subtitle" diff --git a/src/pages/Travel/Card/TravelFilter.js b/src/pages/Travel/Card/TravelFilter.js index f5f4520fd..05436834f 100644 --- a/src/pages/Travel/Card/TravelFilter.js +++ b/src/pages/Travel/Card/TravelFilter.js @@ -11,6 +11,7 @@ export default { 'agencyModeFk', 'isRaid', 'daysInForward', + 'availabled', ], include: [ { diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index 16d42f104..9f9552611 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -10,6 +10,8 @@ import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue' import FetchData from 'src/components/FetchData.vue'; import VnRow from 'components/ui/VnRow.vue'; import { toDate, toCurrency, toCelsius } from 'src/filters'; +import { toDateTimeFormat } from 'src/filters/date.js'; +import { dashIfEmpty } from 'src/filters'; import axios from 'axios'; import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue'; @@ -333,6 +335,12 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <VnLv :label="t('globals.reference')" :value="travel.ref" /> <VnLv label="m³" :value="travel.m3" /> <VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" /> + <VnLv + :label="t('travel.summary.availabled')" + :value=" + dashIfEmpty(toDateTimeFormat(travel.availabled)) + " + /> </QCard> <QCard class="full-width"> <VnTitle :text="t('travel.summary.entries')" /> diff --git a/src/pages/Travel/Card/TravelThermographsForm.vue b/src/pages/Travel/Card/TravelThermographsForm.vue index 7aec32972..446e5d506 100644 --- a/src/pages/Travel/Card/TravelThermographsForm.vue +++ b/src/pages/Travel/Card/TravelThermographsForm.vue @@ -209,7 +209,7 @@ const onThermographCreated = async (data) => { }" sort-by="thermographFk ASC" option-label="thermographFk" - option-filter-value="id" + option-filter-value="thermographFk" :disable="viewAction === 'edit'" :tooltip="t('New thermograph')" :roles-allowed-to-create="['logistic']" diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue index dee9d923a..ac46caa44 100644 --- a/src/pages/Travel/ExtraCommunity.vue +++ b/src/pages/Travel/ExtraCommunity.vue @@ -2,6 +2,7 @@ import { onMounted, ref, computed, watch } from 'vue'; import { QBtn } from 'quasar'; import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; @@ -22,6 +23,8 @@ import VnPopup from 'src/components/common/VnPopup.vue'; const stateStore = useStateStore(); const { t } = useI18n(); const { openReport } = usePrintService(); +const route = useRoute(); +const tableParams = ref(); const shippedFrom = ref(Date.vnNew()); const landedTo = ref(Date.vnNew()); @@ -143,7 +146,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('globals.pageTitles.supplier'), + label: t('extraCommunity.cargoShip'), field: 'cargoSupplierNickname', name: 'cargoSupplierNickname', align: 'left', @@ -171,7 +174,7 @@ const columns = computed(() => [ ? value.reduce((sum, entry) => { return sum + (entry.invoiceAmount || 0); }, 0) - : 0 + : 0, ), }, { @@ -200,7 +203,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('kg'), + label: t('extraCommunity.kg'), field: 'kg', name: 'kg', align: 'left', @@ -208,7 +211,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('physicKg'), + label: t('extraCommunity.physicKg'), field: 'loadedKg', name: 'loadedKg', align: 'left', @@ -232,7 +235,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('shipped'), + label: t('extraCommunity.shipped'), field: 'shipped', name: 'shipped', align: 'left', @@ -249,7 +252,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('landed'), + label: t('extraCommunity.landed'), field: 'landed', name: 'landed', align: 'left', @@ -258,7 +261,7 @@ const columns = computed(() => [ format: (value) => toDate(value), }, { - label: t('notes'), + label: t('extraCommunity.notes'), field: '', name: 'notes', align: 'center', @@ -284,7 +287,7 @@ watch( if (!arrayData.store.data) return; onStoreDataChange(); }, - { deep: true, immediate: true } + { deep: true, immediate: true }, ); const openReportPdf = () => { @@ -451,13 +454,24 @@ const getColor = (percentage) => { for (const { value, className } of travelKgPercentages.value) if (percentage > value) return className; }; + +const filteredEntries = (entries) => { + if (!tableParams?.value?.entrySupplierFk) return entries; + return entries?.filter( + (entry) => entry.supplierFk === tableParams?.value?.entrySupplierFk, + ); +}; + +watch(route, () => { + tableParams.value = JSON.parse(route.query.table); +}); </script> <template> <VnSearchbar data-key="ExtraCommunity" :limit="20" - :label="t('searchExtraCommunity')" + :label="t('extraCommunity.searchExtraCommunity')" /> <RightMenu> <template #right-panel> @@ -521,7 +535,7 @@ const getColor = (percentage) => { ? tableColumnComponents[col.name].event( rows[props.rowIndex][col.field], col.field, - props.rowIndex + props.rowIndex, ) : {} " @@ -546,7 +560,7 @@ const getColor = (percentage) => { }, { link: ['id', 'cargoSupplierNickname'].includes( - col.name + col.name, ), }, ]" @@ -564,9 +578,8 @@ const getColor = (percentage) => { </component> </QTd> </QTr> - <QTr - v-for="(entry, index) in props.row.entries" + v-for="(entry, index) in filteredEntries(props.row.entries)" :key="index" :props="props" class="bg-vn-secondary-row cursor-pointer" @@ -598,7 +611,7 @@ const getColor = (percentage) => { name="warning" color="negative" size="md" - :title="t('requiresInspection')" + :title="t('extraCommunity.requiresInspection')" > </QIcon> </QTd> @@ -709,24 +722,3 @@ const getColor = (percentage) => { width: max-content; } </style> -<i18n> -en: - searchExtraCommunity: Search for extra community shipping - kg: BI. KG - physicKg: Phy. KG - shipped: W. shipped - landed: W. landed - requiresInspection: Requires inspection - BIP: Boder Inspection Point - notes: Notes -es: - searchExtraCommunity: Buscar por envío extra comunitario - kg: KG Bloq. - physicKg: KG físico - shipped: F. envío - landed: F. llegada - notes: Notas - Open as PDF: Abrir como PDF - requiresInspection: Requiere inspección - BIP: Punto de Inspección Fronteriza -</i18n> diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index b22574632..29d342334 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -79,7 +79,7 @@ warehouses(); <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`extraCommunity.filter.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> @@ -92,7 +92,7 @@ warehouses(); <QItem> <QItemSection> <VnInput - :label="t('params.reference')" + :label="t('extraCommunity.filter.reference')" v-model="params.reference" is-outlined /> @@ -103,7 +103,7 @@ warehouses(); <QInput v-model="params.totalEntries" type="number" - :label="t('params.totalEntries')" + :label="t('extraCommunity.filter.totalEntries')" dense outlined rounded @@ -133,10 +133,10 @@ warehouses(); <QItem> <QItemSection> <VnSelect - :label="t('params.agencyModeFk')" + :label="t('extraCommunity.filter.agencyModeFk')" v-model="params.agencyModeFk" :options="agenciesOptions" - option-value="agencyFk" + option-value="id" option-label="name" hide-selected dense @@ -148,7 +148,7 @@ warehouses(); <QItem> <QItemSection> <VnInputDate - :label="t('params.shippedFrom')" + :label="t('extraCommunity.filter.shippedFrom')" v-model="params.shippedFrom" @update:model-value="searchFn()" is-outlined @@ -158,7 +158,7 @@ warehouses(); <QItem> <QItemSection> <VnInputDate - :label="t('params.landedTo')" + :label="t('extraCommunity.filter.landedTo')" v-model="params.landedTo" @update:model-value="searchFn()" is-outlined @@ -168,7 +168,7 @@ warehouses(); <QItem v-if="warehousesByContinent[params.continent]"> <QItemSection> <VnSelect - :label="t('params.warehouseOutFk')" + :label="t('extraCommunity.filter.warehouseOutFk')" v-model="params.warehouseOutFk" :options="warehousesByContinent[params.continent]" option-value="id" @@ -183,7 +183,7 @@ warehouses(); <QItem v-else> <QItemSection> <VnSelect - :label="t('params.warehouseOutFk')" + :label="t('extraCommunity.filter.warehouseOutFk')" v-model="params.warehouseOutFk" :options="warehousesOptions" option-value="id" @@ -198,7 +198,7 @@ warehouses(); <QItem> <QItemSection> <VnSelect - :label="t('params.warehouseInFk')" + :label="t('extraCommunity.filter.warehouseInFk')" v-model="params.warehouseInFk" :options="warehousesOptions" option-value="id" @@ -213,6 +213,7 @@ warehouses(); <QItem> <QItemSection> <VnSelectSupplier + :label="t('extraCommunity.cargoShip')" v-model="params.cargoSupplierFk" hide-selected dense @@ -221,10 +222,21 @@ warehouses(); /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnSelectSupplier + v-model="params.entrySupplierFk" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <VnSelect - :label="t('params.continent')" + :label="t('extraCommunity.filter.continent')" v-model="params.continent" :options="continentsOptions" option-value="code" @@ -240,30 +252,3 @@ warehouses(); </template> </VnFilterPanel> </template> - -<i18n> -en: - params: - id: Id - reference: Reference - totalEntries: Total entries - agencyModeFk: Agency - warehouseInFk: Warehouse In - warehouseOutFk: Warehouse Out - shippedFrom: Shipped from - landedTo: Landed to - cargoSupplierFk: Supplier - continent: Continent out -es: - params: - id: Id - reference: Referencia - totalEntries: Ent. totales - agencyModeFk: Agencia - warehouseInFk: Alm. entrada - warehouseOutFk: Alm. salida - shippedFrom: Llegada desde - landedTo: Llegada hasta - cargoSupplierFk: Proveedor - continent: Cont. Salida -</i18n> diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index e90c01be2..b227afcb2 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -10,6 +10,9 @@ import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js'; import TravelFilter from './TravelFilter.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnSection from 'src/components/common/VnSection.vue'; +import VnInputTime from 'src/components/common/VnInputTime.vue'; +import VnInputDate from 'src/components/common/VnInputDate.vue'; +import { toDateTimeFormat } from 'src/filters/date'; const { viewSummary } = useSummaryDialog(); const router = useRouter(); @@ -167,6 +170,17 @@ const columns = computed(() => [ cardVisible: true, create: true, }, + { + align: 'left', + name: 'availabled', + label: t('travel.summary.availabled'), + component: 'input', + columnClass: 'expand', + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(toDateTimeFormat(row.availabled)), + }, { align: 'right', label: '', @@ -269,6 +283,16 @@ const columns = computed(() => [ :class="{ 'is-active': row.isReceived }" /> </template> + <template #more-create-dialog="{ data }"> + <VnInputDate + v-model="data.availabled" + :label="t('travel.summary.availabled')" + /> + <VnInputTime + v-model="data.availabled" + :label="t('travel.summary.availabledHour')" + /> + </template> <template #moreFilterPanel="{ params }"> <VnInputNumber :label="t('params.scopeDays')" diff --git a/src/pages/Travel/locale/en.yml b/src/pages/Travel/locale/en.yml new file mode 100644 index 000000000..ddef66f2f --- /dev/null +++ b/src/pages/Travel/locale/en.yml @@ -0,0 +1,22 @@ +extraCommunity: + cargoShip: Cargo ship + searchExtraCommunity: Search for extra community shipping + kg: BI. KG + physicKg: Phy. KG + shipped: W. shipped + landed: W. landed + requiresInspection: Requires inspection + BIP: Boder Inspection Point + notes: Notes + filter: + id: Id + reference: Reference + totalEntries: Total entries + agencyModeFk: Agency + warehouseInFk: Warehouse In + warehouseOutFk: Warehouse Out + shippedFrom: Shipped from + landedTo: Landed to + cargoSupplierFk: Cargo supplier + continent: Continent out + entrySupplierFk: Supplier diff --git a/src/pages/Travel/locale/es.yml b/src/pages/Travel/locale/es.yml new file mode 100644 index 000000000..1542d8892 --- /dev/null +++ b/src/pages/Travel/locale/es.yml @@ -0,0 +1,23 @@ +extraCommunity: + cargoShip: Carguera + searchExtraCommunity: Buscar por envío extra comunitario + kg: KG Bloq. + physicKg: KG físico + shipped: F. envío + landed: F. llegada + notes: Notas + Open as PDF: Abrir como PDF + requiresInspection: Requiere inspección + BIP: Punto de Inspección Fronteriza + filter: + id: Id + reference: Referencia + totalEntries: Ent. totales + agencyModeFk: Agencia + warehouseInFk: Alm. entrada + warehouseOutFk: Alm. salida + shippedFrom: Llegada desde + landedTo: Llegada hasta + cargoSupplierFk: Carguera + continent: Cont. Salida + entrySupplierFk: Proveedor diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index ffebaf5ea..de3f634e2 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -10,7 +10,7 @@ import axios from 'axios'; import VnImg from 'src/components/ui/VnImg.vue'; import EditPictureForm from 'components/EditPictureForm.vue'; import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; -import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; const $props = defineProps({ id: { @@ -50,9 +50,9 @@ const handlePhotoUpdated = (evt = false) => { <template> <CardDescriptor ref="cardDescriptorRef" - module="Worker" :data-key="dataKey" url="Workers/summary" + :filter="{ where: { id: entityId } }" title="user.nickname" @on-fetch="getIsExcluded" > diff --git a/src/pages/Worker/Card/WorkerDescriptorMenu.vue b/src/pages/Worker/Card/WorkerDescriptorMenu.vue index 8d82dc839..0dcb4fd71 100644 --- a/src/pages/Worker/Card/WorkerDescriptorMenu.vue +++ b/src/pages/Worker/Card/WorkerDescriptorMenu.vue @@ -53,7 +53,7 @@ const showChangePasswordDialog = () => { </QItemSection> </QItem> <QItem - v-if="!worker.user.emailVerified && user.id == worker.id" + v-if="!worker.user.emailVerified && user.id != worker.id" v-ripple clickable @click="showChangePasswordDialog" diff --git a/src/pages/Worker/Card/WorkerDescriptorProxy.vue b/src/pages/Worker/Card/WorkerDescriptorProxy.vue index 43deb7821..a142570f9 100644 --- a/src/pages/Worker/Card/WorkerDescriptorProxy.vue +++ b/src/pages/Worker/Card/WorkerDescriptorProxy.vue @@ -12,11 +12,6 @@ const $props = defineProps({ <template> <QPopupProxy> - <WorkerDescriptor - v-if="$props.id" - :id="$props.id" - :summary="WorkerSummary" - data-key="workerDescriptorProxy" - /> + <WorkerDescriptor v-if="$props.id" :id="$props.id" :summary="WorkerSummary" /> </QPopupProxy> </template> diff --git a/src/pages/Worker/Card/WorkerFormation.vue b/src/pages/Worker/Card/WorkerFormation.vue index 6fd5a4eae..e8680f7dd 100644 --- a/src/pages/Worker/Card/WorkerFormation.vue +++ b/src/pages/Worker/Card/WorkerFormation.vue @@ -94,6 +94,7 @@ const columns = computed(() => [ align: 'left', name: 'hasDiploma', label: t('worker.formation.tableVisibleColumns.hasDiploma'), + component: 'checkbox', create: true, }, { @@ -118,7 +119,7 @@ const columns = computed(() => [ :url="`Workers/${entityId}/trainingCourse`" :url-create="`Workers/${entityId}/trainingCourse`" save-url="TrainingCourses/crud" - :filter="courseFilter" + :user-filter="courseFilter" :create="{ urlCreate: 'trainingCourses', title: t('Create training course'), diff --git a/src/pages/Worker/Card/WorkerMedical.vue b/src/pages/Worker/Card/WorkerMedical.vue index c220df76a..c04f6496b 100644 --- a/src/pages/Worker/Card/WorkerMedical.vue +++ b/src/pages/Worker/Card/WorkerMedical.vue @@ -3,11 +3,23 @@ import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import VnTable from 'components/VnTable/VnTable.vue'; +import { dashIfEmpty } from 'src/filters'; const tableRef = ref(); const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => route.params.id); +const centerFilter = { + include: [ + { + relation: 'center', + scope: { + fields: ['id', 'name'], + }, + }, + ], +}; + const columns = [ { align: 'left', @@ -36,6 +48,9 @@ const columns = [ url: 'medicalCenters', fields: ['id', 'name'], }, + format: (row, dashIfEmpty) => { + return dashIfEmpty(row.center?.name); + }, }, { align: 'left', @@ -84,6 +99,7 @@ const columns = [ ref="tableRef" data-key="WorkerMedical" :url="`Workers/${entityId}/medicalReview`" + :user-filter="centerFilter" save-url="MedicalReviews/crud" :create="{ urlCreate: 'medicalReviews', diff --git a/src/pages/Worker/Card/WorkerPit.vue b/src/pages/Worker/Card/WorkerPit.vue index 40e814452..3de60d6a0 100644 --- a/src/pages/Worker/Card/WorkerPit.vue +++ b/src/pages/Worker/Card/WorkerPit.vue @@ -176,6 +176,7 @@ const deleteRelative = async (id) => { :label="t('isDescendant')" v-model="row.isDescendant" class="q-gutter-xs q-mb-xs" + data-cy="Descendant/Ascendant" /> <VnSelect :label="t('disabilityGrades')" diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 992f6ec71..78c5dfd82 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -9,7 +9,7 @@ import CardSummary from 'components/ui/CardSummary.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue'; -import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import { useAdvancedSummary } from 'src/composables/useAdvancedSummary'; import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; diff --git a/src/pages/Department/Card/DepartmentBasicData.vue b/src/pages/Worker/Department/Card/DepartmentBasicData.vue similarity index 100% rename from src/pages/Department/Card/DepartmentBasicData.vue rename to src/pages/Worker/Department/Card/DepartmentBasicData.vue diff --git a/src/pages/Department/Card/DepartmentCard.vue b/src/pages/Worker/Department/Card/DepartmentCard.vue similarity index 77% rename from src/pages/Department/Card/DepartmentCard.vue rename to src/pages/Worker/Department/Card/DepartmentCard.vue index 64ea24d42..2e3f11521 100644 --- a/src/pages/Department/Card/DepartmentCard.vue +++ b/src/pages/Worker/Department/Card/DepartmentCard.vue @@ -1,6 +1,6 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; -import DepartmentDescriptor from 'pages/Department/Card/DepartmentDescriptor.vue'; +import DepartmentDescriptor from 'pages/Worker/Department/Card/DepartmentDescriptor.vue'; </script> <template> <VnCardBeta diff --git a/src/pages/Department/Card/DepartmentDescriptor.vue b/src/pages/Worker/Department/Card/DepartmentDescriptor.vue similarity index 99% rename from src/pages/Department/Card/DepartmentDescriptor.vue rename to src/pages/Worker/Department/Card/DepartmentDescriptor.vue index ecd7fa36c..4b7dfd9b8 100644 --- a/src/pages/Department/Card/DepartmentDescriptor.vue +++ b/src/pages/Worker/Department/Card/DepartmentDescriptor.vue @@ -42,7 +42,6 @@ const { openConfirmationModal } = useVnConfirm(); <template> <CardDescriptor ref="DepartmentDescriptorRef" - module="Department" :url="`Departments/${entityId}`" :summary="$props.summary" :to-module="{ name: 'WorkerDepartment' }" diff --git a/src/pages/Department/Card/DepartmentDescriptorProxy.vue b/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue similarity index 100% rename from src/pages/Department/Card/DepartmentDescriptorProxy.vue rename to src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue diff --git a/src/pages/Department/Card/DepartmentSummary.vue b/src/pages/Worker/Department/Card/DepartmentSummary.vue similarity index 95% rename from src/pages/Department/Card/DepartmentSummary.vue rename to src/pages/Worker/Department/Card/DepartmentSummary.vue index 49c46b236..3719137e4 100644 --- a/src/pages/Department/Card/DepartmentSummary.vue +++ b/src/pages/Worker/Department/Card/DepartmentSummary.vue @@ -31,20 +31,18 @@ onMounted(async () => { ref="summary" :url="`Departments/${entityId}`" class="full-width" - style="max-width: 900px" - module-name="Department" > <template #header="{ entity }"> <div>{{ entity.name }}</div> </template> <template #body="{ entity: department }"> - <QCard class="column"> + <QCard class="vn-one"> <VnTitle :url="`#/worker/department/${entityId}/basic-data`" :text="t('Basic data')" /> <div class="full-width row wrap justify-between content-between"> - <div class="column" style="min-width: 50%"> + <div class="column"> <VnLv :label="t('globals.name')" :value="department.name" dash /> <VnLv :label="t('globals.code')" :value="department.code" dash /> <VnLv diff --git a/src/pages/Department/Card/DepartmentSummaryDialog.vue b/src/pages/Worker/Department/Card/DepartmentSummaryDialog.vue similarity index 100% rename from src/pages/Department/Card/DepartmentSummaryDialog.vue rename to src/pages/Worker/Department/Card/DepartmentSummaryDialog.vue diff --git a/src/pages/Worker/WorkerDepartmentTree.vue b/src/pages/Worker/WorkerDepartmentTree.vue index 14009134b..9baf5ee57 100644 --- a/src/pages/Worker/WorkerDepartmentTree.vue +++ b/src/pages/Worker/WorkerDepartmentTree.vue @@ -3,7 +3,7 @@ import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useState } from 'src/composables/useState'; import { useQuasar } from 'quasar'; -import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import CreateDepartmentChild from './CreateDepartmentChild.vue'; import axios from 'axios'; import { useRouter } from 'vue-router'; diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 5206f1e62..03013f011 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -1,5 +1,7 @@ <script setup> import { useI18n } from 'vue-i18n'; +import { ref } from 'vue'; +import FetchData from 'components/FetchData.vue'; import FormModel from 'src/components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; @@ -7,10 +9,23 @@ import VnInputTime from 'src/components/common/VnInputTime.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; const { t } = useI18n(); +const validAddresses = ref([]); +const addresses = ref([]); + +const setFilteredAddresses = (data) => { + const validIds = new Set(validAddresses.value.map((item) => item.addressFk)); + addresses.value = data.filter((address) => validIds.has(address.id)); +}; </script> <template> - <FormModel :url="`Zones/${$route.params.id}`" auto-load model="zone"> + <FetchData + url="RoadmapAddresses" + auto-load + @on-fetch="(data) => (validAddresses = data)" + /> + <FetchData url="Addresses" auto-load @on-fetch="setFilteredAddresses" /> + <FormModel auto-load model="Zone"> <template #form="{ data, validate }"> <VnRow> <VnInput @@ -18,9 +33,9 @@ const { t } = useI18n(); :label="t('Name')" clearable v-model="data.name" + :required="true" /> </VnRow> - <VnRow> <VnSelect v-model="data.agencyModeFk" @@ -69,7 +84,7 @@ const { t } = useI18n(); type="number" min="0" /> - <VnInputTime v-model="data.hour" :label="t('Closing')" /> + <VnInputTime v-model="data.hour" :label="t('Closing')" :required="true" /> </VnRow> <VnRow> @@ -78,7 +93,7 @@ const { t } = useI18n(); :label="t('Price')" type="number" min="0" - required="true" + :required="true" clearable /> <VnInput @@ -86,7 +101,7 @@ const { t } = useI18n(); :label="t('Price optimum')" type="number" min="0" - required="true" + :required="true" clearable /> </VnRow> @@ -103,12 +118,14 @@ const { t } = useI18n(); v-model="data.addressFk" option-value="id" option-label="nickname" - url="Addresses" + :options="addresses" :fields="['id', 'nickname']" sort-by="id" hide-selected map-options :rules="validate('data.addressFk')" + :filter-options="['id']" + :where="filterWhere" /> </VnRow> <VnRow> diff --git a/src/pages/Zone/Card/ZoneCalendar.vue b/src/pages/Zone/Card/ZoneCalendar.vue deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue index 49237a02b..27676212e 100644 --- a/src/pages/Zone/Card/ZoneDescriptor.vue +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -25,12 +25,7 @@ const entityId = computed(() => { </script> <template> - <CardDescriptor - module="Zone" - :url="`Zones/${entityId}`" - :filter="filter" - data-key="Zone" - > + <CardDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> <template #menu="{ entity }"> <ZoneDescriptorMenuItems :zone="entity" /> </template> diff --git a/src/pages/Zone/Card/ZoneSearchbar.vue b/src/pages/Zone/Card/ZoneSearchbar.vue index f7a59e97f..d1188a1e8 100644 --- a/src/pages/Zone/Card/ZoneSearchbar.vue +++ b/src/pages/Zone/Card/ZoneSearchbar.vue @@ -22,15 +22,50 @@ const exprBuilder = (param, value) => { return /^\d+$/.test(value) ? { id: value } : { name: { like: `%${value}%` } }; } }; + +const tableFilter = { + include: [ + { + relation: 'agencyMode', + scope: { + fields: ['id', 'name'], + }, + }, + { + relation: 'address', + scope: { + fields: ['id', 'nickname', 'provinceFk', 'postalCode'], + include: [ + { + relation: 'province', + scope: { + fields: ['id', 'name'], + }, + }, + { + relation: 'postcode', + scope: { + fields: ['code', 'townFk'], + include: { + relation: 'town', + scope: { + fields: ['id', 'name'], + }, + }, + }, + }, + ], + }, + }, + ], +}; </script> <template> <VnSearchbar data-key="ZonesList" url="Zones" - :filter="{ - include: { relation: 'agencyMode', scope: { fields: ['name'] } }, - }" + :filter="tableFilter" :expr-builder="exprBuilder" :label="t('list.searchZone')" :info="t('list.searchInfo')" diff --git a/src/pages/Zone/ZoneFilterPanel.vue b/src/pages/Zone/ZoneFilterPanel.vue index 3a35527ab..bbe12189a 100644 --- a/src/pages/Zone/ZoneFilterPanel.vue +++ b/src/pages/Zone/ZoneFilterPanel.vue @@ -38,7 +38,12 @@ const agencies = ref([]); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput :label="t('list.name')" v-model="params.name" is-outlined /> + <VnInput + :label="t('list.name')" + v-model="params.name" + is-outlined + data-cy="zoneFilterPanelNameInput" + /> </QItemSection> </QItem> <QItem> @@ -53,6 +58,7 @@ const agencies = ref([]); dense outlined rounded + data-cy="zoneFilterPanelAgencySelect" > </VnSelect> </QItemSection> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index e4a1774fe..a82bbb285 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router'; import { computed, ref } from 'vue'; import axios from 'axios'; -import { toCurrency } from 'src/filters'; +import { dashIfEmpty, toCurrency } from 'src/filters'; import { toTimeFormat } from 'src/filters/date'; import { useVnConfirm } from 'composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; @@ -17,7 +17,6 @@ import VnInputTime from 'src/components/common/VnInputTime.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; import ZoneFilterPanel from './ZoneFilterPanel.vue'; import ZoneSearchbar from './Card/ZoneSearchbar.vue'; -import FetchData from 'src/components/FetchData.vue'; const { t } = useI18n(); const router = useRouter(); @@ -26,7 +25,6 @@ const { viewSummary } = useSummaryDialog(); const { openConfirmationModal } = useVnConfirm(); const tableRef = ref(); const warehouseOptions = ref([]); -const validAddresses = ref([]); const tableFilter = { include: [ @@ -67,7 +65,6 @@ const tableFilter = { const columns = computed(() => [ { - align: 'left', name: 'id', label: t('list.id'), chip: { @@ -77,6 +74,8 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + columnClass: 'shrink-column', + component: 'number', }, { align: 'left', @@ -108,7 +107,6 @@ const columns = computed(() => [ format: (row, dashIfEmpty) => dashIfEmpty(row?.agencyMode?.name), }, { - align: 'left', name: 'price', label: t('list.price'), cardVisible: true, @@ -116,9 +114,11 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + columnClass: 'shrink-column', + component: 'number', }, { - align: 'left', + align: 'center', name: 'hour', label: t('list.close'), cardVisible: true, @@ -131,6 +131,7 @@ const columns = computed(() => [ label: t('list.addressFk'), cardVisible: true, columnFilter: false, + columnClass: 'expand', }, { align: 'right', @@ -161,97 +162,91 @@ const handleClone = (id) => { openConfirmationModal( t('list.confirmCloneTitle'), t('list.confirmCloneSubtitle'), - () => clone(id) + () => clone(id), ); }; -function showValidAddresses(row) { - if (row.addressFk) { - const isValid = validAddresses.value.some( - (address) => address.addressFk === row.addressFk - ); - if (isValid) - return `${row.address?.nickname}, - ${row.address?.postcode?.town?.name} (${row.address?.province?.name})`; - else return '-'; - } - return '-'; +function formatRow(row) { + if (!row?.address) return '-'; + return dashIfEmpty(`${row?.address?.nickname}, + ${row?.address?.postcode?.town?.name} (${row?.address?.province?.name})`); } </script> <template> - <FetchData - url="RoadmapAddresses" - auto-load - @on-fetch="(data) => (validAddresses = data)" - /> <ZoneSearchbar /> <RightMenu> <template #right-panel> <ZoneFilterPanel data-key="ZonesList" /> </template> </RightMenu> - <VnTable - ref="tableRef" - data-key="ZonesList" - url="Zones" - :create="{ - urlCreate: 'Zones', - title: t('list.createZone'), - onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`), - formInitialData: {}, - }" - :user-filter="tableFilter" - :columns="columns" - redirect="zone" - :right-search="false" - > - <template #column-addressFk="{ row }"> - {{ showValidAddresses(row) }} - </template> - <template #more-create-dialog="{ data }"> - <VnSelect - url="AgencyModes" - v-model="data.agencyModeFk" - option-value="id" - option-label="name" - :label="t('list.agency')" - /> - <VnInput - v-model="data.price" - :label="t('list.price')" - min="0" - type="number" - required="true" - /> - <VnInput - v-model="data.bonus" - :label="t('zone.bonus')" - min="0" - type="number" - /> - <VnInput - v-model="data.travelingDays" - :label="t('zone.travelingDays')" - type="number" - min="0" - /> - <VnInputTime v-model="data.hour" :label="t('list.close')" /> - <VnSelect - url="Warehouses" - v-model="data.warehouseFK" - option-value="id" - option-label="name" - :label="t('list.warehouse')" - :options="warehouseOptions" - /> - <QCheckbox - v-model="data.isVolumetric" - :label="t('list.isVolumetric')" - :toggle-indeterminate="false" - /> - </template> - </VnTable> + <div class="table-container"> + <div class="column items-center"> + <VnTable + ref="tableRef" + data-key="ZonesList" + url="Zones" + :create="{ + urlCreate: 'Zones', + title: t('list.createZone'), + onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`), + formInitialData: {}, + }" + :user-filter="tableFilter" + :columns="columns" + redirect="zone" + :right-search="false" + table-height="85vh" + order="id ASC" + > + <template #column-addressFk="{ row }"> + {{ dashIfEmpty(formatRow(row)) }} + </template> + <template #more-create-dialog="{ data }"> + <VnSelect + url="AgencyModes" + v-model="data.agencyModeFk" + option-value="id" + option-label="name" + :label="t('list.agency')" + /> + <VnInput + v-model="data.price" + :label="t('list.price')" + min="0" + type="number" + required="true" + /> + <VnInput + v-model="data.bonus" + :label="t('zone.bonus')" + min="0" + type="number" + /> + <VnInput + v-model="data.travelingDays" + :label="t('zone.travelingDays')" + type="number" + min="0" + /> + <VnInputTime v-model="data.hour" :label="t('list.close')" /> + <VnSelect + url="Warehouses" + v-model="data.warehouseFK" + option-value="id" + option-label="name" + :label="t('list.warehouse')" + :options="warehouseOptions" + /> + <QCheckbox + v-model="data.isVolumetric" + :label="t('list.isVolumetric')" + :toggle-indeterminate="false" + /> + </template> + </VnTable> + </div> + </div> </template> <i18n> @@ -259,3 +254,20 @@ es: Search zone: Buscar zona You can search zones by id or name: Puedes buscar zonas por id o nombre </i18n> + +<style lang="scss" scoped> +.table-container { + display: flex; + justify-content: center; +} +.column { + display: flex; + flex-direction: column; + align-items: center; + min-width: 70%; +} + +:deep(.shrink-column) { + width: 8%; +} +</style> diff --git a/src/pages/Zone/ZoneUpcoming.vue b/src/pages/Zone/ZoneUpcoming.vue index c74ae6078..adcdfbc04 100644 --- a/src/pages/Zone/ZoneUpcoming.vue +++ b/src/pages/Zone/ZoneUpcoming.vue @@ -56,7 +56,7 @@ onMounted(() => weekdayStore.initStore()); <ZoneSearchbar /> <VnSubToolbar /> <QPage class="column items-center q-pa-md"> - <QCard class="full-width q-pa-md"> + <QCard class="containerShrinked q-pa-md"> <div v-for="(detail, index) in details" :key="index" diff --git a/src/pages/Zone/locale/en.yml b/src/pages/Zone/locale/en.yml index 5fd1a3ea7..e53e7b560 100644 --- a/src/pages/Zone/locale/en.yml +++ b/src/pages/Zone/locale/en.yml @@ -44,6 +44,8 @@ summary: filterPanel: name: Name agencyModeFk: Agency + id: ID + price: Price deliveryPanel: pickup: Pick up delivery: Delivery diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml index 575b12f7a..bc31e74a9 100644 --- a/src/pages/Zone/locale/es.yml +++ b/src/pages/Zone/locale/es.yml @@ -45,6 +45,8 @@ summary: filterPanel: name: Nombre agencyModeFk: Agencia + id: ID + price: Precio deliveryPanel: pickup: Recogida delivery: Entrega diff --git a/src/router/index.js b/src/router/index.js index f0d9487c6..4403901cb 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -107,7 +107,10 @@ export default defineRouter(function (/* { store, ssrContext } */) { 'Failed to fetch dynamically imported module', 'Importing a module script failed', ]; - state.set('error', errorMessages.some(message.includes)); + state.set( + 'error', + errorMessages.some((error) => message.startsWith(error)), + ); }); return Router; }); diff --git a/src/router/modules/entry.js b/src/router/modules/entry.js index f362c7653..b5656dc5f 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -6,13 +6,7 @@ const entryCard = { component: () => import('src/pages/Entry/Card/EntryCard.vue'), redirect: { name: 'EntrySummary' }, meta: { - menu: [ - 'EntryBasicData', - 'EntryBuys', - 'EntryNotes', - 'EntryDms', - 'EntryLog', - ], + menu: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryDms', 'EntryLog'], }, children: [ { @@ -91,7 +85,7 @@ export default { 'EntryLatestBuys', 'EntryStockBought', 'EntryWasteRecalc', - ] + ], }, component: RouterView, redirect: { name: 'EntryMain' }, @@ -103,7 +97,7 @@ export default { redirect: { name: 'EntryIndexMain' }, children: [ { - path:'', + path: '', name: 'EntryIndexMain', redirect: { name: 'EntryList' }, component: () => import('src/pages/Entry/EntryList.vue'), @@ -115,6 +109,7 @@ export default { title: 'list', icon: 'view_list', }, + component: () => import('src/pages/Entry/EntryList.vue'), }, entryCard, ], @@ -127,7 +122,7 @@ export default { icon: 'add', }, component: () => import('src/pages/Entry/EntryCreate.vue'), - }, + }, { path: 'my', name: 'MyEntries', @@ -167,4 +162,4 @@ export default { ], }, ], -}; \ No newline at end of file +}; diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js index 55fb04278..c085dd8dc 100644 --- a/src/router/modules/shelving.js +++ b/src/router/modules/shelving.js @@ -3,7 +3,7 @@ import { RouterView } from 'vue-router'; const parkingCard = { name: 'ParkingCard', path: ':id', - component: () => import('src/pages/Parking/Card/ParkingCard.vue'), + component: () => import('src/pages/Shelving/Parking/Card/ParkingCard.vue'), redirect: { name: 'ParkingSummary' }, meta: { menu: ['ParkingBasicData', 'ParkingLog'], @@ -16,7 +16,7 @@ const parkingCard = { title: 'summary', icon: 'launch', }, - component: () => import('src/pages/Parking/Card/ParkingSummary.vue'), + component: () => import('src/pages/Shelving/Parking/Card/ParkingSummary.vue'), }, { path: 'basic-data', @@ -25,7 +25,8 @@ const parkingCard = { title: 'basicData', icon: 'vn:settings', }, - component: () => import('src/pages/Parking/Card/ParkingBasicData.vue'), + component: () => + import('src/pages/Shelving/Parking/Card/ParkingBasicData.vue'), }, { path: 'log', @@ -34,7 +35,7 @@ const parkingCard = { title: 'log', icon: 'history', }, - component: () => import('src/pages/Parking/Card/ParkingLog.vue'), + component: () => import('src/pages/Shelving/Parking/Card/ParkingLog.vue'), }, ], }; @@ -127,7 +128,7 @@ export default { title: 'parkingList', icon: 'view_list', }, - component: () => import('src/pages/Parking/ParkingList.vue'), + component: () => import('src/pages/Shelving/Parking/ParkingList.vue'), children: [ { path: 'list', diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index e5b423f64..bfcb78787 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -192,7 +192,13 @@ export default { icon: 'vn:ticket', moduleName: 'Ticket', keyBinding: 't', - menu: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], + menu: [ + 'TicketList', + 'TicketAdvance', + 'TicketWeekly', + 'TicketFuture', + 'TicketNegative', + ], }, component: RouterView, redirect: { name: 'TicketMain' }, @@ -229,6 +235,32 @@ export default { }, component: () => import('src/pages/Ticket/TicketCreate.vue'), }, + { + path: 'negative', + redirect: { name: 'TicketNegative' }, + children: [ + { + name: 'TicketNegative', + meta: { + title: 'negative', + icon: 'exposure', + }, + component: () => + import('src/pages/Ticket/Negative/TicketLackList.vue'), + path: '', + }, + { + name: 'NegativeDetail', + path: ':id', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => + import('src/pages/Ticket/Negative/TicketLackDetail.vue'), + }, + ], + }, { path: 'weekly', name: 'TicketWeekly', diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index faaa23fc8..3eb95a96e 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -201,7 +201,7 @@ const workerCard = { const departmentCard = { name: 'DepartmentCard', path: ':id', - component: () => import('src/pages/Department/Card/DepartmentCard.vue'), + component: () => import('src/pages/Worker/Department/Card/DepartmentCard.vue'), redirect: { name: 'DepartmentSummary' }, meta: { moduleName: 'Department', @@ -215,7 +215,8 @@ const departmentCard = { title: 'summary', icon: 'launch', }, - component: () => import('src/pages/Department/Card/DepartmentSummary.vue'), + component: () => + import('src/pages/Worker/Department/Card/DepartmentSummary.vue'), }, { path: 'basic-data', @@ -224,7 +225,8 @@ const departmentCard = { title: 'basicData', icon: 'vn:settings', }, - component: () => import('src/pages/Department/Card/DepartmentBasicData.vue'), + component: () => + import('src/pages/Worker/Department/Card/DepartmentBasicData.vue'), }, ], }; diff --git a/src/utils/notifyResults.js b/src/utils/notifyResults.js new file mode 100644 index 000000000..e87ad6c6f --- /dev/null +++ b/src/utils/notifyResults.js @@ -0,0 +1,19 @@ +import { Notify } from 'quasar'; + +export default function (results, key) { + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + const data = JSON.parse(result.value.config.data); + Notify.create({ + type: 'positive', + message: `Operación (${index + 1}) ${data[key]} completada con éxito.`, + }); + } else { + const data = JSON.parse(result.reason.config.data); + Notify.create({ + type: 'negative', + message: `Operación (${index + 1}) ${data[key]} fallida: ${result.reason.message}`, + }); + } + }); +} diff --git a/test/cypress/integration/Order/orderCatalog.spec.js b/test/cypress/integration/Order/orderCatalog.spec.js index cffc47f91..1770a6b56 100644 --- a/test/cypress/integration/Order/orderCatalog.spec.js +++ b/test/cypress/integration/Order/orderCatalog.spec.js @@ -45,7 +45,6 @@ describe('OrderCatalog', () => { ).type('{enter}'); cy.get(':nth-child(1) > [data-cy="catalogFilterCategory"]').click(); cy.dataCy('catalogFilterValueDialogBtn').last().click(); - cy.get('[data-cy="catalogFilterValueDialogTagSelect"]').click(); cy.selectOption("[data-cy='catalogFilterValueDialogTagSelect']", 'Tallos'); cy.dataCy('catalogFilterValueDialogValueInput').find('input').focus(); cy.dataCy('catalogFilterValueDialogValueInput').find('input').type('2'); diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/client/clientAddress.spec.js index db876b64b..434180047 100644 --- a/test/cypress/integration/client/clientAddress.spec.js +++ b/test/cypress/integration/client/clientAddress.spec.js @@ -3,11 +3,46 @@ describe('Client consignee', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1110/address', { - timeout: 5000, - }); + cy.visit('#/customer/1107/address'); + cy.domContentLoad(); }); it('Should load layout', () => { cy.get('.q-card').should('be.visible'); }); + + it('check as equalizated', function () { + cy.get('.q-card__section > .address-card').then(($el) => { + let addressCards_before = $el.length; + + cy.get('.q-page-sticky > div > .q-btn').click(); + const addressName = 'test'; + cy.dataCy('Consignee_input').type(addressName); + cy.dataCy('Location_select').click(); + cy.get('[role="listbox"] .q-item:nth-child(1)').click(); + cy.dataCy('Street address_input').type('TEST ADDRESS'); + cy.get('.q-btn-group > .q-btn--standard').click(); + cy.location('href').should('contain', '#/customer/1107/address'); + cy.get('.q-card__section > .address-card').should( + 'have.length', + addressCards_before + 1, + ); + cy.get('.q-card__section > .address-card') + .eq(addressCards_before) + .should('be.visible') + .get('.text-weight-bold') + .eq(addressCards_before - 1) + .should('contain', addressName) + .click(); + }); + cy.get( + '.q-card > :nth-child(1) > :nth-child(2) > .q-checkbox > .q-checkbox__inner', + ) + .should('have.class', 'q-checkbox__inner--falsy') + .click(); + + cy.get('.q-btn-group > .q-btn--standard > .q-btn__content').click(); + cy.get( + ':nth-child(2) > :nth-child(2) > .flex > .q-mr-lg > .q-checkbox__inner', + ).should('have.class', 'q-checkbox__inner--truthy'); + }); }); diff --git a/test/cypress/integration/client/clientFiscalData.spec.js b/test/cypress/integration/client/clientFiscalData.spec.js index 05e0772e9..d189f896a 100644 --- a/test/cypress/integration/client/clientFiscalData.spec.js +++ b/test/cypress/integration/client/clientFiscalData.spec.js @@ -3,9 +3,8 @@ describe('Client fiscal data', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1107/fiscal-data', { - timeout: 5000, - }); + cy.visit('#/customer/1107/fiscal-data'); + cy.domContentLoad(); }); it('Should change required value when change customer', () => { cy.get('.q-card').should('be.visible'); @@ -15,4 +14,25 @@ describe('Client fiscal data', () => { cy.get('.q-item > .q-item__label').should('have.text', ' #1'); cy.dataCy('sageTaxTypeFk').filter('input').should('have.attr', 'required'); }); + + it('check as equalizated', () => { + cy.get( + ':nth-child(1) > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg', + ).click(); + cy.get('.q-btn-group > .q-btn--standard > .q-btn__content').click(); + + cy.get('.q-card > :nth-child(1) > span').should( + 'contain', + 'You changed the equalization tax', + ); + + cy.get('.q-card > :nth-child(2) > span').should( + 'have.text', + 'Do you want to spread the change?', + ); + cy.get('[data-cy="VnConfirm_confirm"] > .q-btn__content > .block').click(); + cy.get( + '.bg-warning > .q-notification__wrapper > .q-notification__content > .q-notification__message', + ).should('have.text', 'Equivalent tax spreaded'); + }); }); diff --git a/test/cypress/integration/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js new file mode 100644 index 000000000..4f99f0cb6 --- /dev/null +++ b/test/cypress/integration/entry/entryList.spec.js @@ -0,0 +1,224 @@ +describe('Entry', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/list`); + }); + + it('Filter deleted entries and other fields', () => { + createEntry(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + cy.waitForElement('[data-cy="entry-buys"]'); + deleteEntry(); + cy.typeSearchbar('{enter}'); + cy.get('span[title="Date"]').click().click(); + cy.typeSearchbar('{enter}'); + cy.url().should('include', 'order'); + cy.get('td[data-row-index="0"][data-col-field="landed"]').should( + 'have.text', + '-', + ); + }); + + it('Create entry, modify travel and add buys', () => { + createEntryAndBuy(); + cy.get('a[data-cy="EntryBasicData-menu-item"]').click(); + selectTravel('two'); + cy.saveCard(); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + deleteEntry(); + }); + + it('Clone entry and recalculate rates', () => { + createEntry(); + + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.url().then((previousUrl) => { + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="clone-entry"]').should('be.visible').click(); + + cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned'); + + cy.url() + .should('not.eq', previousUrl) + .then(() => { + cy.waitForElement('[data-cy="entry-buys"]'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.get('div[data-cy="recalculate-rates"]').click(); + + cy.get('.q-notification__message') + .eq(2) + .should('have.text', 'Entry prices recalculated'); + + cy.get('[data-cy="descriptor-more-opts"]').click(); + deleteEntry(); + + cy.log(previousUrl); + + cy.visit(previousUrl); + + cy.waitForElement('[data-cy="entry-buys"]'); + deleteEntry(); + }); + }); + }); + + it('Should notify when entry is lock by another user', () => { + const checkLockMessage = () => { + cy.get('[data-cy="entry-lock-confirm"]').should('be.visible'); + cy.get('[data-cy="VnConfirm_message"] > span').should( + 'contain.text', + 'This entry has been locked by buyerNick', + ); + }; + + createEntry(); + goToEntryBuys(); + cy.get('.q-notification__message') + .eq(1) + .should('have.text', 'The entry has been locked successfully'); + + cy.login('logistic'); + cy.reload(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_cancel"]').click(); + cy.url().should('include', 'summary'); + + goToEntryBuys(); + checkLockMessage(); + cy.get('[data-cy="VnConfirm_confirm"]').click(); + cy.url().should('include', 'buys'); + + deleteEntry(); + }); + + it('Edit buys and use toolbar actions', () => { + const COLORS = { + negative: 'rgb(251, 82, 82)', + positive: 'rgb(200, 228, 132)', + enabled: 'rgb(255, 255, 255)', + disable: 'rgb(168, 168, 168)', + }; + + const selectCell = (field, row = 0) => + cy.get(`td[data-col-field="${field}"][data-row-index="${row}"]`); + const selectSpan = (field, row = 0) => selectCell(field, row).find('div > span'); + const selectButton = (cySelector) => cy.get(`button[data-cy="${cySelector}"]`); + const clickAndType = (field, value, row = 0) => { + selectCell(field, row).click().type(`${value}{esc}`); + }; + const checkText = (field, expectedText, row = 0) => + selectCell(field, row).should('have.text', expectedText); + const checkColor = (field, expectedColor, row = 0) => + selectSpan(field, row).should('have.css', 'color', expectedColor); + + createEntryAndBuy(); + + selectCell('isIgnored').click().click().type('{esc}'); + checkText('isIgnored', 'close'); + + clickAndType('stickers', '1'); + checkText('stickers', '0/01'); + checkText('quantity', '1'); + checkText('amount', '50.00'); + clickAndType('packing', '2'); + checkText('packing', '12'); + checkText('weight', '12.0'); + checkText('quantity', '12'); + checkText('amount', '600.00'); + checkColor('packing', COLORS.enabled); + + selectCell('groupingMode').click().click().click(); + checkColor('packing', COLORS.disable); + checkColor('grouping', COLORS.enabled); + + selectCell('buyingValue').click().clear().type('{backspace}{backspace}1'); + checkText('amount', '12.00'); + checkColor('minPrice', COLORS.disable); + + selectCell('hasMinPrice').click().click(); + checkColor('minPrice', COLORS.enabled); + selectCell('hasMinPrice').click(); + + cy.saveCard(); + cy.get('span[data-cy="footer-stickers"]').should('have.text', '1'); + cy.get('.q-notification__message').contains('Data saved'); + + selectButton('change-quantity-sign').should('be.disabled'); + selectButton('check-buy-amount').should('be.disabled'); + cy.get('tr.cursor-pointer > .q-table--col-auto-width > .q-checkbox').click(); + selectButton('change-quantity-sign').should('be.enabled'); + selectButton('check-buy-amount').should('be.enabled'); + + selectButton('change-quantity-sign').click(); + selectButton('set-negative-quantity').click(); + checkText('quantity', '-12'); + selectButton('set-positive-quantity').click(); + checkText('quantity', '12'); + checkColor('amount', COLORS.disable); + + selectButton('check-buy-amount').click(); + selectButton('uncheck-amount').click(); + checkColor('amount', COLORS.disable); + + selectButton('check-amount').click(); + checkColor('amount', COLORS.positive); + cy.saveCard(); + + cy.get('span[data-cy="footer-amount"]').should( + 'have.css', + 'color', + COLORS.positive, + ); + + deleteEntry(); + }); + + function goToEntryBuys() { + const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; + cy.get(entryBuySelector).should('be.visible'); + cy.waitForElement('[data-cy="entry-buys"]'); + cy.get(entryBuySelector).click(); + } + + function deleteEntry() { + cy.get('[data-cy="descriptor-more-opts"]').click(); + cy.waitForElement('div[data-cy="delete-entry"]'); + cy.get('div[data-cy="delete-entry"]').should('be.visible').click(); + cy.url().should('include', 'list'); + } + + function createEntryAndBuy() { + createEntry(); + createBuy(); + } + + function createEntry() { + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + selectTravel('one'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + cy.url().should('include', 'summary'); + cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); + } + + function selectTravel(warehouse) { + cy.get('i[data-cy="Travel_icon"]').click(); + cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('button[data-cy="save-filter-travel-form"]').click(); + cy.get('tr').eq(1).click(); + } + + function createBuy() { + cy.get('a[data-cy="EntryBuys-menu-item"]').click(); + cy.get('a[data-cy="EntryBuys-menu-item"]').click(); + cy.get('button[data-cy="vnTableCreateBtn"]').click(); + + cy.get('input[data-cy="itemFk-create-popup"]').type('1'); + cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); + cy.get('button[data-cy="FormModelPopup_save"]').click(); + } +}); diff --git a/test/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/myEntry.spec.js index 49d75cf39..ed469d9e2 100644 --- a/test/cypress/integration/entry/myEntry.spec.js +++ b/test/cypress/integration/entry/myEntry.spec.js @@ -8,11 +8,9 @@ describe('EntryMy when is supplier', () => { }, }); }); - + it('should open buyLabel when is supplier', () => { - cy.get( - '[to="/null/3"] > .q-card > :nth-child(2) > .q-btn > .q-btn__content > .q-icon' - ).click(); + cy.dataCy('cardBtn').eq(2).click(); cy.dataCy('printLabelsBtn').click(); cy.window().its('open').should('be.called'); }); diff --git a/test/cypress/integration/entry/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js index 078ad19cc..bc36156b4 100644 --- a/test/cypress/integration/entry/stockBought.spec.js +++ b/test/cypress/integration/entry/stockBought.spec.js @@ -6,6 +6,7 @@ describe('EntryStockBought', () => { }); it('Should edit the reserved space', () => { cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); + cy.get('[data-col-field="reserve"][data-row-index="0"]').click(); cy.get('input[name="reserve"]').type('10{enter}'); cy.get('button[title="Save"]').click(); cy.get('.q-notification__message').should('have.text', 'Data saved'); @@ -15,25 +16,35 @@ describe('EntryStockBought', () => { cy.get('input[aria-label="Reserve"]').type('1'); cy.get('input[aria-label="Date"]').eq(1).clear(); cy.get('input[aria-label="Date"]').eq(1).type('01-01'); - cy.get('input[aria-label="Buyer"]').type('buyerboss{downarrow}{enter}'); + cy.get('input[aria-label="Buyer"]').type('buyerBossNick'); + cy.get('div[role="listbox"] > div > div[role="option"]') + .eq(0) + .should('be.visible') + .click(); + + cy.get('[data-cy="FormModelPopup_save"]').click(); cy.get('.q-notification__message').should('have.text', 'Data created'); + + cy.get('[data-col-field="reserve"][data-row-index="1"]').click().clear(); + cy.get('[data-cy="searchBtn"]').eq(1).click(); + cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata') + .should('have.text', 'warningNo data available') + .type('{esc}'); + cy.get('[data-col-field="reserve"][data-row-index="1"]') + .click() + .type('{backspace}{enter}'); + cy.get('[data-cy="crudModelDefaultSaveBtn"]').should('be.enabled').click(); + cy.get('.q-notification__message').eq(1).should('have.text', 'Data saved'); }); it('Should check detail for the buyer', () => { - cy.get(':nth-child(1) > .sticky > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('[data-cy="searchBtn"]').eq(0).click(); cy.get('tBody > tr').eq(1).its('length').should('eq', 1); }); - it('Should check detail for the buyerBoss and had no content', () => { - cy.get(':nth-child(2) > .sticky > .q-btn > .q-btn__content > .q-icon').click(); - cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata').should( - 'have.text', - 'warningNo data available' - ); - }); + it('Should edit travel m3 and refresh', () => { - cy.get('.vn-row > div > .q-btn > .q-btn__content > .q-icon').click(); - cy.get('input[aria-label="m3"]').clear(); - cy.get('input[aria-label="m3"]').type('60'); - cy.get('.q-mt-lg > .q-btn--standard > .q-btn__content > .block').click(); + cy.get('[data-cy="edit-travel"]').should('be.visible').click(); + cy.get('input[aria-label="m3"]').clear().type('60'); + cy.get('[data-cy="FormModelPopup_save"]').click(); cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js index 2016fca6d..11ca1bb59 100644 --- a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js @@ -1,9 +1,9 @@ /// <reference types="cypress" /> describe('InvoiceInBasicData', () => { - const formInputs = '.q-form > .q-card input'; const firstFormSelect = '.q-card > .vn-row:nth-child(1) > .q-select'; - const documentBtns = '[data-cy="dms-buttons"] button'; const dialogInputs = '.q-dialog input'; + const resetBtn = '.q-btn-group--push > .q-btn--flat'; + const getDocumentBtns = (opt) => `[data-cy="dms-buttons"] > :nth-child(${opt})`; beforeEach(() => { cy.login('developer'); @@ -11,13 +11,16 @@ describe('InvoiceInBasicData', () => { }); it('should edit the provideer and supplier ref', () => { - cy.selectOption(firstFormSelect, 'Bros'); - cy.get('[title="Reset"]').click(); - cy.get(formInputs).eq(1).type('{selectall}4739'); - cy.saveCard(); + cy.dataCy('UnDeductibleVatSelect').type('4751000000'); + cy.get('.q-menu .q-item').contains('4751000000').click(); + cy.get(resetBtn).click(); - cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Plants nick'); - cy.get(formInputs).eq(1).invoke('val').should('eq', '4739'); + cy.waitForElement('#formModel').within(() => { + cy.dataCy('vnSupplierSelect').type('Bros nick'); + }) + cy.get('.q-menu .q-item').contains('Bros nick').click(); + cy.saveCard(); + cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Bros nick'); }); it('should edit, remove and create the dms data', () => { @@ -25,18 +28,18 @@ describe('InvoiceInBasicData', () => { const secondInput = "I don't know what posting here!"; //edit - cy.get(documentBtns).eq(1).click(); + cy.get(getDocumentBtns(2)).click(); cy.get(dialogInputs).eq(0).type(`{selectall}${firtsInput}`); cy.get('textarea').type(`{selectall}${secondInput}`); cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get(documentBtns).eq(1).click(); + cy.get(getDocumentBtns(2)).click(); cy.get(dialogInputs).eq(0).invoke('val').should('eq', firtsInput); cy.get('textarea').invoke('val').should('eq', secondInput); cy.get('[data-cy="FormModelPopup_save"]').click(); cy.checkNotification('Data saved'); //remove - cy.get(documentBtns).eq(2).click(); + cy.get(getDocumentBtns(3)).click(); cy.get('[data-cy="VnConfirm_confirm"]').click(); cy.checkNotification('Data saved'); @@ -46,7 +49,7 @@ describe('InvoiceInBasicData', () => { 'test/cypress/fixtures/image.jpg', { force: true, - } + }, ); cy.get('[data-cy="FormModelPopup_save"]').click(); cy.checkNotification('Data saved'); diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js index f8b403a45..1e7ce1003 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -36,7 +36,7 @@ describe('InvoiceInVat', () => { cy.get(dialogInputs).eq(0).type(randomInt); cy.get(dialogInputs).eq(1).type('This is a dummy expense'); - cy.get('button[type="submit"]').click(); + cy.get('[data-cy="FormModelPopup_save"]').click(); cy.get('.q-notification__message').should('have.text', 'Data created'); }); }); diff --git a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js index 5f629df0b..02b7fbb43 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js @@ -7,9 +7,7 @@ describe('InvoiceOut negative bases', () => { }); it('should filter and download as CSV', () => { - cy.get( - ':nth-child(7) > .full-width > :nth-child(1) > .column > div.q-px-xs > .q-field > .q-field__inner > .q-field__control' - ).type('23{enter}'); + cy.get('input[name="ticketFk"]').type('23{enter}'); cy.get('#subToolbar > .q-btn').click(); cy.checkNotification('CSV downloaded successfully'); }); diff --git a/test/cypress/integration/item/ItemProposal.spec.js b/test/cypress/integration/item/ItemProposal.spec.js new file mode 100644 index 000000000..b3ba9f676 --- /dev/null +++ b/test/cypress/integration/item/ItemProposal.spec.js @@ -0,0 +1,11 @@ +/// <reference types="cypress" /> +describe('ItemProposal', () => { + beforeEach(() => { + const ticketId = 1; + + cy.login('developer'); + cy.visit(`/#/ticket/${ticketId}/summary`); + }); + + describe('Handle item proposal selected', () => {}); +}); diff --git a/test/cypress/integration/item/itemTag.spec.js b/test/cypress/integration/item/itemTag.spec.js index 10d68d08a..d1596f693 100644 --- a/test/cypress/integration/item/itemTag.spec.js +++ b/test/cypress/integration/item/itemTag.spec.js @@ -13,7 +13,7 @@ describe('Item tag', () => { cy.dataCy('Tag_select').eq(7).type('Tallos'); cy.get('.q-menu .q-item').contains('Tallos').click(); cy.get(':nth-child(8) > [label="Value"]').type('1'); - +cy.dataCy('crudModelDefaultSaveBtn').click(); + cy.dataCy('crudModelDefaultSaveBtn').click(); cy.checkNotification("The tag or priority can't be repeated for an item"); }); @@ -26,8 +26,11 @@ describe('Item tag', () => { cy.get(':nth-child(8) > [label="Value"]').type('50'); cy.dataCy('crudModelDefaultSaveBtn').click(); cy.checkNotification('Data saved'); - cy.dataCy('itemTags').children(':nth-child(8)').find('.justify-center > .q-icon').click(); + cy.dataCy('itemTags') + .children(':nth-child(8)') + .find('.justify-center > .q-icon') + .click(); cy.dataCy('VnConfirm_confirm').click(); cy.checkNotification('Data saved'); }); -}); \ No newline at end of file +}); diff --git a/test/cypress/integration/route/routeExtendedList.spec.js b/test/cypress/integration/route/routeExtendedList.spec.js new file mode 100644 index 000000000..34d3d3a29 --- /dev/null +++ b/test/cypress/integration/route/routeExtendedList.spec.js @@ -0,0 +1,205 @@ +describe('Route extended list', () => { + const getSelector = (colField) => `tr:last-child > [data-col-field="${colField}"]`; + + const selectors = { + worker: getSelector('workerFk'), + agency: getSelector('agencyModeFk'), + vehicle: getSelector('vehicleFk'), + date: getSelector('dated'), + description: getSelector('description'), + served: getSelector('isOk'), + lastRowSelectCheckBox: 'tbody > tr:last-child > :nth-child(1) .q-checkbox__inner', + removeBtn: '[title="Remove"]', + resetBtn: '[title="Reset"]', + confirmBtn: 'VnConfirm_confirm', + saveBtn: 'crudModelDefaultSaveBtn', + saveFormBtn: 'FormModelPopup_save', + cloneBtn: '#st-actions > .q-btn-group > :nth-child(1)', + downloadBtn: '#st-actions > .q-btn-group > :nth-child(2)', + markServedBtn: '#st-actions > .q-btn-group > :nth-child(3)', + searchbar: 'searchbar', + firstTicketsRowSelectCheckBox: + '.q-card > :nth-child(2) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(1) > .q-table--col-auto-width > .q-checkbox > .q-checkbox__inner > .q-checkbox__bg > .q-checkbox__svg', + }; + + const checkboxState = { + check: 'check', + uncheck: 'close', + }; + const url = '/#/route/extended-list'; + const dataCreated = 'Data created'; + const dataSaved = 'Data saved'; + + const originalFields = [ + { selector: selectors.worker, type: 'select', value: 'logistic' }, + { selector: selectors.agency, type: 'select', value: 'Super-Man delivery' }, + { selector: selectors.vehicle, type: 'select', value: '3333-IMK' }, + { selector: selectors.date, type: 'date', value: '01/02/2024' }, + { selector: selectors.description, type: 'input', value: 'Test route' }, + { selector: selectors.served, type: 'checkbox', value: checkboxState.uncheck }, + ]; + + const updateFields = [ + { selector: selectors.worker, type: 'select', value: 'salesperson' }, + { selector: selectors.agency, type: 'select', value: 'inhouse pickup' }, + { selector: selectors.vehicle, type: 'select', value: '1111-IMK' }, + { selector: selectors.date, type: 'date', value: '01/01/2001' }, + { selector: selectors.description, type: 'input', value: 'Description updated' }, + { selector: selectors.served, type: 'checkbox', value: checkboxState.check }, + ]; + + function fillField(selector, type, value) { + switch (type) { + case 'select': + cy.get(selector).should('be.visible').click(); + cy.dataCy('null_select').clear().type(value); + cy.get('.q-item').contains(value).click(); + break; + case 'input': + cy.get(selector).should('be.visible').click(); + cy.dataCy('null_input').clear().type(`${value}{enter}`); + break; + case 'date': + cy.get(selector).should('be.visible').click(); + cy.dataCy('null_inputDate').clear().type(`${value}{enter}`); + break; + case 'checkbox': + cy.get(selector).should('be.visible').click().click(); + break; + } + } + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(url); + cy.typeSearchbar('{enter}'); + }); + + after(() => { + cy.visit(url); + cy.typeSearchbar('{enter}'); + cy.get(selectors.lastRowSelectCheckBox).click(); + + cy.get(selectors.removeBtn).click(); + cy.dataCy(selectors.confirmBtn).click(); + }); + + it('Should list routes', () => { + cy.get('.q-table') + .children() + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + + it('Should create new route', () => { + cy.addBtnClick(); + + const data = { + Worker: { val: 'logistic', type: 'select' }, + Agency: { val: 'Super-Man delivery', type: 'select' }, + Vehicle: { val: '3333-IMK', type: 'select' }, + Date: { val: '02-01-2024', type: 'date' }, + From: { val: '01-01-2024', type: 'date' }, + To: { val: '10-01-2024', type: 'date' }, + 'Km start': { val: 1000 }, + 'Km end': { val: 1200 }, + Description: { val: 'Test route' }, + }; + + cy.fillInForm(data); + + cy.dataCy(selectors.saveFormBtn).click(); + cy.checkNotification(dataCreated); + cy.url().should('include', '/summary'); + }); + + it('Should reset changed values when click reset button', () => { + updateFields.forEach(({ selector, type, value }) => { + fillField(selector, type, value); + }); + + cy.get('[title="Reset"]').click(); + + originalFields.forEach(({ selector, value }) => { + cy.validateContent(selector, value); + }); + }); + + it('Should clone selected route', () => { + cy.get(selectors.lastRowSelectCheckBox).click(); + cy.get(selectors.cloneBtn).click(); + cy.dataCy('route.Starting date_inputDate').type('10-05-2001{enter}'); + cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); + cy.validateContent(selectors.date, '05/10/2001'); + }); + + it('Should download selected route', () => { + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.get(selectors.lastRowSelectCheckBox).click(); + cy.get(selectors.downloadBtn).click(); + cy.wait(5000); + + const fileName = 'download.zip'; + cy.readFile(`${downloadsFolder}/${fileName}`).should('exist'); + + cy.task('deleteFile', `${downloadsFolder}/${fileName}`).then((deleted) => { + expect(deleted).to.be.true; + }); + }); + + it('Should mark as served the selected route', () => { + cy.get(selectors.lastRowSelectCheckBox).click(); + cy.get(selectors.markServedBtn).click(); + + cy.typeSearchbar('{enter}'); + cy.validateContent(selectors.served, checkboxState.check); + }); + + it('Should delete the selected route', () => { + cy.get(selectors.lastRowSelectCheckBox).click(); + + cy.get(selectors.removeBtn).click(); + cy.dataCy(selectors.confirmBtn).click(); + + cy.checkNotification(dataSaved); + }); + + it('Should save changes in route', () => { + updateFields.forEach(({ selector, type, value }) => { + fillField(selector, type, value); + }); + + cy.dataCy(selectors.saveBtn).should('not.be.disabled').click(); + cy.checkNotification(dataSaved); + + cy.typeSearchbar('{enter}'); + + updateFields.forEach(({ selector, value }) => { + cy.validateContent(selector, value); + }); + }); + + it('Should add ticket to route', () => { + cy.dataCy('tableAction-0').last().click(); + cy.get(selectors.firstTicketsRowSelectCheckBox).click(); + cy.get('.q-card__actions > .q-btn--standard > .q-btn__content').click(); + cy.checkNotification(dataSaved); + }); + + it('Should open summary pop-up when click summuary icon', () => { + cy.dataCy('tableAction-1').last().click(); + cy.get('.summaryHeader > :nth-child(2').should('contain', updateFields[4].value); + }); + + it('Should redirect to the summary from the route summary pop-up', () => { + cy.dataCy('tableAction-1').last().click(); + cy.get('.header > .q-icon').should('be.visible').click(); + cy.url().should('include', '/summary'); + }); + + it('Should redirect to the summary when click go to summary icon', () => { + cy.dataCy('tableAction-2').last().click(); + cy.url().should('include', '/summary'); + }); +}); diff --git a/test/cypress/integration/route/routeList.spec.js b/test/cypress/integration/route/routeList.spec.js index 4da43ce8e..976ce7352 100644 --- a/test/cypress/integration/route/routeList.spec.js +++ b/test/cypress/integration/route/routeList.spec.js @@ -4,9 +4,6 @@ describe('Route', () => { cy.login('developer'); cy.visit(`/#/route/extended-list`); }); - const getVnSelect = - '> :nth-child(1) > .column > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'; - const getRowColumn = (row, column) => `:nth-child(${row}) > :nth-child(${column})`; it('Route list create route', () => { cy.addBtnClick(); @@ -17,15 +14,23 @@ describe('Route', () => { it('Route list search and edit', () => { cy.get('#searchbar input').type('{enter}'); - cy.get('input[name="description"]').type('routeTestOne{enter}'); + cy.get('[data-col-field="description"][data-row-index="0"]') + .click() + .type('routeTestOne{enter}'); cy.get('.q-table tr') .its('length') .then((rowCount) => { expect(rowCount).to.be.greaterThan(0); }); - cy.get(getRowColumn(1, 3) + getVnSelect).type('{downArrow}{enter}'); - cy.get(getRowColumn(1, 4) + getVnSelect).type('{downArrow}{enter}'); - cy.get(getRowColumn(1, 5) + getVnSelect).type('{downArrow}{enter}'); + cy.get('[data-col-field="workerFk"][data-row-index="0"]') + .click() + .type('{downArrow}{enter}'); + cy.get('[data-col-field="agencyModeFk"][data-row-index="0"]') + .click() + .type('{downArrow}{enter}'); + cy.get('[data-col-field="vehicleFk"][data-row-index="0"]') + .click() + .type('{downArrow}{enter}'); cy.get('button[title="Save"]').click(); cy.get('.q-notification__message').should('have.text', 'Data saved'); }); diff --git a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js new file mode 100644 index 000000000..9ea1cff63 --- /dev/null +++ b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js @@ -0,0 +1,147 @@ +/// <reference types="cypress" /> +describe('Ticket Lack detail', () => { + beforeEach(() => { + cy.login('developer'); + cy.intercept('GET', /\/api\/Tickets\/itemLack\/5.*$/, { + statusCode: 200, + body: [ + { + saleFk: 33, + code: 'OK', + ticketFk: 142, + nickname: 'Malibu Point', + shipped: '2000-12-31T23:00:00.000Z', + hour: 0, + quantity: 50, + agName: 'Super-Man delivery', + alertLevel: 0, + stateName: 'OK', + stateId: 3, + itemFk: 5, + price: 1.79, + alertLevelCode: 'FREE', + zoneFk: 9, + zoneName: 'Zone superMan', + theoreticalhour: '2011-11-01T22:59:00.000Z', + isRookie: 1, + turno: 1, + peticionCompra: 1, + hasObservation: 1, + hasToIgnore: 1, + isBasket: 1, + minTimed: 0, + customerId: 1104, + customerName: 'Tony Stark', + observationTypeCode: 'administrative', + }, + ], + }).as('getItemLack'); + + cy.visit('/#/ticket/negative/5'); + cy.wait('@getItemLack'); + }); + describe('Table actions', () => { + it.skip('should display only one row in the lack list', () => { + cy.location('href').should('contain', '#/ticket/negative/5'); + + cy.get('[data-cy="changeItem"]').should('be.disabled'); + cy.get('[data-cy="changeState"]').should('be.disabled'); + cy.get('[data-cy="changeQuantity"]').should('be.disabled'); + cy.get('[data-cy="itemProposal"]').should('be.disabled'); + cy.get('[data-cy="transferLines"]').should('be.disabled'); + cy.get('tr.cursor-pointer > :nth-child(1)').click(); + cy.get('[data-cy="changeItem"]').should('be.enabled'); + cy.get('[data-cy="changeState"]').should('be.enabled'); + cy.get('[data-cy="changeQuantity"]').should('be.enabled'); + cy.get('[data-cy="itemProposal"]').should('be.enabled'); + cy.get('[data-cy="transferLines"]').should('be.enabled'); + }); + }); + describe('Item proposal', () => { + beforeEach(() => { + cy.get('tr.cursor-pointer > :nth-child(1)').click(); + + cy.intercept('GET', /\/api\/Items\/getSimilar\?.*$/, { + statusCode: 200, + body: [ + { + id: 1, + longName: 'Ranged weapon longbow 50cm', + subName: 'Stark Industries', + tag5: 'Color', + value5: 'Brown', + match5: 0, + match6: 0, + match7: 0, + match8: 1, + tag6: 'Categoria', + value6: '+1 precission', + tag7: 'Tallos', + value7: '1', + tag8: null, + value8: null, + available: 20, + calc_id: 6, + counter: 0, + minQuantity: 1, + visible: null, + price2: 1, + }, + { + id: 2, + longName: 'Ranged weapon longbow 100cm', + subName: 'Stark Industries', + tag5: 'Color', + value5: 'Brown', + match5: 0, + match6: 1, + match7: 0, + match8: 1, + tag6: 'Categoria', + value6: '+1 precission', + tag7: 'Tallos', + value7: '1', + tag8: null, + value8: null, + available: 50, + calc_id: 6, + counter: 1, + minQuantity: 5, + visible: null, + price2: 10, + }, + { + id: 3, + longName: 'Ranged weapon longbow 200cm', + subName: 'Stark Industries', + tag5: 'Color', + value5: 'Brown', + match5: 1, + match6: 1, + match7: 1, + match8: 1, + tag6: 'Categoria', + value6: '+1 precission', + tag7: 'Tallos', + value7: '1', + tag8: null, + value8: null, + available: 185, + calc_id: 6, + counter: 10, + minQuantity: 10, + visible: null, + price2: 100, + }, + ], + }).as('getItemGetSimilar'); + cy.get('[data-cy="itemProposal"]').click(); + cy.wait('@getItemGetSimilar'); + }); + describe('Replace item if', () => { + it.only('Quantity is less than available', () => { + cy.get(':nth-child(1) > .text-right > .q-btn').click(); + }); + }); + }); +}); diff --git a/test/cypress/integration/ticket/negative/TicketLackList.spec.js b/test/cypress/integration/ticket/negative/TicketLackList.spec.js new file mode 100644 index 000000000..01ab4f621 --- /dev/null +++ b/test/cypress/integration/ticket/negative/TicketLackList.spec.js @@ -0,0 +1,36 @@ +/// <reference types="cypress" /> +describe('Ticket Lack list', () => { + beforeEach(() => { + cy.login('developer'); + cy.intercept('GET', /Tickets\/itemLack\?.*$/, { + statusCode: 200, + body: [ + { + itemFk: 5, + longName: 'Ranged weapon pistol 9mm', + warehouseFk: 1, + producer: null, + size: 15, + category: null, + warehouse: 'Warehouse One', + lack: -50, + inkFk: 'SLV', + timed: '2025-01-25T22:59:00.000Z', + minTimed: '23:59', + originFk: 'Holand', + }, + ], + }).as('getLack'); + + cy.visit('/#/ticket/negative'); + }); + + describe('Table actions', () => { + it('should display only one row in the lack list', () => { + cy.wait('@getLack', { timeout: 10000 }); + + cy.get('.q-virtual-scroll__content > :nth-child(1) > .sticky').click(); + cy.location('href').should('contain', '#/ticket/negative/5'); + }); + }); +}); diff --git a/test/cypress/integration/ticket/ticketSale.spec.js b/test/cypress/integration/ticket/ticketSale.spec.js index aed8dc85a..63562bd26 100644 --- a/test/cypress/integration/ticket/ticketSale.spec.js +++ b/test/cypress/integration/ticket/ticketSale.spec.js @@ -1,122 +1,208 @@ /// <reference types="cypress" /> describe('TicketSale', () => { - beforeEach(() => { - cy.login('developer'); - cy.viewport(1920, 1080); - cy.visit('/#/ticket/31/sale'); - }); - - const firstRow = 'tbody > :nth-child(1)'; - - const selectFirstRow = () => { - cy.waitForElement(firstRow); - cy.get(firstRow).find('.q-checkbox__inner').click(); - }; - - it('it should add item to basket', () => { - cy.window().then((win) => { - cy.stub(win, 'open').as('windowOpen'); + describe('Free ticket #31', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/sale'); }); - cy.dataCy('ticketSaleAddToBasketBtn').should('exist'); - cy.dataCy('ticketSaleAddToBasketBtn').click(); - cy.get('@windowOpen').should('be.calledWithMatch', /\/order\/\d+\/catalog/); - }); - it('should send SMS', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="sendShortageSMSItem"]'); - cy.dataCy('sendShortageSMSItem').should('exist'); - cy.dataCy('sendShortageSMSItem').click(); - cy.dataCy('vnSmsDialog').should('exist'); - cy.dataCy('sendSmsBtn').click(); - cy.checkNotification('SMS sent'); - }); + const firstRow = 'tbody > :nth-child(1)'; - it('should recalculate price when "Recalculate price" is clicked', () => { - cy.intercept('POST', '**/recalculatePrice').as('recalculatePrice'); - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="recalculatePriceItem"]'); - cy.dataCy('recalculatePriceItem').should('exist'); - cy.dataCy('recalculatePriceItem').click(); - cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200); - cy.checkNotification('Data saved'); - }); + const selectFirstRow = () => { + cy.waitForElement(firstRow); + cy.get(firstRow).find('.q-checkbox__inner').click(); + }; - it('should update discount when "Update discount" is clicked', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="updateDiscountItem"]'); - cy.dataCy('updateDiscountItem').should('exist'); - cy.dataCy('updateDiscountItem').click(); - cy.waitForElement('[data-cy="ticketSaleDiscountInput"]'); - cy.dataCy('ticketSaleDiscountInput').find('input').focus(); - cy.dataCy('ticketSaleDiscountInput').find('input').type('10'); - cy.dataCy('saveManaBtn').click(); - cy.waitForElement('.q-notification__message'); - cy.checkNotification('Data saved'); - }); + it('it should add item to basket', () => { + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen'); + }); + cy.dataCy('ticketSaleAddToBasketBtn').should('exist'); + cy.dataCy('ticketSaleAddToBasketBtn').click(); + cy.get('@windowOpen').should('be.calledWithMatch', /\/order\/\d+\/catalog/); + }); - it('adds claim', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.dataCy('createClaimItem').click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.url().should('contain', 'claim/'); - // Delete created claim to avoid cluttering the database - cy.dataCy('descriptor-more-opts').click(); - cy.dataCy('deleteClaim').click(); - cy.dataCy('VnConfirm_confirm').click(); - cy.checkNotification('Data deleted'); - }); + it('should send SMS', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="sendShortageSMSItem"]'); + cy.dataCy('sendShortageSMSItem').should('exist'); + cy.dataCy('sendShortageSMSItem').click(); + cy.dataCy('vnSmsDialog').should('exist'); + cy.dataCy('sendSmsBtn').click(); + cy.checkNotification('SMS sent'); + }); - it('marks row as reserved', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="markAsReservedItem"]'); - cy.dataCy('markAsReservedItem').click(); - cy.dataCy('ticketSaleReservedIcon').should('exist'); - }); + it('should recalculate price when "Recalculate price" is clicked', () => { + cy.intercept('POST', '**/recalculatePrice').as('recalculatePrice'); + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="recalculatePriceItem"]'); + cy.dataCy('recalculatePriceItem').should('exist'); + cy.dataCy('recalculatePriceItem').click(); + cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200); + cy.checkNotification('Data saved'); + }); - it('unmarks row as reserved', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.waitForElement('[data-cy="unmarkAsReservedItem"]'); - cy.dataCy('unmarkAsReservedItem').click(); - cy.dataCy('ticketSaleReservedIcon').should('not.exist'); - }); + it('should update discount when "Update discount" is clicked', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="updateDiscountItem"]'); + cy.dataCy('updateDiscountItem').should('exist'); + cy.dataCy('updateDiscountItem').click(); + cy.waitForElement('[data-cy="ticketSaleDiscountInput"]'); + cy.dataCy('ticketSaleDiscountInput').find('input').focus(); + cy.dataCy('ticketSaleDiscountInput').find('input').type('10'); + cy.dataCy('saveManaBtn').click(); + cy.waitForElement('.q-notification__message'); + cy.checkNotification('Data saved'); + }); - it('refunds row with warehouse', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.dataCy('ticketSaleRefundItem').click(); - cy.dataCy('ticketSaleRefundWithWarehouse').click(); - cy.checkNotification('The following refund ticket have been created'); - }); + it('adds claim', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('createClaimItem').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.url().should('contain', 'claim/'); + // Delete created claim to avoid cluttering the database + cy.dataCy('descriptor-more-opts').click(); + cy.dataCy('deleteClaim').click(); + cy.dataCy('VnConfirm_confirm').click(); + cy.checkNotification('Data deleted'); + }); - it('refunds row without warehouse', () => { - selectFirstRow(); - cy.dataCy('ticketSaleMoreActionsDropdown').click(); - cy.dataCy('ticketSaleRefundItem').click(); - cy.dataCy('ticketSaleRefundWithoutWarehouse').click(); - cy.checkNotification('The following refund ticket have been created'); - }); + it('marks row as reserved', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="markAsReservedItem"]'); + cy.dataCy('markAsReservedItem').click(); + cy.dataCy('ticketSaleReservedIcon').should('exist'); + }); - it('transfer sale to a new ticket', () => { - cy.visit('/#/ticket/32/sale'); - cy.get('.q-item > .q-item__label').should('have.text', ' #32'); - selectFirstRow(); - cy.dataCy('ticketSaleTransferBtn').click(); - cy.dataCy('ticketTransferPopup').should('exist'); - cy.dataCy('ticketTransferNewTicketBtn').click(); - //check the new ticket has been created succesfully - cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); - }); + it('unmarks row as reserved', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.waitForElement('[data-cy="unmarkAsReservedItem"]'); + cy.dataCy('unmarkAsReservedItem').click(); + cy.dataCy('ticketSaleReservedIcon').should('not.exist'); + }); - it('should redirect to ticket logs', () => { - cy.get(firstRow).find('.q-btn:last').click(); - cy.url().should('match', /\/ticket\/31\/log/); + it('refunds row with warehouse', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('ticketSaleRefundItem').click(); + cy.dataCy('ticketSaleRefundWithWarehouse').click(); + cy.checkNotification('The following refund ticket have been created'); + }); + + it('refunds row without warehouse', () => { + selectFirstRow(); + cy.dataCy('ticketSaleMoreActionsDropdown').click(); + cy.dataCy('ticketSaleRefundItem').click(); + cy.dataCy('ticketSaleRefundWithoutWarehouse').click(); + cy.checkNotification('The following refund ticket have been created'); + }); + + it('transfer sale to a new ticket', () => { + cy.visit('/#/ticket/32/sale'); + cy.get('.q-item > .q-item__label').should('have.text', ' #32'); + selectFirstRow(); + cy.dataCy('ticketSaleTransferBtn').click(); + cy.dataCy('ticketTransferPopup').should('exist'); + cy.dataCy('ticketTransferNewTicketBtn').click(); + //check the new ticket has been created succesfully + cy.get('.q-item > .q-item__label').should('not.have.text', ' #32'); + }); + + it('should redirect to ticket logs', () => { + cy.get(firstRow).find('.q-btn:last').click(); + cy.url().should('match', /\/ticket\/31\/log/); + }); + }); + describe('Ticket prepared #23', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/23/sale'); + }); + + const firstRow = 'tbody > :nth-child(1)'; + + const selectFirstRow = () => { + cy.waitForElement(firstRow); + cy.get(firstRow).find('.q-checkbox__inner').click(); + }; + + it('update price', () => { + const price = Number((Math.random() * 99 + 1).toFixed(2)); + cy.waitForElement(firstRow); + cy.get(':nth-child(10) > .q-btn').click(); + cy.waitForElement('[data-cy="ticketEditManaProxy"]'); + cy.dataCy('ticketEditManaProxy').should('exist'); + cy.waitForElement('[data-cy="Price_input"]'); + cy.dataCy('Price_input').clear(); + cy.dataCy('Price_input').type(price); + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + + cy.get(':nth-child(10) > .q-btn > .q-btn__content').should( + 'have.text', + `€${price}`, + ); + }); + it('update dicount', () => { + const discount = Math.floor(Math.random() * 100) + 1; + selectFirstRow(); + cy.get(':nth-child(11) > .q-btn').click(); + cy.waitForElement('[data-cy="ticketEditManaProxy"]'); + cy.dataCy('ticketEditManaProxy').should('exist'); + cy.waitForElement('[data-cy="Disc_input"]'); + cy.dataCy('Disc_input').clear(); + cy.dataCy('Disc_input').type(discount); + cy.dataCy('saveManaBtn').click(); + handleVnConfirm(); + + cy.get(':nth-child(11) > .q-btn > .q-btn__content').should( + 'have.text', + `${discount}.00%`, + ); + }); + + it('change concept', () => { + const quantity = Math.floor(Math.random() * 100) + 1; + cy.waitForElement(firstRow); + cy.get(':nth-child(8) > .row').click(); + cy.get( + '.q-menu > [data-v-ca3f07a4=""] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > [data-cy="undefined_input"]', + ) + .type(quantity) + .type('{enter}'); + handleVnConfirm(); + + cy.get(':nth-child(8) >.row').should('contain.text', `${quantity}`); + }); + it('changequantity ', () => { + const quantity = Math.floor(Math.random() * 100) + 1; + cy.waitForElement(firstRow); + cy.dataCy('ticketSaleQuantityInput').clear(); + cy.dataCy('ticketSaleQuantityInput').type(quantity).trigger('tab'); + cy.get('.q-page > :nth-child(6)').click(); + + handleVnConfirm(); + + cy.get('[data-cy="ticketSaleQuantityInput"]') + .find('[data-cy="undefined_input"]') + .should('have.value', `${quantity}`); + }); }); }); + +function handleVnConfirm() { + cy.get('[data-cy="VnConfirm_confirm"] > .q-btn__content > .block').click(); + cy.waitForElement('.q-notification__message'); + + cy.get('.q-notification__message').should('be.visible'); + cy.checkNotification('Data saved'); +} diff --git a/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js index 343c1c127..49d7d9f01 100644 --- a/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js +++ b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js @@ -8,8 +8,8 @@ describe('WagonTypeCreate', () => { it('should create a new wagon type and then delete it', () => { cy.get('.q-page-sticky > div > .q-btn').click(); - cy.get('input').first().type('Example for testing'); - cy.get('button[type="submit"]').click(); + cy.dataCy('Name_input').type('Example for testing'); + cy.get('[data-cy="FormModelPopup_save"]').click(); cy.get('[title="Remove"] > .q-btn__content > .q-icon').first().click(); }); }); diff --git a/test/cypress/integration/worker/workerCreate.spec.js b/test/cypress/integration/worker/workerCreate.spec.js index 7f2810395..71fd6b347 100644 --- a/test/cypress/integration/worker/workerCreate.spec.js +++ b/test/cypress/integration/worker/workerCreate.spec.js @@ -2,9 +2,24 @@ describe('WorkerCreate', () => { const externalRadio = '.q-radio:nth-child(2)'; const developerBossId = 120; const payMethodCross = - '.grid-create .full-width > :nth-child(9) .q-select .q-field__append:not(.q-anchor--skip)'; + ':nth-child(9) > .q-select > .q-field__inner > .q-field__control > :nth-child(2)'; const saveBtn = '.q-mt-lg > .q-btn--standard'; + const internalWithOutPay = { + Fi: { val: '78457139E' }, + 'Web user': { val: 'manolo' }, + Name: { val: 'Manolo' }, + 'Last name': { val: 'Hurtado' }, + 'Personal email': { val: 'manolo@mydomain.com' }, + Company: { val: 'VNL', type: 'select' }, + Street: { val: 'S/ DEFAULTWORKERSTREET' }, + Location: { val: 1, type: 'select' }, + Phone: { val: '123456789' }, + 'Worker code': { val: 'DWW' }, + Boss: { val: developerBossId, type: 'select' }, + Birth: { val: '11-12-2022', type: 'date' }, + }; + const internal = { Fi: { val: '78457139E' }, 'Web user': { val: 'manolo' }, @@ -14,6 +29,7 @@ describe('WorkerCreate', () => { Company: { val: 'VNL', type: 'select' }, Street: { val: 'S/ DEFAULTWORKERSTREET' }, Location: { val: 1, type: 'select' }, + 'Pay method': { val: 1, type: 'select' }, Phone: { val: '123456789' }, 'Worker code': { val: 'DWW' }, Boss: { val: developerBossId, type: 'select' }, @@ -37,17 +53,14 @@ describe('WorkerCreate', () => { }); it('should throw an error if a pay method has not been selected', () => { - cy.fillInForm(internal); + cy.fillInForm(internalWithOutPay); cy.get(payMethodCross).click(); cy.get(saveBtn).click(); cy.checkNotification('Payment method is required'); }); it('should create an internal', () => { - cy.fillInForm({ - ...internal, - 'Pay method': { val: 'PayMethod one', type: 'select' }, - }); + cy.fillInForm(internal); cy.get(saveBtn).click(); cy.checkNotification('Data created'); }); diff --git a/test/cypress/integration/worker/workerNotificationsManager.spec.js b/test/cypress/integration/worker/workerNotificationsManager.spec.js index f121b3894..ad48d8a6c 100644 --- a/test/cypress/integration/worker/workerNotificationsManager.spec.js +++ b/test/cypress/integration/worker/workerNotificationsManager.spec.js @@ -18,7 +18,7 @@ describe('WorkerNotificationsManager', () => { cy.visit(`/#/worker/${salesPersonId}/notifications`); cy.get(firstAvailableNotification).click(); cy.checkNotification( - 'The notification subscription of this worker cant be modified' + 'The notification subscription of this worker cant be modified', ); }); diff --git a/test/cypress/integration/worker/workerPit.spec.js b/test/cypress/integration/worker/workerPit.spec.js index cc3a87637..19cbebc20 100644 --- a/test/cypress/integration/worker/workerPit.spec.js +++ b/test/cypress/integration/worker/workerPit.spec.js @@ -8,7 +8,8 @@ describe('WorkerPit', () => { const spousePensionInput = '[data-cy="Spouse Pension_input"]'; const spousePension = '120'; const addRelative = '[data-cy="addRelative"]'; - const isDescendantSelect = '[data-cy="Descendant/Ascendant_select"]'; + const isDescendantSelect = '[data-cy="Descendant/Ascendant"]'; + const Descendant = 'Descendiente'; const birthedInput = '[data-cy="Birth Year_input"]'; const birthed = '2002'; const adoptionYearInput = '[data-cy="Adoption Year_input"]'; @@ -28,11 +29,8 @@ describe('WorkerPit', () => { cy.get(spouseNifInput).type(spouseNif); cy.get(spousePensionInput).type(spousePension); cy.get(savePIT).click(); - }); - - it('complete relative', () => { cy.get(addRelative).click(); - cy.get(isDescendantSelect).type('{downArrow}{downArrow}{enter}'); + cy.get(isDescendantSelect).type(Descendant); cy.get(birthedInput).type(birthed); cy.get(adoptionYearInput).type(adoptionYear); cy.get(saveRelative).click(); diff --git a/test/cypress/integration/zone/zoneList.spec.js b/test/cypress/integration/zone/zoneList.spec.js index 8d01d4e4e..68e924635 100644 --- a/test/cypress/integration/zone/zoneList.spec.js +++ b/test/cypress/integration/zone/zoneList.spec.js @@ -1,4 +1,5 @@ describe('ZoneList', () => { + const agency = 'inhouse pickup'; beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -6,11 +7,15 @@ describe('ZoneList', () => { }); it('should filter by agency', () => { - cy.get('input[aria-label="Agency"]').type('{downArrow}{enter}'); + cy.dataCy('zoneFilterPanelNameInput').type('{downArrow}{enter}'); }); it('should open the zone summary', () => { - cy.get('input[aria-label="Name"]').type('zone refund'); - cy.get('.q-scrollarea__content > .q-btn--standard > .q-btn__content').click(); + cy.dataCy('zoneFilterPanelAgencySelect').type(agency); + cy.get('.q-menu .q-item').contains(agency).click(); + cy.get(':nth-child(1) > [data-col-field="agencyModeFk"]').should( + 'include.text', + agency, + ); }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 2c93fbf84..aa4a1219e 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -87,36 +87,55 @@ Cypress.Commands.add('getValue', (selector) => { }); // Fill Inputs -Cypress.Commands.add('selectOption', (selector, option, timeout = 5000) => { +Cypress.Commands.add('selectOption', (selector, option, timeout = 2500) => { cy.waitForElement(selector, timeout); - cy.get(selector).click(); - cy.get(selector).invoke('data', 'url').as('dataUrl'); - cy.get(selector) - .clear() - .type(option) - .then(() => { - cy.get('.q-menu', { timeout }) - .should('be.visible') // Asegurarse de que el menú está visible - .and('exist') // Verificar que el menú existe - .then(() => { - cy.get('@dataUrl').then((url) => { - if (url) { - // Esperar a que el menú no esté visible (desaparezca) - cy.get('.q-menu').should('not.be.visible'); - // Ahora esperar a que el menú vuelva a aparecer - cy.get('.q-menu').should('be.visible').and('exist'); - } - }); - }); - }); - // Finalmente, seleccionar la opción deseada - cy.get('.q-menu:visible') // Asegurarse de que estamos dentro del menú visible - .find('.q-item') // Encontrar los elementos de las opciones - .contains(option) // Verificar que existe una opción que contenga el texto deseado - .click(); // Hacer clic en la opción + cy.get(selector, { timeout }) + .should('exist') + .should('be.visible') + .click() + .then(($el) => { + cy.wrap($el.is('input') ? $el : $el.find('input')) + .invoke('attr', 'aria-controls') + .then((ariaControl) => selectItem(selector, option, ariaControl)); + }); }); +function selectItem(selector, option, ariaControl, hasWrite = true) { + if (!hasWrite) cy.wait(100); + + getItems(ariaControl).then((items) => { + const matchingItem = items + .toArray() + .find((item) => item.innerText.includes(option)); + if (matchingItem) return cy.wrap(matchingItem).click(); + + if (hasWrite) cy.get(selector).clear().type(option, { delay: 0 }); + return selectItem(selector, option, ariaControl, false); + }); +} + +function getItems(ariaControl, startTime = Cypress._.now(), timeout = 2500) { + // Se intenta obtener la lista de opciones del desplegable de manera recursiva + return cy + .get('#' + ariaControl, { timeout }) + .should('exist') + .find('.q-item') + .should('exist') + .then(($items) => { + if (!$items?.length || $items.first().text().trim() === '') { + if (Cypress._.now() - startTime > timeout) { + throw new Error( + `getItems: Tiempo de espera (${timeout}ms) excedido.`, + ); + } + return getItems(ariaControl, startTime, timeout); + } + + return cy.wrap($items); + }); +} + Cypress.Commands.add('countSelectOptions', (selector, option) => { cy.waitForElement(selector); cy.get(selector).click({ force: true }); diff --git a/test/cypress/support/waitUntil.js b/test/cypress/support/waitUntil.js index 5fb47a2d8..359f8643f 100644 --- a/test/cypress/support/waitUntil.js +++ b/test/cypress/support/waitUntil.js @@ -1,7 +1,7 @@ const waitUntil = (subject, checkFunction, originalOptions = {}) => { if (!(checkFunction instanceof Function)) { throw new Error( - '`checkFunction` parameter should be a function. Found: ' + checkFunction + '`checkFunction` parameter should be a function. Found: ' + checkFunction, ); }