From 3a9a9bd517f15f2d2f59e4a71351cfb247297b78 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 15:30:30 +0100 Subject: [PATCH 1/3] fix: check type variable --- src/pages/Ticket/Card/TicketEditMana.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index 14eec9db93d..c1bc2639bfe 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -48,7 +48,7 @@ defineExpose({ save }); <template> <QPopupProxy ref="QPopupProxyRef" data-cy="ticketEditManaProxy"> <div class="container"> - <QSpinner v-if="!mana" color="primary" size="md" /> + <QSpinner v-if="typeof mana === 'number' && mana" color="primary" size="md" /> <div v-else> <div class="header">Mana: {{ toCurrency(mana) }}</div> <div class="q-pa-md"> From ab5ae580b3bfbfb2291b492d25317864c52bdb2c Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 16:08:40 +0100 Subject: [PATCH 2/3] fix: check type variable --- src/pages/Ticket/Card/TicketEditMana.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index 14eec9db93d..b3ba870fb57 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -48,7 +48,11 @@ defineExpose({ save }); <template> <QPopupProxy ref="QPopupProxyRef" data-cy="ticketEditManaProxy"> <div class="container"> - <QSpinner v-if="!mana" color="primary" size="md" /> + <QSpinner + v-if="!(typeof mana === 'number' && mana >= 0)" + color="primary" + size="md" + /> <div v-else> <div class="header">Mana: {{ toCurrency(mana) }}</div> <div class="q-pa-md"> From 223a1ea4490ea6ad2a00c60297fd3c74cd713338 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 15:52:21 +0000 Subject: [PATCH 3/3] revert 1015acefb7e400be2d8b5958dba69b4d98276b34 revert Merge branch 'test' into master --- cypress.config.js | 4 +- package.json | 144 +- quasar.config.js | 1 + src/boot/defaults/constants.js | 2 - src/boot/keyShortcut.js | 17 +- src/boot/qformMixin.js | 23 +- src/boot/quasar.js | 1 - src/components/CreateBankEntityForm.vue | 2 +- src/components/CrudModel.vue | 16 +- src/components/FilterTravelForm.vue | 4 +- src/components/FormModel.vue | 46 +- src/components/FormModelPopup.vue | 52 +- src/components/ItemsFilterPanel.vue | 4 +- src/components/LeftMenu.vue | 69 +- src/components/LeftMenuItem.vue | 1 - src/components/RefundInvoiceForm.vue | 15 +- src/components/TicketProblems.vue | 84 +- src/components/TransferInvoiceForm.vue | 15 +- src/components/VnTable/VnColumn.vue | 51 +- src/components/VnTable/VnFilter.vue | 58 +- src/components/VnTable/VnOrder.vue | 101 +- src/components/VnTable/VnTable.vue | 573 ++------ src/components/VnTable/VnTableFilter.vue | 57 +- src/components/VnTable/VnVisibleColumn.vue | 19 +- src/components/__tests__/FormModel.spec.js | 12 +- src/components/__tests__/Leftmenu.spec.js | 372 +---- src/components/__tests__/UserPanel.spec.js | 100 +- src/components/common/VnCard.vue | 39 +- src/components/common/VnCardBeta.vue | 61 +- src/components/common/VnCheckbox.vue | 43 - src/components/common/VnColor.vue | 32 - src/components/common/VnComponent.vue | 6 +- src/components/common/VnDmsList.vue | 12 +- src/components/common/VnInput.vue | 22 +- src/components/common/VnInputDate.vue | 8 +- src/components/common/VnInputNumber.vue | 2 - src/components/common/VnPopupProxy.vue | 38 - src/components/common/VnSection.vue | 9 +- src/components/common/VnSelect.vue | 22 +- src/components/common/VnSelectCache.vue | 4 +- src/components/common/VnSelectDialog.vue | 2 + src/components/common/VnSelectSupplier.vue | 6 +- .../common/VnSelectTravelExtended.vue | 50 - .../common/__tests__/VnNotes.spec.js | 151 +-- src/components/ui/CardDescriptor.vue | 52 +- src/components/ui/CardSummary.vue | 14 +- src/components/ui/SkeletonDescriptor.vue | 65 +- src/components/ui/VnConfirm.vue | 3 +- src/components/ui/VnFilterPanel.vue | 16 +- src/components/ui/VnMoreOptions.vue | 2 +- src/components/ui/VnNotes.vue | 94 +- src/components/ui/VnStockValueDisplay.vue | 41 - src/components/ui/VnSubToolbar.vue | 11 +- .../ui/__tests__/CardSummary.spec.js | 14 +- .../__tests__/useArrayData.spec.js | 29 +- src/composables/checkEntryLock.js | 65 - src/composables/getColAlign.js | 22 - src/composables/useArrayData.js | 13 +- src/composables/useRole.js | 10 - src/css/app.scss | 28 +- src/css/quasar.variables.scss | 6 +- src/filters/toDate.js | 11 +- src/i18n/locale/en.yml | 117 -- src/i18n/locale/es.yml | 225 +--- src/layouts/MainLayout.vue | 2 +- src/layouts/OutLayout.vue | 5 +- src/pages/Account/AccountAliasList.vue | 10 +- src/pages/Account/AccountExprBuilder.js | 18 - src/pages/Account/AccountList.vue | 26 +- src/pages/Account/Alias/AliasExprBuilder.js | 8 - src/pages/Account/Alias/Card/AliasCard.vue | 10 +- .../Account/Alias/Card/AliasDescriptor.vue | 11 +- src/pages/Account/Alias/Card/AliasSummary.vue | 19 +- src/pages/Account/Card/AccountBasicData.vue | 38 +- src/pages/Account/Card/AccountCard.vue | 10 +- src/pages/Account/Card/AccountDescriptor.vue | 43 +- .../Account/Card/AccountDescriptorMenu.vue | 27 +- src/pages/Account/Card/AccountFilter.js | 3 - src/pages/Account/Card/AccountMailAlias.vue | 7 +- src/pages/Account/Card/AccountSummary.vue | 41 +- src/pages/Account/Role/AccountRoles.vue | 18 +- src/pages/Account/Role/Card/RoleBasicData.vue | 14 +- src/pages/Account/Role/Card/RoleCard.vue | 7 +- .../Account/Role/Card/RoleDescriptor.vue | 16 +- src/pages/Account/Role/Card/RoleSummary.vue | 23 +- src/pages/Account/Role/Card/SubRoles.vue | 6 +- src/pages/Account/Role/RoleExprBuilder.js | 16 - src/pages/Claim/Card/ClaimBasicData.vue | 1 + src/pages/Claim/Card/ClaimCard.vue | 9 +- src/pages/Claim/Card/ClaimDescriptor.vue | 17 +- src/pages/Claim/Card/ClaimLines.vue | 8 +- src/pages/Claim/Card/ClaimNotes.vue | 3 +- src/pages/Claim/Card/ClaimPhoto.vue | 4 +- src/pages/Claim/ClaimList.vue | 2 +- src/pages/Customer/Card/CustomerAddress.vue | 8 +- src/pages/Customer/Card/CustomerBalance.vue | 4 +- src/pages/Customer/Card/CustomerBasicData.vue | 4 +- .../Customer/Card/CustomerBillingData.vue | 2 +- src/pages/Customer/Card/CustomerCard.vue | 4 +- .../Customer/Card/CustomerConsumption.vue | 95 +- src/pages/Customer/Card/CustomerContacts.vue | 2 +- .../Customer/Card/CustomerCreditContracts.vue | 2 +- .../Customer/Card/CustomerDescriptor.vue | 42 +- .../Customer/Card/CustomerDescriptorMenu.vue | 17 - .../Customer/Card/CustomerFileManagement.vue | 2 +- .../Customer/Card/CustomerFiscalData.vue | 32 +- src/pages/Customer/Card/CustomerNotes.vue | 1 - src/pages/Customer/Card/CustomerSamples.vue | 2 +- src/pages/Customer/Card/CustomerWebAccess.vue | 2 +- src/pages/Customer/CustomerFilter.vue | 6 +- src/pages/Customer/CustomerList.vue | 4 +- .../Customer/Defaulter/CustomerDefaulter.vue | 2 +- .../components/CustomerAddressEdit.vue | 4 +- .../components/CustomerNewPayment.vue | 6 +- .../components/CustomerSamplesCreate.vue | 9 +- src/pages/Customer/locale/en.yml | 3 - src/pages/Customer/locale/es.yml | 3 - .../Department/Card/DepartmentBasicData.vue | 35 +- .../Department/Card/DepartmentCard.vue | 4 +- .../Department/Card/DepartmentDescriptor.vue | 23 +- .../Card/DepartmentDescriptorProxy.vue | 0 .../Department/Card/DepartmentSummary.vue | 2 +- .../Card/DepartmentSummaryDialog.vue | 0 src/pages/Entry/Card/EntryBasicData.vue | 63 +- src/pages/Entry/Card/EntryBuys.vue | 1196 ++++++----------- src/pages/Entry/Card/EntryCard.vue | 6 +- src/pages/Entry/Card/EntryDescriptor.vue | 158 +-- src/pages/Entry/Card/EntryFilter.js | 17 +- src/pages/Entry/Card/EntryNotes.vue | 4 +- src/pages/Entry/Card/EntrySummary.vue | 392 ++++-- src/pages/Entry/EntryFilter.vue | 277 ++-- src/pages/Entry/EntryList.vue | 372 ++--- src/pages/Entry/EntryStockBought.vue | 18 +- src/pages/Entry/EntryStockBoughtDetail.vue | 22 +- src/pages/Entry/locale/en.yml | 82 +- src/pages/Entry/locale/es.yml | 105 +- .../InvoiceIn/Card/InvoiceInBasicData.vue | 6 +- src/pages/InvoiceIn/Card/InvoiceInCard.vue | 41 +- .../InvoiceIn/Card/InvoiceInDescriptor.vue | 33 +- .../Card/InvoiceInDescriptorMenu.vue | 4 +- src/pages/InvoiceIn/Card/InvoiceInDueDay.vue | 26 +- src/pages/InvoiceIn/Card/InvoiceInFilter.js | 33 - .../InvoiceIn/Card/InvoiceInIntrastat.vue | 2 +- src/pages/InvoiceIn/Card/InvoiceInSummary.vue | 13 +- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 78 +- src/pages/InvoiceIn/InvoiceInList.vue | 5 +- src/pages/InvoiceIn/InvoiceInToBook.vue | 56 +- src/pages/InvoiceIn/locale/en.yml | 5 +- src/pages/InvoiceIn/locale/es.yml | 9 +- src/pages/InvoiceOut/Card/InvoiceOutCard.vue | 4 +- .../InvoiceOut/Card/InvoiceOutDescriptor.vue | 28 +- src/pages/InvoiceOut/Card/InvoiceOutFilter.js | 16 - .../{components => Card}/CreateGenusForm.vue | 0 .../{components => Card}/CreateSpecieForm.vue | 0 src/pages/Item/Card/ItemBarcode.vue | 2 +- src/pages/Item/Card/ItemBasicData.vue | 42 +- src/pages/Item/Card/ItemBotanical.vue | 4 +- src/pages/Item/Card/ItemCard.vue | 2 +- src/pages/Item/Card/ItemDescriptor.vue | 26 +- src/pages/Item/Card/ItemDescriptorProxy.vue | 6 +- src/pages/Item/Card/ItemShelving.vue | 10 +- src/pages/Item/Card/ItemTags.vue | 2 +- src/pages/Item/ItemFixedPrice.vue | 16 +- .../Item/ItemType/Card/ItemTypeBasicData.vue | 7 +- src/pages/Item/ItemType/Card/ItemTypeCard.vue | 6 +- .../Item/ItemType/Card/ItemTypeDescriptor.vue | 40 +- .../Item/ItemType/Card/ItemTypeFilter.js | 8 - .../Item/ItemType/Card/ItemTypeSummary.vue | 15 +- src/pages/Item/components/ItemProposal.vue | 332 ----- .../Item/components/ItemProposalProxy.vue | 56 - src/pages/Item/locale/en.yml | 24 +- src/pages/Item/locale/es.yml | 31 +- src/pages/Monitor/MonitorOrders.vue | 2 +- src/pages/Monitor/locale/en.yml | 1 - src/pages/Monitor/locale/es.yml | 1 - .../Order/Card/CatalogFilterValueDialog.vue | 2 +- src/pages/Order/Card/OrderBasicData.vue | 6 +- src/pages/Order/Card/OrderCard.vue | 4 +- src/pages/Order/Card/OrderCatalogFilter.vue | 4 +- .../Order/Card/OrderCatalogItemDialog.vue | 8 +- src/pages/Order/Card/OrderDescriptor.vue | 38 +- src/pages/Order/Card/OrderFilter.js | 26 - src/pages/Order/Card/OrderLines.vue | 4 +- src/pages/Order/Card/OrderSummary.vue | 2 +- src/pages/Order/OrderList.vue | 7 +- .../Parking/Card/ParkingBasicData.vue | 18 +- .../Parking/Card/ParkingCard.vue | 6 +- .../Parking/Card/ParkingDescriptor.vue | 16 +- .../Parking/Card/ParkingLog.vue | 0 .../Parking/Card/ParkingSummary.vue | 0 .../{Shelving => }/Parking/ParkingFilter.vue | 0 .../{Shelving => }/Parking/ParkingList.vue | 13 +- .../{Shelving => }/Parking/locale/en.yml | 0 .../{Shelving => }/Parking/locale/es.yml | 0 src/pages/Route/Agency/AgencyList.vue | 4 +- .../Route/Agency/Card/AgencyBasicData.vue | 2 +- src/pages/Route/Agency/Card/AgencyCard.vue | 2 +- .../Route/Agency/Card/AgencyDescriptor.vue | 1 + .../Route/Agency/Card/AgencyWorkcenter.vue | 2 +- src/pages/Route/Card/RouteCard.vue | 5 +- src/pages/Route/Card/RouteDescriptor.vue | 70 +- src/pages/Route/Card/RouteFilter.js | 39 - src/pages/Route/Card/RouteFilter.vue | 2 +- src/pages/Route/Card/RouteForm.vue | 54 +- src/pages/Route/Roadmap/RoadmapBasicData.vue | 5 +- src/pages/Route/Roadmap/RoadmapCard.vue | 2 +- src/pages/Route/Roadmap/RoadmapDescriptor.vue | 18 +- src/pages/Route/Roadmap/RoadmapFilter.js | 3 - src/pages/Route/Roadmap/RoadmapStops.vue | 2 +- src/pages/Route/Roadmap/RoadmapSummary.vue | 3 +- src/pages/Route/RouteExtendedList.vue | 152 +-- src/pages/Route/RouteList.vue | 31 - src/pages/Route/RouteTickets.vue | 18 +- .../Route/Vehicle/Card/VehicleBasicData.vue | 162 --- src/pages/Route/Vehicle/Card/VehicleCard.vue | 13 - .../Route/Vehicle/Card/VehicleDescriptor.vue | 49 - .../Route/Vehicle/Card/VehicleSummary.vue | 127 -- src/pages/Route/Vehicle/VehicleFilter.js | 76 -- src/pages/Route/Vehicle/VehicleList.vue | 224 --- src/pages/Route/Vehicle/locale/en.yml | 20 - src/pages/Route/Vehicle/locale/es.yml | 20 - src/pages/Shelving/Card/ShelvingCard.vue | 4 +- .../Shelving/Card/ShelvingDescriptor.vue | 30 +- src/pages/Shelving/Card/ShelvingFilter.js | 15 - src/pages/Shelving/Card/ShelvingForm.vue | 32 +- src/pages/Shelving/Card/ShelvingSearchbar.vue | 8 +- src/pages/Shelving/Card/ShelvingSummary.vue | 37 +- .../Shelving/Parking/Card/ParkingFilter.js | 4 - .../Shelving/Parking/ParkingExprBuilder.js | 10 - src/pages/Shelving/ShelvingExprBuilder.js | 10 - src/pages/Shelving/ShelvingList.vue | 26 +- src/pages/Supplier/Card/SupplierAccounts.vue | 6 +- src/pages/Supplier/Card/SupplierAddresses.vue | 2 +- .../Supplier/Card/SupplierAgencyTerm.vue | 2 +- src/pages/Supplier/Card/SupplierBasicData.vue | 3 +- src/pages/Supplier/Card/SupplierCard.vue | 16 +- .../Supplier/Card/SupplierConsumption.vue | 103 +- src/pages/Supplier/Card/SupplierContacts.vue | 2 +- .../Supplier/Card/SupplierDescriptor.vue | 49 +- src/pages/Supplier/Card/SupplierFilter.js | 35 - .../Supplier/Card/SupplierFiscalData.vue | 22 +- src/pages/Supplier/SupplierList.vue | 91 +- src/pages/Supplier/SupplierListFilter.vue | 122 ++ .../Ticket/Card/BasicData/TicketBasicData.vue | 16 +- .../Card/BasicData/TicketBasicDataForm.vue | 4 +- .../Card/BasicData/TicketBasicDataView.vue | 116 +- src/pages/Ticket/Card/TicketCard.vue | 8 +- src/pages/Ticket/Card/TicketComponents.vue | 2 +- src/pages/Ticket/Card/TicketDescriptor.vue | 139 +- src/pages/Ticket/Card/TicketExpedition.vue | 2 +- src/pages/Ticket/Card/TicketFilter.js | 72 - src/pages/Ticket/Card/TicketNotes.vue | 4 +- src/pages/Ticket/Card/TicketPackage.vue | 4 +- src/pages/Ticket/Card/TicketSale.vue | 60 +- src/pages/Ticket/Card/TicketService.vue | 6 +- src/pages/Ticket/Card/TicketSplit.vue | 37 - src/pages/Ticket/Card/TicketSummary.vue | 81 +- src/pages/Ticket/Card/TicketTracking.vue | 4 +- src/pages/Ticket/Card/TicketTransfer.vue | 131 +- src/pages/Ticket/Card/TicketTransferProxy.vue | 54 - src/pages/Ticket/Card/components/split.js | 22 - .../Ticket/Negative/TicketLackDetail.vue | 198 --- .../Ticket/Negative/TicketLackFilter.vue | 175 --- src/pages/Ticket/Negative/TicketLackList.vue | 227 ---- src/pages/Ticket/Negative/TicketLackTable.vue | 356 ----- .../Negative/components/ChangeItemDialog.vue | 90 -- .../components/ChangeQuantityDialog.vue | 84 -- .../Negative/components/ChangeStateDialog.vue | 91 -- src/pages/Ticket/TicketFuture.vue | 561 +++++--- src/pages/Ticket/TicketFutureFilter.vue | 4 +- src/pages/Ticket/locale/en.yml | 87 +- src/pages/Ticket/locale/es.yml | 83 -- src/pages/Travel/Card/TravelBasicData.vue | 19 +- src/pages/Travel/Card/TravelCard.vue | 36 +- src/pages/Travel/Card/TravelDescriptor.vue | 1 + src/pages/Travel/Card/TravelFilter.js | 1 - src/pages/Travel/Card/TravelSummary.vue | 8 - src/pages/Travel/Card/TravelThermographs.vue | 2 +- src/pages/Travel/ExtraCommunityFilter.vue | 2 +- src/pages/Travel/TravelList.vue | 24 - src/pages/Wagon/Card/WagonCard.vue | 2 +- src/pages/Wagon/Type/WagonTypeList.vue | 8 +- src/pages/Worker/Card/WorkerBasicData.vue | 17 +- src/pages/Worker/Card/WorkerCalendar.vue | 32 +- .../Worker/Card/WorkerCalendarFilter.vue | 2 + src/pages/Worker/Card/WorkerCard.vue | 7 +- src/pages/Worker/Card/WorkerDescriptor.vue | 9 +- .../Worker/Card/WorkerDescriptorProxy.vue | 7 +- src/pages/Worker/Card/WorkerFormation.vue | 3 +- src/pages/Worker/Card/WorkerMedical.vue | 16 - src/pages/Worker/Card/WorkerOperator.vue | 19 +- src/pages/Worker/Card/WorkerPda.vue | 10 +- src/pages/Worker/Card/WorkerPit.vue | 2 +- src/pages/Worker/Card/WorkerSummary.vue | 2 +- src/pages/Worker/Card/WorkerTimeControl.vue | 16 +- src/pages/Worker/WorkerDepartmentTree.vue | 4 +- src/pages/Zone/Card/ZoneBasicData.vue | 33 +- src/pages/Zone/Card/ZoneCard.vue | 12 +- src/pages/Zone/Card/ZoneDescriptor.vue | 44 +- src/pages/Zone/Card/ZoneEvents.vue | 4 +- src/pages/Zone/Card/ZoneFilter.js | 10 - src/pages/Zone/Card/ZoneSearchbar.vue | 41 +- src/pages/Zone/Card/ZoneSummary.vue | 18 +- src/pages/Zone/Card/ZoneWarehouses.vue | 2 +- src/pages/Zone/Delivery/ZoneDeliveryList.vue | 2 +- src/pages/Zone/Upcoming/ZoneUpcomingList.vue | 2 +- src/pages/Zone/ZoneList.vue | 29 +- src/router/modules/account/aliasCard.js | 2 +- src/router/modules/account/roleCard.js | 1 - src/router/modules/entry.js | 17 +- src/router/modules/route.js | 52 - src/router/modules/shelving.js | 11 +- src/router/modules/supplier.js | 315 ++--- src/router/modules/ticket.js | 34 +- src/router/modules/worker.js | 9 +- .../__tests__/useNavigationStore.spec.js | 153 --- src/stores/useArrayDataStore.js | 1 - src/utils/notifyResults.js | 19 - .../integration/Order/orderCatalog.spec.js | 1 + .../integration/entry/entryList.spec.js | 224 --- .../integration/entry/stockBought.spec.js | 37 +- .../invoiceIn/invoiceInBasicData.spec.js | 27 +- .../invoiceIn/invoiceInVat.spec.js | 2 +- .../invoiceOutNegativeBases.spec.js | 4 +- .../integration/item/ItemProposal.spec.js | 11 - test/cypress/integration/item/itemTag.spec.js | 5 +- .../parking/parkingBasicData.spec.js | 4 +- .../route/agency/agencyWorkCenter.spec.js | 1 - .../integration/route/routeList.spec.js | 19 +- .../route/vehicle/vehicleDescriptor.spec.js | 13 - .../ticket/negative/TicketLackDetail.spec.js | 147 -- .../ticket/negative/TicketLackList.spec.js | 36 - .../integration/ticket/ticketList.spec.js | 25 - .../vnComponent/VnShortcut.spec.js | 11 - .../wagon/wagonType/wagonTypeCreate.spec.js | 2 +- .../integration/zone/zoneBasicData.spec.js | 16 +- test/cypress/support/commands.js | 71 +- test/cypress/support/waitUntil.js | 2 +- 338 files changed, 4377 insertions(+), 9582 deletions(-) delete mode 100644 src/boot/defaults/constants.js delete mode 100644 src/components/common/VnCheckbox.vue delete mode 100644 src/components/common/VnColor.vue delete mode 100644 src/components/common/VnPopupProxy.vue delete mode 100644 src/components/common/VnSelectTravelExtended.vue delete mode 100644 src/components/ui/VnStockValueDisplay.vue delete mode 100644 src/composables/checkEntryLock.js delete mode 100644 src/composables/getColAlign.js delete mode 100644 src/pages/Account/AccountExprBuilder.js delete mode 100644 src/pages/Account/Alias/AliasExprBuilder.js delete mode 100644 src/pages/Account/Card/AccountFilter.js delete mode 100644 src/pages/Account/Role/RoleExprBuilder.js rename src/pages/{Worker => }/Department/Card/DepartmentBasicData.vue (73%) rename src/pages/{Worker => }/Department/Card/DepartmentCard.vue (70%) rename src/pages/{Worker => }/Department/Card/DepartmentDescriptor.vue (84%) rename src/pages/{Worker => }/Department/Card/DepartmentDescriptorProxy.vue (100%) rename src/pages/{Worker => }/Department/Card/DepartmentSummary.vue (99%) rename src/pages/{Worker => }/Department/Card/DepartmentSummaryDialog.vue (100%) delete mode 100644 src/pages/InvoiceIn/Card/InvoiceInFilter.js delete mode 100644 src/pages/InvoiceOut/Card/InvoiceOutFilter.js rename src/pages/Item/{components => Card}/CreateGenusForm.vue (100%) rename src/pages/Item/{components => Card}/CreateSpecieForm.vue (100%) delete mode 100644 src/pages/Item/ItemType/Card/ItemTypeFilter.js delete mode 100644 src/pages/Item/components/ItemProposal.vue delete mode 100644 src/pages/Item/components/ItemProposalProxy.vue delete mode 100644 src/pages/Order/Card/OrderFilter.js rename src/pages/{Shelving => }/Parking/Card/ParkingBasicData.vue (68%) rename src/pages/{Shelving => }/Parking/Card/ParkingCard.vue (53%) rename src/pages/{Shelving => }/Parking/Card/ParkingDescriptor.vue (58%) rename src/pages/{Shelving => }/Parking/Card/ParkingLog.vue (100%) rename src/pages/{Shelving => }/Parking/Card/ParkingSummary.vue (100%) rename src/pages/{Shelving => }/Parking/ParkingFilter.vue (100%) rename src/pages/{Shelving => }/Parking/ParkingList.vue (90%) rename src/pages/{Shelving => }/Parking/locale/en.yml (100%) rename src/pages/{Shelving => }/Parking/locale/es.yml (100%) delete mode 100644 src/pages/Route/Card/RouteFilter.js delete mode 100644 src/pages/Route/Roadmap/RoadmapFilter.js delete mode 100644 src/pages/Route/Vehicle/Card/VehicleBasicData.vue delete mode 100644 src/pages/Route/Vehicle/Card/VehicleCard.vue delete mode 100644 src/pages/Route/Vehicle/Card/VehicleDescriptor.vue delete mode 100644 src/pages/Route/Vehicle/Card/VehicleSummary.vue delete mode 100644 src/pages/Route/Vehicle/VehicleFilter.js delete mode 100644 src/pages/Route/Vehicle/VehicleList.vue delete mode 100644 src/pages/Route/Vehicle/locale/en.yml delete mode 100644 src/pages/Route/Vehicle/locale/es.yml delete mode 100644 src/pages/Shelving/Card/ShelvingFilter.js delete mode 100644 src/pages/Shelving/Parking/Card/ParkingFilter.js delete mode 100644 src/pages/Shelving/Parking/ParkingExprBuilder.js delete mode 100644 src/pages/Shelving/ShelvingExprBuilder.js delete mode 100644 src/pages/Supplier/Card/SupplierFilter.js create mode 100644 src/pages/Supplier/SupplierListFilter.vue delete mode 100644 src/pages/Ticket/Card/TicketFilter.js delete mode 100644 src/pages/Ticket/Card/TicketSplit.vue delete mode 100644 src/pages/Ticket/Card/TicketTransferProxy.vue delete mode 100644 src/pages/Ticket/Card/components/split.js delete mode 100644 src/pages/Ticket/Negative/TicketLackDetail.vue delete mode 100644 src/pages/Ticket/Negative/TicketLackFilter.vue delete mode 100644 src/pages/Ticket/Negative/TicketLackList.vue delete mode 100644 src/pages/Ticket/Negative/TicketLackTable.vue delete mode 100644 src/pages/Ticket/Negative/components/ChangeItemDialog.vue delete mode 100644 src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue delete mode 100644 src/pages/Ticket/Negative/components/ChangeStateDialog.vue delete mode 100644 src/pages/Zone/Card/ZoneFilter.js delete mode 100644 src/stores/__tests__/useNavigationStore.spec.js delete mode 100644 src/utils/notifyResults.js delete mode 100644 test/cypress/integration/entry/entryList.spec.js delete mode 100644 test/cypress/integration/item/ItemProposal.spec.js delete mode 100644 test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js delete mode 100644 test/cypress/integration/ticket/negative/TicketLackDetail.spec.js delete mode 100644 test/cypress/integration/ticket/negative/TicketLackList.spec.js diff --git a/cypress.config.js b/cypress.config.js index a9e27fcfdcb..1924144f624 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: false, - watchForFileChanges: false, + experimentalRunAllSpecs: true, + watchForFileChanges: true, reporter: 'cypress-mochawesome-reporter', reporterOptions: { charts: true, diff --git a/package.json b/package.json index d23ed0cedd3..17f39cad710 100644 --- a/package.json +++ b/package.json @@ -1,74 +1,74 @@ { - "name": "salix-front", - "version": "25.08.0", - "description": "Salix frontend", - "productName": "Salix", - "author": "Verdnatura", - "private": true, - "packageManager": "pnpm@8.15.1", - "type": "module", - "scripts": { - "resetDatabase": "cd ../salix && gulp docker", - "lint": "eslint --ext .js,.vue ./", - "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", - "test:e2e": "cypress open", - "test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run", - "test": "echo \"See package.json => scripts for available tests.\" && exit 0", - "test:unit": "vitest", - "test:unit:ci": "vitest run", - "commitlint": "commitlint --edit", - "prepare": "npx husky install", - "addReferenceTag": "node .husky/addReferenceTag.js", - "docs:dev": "vitepress dev docs", - "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" - }, - "dependencies": { - "@quasar/cli": "^2.4.1", - "@quasar/extras": "^1.16.16", - "axios": "^1.4.0", - "chromium": "^3.0.3", - "croppie": "^2.6.5", - "moment": "^2.30.1", - "pinia": "^2.1.3", - "quasar": "^2.17.7", - "validator": "^13.9.0", - "vue": "^3.5.13", - "vue-i18n": "^9.3.0", - "vue-router": "^4.2.5" - }, - "devDependencies": { - "@commitlint/cli": "^19.2.1", - "@commitlint/config-conventional": "^19.1.0", - "@intlify/unplugin-vue-i18n": "^0.8.2", - "@pinia/testing": "^0.1.2", - "@quasar/app-vite": "^2.0.8", - "@quasar/quasar-app-extension-qcalendar": "^4.0.2", - "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", - "@vue/test-utils": "^2.4.4", - "autoprefixer": "^10.4.14", - "cypress": "^13.6.6", - "cypress-mochawesome-reporter": "^3.8.2", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-cypress": "^4.1.0", - "eslint-plugin-vue": "^9.32.0", - "husky": "^8.0.0", - "postcss": "^8.4.23", - "prettier": "^3.4.2", - "sass": "^1.83.4", - "vitepress": "^1.6.3", - "vitest": "^0.34.0" - }, - "engines": { - "node": "^20 || ^18 || ^16", - "npm": ">= 8.1.2", - "yarn": ">= 1.21.1", - "bun": ">= 1.0.25" - }, - "overrides": { - "@vitejs/plugin-vue": "^5.2.1", - "vite": "^6.0.11", - "vitest": "^0.31.1" - } + "name": "salix-front", + "version": "25.06.0", + "description": "Salix frontend", + "productName": "Salix", + "author": "Verdnatura", + "private": true, + "packageManager": "pnpm@8.15.1", + "type": "module", + "scripts": { + "resetDatabase": "cd ../salix && gulp docker", + "lint": "eslint --ext .js,.vue ./", + "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", + "test:e2e": "cypress open", + "test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run", + "test": "echo \"See package.json => scripts for available tests.\" && exit 0", + "test:unit": "vitest", + "test:unit:ci": "vitest run", + "commitlint": "commitlint --edit", + "prepare": "npx husky install", + "addReferenceTag": "node .husky/addReferenceTag.js", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "dependencies": { + "@quasar/cli": "^2.4.1", + "@quasar/extras": "^1.16.16", + "axios": "^1.4.0", + "chromium": "^3.0.3", + "croppie": "^2.6.5", + "moment": "^2.30.1", + "pinia": "^2.1.3", + "quasar": "^2.17.7", + "validator": "^13.9.0", + "vue": "^3.5.13", + "vue-i18n": "^9.3.0", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "@intlify/unplugin-vue-i18n": "^0.8.2", + "@pinia/testing": "^0.1.2", + "@quasar/app-vite": "^2.0.8", + "@quasar/quasar-app-extension-qcalendar": "^4.0.2", + "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", + "@vue/test-utils": "^2.4.4", + "autoprefixer": "^10.4.14", + "cypress": "^13.6.6", + "cypress-mochawesome-reporter": "^3.8.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-cypress": "^4.1.0", + "eslint-plugin-vue": "^9.32.0", + "husky": "^8.0.0", + "postcss": "^8.4.23", + "prettier": "^3.4.2", + "sass": "^1.83.4", + "vitepress": "^1.6.3", + "vitest": "^0.34.0" + }, + "engines": { + "node": "^20 || ^18 || ^16", + "npm": ">= 8.1.2", + "yarn": ">= 1.21.1", + "bun": ">= 1.0.25" + }, + "overrides": { + "@vitejs/plugin-vue": "^5.2.1", + "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 9467c92af45..6d545c02658 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -30,6 +30,7 @@ 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/defaults/constants.js b/src/boot/defaults/constants.js deleted file mode 100644 index c96ceb2d185..00000000000 --- a/src/boot/defaults/constants.js +++ /dev/null @@ -1,2 +0,0 @@ -export const langs = ['en', 'es']; -export const decimalPlaces = 2; diff --git a/src/boot/keyShortcut.js b/src/boot/keyShortcut.js index 6da06c8bf0d..5afb5b74a36 100644 --- a/src/boot/keyShortcut.js +++ b/src/boot/keyShortcut.js @@ -1,6 +1,6 @@ export default { - mounted(el, binding) { - const shortcut = binding.value || '+'; + mounted: function (el, binding) { + const shortcut = binding.value ?? '+'; const { key, ctrl, alt, callback } = typeof shortcut === 'string' @@ -8,24 +8,25 @@ export default { key: shortcut, ctrl: true, alt: true, - callback: () => el?.click(), + callback: () => + document + .querySelector(`button[shortcut="${shortcut}"]`) + ?.click(), } : binding.value; - if (!el.hasAttribute('shortcut')) { - el.setAttribute('shortcut', key); - } - const handleKeydown = (event) => { if (event.key === key && (!ctrl || event.ctrlKey) && (!alt || event.altKey)) { callback(); } }; + // Attach the event listener to the window window.addEventListener('keydown', handleKeydown); + el._handleKeydown = handleKeydown; }, - unmounted(el) { + unmounted: function (el) { if (el._handleKeydown) { window.removeEventListener('keydown', el._handleKeydown); } diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 182c51e4793..97d80c670d0 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,5 +30,22 @@ 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 a8c397b8391..54751768235 100644 --- a/src/boot/quasar.js +++ b/src/boot/quasar.js @@ -51,5 +51,4 @@ export default boot(({ app }) => { await useCau(response, message); }; - app.provide('app', app); }); diff --git a/src/components/CreateBankEntityForm.vue b/src/components/CreateBankEntityForm.vue index 7c4b94a6abc..2da3aa9940f 100644 --- a/src/components/CreateBankEntityForm.vue +++ b/src/components/CreateBankEntityForm.vue @@ -14,7 +14,7 @@ const { t } = useI18n(); const bicInputRef = ref(null); const state = useState(); -const customer = computed(() => state.get('Customer')); +const customer = computed(() => state.get('customer')); const countriesFilter = { fields: ['id', 'name', 'code'], diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 93a2ac96a79..d569dfda1a8 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -64,10 +64,6 @@ const $props = defineProps({ type: Function, default: null, }, - beforeSaveFn: { - type: Function, - default: null, - }, goTo: { type: String, default: '', @@ -180,11 +176,7 @@ async function saveChanges(data) { hasChanges.value = false; return; } - let changes = data || getChanges(); - if ($props.beforeSaveFn) { - changes = await $props.beforeSaveFn(changes, getChanges); - } - + const changes = data || getChanges(); try { await axios.post($props.saveUrl || $props.url + '/crud', changes); } finally { @@ -237,12 +229,12 @@ async function remove(data) { componentProps: { title: t('globals.confirmDeletion'), message: t('globals.confirmDeletionMessage'), - data: { deletes: ids }, + newData, ids, - promise: saveChanges, }, }) .onOk(async () => { + await saveChanges({ deletes: ids }); newData = newData.filter((form) => !ids.some((id) => id == form[pk])); fetch(newData); }); @@ -382,8 +374,6 @@ 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/FilterTravelForm.vue b/src/components/FilterTravelForm.vue index 765d97763f0..4d43c38100c 100644 --- a/src/components/FilterTravelForm.vue +++ b/src/components/FilterTravelForm.vue @@ -181,7 +181,6 @@ const selectTravel = ({ id }) => { color="primary" :disabled="isLoading" :loading="isLoading" - data-cy="save-filter-travel-form" /> </div> <QTable @@ -192,10 +191,9 @@ 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 data-cy="travelFk-travel-form"> + <QTd auto-width @click.stop> <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 04ef13d45c3..3842ff94781 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, useAttrs } from 'vue'; +import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue'; import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -22,7 +22,6 @@ const { validate } = useValidator(); const { notify } = useNotify(); const route = useRoute(); const myForm = ref(null); -const attrs = useAttrs(); const $props = defineProps({ url: { type: String, @@ -85,7 +84,7 @@ const $props = defineProps({ }, reload: { type: Boolean, - default: true, + default: false, }, defaultTrim: { type: Boolean, @@ -106,15 +105,15 @@ const isLoading = ref(false); // Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas const isResetting = ref(false); const hasChanges = ref(!$props.observeFormChanges); -const originalData = computed(() => state.get(modelValue)); -const formData = ref(); +const originalData = ref({}); +const formData = computed(() => state.get(modelValue)); const defaultButtons = computed(() => ({ save: { dataCy: 'saveDefaultBtn', color: 'primary', icon: 'save', label: 'globals.save', - click: async () => await save(), + click: () => myForm.value.submit(), type: 'submit', }, reset: { @@ -128,6 +127,8 @@ const defaultButtons = computed(() => ({ })); onMounted(async () => { + originalData.value = JSON.parse(JSON.stringify($props.formInitialData ?? {})); + nextTick(() => (componentIsRendered.value = true)); // Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla @@ -159,18 +160,10 @@ if (!$props.url) (val) => updateAndEmit('onFetch', { val }), ); -watch( - originalData, - (val) => { - if (val) formData.value = JSON.parse(JSON.stringify(val)); - }, - { immediate: true }, -); - watch( () => [$props.url, $props.filter], async () => { - state.set(modelValue, null); + originalData.value = null; reset(); await fetch(); }, @@ -205,6 +198,7 @@ async function fetch() { updateAndEmit('onFetch', { val: data }); } catch (e) { state.set(modelValue, {}); + originalData.value = {}; throw e; } } @@ -247,7 +241,6 @@ async function saveAndGo() { } function reset() { - formData.value = JSON.parse(JSON.stringify(originalData.value)); updateAndEmit('onFetch', { val: originalData.value }); if ($props.observeFormChanges) { hasChanges.value = false; @@ -272,6 +265,7 @@ function filter(value, update, filterOptions) { function updateAndEmit(evt, { val, res, old } = { val: null, res: null, old: null }) { state.set(modelValue, val); + originalData.value = val && JSON.parse(JSON.stringify(val)); if (!$props.url) arrayData.store.data = val; emit(evt, state.get(modelValue), res, old); @@ -285,22 +279,6 @@ 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, @@ -315,12 +293,12 @@ defineExpose({ <QForm ref="myForm" v-if="formData" - @submit.prevent - @keyup.prevent="onKeyup" + @submit="save" @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 85943e91e5f..afdc6efca71 100644 --- a/src/components/FormModelPopup.vue +++ b/src/components/FormModelPopup.vue @@ -1,13 +1,12 @@ <script setup> -import { ref, computed, useAttrs, nextTick } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useState } from 'src/composables/useState'; import FormModel from 'components/FormModel.vue'; const emit = defineEmits(['onDataSaved', 'onDataCanceled']); -const props = defineProps({ +defineProps({ title: { type: String, default: '', @@ -16,41 +15,23 @@ const props = defineProps({ type: String, default: '', }, - showSaveAndContinueBtn: { - type: Boolean, - default: false, - }, }); const { t } = useI18n(); -const attrs = useAttrs(); -const state = useState(); + const formModelRef = ref(null); const closeButton = ref(null); -const isSaveAndContinue = ref(props.showSaveAndContinueBtn); -const isLoading = computed(() => formModelRef.value?.isLoading); -const reset = computed(() => formModelRef.value?.reset); -const onDataSaved = async (formData, requestResponse) => { - if (!isSaveAndContinue.value) closeButton.value?.click(); - if (isSaveAndContinue.value) { - await nextTick(); - state.set(attrs.model, attrs.formInitialData); - } - isSaveAndContinue.value = props.showSaveAndContinueBtn; +const onDataSaved = (formData, requestResponse) => { + if (closeButton.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); defineExpose({ isLoading, onDataSaved, - isSaveAndContinue, - reset, }); </script> @@ -78,16 +59,15 @@ defineExpose({ flat :disabled="isLoading" :loading="isLoading" - data-cy="FormModelPopup_cancel" - v-close-popup - z-max @click="emit('onDataCanceled')" + v-close-popup + data-cy="FormModelPopup_cancel" + z-max /> <QBtn - :flat="showSaveAndContinueBtn" :label="t('globals.save')" :title="t('globals.save')" - @click="onClick(false)" + type="submit" color="primary" class="q-ml-sm" :disabled="isLoading" @@ -95,18 +75,6 @@ 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 f73753a6bcc..36123b8345e 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -281,7 +281,7 @@ const setCategoryList = (data) => { <QItem class="q-mt-lg"> <QBtn icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat class="fill-icon-on-hover q-px-xs" color="primary" @@ -327,6 +327,7 @@ en: active: Is active visible: Is visible floramondo: Is floramondo + salesPersonFk: Buyer categoryFk: Category es: @@ -337,6 +338,7 @@ es: active: Activo visible: Visible floramondo: Floramondo + salesPersonFk: Comprador categoryFk: Categoría Plant: Planta natural Flower: Flor fresca diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index 9a994949943..644f831d44d 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -41,6 +41,7 @@ const filteredItems = computed(() => { return locale.includes(normalizedSearch); }); }); + const filteredPinnedModules = computed(() => { if (!search.value) return pinnedModules.value; const normalizedSearch = search.value @@ -71,7 +72,7 @@ watch( items.value = []; getRoutes(); }, - { deep: true }, + { deep: true } ); function findMatches(search, item) { @@ -103,40 +104,33 @@ function addChildren(module, route, parent) { } function getRoutes() { - const handleRoutes = { - main: getMainRoutes, - card: getCardRoutes, - }; - try { - handleRoutes[props.source](); - } catch (error) { - throw new Error(`Method is not defined`); - } -} -function getMainRoutes() { - const modules = Object.assign([], navigation.getModules().value); + if (props.source === 'main') { + const modules = Object.assign([], navigation.getModules().value); - for (const item of modules) { - const moduleDef = routes.find( - (route) => toLowerCamel(route.name) === item.module, + for (const item of modules) { + const moduleDef = routes.find( + (route) => toLowerCamel(route.name) === item.module + ); + if (!moduleDef) continue; + item.children = []; + + addChildren(item.module, moduleDef, item.children); + } + + items.value = modules; + } + + if (props.source === 'card') { + const currentRoute = route.matched[1]; + const currentModule = toLowerCamel(currentRoute.name); + let moduleDef = routes.find( + (route) => toLowerCamel(route.name) === currentModule ); - if (!moduleDef) continue; - item.children = []; - addChildren(item.module, moduleDef, item.children); + if (!moduleDef) return; + if (!moduleDef?.menus) moduleDef = betaGetRoutes(); + addChildren(currentModule, moduleDef, items.value); } - - items.value = modules; -} - -function getCardRoutes() { - const currentRoute = route.matched[1]; - const currentModule = toLowerCamel(currentRoute.name); - let moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule); - - if (!moduleDef) return; - if (!moduleDef?.menus) moduleDef = betaGetRoutes(); - addChildren(currentModule, moduleDef, items.value); } function betaGetRoutes() { @@ -229,16 +223,9 @@ const searchModule = () => { </template> <template v-for="(item, index) in filteredItems" :key="item.name"> <template - v-if=" - search || - (item.children && !filteredPinnedModules.has(item.name)) - " + v-if="search ||item.children && !filteredPinnedModules.has(item.name)" > - <LeftMenuItem - :item="item" - group="modules" - :class="search && index === 0 ? 'searched' : ''" - > + <LeftMenuItem :item="item" group="modules" :class="search && index === 0 ? 'searched' : ''"> <template #side> <QBtn v-if="item.isPinned === true" @@ -355,7 +342,7 @@ const searchModule = () => { .header { color: var(--vn-label-color); } -.searched { +.searched{ background-color: var(--vn-section-hover-color); } </style> diff --git a/src/components/LeftMenuItem.vue b/src/components/LeftMenuItem.vue index c0cee44fe86..a3112b17fd3 100644 --- a/src/components/LeftMenuItem.vue +++ b/src/components/LeftMenuItem.vue @@ -26,7 +26,6 @@ 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 6dcb8b3903e..590acede005 100644 --- a/src/components/RefundInvoiceForm.vue +++ b/src/components/RefundInvoiceForm.vue @@ -9,7 +9,6 @@ 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: { @@ -132,11 +131,15 @@ const refund = async () => { :required="true" /> </VnRow ><VnRow> - <VnCheckbox - v-model="invoiceParams.inheritWarehouse" - :label="t('Inherit warehouse')" - :info="t('Inherit warehouse tooltip')" - /> + <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> </VnRow> </template> </FormPopup> diff --git a/src/components/TicketProblems.vue b/src/components/TicketProblems.vue index 783f2556fe8..934b13a1c1d 100644 --- a/src/components/TicketProblems.vue +++ b/src/components/TicketProblems.vue @@ -4,21 +4,26 @@ 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> + <span> <QIcon - v-if="row?.risk" + v-if="row.isTaxDataChecked === 0" + name="vn:no036" + color="primary" + size="xs" + > + <QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip> + </QIcon> + <QIcon v-if="row.hasTicketRequest" name="vn:buyrequest" color="primary" size="xs"> + <QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip> + </QIcon> + <QIcon v-if="row.itemShortage" name="vn:unavailable" color="primary" size="xs"> + <QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip> + </QIcon> + <QIcon v-if="row.isFreezed" name="vn:frozen" color="primary" size="xs"> + <QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip> + </QIcon> + <QIcon + v-if="row.risk" name="vn:risk" :color="row.hasHighRisk ? 'negative' : 'primary'" size="xs" @@ -28,57 +33,10 @@ defineProps({ row: { type: Object, required: true } }); {{ toCurrency(row.risk - row.credit) }} </QTooltip> </QIcon> - <QIcon - v-if="row?.hasComponentLack" - name="vn:components" - color="primary" - size="xs" - > + <QIcon v-if="row.hasComponentLack" name="vn:components" color="primary" size="xs"> <QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip> </QIcon> - <QIcon v-if="row?.hasItemDelay" color="primary" size="xs" name="vn:hasItemDelay"> - <QTooltip> - {{ $t('ticket.summary.hasItemDelay') }} - </QTooltip> - </QIcon> - <QIcon v-if="row?.hasItemLost" color="primary" size="xs" name="vn:hasItemLost"> - <QTooltip> - {{ $t('salesTicketsTable.hasItemLost') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row?.hasItemShortage" - name="vn:unavailable" - color="primary" - size="xs" - > - <QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip> - </QIcon> - <QIcon v-if="row?.hasRounding" color="primary" name="sync_problem" size="xs"> - <QTooltip> - {{ $t('ticketList.rounding') }} - </QTooltip> - </QIcon> - <QIcon - v-if="row?.hasTicketRequest" - name="vn:buyrequest" - color="primary" - size="xs" - > - <QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip> - </QIcon> - <QIcon - v-if="row?.isTaxDataChecked !== 0" - name="vn:no036" - color="primary" - size="xs" - > - <QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip> - </QIcon> - <QIcon v-if="row?.isFreezed" name="vn:frozen" color="primary" size="xs"> - <QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip> - </QIcon> - <QIcon v-if="row?.isTooLittle" name="vn:isTooLittle" color="primary" size="xs"> + <QIcon v-if="row.isTooLittle" name="vn:isTooLittle" color="primary" size="xs"> <QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip> </QIcon> </span> diff --git a/src/components/TransferInvoiceForm.vue b/src/components/TransferInvoiceForm.vue index c4ef1454ad8..aa71070d6cd 100644 --- a/src/components/TransferInvoiceForm.vue +++ b/src/components/TransferInvoiceForm.vue @@ -10,7 +10,6 @@ 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: { @@ -187,11 +186,15 @@ const makeInvoice = async () => { /> </VnRow> <VnRow> - <VnCheckbox - v-model="checked" - :label="t('Bill destination client')" - :info="t('transferInvoiceInfo')" - /> + <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> </VnRow> </template> </FormPopup> diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index d0e24538880..9e9bfad6982 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -1,8 +1,9 @@ <script setup> import { markRaw, computed } from 'vue'; -import { QIcon, QToggle } from 'quasar'; +import { QIcon, QCheckbox } 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'; @@ -11,11 +12,8 @@ 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, @@ -41,18 +39,10 @@ const $props = defineProps({ type: Object, default: null, }, - autofocus: { - type: Boolean, - default: false, - }, showLabel: { type: Boolean, default: null, }, - eventHandlers: { - type: Object, - default: null, - }, }); const defaultSelect = { @@ -109,8 +99,7 @@ const defaultComponents = { }, }, checkbox: { - ref: 'checkbox', - component: markRaw(VnCheckbox), + component: markRaw(QCheckbox), attrs: ({ model }) => { const defaultAttrs = { disable: !$props.isEditable, @@ -126,10 +115,6 @@ const defaultComponents = { }, forceAttrs: { label: $props.showLabel && $props.column.label, - autofocus: true, - }, - events: { - blur: () => emit('blur'), }, }, select: { @@ -140,19 +125,12 @@ 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(() => { @@ -182,28 +160,7 @@ const col = computed(() => { return newColumn; }); -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; - }, {}); -}); +const components = computed(() => $props.components ?? defaultComponents); </script> <template> <div class="row no-wrap"> diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 0de3834eadb..426f5c716fe 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -1,12 +1,14 @@ <script setup> import { markRaw, computed } from 'vue'; -import { QCheckbox, QToggle } from 'quasar'; +import { QCheckbox } 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 VnColumn from 'components/VnTable/VnColumn.vue'; +import VnTableColumn from 'components/VnTable/VnColumn.vue'; const $props = defineProps({ column: { @@ -25,10 +27,6 @@ const $props = defineProps({ type: String, default: 'table', }, - customClass: { - type: String, - default: '', - }, }); defineExpose({ addFilter, props: $props }); @@ -36,7 +34,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); @@ -48,18 +46,19 @@ 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-pt-none fit ${$props.customClass}`, + class: 'q-px-sm q-pb-xs q-pt-none fit', dense: true, filled: !$props.showTitle, }, @@ -110,24 +109,14 @@ const components = { component: markRaw(QCheckbox), event: updateEvent, attrs: { - class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit', + dense: true, + class: $props.showTitle ? 'q-py-sm q-mt-md' : '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) { @@ -143,8 +132,19 @@ 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,8 +152,13 @@ const onTabPressed = async () => { }; </script> <template> - <div v-if="showFilter" class="full-width" style="overflow: hidden"> - <VnColumn + <div + v-if="showFilter" + class="full-width" + :class="alignRow()" + style="max-height: 45px; overflow: hidden" + > + <VnTableColumn :column="$props.column" default="input" v-model="model" @@ -163,8 +168,3 @@ 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 47ed9acf4ff..8ffdfe2bc3e 100644 --- a/src/components/VnTable/VnOrder.vue +++ b/src/components/VnTable/VnOrder.vue @@ -23,10 +23,6 @@ const $props = defineProps({ type: Boolean, default: false, }, - align: { - type: String, - default: 'end', - }, }); const hover = ref(); const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); @@ -45,78 +41,55 @@ 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="items-center no-wrap cursor-pointer title" - :style="textAlignToFlex(align)" + class="row items-center no-wrap cursor-pointer" > <span :title="label">{{ label }}</span> - <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" + <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 - 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> + {{ model?.index }} + <QIcon + :name=" + model?.index + ? model?.direction == 'DESC' + ? 'arrow_downward' + : 'arrow_upward' + : 'swap_vert' + " + size="xs" + /> + </div> + </QChip> </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 7ff56860f8b..6e5f9fef4c6 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,38 +1,22 @@ <script setup> -import { - ref, - onBeforeMount, - onMounted, - onUnmounted, - computed, - watch, - h, - render, - inject, - useAttrs, - nextTick, -} from 'vue'; -import { useArrayData } from 'src/composables/useArrayData'; +import { ref, onBeforeMount, onMounted, computed, watch, useAttrs } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; -import { useQuasar, date } from 'quasar'; +import { useQuasar } 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 VnColumn from 'components/VnTable/VnColumn.vue'; +import VnTableColumn 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, @@ -58,6 +42,10 @@ const $props = defineProps({ type: [Function, Boolean], default: null, }, + rowCtrlClick: { + type: [Function, Boolean], + default: null, + }, redirect: { type: String, default: null, @@ -126,19 +114,7 @@ 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(); @@ -156,18 +132,10 @@ 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 showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); -const selectRegex = /select/; -const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); const tableModes = [ { icon: 'view_column', @@ -188,8 +156,7 @@ onBeforeMount(() => { hasParams.value = urlParams && Object.keys(urlParams).length !== 0; }); -onMounted(async () => { - if ($props.isEditable) document.addEventListener('click', clickHandler); +onMounted(() => { mode.value = quasar.platform.is.mobile && !$props.disableOption?.card ? CARD_MODE @@ -211,25 +178,14 @@ onMounted(async () => { } }); -onUnmounted(async () => { - if ($props.isEditable) document.removeEventListener('click', clickHandler); -}); - watch( () => $props.columns, (value) => splitColumns(value), { immediate: true }, ); -defineExpose({ - create: createForm, - reload, - redirect: redirectFn, - selected, - CrudModelRef, - params, - tableRef, -}); +const isTableMode = computed(() => mode.value == TABLE_MODE); +const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); function splitColumns(columns) { splittedColumns.value = { @@ -275,6 +231,16 @@ 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}` }); } @@ -296,6 +262,21 @@ 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(_); @@ -324,237 +305,6 @@ 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 || row[col?.name + 'TextValue']) { - 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}` }); } @@ -565,7 +315,7 @@ function cardClick(_, row) { v-model="stateStore.rightDrawer" side="right" :width="256" - :overlay="$props.overlay" + show-if-above > <QScrollArea class="fit"> <VnTableFilter @@ -586,7 +336,7 @@ function cardClick(_, row) { <CrudModel v-bind="$attrs" :class="$attrs['class'] ?? 'q-px-md'" - :limit="$attrs['limit'] ?? 100" + :limit="$attrs['limit'] ?? 20" ref="CrudModelRef" @on-fetch="(...args) => emit('onFetch', ...args)" :search-url="searchUrl" @@ -602,12 +352,8 @@ function cardClick(_, row) { <QTable ref="tableRef" v-bind="table" - :class="[ - 'vnTable', - table ? 'selection-cell' : '', - $props.footer ? 'last-row-sticky' : '', - ]" - wrap-cells + class="vnTable" + :class="{ 'last-row-sticky': $props.footer }" :columns="splittedColumns.columns" :rows="rows" v-model:selected="selected" @@ -621,13 +367,11 @@ function cardClick(_, row) { @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" @@ -641,7 +385,6 @@ function cardClick(_, row) { dense :options="tableModes.filter((mode) => !mode.disable)" /> - <QBtn v-if="showRightIcon" icon="filter_alt" @@ -653,39 +396,32 @@ function cardClick(_, row) { <template #header-cell="{ col }"> <QTh v-if="col.visible ?? true" - v-bind:class="col.headerClass" - class="body-cell" - :style="col?.width ? `max-width: ${col?.width}` : ''" + :style="col.headerStyle" + :class="col.headerClass" > <div - class="no-padding" - :style="[ - withFilters && $props.columnSearch ? 'height: 75px' : '', - ]" + class="column ellipsis" + :class="`text-${col?.align ?? 'left'}`" + :style="$props.columnSearch ? 'height: 75px' : ''" > - <div style="height: 30px"> + <div class="row items-center no-wrap" 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?.labelAbbreviation ?? col?.label" + :label="col?.label" :data-key="$attrs['data-key']" :search-url="searchUrl" - :align="getColAlign(col)" /> </div> <VnFilter - v-if=" - $props.columnSearch && - col.columnSearch !== false && - withFilters - " + v-if="$props.columnSearch" :column="col" :show-title="true" :data-key="$attrs['data-key']" v-model="params[columnName(col)]" :search-url="searchUrl" - customClass="header-filter" + class="full-width" /> </div> </QTh> @@ -703,67 +439,32 @@ function cardClick(_, row) { </QTd> </template> <template #body-cell="{ col, row, rowIndex }"> + <!-- Columns --> <QTd - class="no-margin q-px-xs" + auto-width + class="no-margin" + :class="[getColAlign(col), col.columnClass]" + :style="col.style" v-if="col.visible ?? true" - :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" + @click.ctrl=" + ($event) => + rowCtrlClickFunction && rowCtrlClickFunction($event, row) + " > - <div - class="no-padding no-margin peter" - style=" - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - " + <slot + :name="`column-${col.name}`" + :col="col" + :row="row" + :row-index="rowIndex" > - <slot - :name="`column-${col.name}`" - :col="col" + <VnTableColumn + :column="col" :row="row" - :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> + :is-editable="col.isEditable ?? isEditable" + v-model="row[col.name]" + component-prop="columnField" + /> + </slot> </QTd> </template> <template #body-cell-tableActions="{ col, row }"> @@ -784,7 +485,7 @@ function cardClick(_, row) { flat dense :class=" - btn.isPrimary ? 'text-primary-light' : 'color-vn-label' + btn.isPrimary ? 'text-primary-light' : 'color-vn-text ' " :style="`visibility: ${ ((btn.show && btn.show(row)) ?? true) @@ -792,7 +493,6 @@ function cardClick(_, row) { : 'hidden' }`" @click="btn.action(row)" - :data-cy="btn?.name ?? `tableAction-${index}`" /> </QTd> </template> @@ -841,7 +541,7 @@ function cardClick(_, row) { </QCardSection> <!-- Fields --> <QCardSection - class="q-pl-sm q-py-xs" + class="q-pl-sm q-pr-lg q-py-xs" :class="$props.cardClass" > <div @@ -862,7 +562,7 @@ function cardClick(_, row) { :row="row" :row-index="index" > - <VnColumn + <VnTableColumn :column="col" :row="row" :is-editable="false" @@ -889,12 +589,12 @@ function cardClick(_, row) { :title="btn.title" :icon="btn.icon" class="q-pa-xs" + flat :class=" btn.isPrimary ? 'text-primary-light' - : 'color-vn-label' + : 'color-vn-text ' " - flat @click="btn.action(row)" /> </QCardSection> @@ -902,17 +602,14 @@ function cardClick(_, row) { </component> </template> <template #bottom-row="{ cols }" v-if="$props.footer"> - <QTr v-if="rows.length" style="height: 45px"> - <QTh v-if="table.selection" /> + <QTr v-if="rows.length" style="height: 30px"> <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}`" - :isEditableColumn="isEditableColumn(col)" - /> + <slot :name="`column-footer-${col.name}`" /> </QTh> </QTr> </template> @@ -931,7 +628,7 @@ function cardClick(_, row) { size="md" round flat - v-shortcut="'+'" + shortcut="+" :disabled="!disabledAttr" /> <QTooltip> @@ -949,52 +646,39 @@ function cardClick(_, row) { color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" data-cy="vnTableCreateBtn" /> <QTooltip self="top right"> {{ createForm?.title }} </QTooltip> </QPageSticky> - <QDialog - v-model="showForm" - transition-show="scale" - transition-hide="scale" - :full-width="createComplement?.isFullWidth ?? false" - data-cy="vn-table-create-dialog" - > + <QDialog v-model="showForm" transition-show="scale" transition-hide="scale"> <FormModelPopup - ref="createRef" v-bind="createForm" :model="$attrs['data-key'] + 'Create'" @on-data-saved="(_, res) => createForm.onDataSaved(res)" > <template #form-inputs="{ 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 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> </template> </FormModelPopup> @@ -1012,42 +696,6 @@ 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); @@ -1064,8 +712,8 @@ es: .grid-three { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, max-content)); - width: 100%; + grid-template-columns: repeat(auto-fit, minmax(350px, max-content)); + max-width: 100%; grid-gap: 20px; margin: 0 auto; } @@ -1073,6 +721,7 @@ es: .grid-create { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, max-content)); + max-width: 100%; grid-gap: 20px; margin: 0 auto; } @@ -1088,9 +737,7 @@ es: } } } -.q-table tbody tr td { - position: relative; -} + .q-table { th { padding: 0; @@ -1139,7 +786,6 @@ es: .vn-label-value { display: flex; flex-direction: row; - align-items: center; color: var(--vn-text-color); .value { overflow: hidden; @@ -1191,15 +837,4 @@ 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 79b903e547a..732605ce5ed 100644 --- a/src/components/VnTable/VnTableFilter.vue +++ b/src/components/VnTable/VnTableFilter.vue @@ -27,36 +27,31 @@ function columnName(col) { </script> <template> <VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true"> - <template #body="{ params, orders, searchFn }"> + <template #body="{ params, orders }"> <div - class="container" + class="row no-wrap flex-center" v-for="col of columns.filter((c) => c.columnFilter ?? true)" :key="col.id" > - <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> + <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> <slot name="moreFilterPanel" :params="params" - :search-fn="searchFn" :orders="orders" :columns="columns" /> @@ -72,21 +67,3 @@ 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 6d15c585e0e..dad950d739c 100644 --- a/src/components/VnTable/VnVisibleColumn.vue +++ b/src/components/VnTable/VnVisibleColumn.vue @@ -32,21 +32,16 @@ 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, labelAbbreviation } = column; + const { label, name } = column; if (skippeds[name]) continue; column.visible = data[name] ?? true; - if (!isLocal) - localColumns.value.push({ - name, - label, - labelAbbreviation, - visible: column.visible, - }); + if (!isLocal) localColumns.value.push({ name, label, visible: column.visible }); } } @@ -157,11 +152,7 @@ onMounted(async () => { <QCheckbox v-for="col in localColumns" :key="col.name" - :label=" - col?.labelAbbreviation - ? col.labelAbbreviation + ` (${col.label ?? col.name})` - : (col.label ?? col.name) - " + :label="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 3dce043749d..e35684bc338 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -57,7 +57,6 @@ 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,13 +93,9 @@ 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 = {}; + const { vm } = mount({ propsData: { url, model, formInitialData } }); + vm.formData.mockKey = 'newVal'; await vm.$nextTick(); - vm.formData = { mockKey: 'newVal' }; - await vm.$nextTick(); - await vm.save(); expect(spy).toHaveBeenCalled(); vm.formData.mockKey = 'mockVal'; @@ -111,7 +106,6 @@ describe('FormModel', () => { const { vm } = mount({ propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' }, }); - await vm.$nextTick(); vm.formData.mockKey = 'newVal'; await vm.$nextTick(); await vm.save(); @@ -125,7 +119,7 @@ describe('FormModel', () => { }); const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); const spySaveFn = vi.spyOn(vm.$props, 'saveFn'); - await vm.$nextTick(); + vm.formData.mockKey = 'newVal'; await vm.$nextTick(); await vm.save(); diff --git a/src/components/__tests__/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js index 4ab8b527fdf..10d9d66fbfb 100644 --- a/src/components/__tests__/Leftmenu.spec.js +++ b/src/components/__tests__/Leftmenu.spec.js @@ -1,11 +1,8 @@ -import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; +import { vi, describe, expect, it, beforeAll } from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import Leftmenu from 'components/LeftMenu.vue'; -import * as vueRouter from 'vue-router'; -import { useNavigationStore } from 'src/stores/useNavigationStore'; -let vm; -let navigation; +import { useNavigationStore } from 'src/stores/useNavigationStore'; vi.mock('src/router/modules', () => ({ default: [ @@ -24,16 +21,6 @@ vi.mock('src/router/modules', () => ({ { path: '', name: 'CustomerMain', - meta: { - menu: 'Customer', - menuChildren: [ - { - name: 'CustomerCreditContracts', - title: 'creditContracts', - icon: 'vn:solunion', - }, - ], - }, children: [ { path: 'list', @@ -41,13 +28,6 @@ vi.mock('src/router/modules', () => ({ meta: { title: 'list', icon: 'view_list', - menuChildren: [ - { - name: 'CustomerCreditContracts', - title: 'creditContracts', - icon: 'vn:solunion', - }, - ], }, }, { @@ -64,325 +44,51 @@ vi.mock('src/router/modules', () => ({ }, ], })); -vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ - matched: [ - { - path: '/', - redirect: { - name: 'Dashboard', + +describe('Leftmenu', () => { + let vm; + let navigation; + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [], + }); + + vm = createWrapper(Leftmenu, { + propsData: { + source: 'main', }, - name: 'Main', - meta: {}, - props: { - default: false, - }, - children: [ + }).vm; + + navigation = useNavigationStore(); + navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true)); + navigation.getModules = vi.fn().mockReturnValue({ + value: [ { - path: '/dashboard', - name: 'Dashboard', - meta: { - title: 'dashboard', - icon: 'dashboard', - }, + name: 'customer', + title: 'customer.pageTitles.customers', + icon: 'vn:customer', + module: 'customer', }, ], - }, - { - path: '/customer', - redirect: { - name: 'CustomerMain', - }, - name: 'Customer', - meta: { - title: 'customers', - icon: 'vn:client', - moduleName: 'Customer', - keyBinding: 'c', - menu: 'customer', - }, - }, - ], - query: {}, - params: {}, - meta: { moduleName: 'mockName' }, - path: 'mockName/1', - name: 'Customer', -}); -function mount(source = 'main') { - vi.spyOn(axios, 'get').mockResolvedValue({ - data: [], - }); - const wrapper = createWrapper(Leftmenu, { - propsData: { - source, - }, - }); - - navigation = useNavigationStore(); - navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true)); - navigation.getModules = vi.fn().mockReturnValue({ - value: [ - { - name: 'customer', - title: 'customer.pageTitles.customers', - icon: 'vn:customer', - module: 'customer', - }, - ], - }); - return wrapper; -} - -describe('getRoutes', () => { - afterEach(() => vi.clearAllMocks()); - const getRoutes = vi.fn().mockImplementation((props, getMethodA, getMethodB) => { - const handleRoutes = { - methodA: getMethodA, - methodB: getMethodB, - }; - try { - handleRoutes[props.source](); - } catch (error) { - throw Error('Method not defined'); - } - }); - - const getMethodA = vi.fn(); - const getMethodB = vi.fn(); - const fn = (props) => getRoutes(props, getMethodA, getMethodB); - - it('should call getMethodB when source is card', () => { - let props = { source: 'methodB' }; - fn(props); - - expect(getMethodB).toHaveBeenCalled(); - expect(getMethodA).not.toHaveBeenCalled(); - }); - it('should call getMethodA when source is main', () => { - let props = { source: 'methodA' }; - fn(props); - - expect(getMethodA).toHaveBeenCalled(); - expect(getMethodB).not.toHaveBeenCalled(); - }); - - it('should call getMethodA when source is not exists or undefined', () => { - let props = { source: 'methodC' }; - expect(() => fn(props)).toThrowError('Method not defined'); - - expect(getMethodA).not.toHaveBeenCalled(); - expect(getMethodB).not.toHaveBeenCalled(); - }); -}); - -describe('Leftmenu as card', () => { - beforeAll(() => { - vm = mount('card').vm; - }); - - it('should get routes for card source', async () => { - vm.getRoutes(); - }); -}); -describe('Leftmenu as main', () => { - beforeEach(() => { - vm = mount().vm; - }); - - it('should initialize with default props', () => { - expect(vm.source).toBe('main'); - }); - - it('should filter items based on search input', async () => { - vm.search = 'cust'; - await vm.$nextTick(); - expect(vm.filteredItems[0].name).toEqual('customer'); - expect(vm.filteredItems[0].module).toEqual('customer'); - }); - it('should filter items based on search input', async () => { - vm.search = 'Rou'; - await vm.$nextTick(); - expect(vm.filteredItems).toEqual([]); - }); - - it('should return pinned items', () => { - vm.items = [ - { name: 'Item 1', isPinned: false }, - { name: 'Item 2', isPinned: true }, - ]; - expect(vm.pinnedModules).toEqual( - new Map([['Item 2', { name: 'Item 2', isPinned: true }]]), - ); - }); - - it('should find matches in routes', () => { - const search = 'child1'; - const item = { - children: [ - { name: 'child1', children: [] }, - { name: 'child2', children: [] }, - ], - }; - const matches = vm.findMatches(search, item); - expect(matches).toEqual([{ name: 'child1', children: [] }]); - }); - it('should not proceed if event is already prevented', async () => { - const item = { module: 'testModule', isPinned: false }; - const event = { - preventDefault: vi.fn(), - stopPropagation: vi.fn(), - defaultPrevented: true, - }; - - await vm.togglePinned(item, event); - - expect(event.preventDefault).not.toHaveBeenCalled(); - expect(event.stopPropagation).not.toHaveBeenCalled(); - }); - - it('should call quasar.notify with success message', async () => { - const item = { module: 'testModule', isPinned: false }; - const event = { - preventDefault: vi.fn(), - stopPropagation: vi.fn(), - defaultPrevented: false, - }; - const response = { data: { id: 1 } }; - - vi.spyOn(axios, 'post').mockResolvedValue(response); - vi.spyOn(vm.quasar, 'notify'); - - await vm.togglePinned(item, event); - - expect(vm.quasar.notify).toHaveBeenCalledWith({ - message: 'Data saved', - type: 'positive', }); }); - it('should handle a single matched route with a menu', () => { - const route = { - matched: [{ meta: { menu: 'customer' } }], - }; - - const result = vm.betaGetRoutes(); - - expect(result.meta.menu).toEqual(route.matched[0].meta.menu); - }); - it('should get routes for main source', () => { - vm.props.source = 'main'; - vm.getRoutes(); - expect(navigation.getModules).toHaveBeenCalled(); - }); - - it('should find direct child matches', () => { - const search = 'child1'; - const item = { - children: [{ name: 'child1' }, { name: 'child2' }], - }; - const result = vm.findMatches(search, item); - expect(result).toEqual([{ name: 'child1' }]); - }); - - it('should find nested child matches', () => { - const search = 'child3'; - const item = { - children: [ - { name: 'child1' }, - { - name: 'child2', - children: [{ name: 'child3' }], - }, - ], - }; - const result = vm.findMatches(search, item); - expect(result).toEqual([{ name: 'child3' }]); - }); -}); - -describe('normalize', () => { - beforeAll(() => { - vm = mount('card').vm; - }); - it('should normalize and lowercase text', () => { - const input = 'ÁÉÍÓÚáéíóú'; - const expected = 'aeiouaeiou'; - expect(vm.normalize(input)).toBe(expected); - }); - - it('should handle empty string', () => { - const input = ''; - const expected = ''; - expect(vm.normalize(input)).toBe(expected); - }); - - it('should handle text without diacritics', () => { - const input = 'hello'; - const expected = 'hello'; - expect(vm.normalize(input)).toBe(expected); - }); - - it('should handle mixed text', () => { - const input = 'Héllo Wórld!'; - const expected = 'hello world!'; - expect(vm.normalize(input)).toBe(expected); - }); -}); - -describe('addChildren', () => { - const module = 'testModule'; - beforeEach(() => { - vm = mount().vm; - vi.clearAllMocks(); - }); - - it('should add menu items to parent if matches are found', () => { - const parent = 'testParent'; - const route = { - meta: { - menu: 'testMenu', + it('should return a proper formated object with two child items', async () => { + const expectedMenuItem = [ + { + children: null, + name: 'CustomerList', + title: 'globals.pageTitles.list', + icon: 'view_list', }, - children: [{ name: 'child1' }, { name: 'child2' }], - }; - vm.addChildren(module, route, parent); - - expect(navigation.addMenuItem).toHaveBeenCalled(); - }); - - it('should handle routes with no meta menu', () => { - const route = { - meta: {}, - menus: {}, - }; - - const parent = []; - - vm.addChildren(module, route, parent); - expect(navigation.addMenuItem).toHaveBeenCalled(); - }); - - it('should handle empty parent array', () => { - const parent = []; - const route = { - meta: { - menu: 'child11', + { + children: null, + name: 'CustomerCreate', + title: 'globals.pageTitles.createCustomer', + icon: 'vn:addperson', }, - children: [ - { - name: 'child1', - meta: { - menuChildren: [ - { - name: 'CustomerCreditContracts', - title: 'creditContracts', - icon: 'vn:solunion', - }, - ], - }, - }, - ], - }; - vm.addChildren(module, route, parent); - expect(navigation.addMenuItem).toHaveBeenCalled(); + ]; + const firstMenuItem = vm.items[0]; + expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem)); }); }); diff --git a/src/components/__tests__/UserPanel.spec.js b/src/components/__tests__/UserPanel.spec.js index 9e449745a89..ac20f911e7f 100644 --- a/src/components/__tests__/UserPanel.spec.js +++ b/src/components/__tests__/UserPanel.spec.js @@ -1,65 +1,61 @@ -import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest'; +import { vi, describe, expect, it, beforeEach, beforeAll, 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, + 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; }); - 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); - await 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); + 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); - await 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); + 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 }); - }); -}); \ No newline at end of file + 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 }); + }); +}); diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 44002c22aad..0d80f43ce94 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -10,11 +10,11 @@ import LeftMenu from 'components/LeftMenu.vue'; import RightMenu from 'components/common/RightMenu.vue'; const props = defineProps({ dataKey: { type: String, required: true }, - url: { type: String, default: undefined }, + baseUrl: { type: String, default: undefined }, + customUrl: { type: String, default: undefined }, filter: { type: Object, default: () => {} }, descriptor: { type: Object, required: true }, filterPanel: { type: Object, default: undefined }, - idInWhere: { type: Boolean, default: false }, searchDataKey: { type: String, default: undefined }, searchbarProps: { type: Object, default: undefined }, redirectOnError: { type: Boolean, default: false }, @@ -23,20 +23,25 @@ const props = defineProps({ const stateStore = useStateStore(); const route = useRoute(); const router = useRouter(); +const url = computed(() => { + if (props.baseUrl) { + return `${props.baseUrl}/${route.params.id}`; + } + return props.customUrl; +}); const searchRightDataKey = computed(() => { if (!props.searchDataKey) return route.name; return props.searchDataKey; }); - const arrayData = useArrayData(props.dataKey, { - url: props.url, - userFilter: props.filter, - oneRecord: true, + url: url.value, + filter: props.filter, }); onBeforeMount(async () => { try { - await fetch(route.params.id); + if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id }; + await arrayData.fetch({ append: false, updateRouter: false }); } catch { const { matched: matches } = router.currentRoute.value; const { path } = matches.at(-1); @@ -44,17 +49,13 @@ onBeforeMount(async () => { } }); -onBeforeRouteUpdate(async (to, from) => { - const id = to.params.id; - if (id !== from.params.id) await fetch(id, true); -}); - -async function fetch(id, append = false) { - const regex = /\/(\d+)/; - if (props.idInWhere) arrayData.store.filter.where = { id }; - else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; - else arrayData.store.url = props.url.replace(regex, `/${id}`); - await arrayData.fetch({ append, updateRouter: false }); +if (props.baseUrl) { + onBeforeRouteUpdate(async (to, from) => { + if (to.params.id !== from.params.id) { + arrayData.store.url = `${props.baseUrl}/${to.params.id}`; + await arrayData.fetch({ append: false, updateRouter: false }); + } + }); } </script> <template> @@ -82,7 +83,7 @@ async function fetch(id, append = false) { <QPage> <VnSubToolbar /> <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="$route.path" /> + <RouterView :key="route.path" /> </div> </QPage> </QPageContainer> diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue index 7c82316dc44..f237a300ca3 100644 --- a/src/components/common/VnCardBeta.vue +++ b/src/components/common/VnCardBeta.vue @@ -1,6 +1,6 @@ <script setup> -import { onBeforeMount } from 'vue'; -import { useRouter, onBeforeRouteUpdate } from 'vue-router'; +import { onBeforeMount, computed } from 'vue'; +import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; @@ -9,9 +9,10 @@ import VnSubToolbar from '../ui/VnSubToolbar.vue'; const props = defineProps({ dataKey: { type: String, required: true }, - url: { type: String, default: undefined }, - idInWhere: { type: Boolean, default: false }, + baseUrl: { type: String, default: undefined }, + customUrl: { type: String, default: undefined }, filter: { type: Object, default: () => {} }, + userFilter: { type: Object, default: () => {} }, descriptor: { type: Object, required: true }, filterPanel: { type: Object, default: undefined }, searchDataKey: { type: String, default: undefined }, @@ -20,42 +21,46 @@ const props = defineProps({ }); const stateStore = useStateStore(); +const route = useRoute(); const router = useRouter(); +const url = computed(() => { + if (props.baseUrl) { + return `${props.baseUrl}/${route.params.id}`; + } + return props.customUrl; +}); + const arrayData = useArrayData(props.dataKey, { - url: props.url, - userFilter: props.filter, - oneRecord: true, + url: url.value, + filter: props.filter, + userFilter: props.userFilter, }); onBeforeMount(async () => { - const route = router.currentRoute.value; try { - await fetch(route.params.id); + if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id }; + await arrayData.fetch({ append: false, updateRouter: false }); } catch { - const { matched: matches } = route; + const { matched: matches } = router.currentRoute.value; const { path } = matches.at(-1); router.push({ path: path.replace(/:id.*/, '') }); } }); -onBeforeRouteUpdate(async (to, from) => { - if (hasRouteParam(to.params)) { - const { matched } = router.currentRoute.value; - const { name } = matched.at(-3); - if (name) { - router.push({ name, params: to.params }); +if (props.baseUrl) { + onBeforeRouteUpdate(async (to, from) => { + if (hasRouteParam(to.params)) { + const { matched } = router.currentRoute.value; + const { name } = matched.at(-3); + if (name) { + router.push({ name, params: to.params }); + } } - } - const id = to.params.id; - if (id !== from.params.id) await fetch(id, true); -}); - -async function fetch(id, append = false) { - const regex = /\/(\d+)/; - if (props.idInWhere) arrayData.store.filter.where = { id }; - else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; - else arrayData.store.url = props.url.replace(regex, `/${id}`); - await arrayData.fetch({ append, updateRouter: false }); + if (to.params.id !== from.params.id) { + arrayData.store.url = `${props.baseUrl}/${to.params.id}`; + await arrayData.fetch({ append: false, updateRouter: false }); + } + }); } function hasRouteParam(params, valueToCheck = ':addressId') { return Object.values(params).includes(valueToCheck); @@ -69,6 +74,6 @@ function hasRouteParam(params, valueToCheck = ':addressId') { </Teleport> <VnSubToolbar /> <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="$route.path" /> + <RouterView :key="route.path" /> </div> </template> diff --git a/src/components/common/VnCheckbox.vue b/src/components/common/VnCheckbox.vue deleted file mode 100644 index 27131d45e2d..00000000000 --- a/src/components/common/VnCheckbox.vue +++ /dev/null @@ -1,43 +0,0 @@ -<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 deleted file mode 100644 index 8a5a787b0ef..00000000000 --- a/src/components/common/VnColor.vue +++ /dev/null @@ -1,32 +0,0 @@ -<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 a9e1c8cffc9..580bcf34836 100644 --- a/src/components/common/VnComponent.vue +++ b/src/components/common/VnComponent.vue @@ -17,8 +17,6 @@ const $props = defineProps({ }, }); -const emit = defineEmits(['blur']); - const componentArray = computed(() => { if (typeof $props.prop === 'object') return [$props.prop]; return $props.prop; @@ -48,8 +46,7 @@ function toValueAttrs(attrs) { <span v-for="toComponent of componentArray" :key="toComponent.name" - class="column fit" - :class="toComponent?.component == 'checkbox' ? 'flex-center' : ''" + class="column flex-center fit" > <component v-if="toComponent?.component" @@ -57,7 +54,6 @@ 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/VnDmsList.vue b/src/components/common/VnDmsList.vue index 424781a2695..36c87bab050 100644 --- a/src/components/common/VnDmsList.vue +++ b/src/components/common/VnDmsList.vue @@ -17,7 +17,7 @@ import { useSession } from 'src/composables/useSession'; const route = useRoute(); const quasar = useQuasar(); const { t } = useI18n(); -const rows = ref([]); +const rows = ref(); const dmsRef = ref(); const formDialog = ref({}); const token = useSession().getTokenMultimedia(); @@ -389,14 +389,6 @@ defineExpose({ </div> </template> </QTable> - <div - v-else - class="info-row q-pa-md text-center" - > - <h5> - {{ t('No data to display') }} - </h5> - </div> </template> </VnPaginate> <QDialog v-model="formDialog.show"> @@ -413,7 +405,7 @@ defineExpose({ fab color="primary" icon="add" - v-shortcut + shortcut="+" @click="showFormDialog()" class="fill-icon" > diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index aeb4a31fd4b..78f08a47991 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -11,7 +11,6 @@ const emit = defineEmits([ 'update:options', 'keyup.enter', 'remove', - 'blur', ]); const $props = defineProps({ @@ -137,7 +136,6 @@ const handleUppercase = () => { :type="$attrs.type" :class="{ required: isRequired }" @keyup.enter="emit('keyup.enter')" - @blur="emit('blur')" @keydown="handleKeydown" :clearable="false" :rules="mixinRules" @@ -145,7 +143,7 @@ const handleUppercase = () => { hide-bottom-space :data-cy="$attrs.dataCy ?? $attrs.label + '_input'" > - <template #prepend v-if="$slots.prepend"> + <template #prepend> <slot name="prepend" /> </template> <template #append> @@ -170,11 +168,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" > @@ -182,7 +180,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"> @@ -196,15 +194,13 @@ 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> @@ -218,4 +214,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> +</i18n> \ No newline at end of file diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 73c825e1e26..a8888aad84b 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(() => { diff --git a/src/components/common/VnInputNumber.vue b/src/components/common/VnInputNumber.vue index 274f78b21a7..165cfae3d06 100644 --- a/src/components/common/VnInputNumber.vue +++ b/src/components/common/VnInputNumber.vue @@ -8,7 +8,6 @@ defineProps({ }); const model = defineModel({ type: [Number, String] }); -const emit = defineEmits(['blur']); </script> <template> <VnInput @@ -25,6 +24,5 @@ const emit = defineEmits(['blur']); model = parseFloat(val).toFixed(decimalPlaces); } " - @blur="emit('blur')" /> </template> diff --git a/src/components/common/VnPopupProxy.vue b/src/components/common/VnPopupProxy.vue deleted file mode 100644 index f386bfff811..00000000000 --- a/src/components/common/VnPopupProxy.vue +++ /dev/null @@ -1,38 +0,0 @@ -<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/VnSection.vue b/src/components/common/VnSection.vue index 4bd17124f3e..ef65b841f07 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -106,14 +106,7 @@ function checkIsMain() { :data-key="dataKey" :array-data="arrayData" :columns="columns" - > - <template #moreFilterPanel="{ params, orders, searchFn }"> - <slot - name="moreFilterPanel" - v-bind="{ params, orders, searchFn }" - /> - </template> - </VnTableFilter> + /> </slot> </template> </RightAdvancedMenu> diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 339f90e0ef8..95fe80a699e 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -10,12 +10,7 @@ const emit = defineEmits(['update:modelValue', 'update:options', 'remove']); const $attrs = useAttrs(); const { t } = useI18n(); -const isRequired = computed(() => { - return useRequired($attrs).isRequired; -}); -const requiredFieldRule = computed(() => { - return useRequired($attrs).requiredFieldRule; -}); +const { isRequired, requiredFieldRule } = useRequired($attrs); const $props = defineProps({ modelValue: { @@ -171,8 +166,7 @@ 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, @@ -221,7 +215,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) { @@ -240,7 +234,7 @@ async function fetchFilter(val) { const { data } = await arrayData.applyFilter( { filter: filterOptions }, - { updateRouter: false }, + { updateRouter: false } ); setOptions(data); return data; @@ -273,7 +267,7 @@ async function filterHandler(val, update) { ref.setOptionIndex(-1); ref.moveOptionSelection(1, true); } - }, + } ); } @@ -309,7 +303,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) { @@ -321,11 +315,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 f0f3357f60f..29cf22dc548 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" @blur="emit('blur')" /> + <VnSelect v-bind="$attrs" :options="$attrs.options ?? options" /> </template> diff --git a/src/components/common/VnSelectDialog.vue b/src/components/common/VnSelectDialog.vue index 41730b21792..a4cd0011d29 100644 --- a/src/components/common/VnSelectDialog.vue +++ b/src/components/common/VnSelectDialog.vue @@ -37,6 +37,7 @@ const isAllowedToCreate = computed(() => { defineExpose({ vnSelectDialogRef: select }); </script> + <template> <VnSelect ref="select" @@ -66,6 +67,7 @@ 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 5b52ae75b24..f86db4f2df0 100644 --- a/src/components/common/VnSelectSupplier.vue +++ b/src/components/common/VnSelectSupplier.vue @@ -1,7 +1,9 @@ <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> @@ -9,13 +11,11 @@ const model = defineModel({ type: [String, Number, Object] }); :label="$t('globals.supplier')" v-bind="$attrs" v-model="model" - url="Suppliers" + :url="url" 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 deleted file mode 100644 index 46538f5f91b..00000000000 --- a/src/components/common/VnSelectTravelExtended.vue +++ /dev/null @@ -1,50 +0,0 @@ -<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/common/__tests__/VnNotes.spec.js b/src/components/common/__tests__/VnNotes.spec.js index 2603bf03c9a..8f24a7f1429 100644 --- a/src/components/common/__tests__/VnNotes.spec.js +++ b/src/components/common/__tests__/VnNotes.spec.js @@ -1,78 +1,51 @@ -import { - describe, - it, - expect, - vi, - beforeAll, - afterEach, - beforeEach, - afterAll, -} from 'vitest'; +import { describe, it, expect, vi, beforeAll, afterEach, beforeEach } from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import VnNotes from 'src/components/ui/VnNotes.vue'; -import vnDate from 'src/boot/vnDate'; describe('VnNotes', () => { let vm; let wrapper; let spyFetch; let postMock; - let patchMock; - let expectedInsertBody; - let expectedUpdateBody; - const defaultOptions = { - url: '/test', - body: { name: 'Tony', lastName: 'Stark' }, - selectType: false, - saveUrl: null, - justInput: false, - }; - function generateWrapper( - options = defaultOptions, - text = null, - observationType = null, - ) { - vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + let expectedBody; + const mockData= {name: 'Tony', lastName: 'Stark', text: 'Test Note', observationTypeFk: 1}; + + function generateExpectedBody() { + expectedBody = {...vm.$props.body, ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }}; + } + + async function setTestParams(text, observationType, type){ + vm.newNote.text = text; + vm.newNote.observationTypeFk = observationType; + wrapper.setProps({ selectType: type }); + } + + beforeAll(async () => { + vi.spyOn(axios, 'get').mockReturnValue({ data: [] }); + wrapper = createWrapper(VnNotes, { - propsData: options, + propsData: { + url: '/test', + body: { name: 'Tony', lastName: 'Stark' }, + } }); wrapper = wrapper.wrapper; vm = wrapper.vm; - vm.newNote.text = text; - vm.newNote.observationTypeFk = observationType; - } - - function createSpyFetch() { - spyFetch = vi.spyOn(vm.$refs.vnPaginateRef, 'fetch'); - } - - function generateExpectedBody() { - expectedInsertBody = { - ...vm.$props.body, - ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }, - }; - expectedUpdateBody = { ...vm.$props.body, ...{ notes: vm.newNote.text } }; - } + }); beforeEach(() => { - postMock = vi.spyOn(axios, 'post'); - patchMock = vi.spyOn(axios, 'patch'); + postMock = vi.spyOn(axios, 'post').mockResolvedValue(mockData); + spyFetch = vi.spyOn(vm.vnPaginateRef, 'fetch').mockImplementation(() => vi.fn()); }); afterEach(() => { vi.clearAllMocks(); - expectedInsertBody = {}; - expectedUpdateBody = {}; - }); - - afterAll(() => { - vi.restoreAllMocks(); + expectedBody = {}; }); describe('insert', () => { - it('should not call axios.post and vnPaginateRef.fetch when newNote.text is null', async () => { - generateWrapper({ selectType: true }); - createSpyFetch(); + it('should not call axios.post and vnPaginateRef.fetch if newNote.text is null', async () => { + await setTestParams( null, null, true ); await vm.insert(); @@ -80,9 +53,8 @@ describe('VnNotes', () => { expect(spyFetch).not.toHaveBeenCalled(); }); - it('should not call axios.post and vnPaginateRef.fetch when newNote.text is empty', async () => { - generateWrapper(null, ''); - createSpyFetch(); + it('should not call axios.post and vnPaginateRef.fetch if newNote.text is empty', async () => { + await setTestParams( "", null, false ); await vm.insert(); @@ -90,9 +62,8 @@ describe('VnNotes', () => { expect(spyFetch).not.toHaveBeenCalled(); }); - it('should not call axios.post and vnPaginateRef.fetch when observationTypeFk is null and selectType is true', async () => { - generateWrapper({ selectType: true }, 'Test Note'); - createSpyFetch(); + it('should not call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is true', async () => { + await setTestParams( "Test Note", null, true ); await vm.insert(); @@ -100,57 +71,37 @@ describe('VnNotes', () => { expect(spyFetch).not.toHaveBeenCalled(); }); - it('should call axios.post and vnPaginateRef.fetch when observationTypeFk is missing and selectType is false', async () => { - generateWrapper(null, 'Test Note'); - createSpyFetch(); + it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is false', async () => { + await setTestParams( "Test Note", null, false ); + generateExpectedBody(); await vm.insert(); - expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody); + expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); + expect(spyFetch).toHaveBeenCalled(); + }); + + it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is setted and selectType is false', async () => { + await setTestParams( "Test Note", 1, false ); + + generateExpectedBody(); + + await vm.insert(); + + expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); expect(spyFetch).toHaveBeenCalled(); }); it('should call axios.post and vnPaginateRef.fetch when newNote is valid', async () => { - generateWrapper({ selectType: true }, 'Test Note', 1); - createSpyFetch(); - generateExpectedBody(); + await setTestParams( "Test Note", 1, true ); + generateExpectedBody(); + await vm.insert(); - expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody); + expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); expect(spyFetch).toHaveBeenCalled(); }); }); - - describe('update', () => { - it('should call axios.patch with saveUrl when saveUrl is set and justInput is true', async () => { - generateWrapper({ - url: '/business', - justInput: true, - saveUrl: '/saveUrlTest', - }); - generateExpectedBody(); - - await vm.update(); - - expect(patchMock).toHaveBeenCalledWith(vm.$props.saveUrl, expectedUpdateBody); - }); - - it('should call axios.patch with url when saveUrl is not set and justInput is true', async () => { - generateWrapper({ - url: '/business', - body: { workerFk: 1110 }, - justInput: true, - }); - generateExpectedBody(); - - await vm.update(); - - expect(patchMock).toHaveBeenCalledWith( - `${vm.$props.url}/${vm.$props.body.workerFk}`, - expectedUpdateBody, - ); - }); - }); -}); +}); \ No newline at end of file diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index e6e7e6fa0f1..43dc15e9bfc 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -6,7 +6,6 @@ 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({ @@ -30,6 +29,10 @@ const $props = defineProps({ type: String, default: null, }, + module: { + type: String, + default: null, + }, summary: { type: Object, default: null, @@ -43,7 +46,6 @@ const $props = defineProps({ const state = useState(); const route = useRoute(); const { t } = useI18n(); -const { copyText } = useClipboard(); const { viewSummary } = useSummaryDialog(); let arrayData; let store; @@ -55,13 +57,12 @@ defineExpose({ getData }); onBeforeMount(async () => { arrayData = useArrayData($props.dataKey, { url: $props.url, - userFilter: $props.filter, + filter: $props.filter, skip: 0, - oneRecord: true, }); store = arrayData.store; entity = computed(() => { - const data = store.data ?? {}; + const data = (Array.isArray(store.data) ? store.data[0] : store.data) ?? {}; if (data) emit('onFetch', data); return data; }); @@ -72,7 +73,7 @@ onBeforeMount(async () => { () => [$props.url, $props.filter], async () => { if (!isSameDataKey.value) await getData(); - }, + } ); }); @@ -83,7 +84,7 @@ async function getData() { try { const { data } = await arrayData.fetch({ append: false, updateRouter: false }); state.set($props.dataKey, data); - emit('onFetch', data); + emit('onFetch', Array.isArray(data) ? data[0] : data); } finally { isLoading.value = false; } @@ -101,21 +102,13 @@ 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); const toModule = computed(() => route.matched[1].path.split('/').length > 2 ? route.matched[1].redirect - : route.matched[1].children[0].redirect, + : route.matched[1].children[0].redirect ); </script> @@ -154,9 +147,7 @@ const toModule = computed(() => {{ t('components.smartCard.openSummary') }} </QTooltip> </QBtn> - <RouterLink - :to="{ name: `${dataKey}Summary`, params: { id: entity.id } }" - > + <RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }"> <QBtn class="link" color="white" @@ -192,22 +183,9 @@ const toModule = computed(() => </slot> </div> </QItemLabel> - <QItem> + <QItem dense> <QItemLabel class="subtitle" caption> #{{ getValueFromPath(subtitle) ?? entity.id }} - <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> @@ -315,11 +293,3 @@ const toModule = computed(() => } } </style> -<i18n> - en: - globals: - copyId: Copy ID - es: - globals: - copyId: Copiar ID -</i18n> diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 6a61994c1db..c815b8e1602 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -40,10 +40,9 @@ const arrayData = useArrayData(props.dataKey, { filter: props.filter, userFilter: props.userFilter, skip: 0, - oneRecord: true, }); const { store } = arrayData; -const entity = computed(() => store.data); +const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data)); const isLoading = ref(false); defineExpose({ @@ -62,7 +61,7 @@ async function fetch() { store.filter = props.filter ?? {}; isLoading.value = true; const { data } = await arrayData.fetch({ append: false, updateRouter: false }); - emit('onFetch', data); + emit('onFetch', Array.isArray(data) ? data[0] : data); isLoading.value = false; } </script> @@ -209,13 +208,4 @@ async function fetch() { .summaryHeader { color: $white; } - -.cardSummary :deep(.q-card__section[content]) { - display: flex; - flex-wrap: wrap; - padding: 0; - > * { - flex: 1; - } -} </style> diff --git a/src/components/ui/SkeletonDescriptor.vue b/src/components/ui/SkeletonDescriptor.vue index f9188221a22..9679751f568 100644 --- a/src/components/ui/SkeletonDescriptor.vue +++ b/src/components/ui/SkeletonDescriptor.vue @@ -1,32 +1,53 @@ -<script setup> -defineProps({ - hasImage: { - type: Boolean, - default: false, - }, -}); -</script> <template> - <div id="descriptor-skeleton" class="bg-vn-page"> + <div id="descriptor-skeleton"> <div class="row justify-between q-pa-sm"> - <QSkeleton square size="30px" v-for="i in 3" :key="i" /> + <QSkeleton square size="40px" /> + <QSkeleton square size="40px" /> + <QSkeleton square height="40px" width="20px" /> </div> - <div class="q-pa-xs" v-if="hasImage"> - <QSkeleton square height="200px" width="100%" /> + <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> - <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 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> </div> - <QCardActions class="q-gutter-x-sm justify-between"> - <QSkeleton size="40px" v-for="i in 5" :key="i" /> + <QCardActions> + <QSkeleton size="40px" /> + <QSkeleton size="40px" /> + <QSkeleton size="40px" /> + <QSkeleton size="40px" /> + <QSkeleton size="40px" /> </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 c6f539879e1..a02b56bdb4e 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" data-cy="VnConfirm_message"> + <QCardSection class="q-pb-none"> <span v-if="message !== false" v-html="message" /> </QCardSection> <QCardSection class="row items-center q-pt-none"> @@ -95,7 +95,6 @@ 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 d6b525dc86b..93f069cc6c8 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -114,7 +114,7 @@ async function clearFilters() { arrayData.resetPagination(); // Filtrar los params no removibles const removableFilters = Object.keys(userParams.value).filter((param) => - $props.unremovableParams.includes(param), + $props.unremovableParams.includes(param) ); const newParams = {}; // Conservar solo los params que no son removibles @@ -162,13 +162,13 @@ const formatTags = (tags) => { const tags = computed(() => { const filteredTags = tagsList.value.filter( - (tag) => !($props.customTags || []).includes(tag.label), + (tag) => !($props.customTags || []).includes(tag.label) ); return formatTags(filteredTags); }); const customTags = computed(() => - tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)), + tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)) ); async function remove(key) { @@ -188,13 +188,10 @@ function formatValue(value) { const getLocale = (label) => { const param = label.split('.').at(-1); const globalLocale = `globals.params.${param}`; - const moduleName = route.meta.moduleName; - const moduleLocale = `${moduleName.toLowerCase()}.${param}`; if (te(globalLocale)) return t(globalLocale); - else if (te(moduleLocale)) return t(moduleLocale); + else if (te(t(`params.${param}`))); else { - const camelCaseModuleName = - moduleName.charAt(0).toLowerCase() + moduleName.slice(1); + const camelCaseModuleName = route.meta.moduleName.charAt(0).toLowerCase() + route.meta.moduleName.slice(1); return t(`${camelCaseModuleName}.params.${param}`); } }; @@ -293,9 +290,6 @@ 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 8a1c7a0f2ec..39e84be2bab 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" data-cy="descriptor-more-opts-menu"> + <QMenu ref="menuRef"> <QList> <slot name="menu" :menu-ref="$refs.menuRef" /> </QList> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index ec6289a67ca..1690a94ba11 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { ref, reactive, useAttrs, computed } from 'vue'; +import { ref, reactive } from 'vue'; import { onBeforeRouteLeave } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -16,27 +16,12 @@ import VnSelect from 'components/common/VnSelect.vue'; import FetchData from 'components/FetchData.vue'; import VnInput from 'components/common/VnInput.vue'; -const emit = defineEmits(['onFetch']); - -const originalAttrs = useAttrs(); - -const $attrs = computed(() => { - const { style, ...rest } = originalAttrs; - return rest; -}); - -const isRequired = computed(() => { - return Object.keys($attrs).includes('required') -}); - const $props = defineProps({ url: { type: String, default: null }, - saveUrl: {type: String, default: null}, filter: { type: Object, default: () => {} }, body: { type: Object, default: () => {} }, addNote: { type: Boolean, default: false }, selectType: { type: Boolean, default: false }, - justInput: { type: Boolean, default: false }, }); const { t } = useI18n(); @@ -44,13 +29,6 @@ const quasar = useQuasar(); const newNote = reactive({ text: null, observationTypeFk: null }); const observationTypes = ref([]); const vnPaginateRef = ref(); -let originalText; - -function handleClick(e) { - if (e.shiftKey && e.key === 'Enter') return; - if ($props.justInput) confirmAndUpdate(); - else insert(); -} async function insert() { if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return; @@ -63,36 +41,8 @@ async function insert() { await axios.post($props.url, newBody); await vnPaginateRef.value.fetch(); } - -function confirmAndUpdate() { - if(!newNote.text && originalText) - quasar - .dialog({ - component: VnConfirm, - componentProps: { - title: t('New note is empty'), - message: t('Are you sure remove this note?'), - }, - }) - .onOk(update) - .onCancel(() => { - newNote.text = originalText; - }); - else update(); -} - -async function update() { - originalText = newNote.text; - const body = $props.body; - const newBody = { - ...body, - ...{ notes: newNote.text }, - }; - await axios.patch(`${$props.saveUrl ?? `${$props.url}/${$props.body.workerFk}`}`, newBody); -} - onBeforeRouteLeave((to, from, next) => { - if ((newNote.text && !$props.justInput) || (newNote.text !== originalText) && $props.justInput) + if (newNote.text) quasar.dialog({ component: VnConfirm, componentProps: { @@ -103,13 +53,6 @@ onBeforeRouteLeave((to, from, next) => { }); else next(); }); - -function fetchData([ data ]) { - newNote.text = data?.notes; - originalText = data?.notes; - emit('onFetch', data); -} - </script> <template> <FetchData @@ -119,19 +62,8 @@ function fetchData([ data ]) { auto-load @on-fetch="(data) => (observationTypes = data)" /> - <FetchData - v-if="justInput" - :url="url" - :filter="filter" - @on-fetch="fetchData" - auto-load - /> - <QCard - class="q-pa-xs q-mb-lg full-width" - :class="{ 'just-input': $props.justInput }" - v-if="$props.addNote || $props.justInput" - > - <QCardSection horizontal v-if="!$props.justInput"> + <QCard class="q-pa-xs q-mb-lg full-width" v-if="$props.addNote"> + <QCardSection horizontal> {{ t('New note') }} </QCardSection> <QCardSection class="q-px-xs q-my-none q-py-none"> @@ -143,19 +75,19 @@ function fetchData([ data ]) { v-model="newNote.observationTypeFk" option-label="description" style="flex: 0.15" - :required="isRequired" + :required="true" @keyup.enter.stop="insert" /> <VnInput v-model.trim="newNote.text" type="textarea" - :label="$props.justInput && newNote.text ? '' : t('Add note here...')" + :label="t('Add note here...')" filled size="lg" autogrow - @keyup.enter.stop="handleClick" - :required="isRequired" + @keyup.enter.stop="insert" clearable + :required="true" > <template #append> <QBtn @@ -163,7 +95,7 @@ function fetchData([ data ]) { icon="save" color="primary" flat - @click="handleClick" + @click="insert" class="q-mb-xs" dense data-cy="saveNote" @@ -174,7 +106,6 @@ function fetchData([ data ]) { </QCardSection> </QCard> <VnPaginate - v-if="!$props.justInput" :data-key="$props.url" :url="$props.url" order="created DESC" @@ -267,11 +198,6 @@ function fetchData([ data ]) { } } } -.just-input { - padding-right: 18px; - margin-bottom: 2px; - box-shadow: none; -} </style> <i18n> es: @@ -279,6 +205,4 @@ function fetchData([ data ]) { New note: Nueva nota Save (Enter): Guardar (Intro) Observation type: Tipo de observación - New note is empty: La nueva nota esta vacia - Are you sure remove this note?: Estas seguro de quitar esta nota? </i18n> diff --git a/src/components/ui/VnStockValueDisplay.vue b/src/components/ui/VnStockValueDisplay.vue deleted file mode 100644 index d8f43323bb0..00000000000 --- a/src/components/ui/VnStockValueDisplay.vue +++ /dev/null @@ -1,41 +0,0 @@ -<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/VnSubToolbar.vue b/src/components/ui/VnSubToolbar.vue index 8d4126d1d53..5ded4be001e 100644 --- a/src/components/ui/VnSubToolbar.vue +++ b/src/components/ui/VnSubToolbar.vue @@ -19,26 +19,23 @@ onMounted(() => { const observer = new MutationObserver( () => (hasContent.value = - actions.value?.childNodes?.length + data.value?.childNodes?.length), + actions.value?.childNodes?.length + data.value?.childNodes?.length) ); if (actions.value) observer.observe(actions.value, opts); if (data.value) observer.observe(data.value, opts); }); -const actionsChildCount = () => !!actions.value?.childNodes?.length; - -onBeforeUnmount(() => stateStore.toggleSubToolbar() && hasSubToolbar); +onBeforeUnmount(() => stateStore.toggleSubToolbar()); </script> <template> <QToolbar id="subToolbar" - v-show="hasContent || $slots['st-actions'] || $slots['st-data']" class="justify-end sticky" + v-show="hasContent || $slots['st-actions'] || $slots['st-data']" > <slot name="st-data"> - <div id="st-data" :class="{ 'full-width': !actionsChildCount() }"> - </div> + <div id="st-data"></div> </slot> <QSpace /> <slot name="st-actions"> diff --git a/src/components/ui/__tests__/CardSummary.spec.js b/src/components/ui/__tests__/CardSummary.spec.js index 2f7f90882fa..411ebf9bb60 100644 --- a/src/components/ui/__tests__/CardSummary.spec.js +++ b/src/components/ui/__tests__/CardSummary.spec.js @@ -51,6 +51,16 @@ describe('CardSummary', () => { expect(vm.store.filter).toEqual('cardFilter'); }); + it('should compute entity correctly from store data', () => { + vm.store.data = [{ id: 1, name: 'Entity 1' }]; + expect(vm.entity).toEqual({ id: 1, name: 'Entity 1' }); + }); + + it('should handle empty data gracefully', () => { + vm.store.data = []; + expect(vm.entity).toBeUndefined(); + }); + it('should respond to prop changes and refetch data', async () => { const newUrl = 'CardSummary/35'; const newKey = 'cardSummaryKey/35'; @@ -62,7 +72,7 @@ describe('CardSummary', () => { expect(vm.store.filter).toEqual({ key: newKey }); }); - it('should return true if route path ends with /summary', () => { + it('should return true if route path ends with /summary' , () => { expect(vm.isSummary).toBe(true); }); -}); +}); \ No newline at end of file diff --git a/src/composables/__tests__/useArrayData.spec.js b/src/composables/__tests__/useArrayData.spec.js index a610ba9eb79..d4c5d094954 100644 --- a/src/composables/__tests__/useArrayData.spec.js +++ b/src/composables/__tests__/useArrayData.spec.js @@ -16,7 +16,7 @@ describe('useArrayData', () => { vi.clearAllMocks(); }); - it('should fetch and replace url with new params', async () => { + it('should fetch and repalce url with new params', async () => { vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); @@ -33,11 +33,11 @@ describe('useArrayData', () => { }); expect(routerReplace.path).toEqual('mockSection/list'); expect(JSON.parse(routerReplace.query.params)).toEqual( - expect.objectContaining(params), + expect.objectContaining(params) ); }); - it('should get data and send new URL without keeping parameters, if there is only one record', async () => { + it('Should get data and send new URL without keeping parameters, if there is only one record', async () => { vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] }); const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); @@ -56,7 +56,7 @@ describe('useArrayData', () => { expect(routerPush.query).toBeUndefined(); }); - it('should get data and send new URL keeping parameters, if you have more than one record', async () => { + it('Should get data and send new URL keeping parameters, if you have more than one record', async () => { vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] }); vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ @@ -95,25 +95,4 @@ describe('useArrayData', () => { expect(routerPush.path).toEqual('mockName/'); expect(routerPush.query.params).toBeDefined(); }); - - it('should return one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ - data: [ - { id: 1, name: 'Entity 1' }, - { id: 2, name: 'Entity 2' }, - ], - }); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', oneRecord: true }); - await arrayData.fetch({}); - - expect(arrayData.store.data).toEqual({ id: 1, name: 'Entity 1' }); - }); - - it('should handle empty data gracefully if has to return one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', oneRecord: true }); - await arrayData.fetch({}); - - expect(arrayData.store.data).toBeUndefined(); - }); }); diff --git a/src/composables/checkEntryLock.js b/src/composables/checkEntryLock.js deleted file mode 100644 index f964dea277f..00000000000 --- a/src/composables/checkEntryLock.js +++ /dev/null @@ -1,65 +0,0 @@ -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 deleted file mode 100644 index a930fd7d86c..00000000000 --- a/src/composables/getColAlign.js +++ /dev/null @@ -1,22 +0,0 @@ -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/useArrayData.js b/src/composables/useArrayData.js index fcc61972a9c..bd3cecf08d6 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -57,7 +57,6 @@ export function useArrayData(key, userOptions) { 'navigate', 'mapKey', 'keepData', - 'oneRecord', ]; if (typeof userOptions === 'object') { for (const option in userOptions) { @@ -113,11 +112,7 @@ export function useArrayData(key, userOptions) { store.isLoading = false; canceller = null; - processData(response.data, { - map: !!store.mapKey, - append, - oneRecord: store.oneRecord, - }); + processData(response.data, { map: !!store.mapKey, append }); return response; } @@ -319,11 +314,7 @@ export function useArrayData(key, userOptions) { return { params, limit }; } - function processData(data, { map = true, append = true, oneRecord = false }) { - if (oneRecord) { - store.data = Array.isArray(data) ? data[0] : data; - return; - } + function processData(data, { map = true, append = true }) { if (!append) { store.data = []; store.map = new Map(); diff --git a/src/composables/useRole.js b/src/composables/useRole.js index ff54b409cda..3ec65dd0af9 100644 --- a/src/composables/useRole.js +++ b/src/composables/useRole.js @@ -27,15 +27,6 @@ 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']); } @@ -44,7 +35,6 @@ export function useRole() { isEmployee, fetch, hasAny, - likeAny, state, }; } diff --git a/src/css/app.scss b/src/css/app.scss index 0c5dc97fa7e..7296b079f3c 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -21,10 +21,7 @@ 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; @@ -40,8 +37,6 @@ body.body--dark { --vn-text-color-contrast: black; background-color: var(--vn-page-color); - - --vn-color-negative: $negative; } a { @@ -80,6 +75,7 @@ a { text-decoration: underline; } +// Removes chrome autofill background input:-webkit-autofill, select:-webkit-autofill { color: var(--vn-text-color); @@ -153,6 +149,11 @@ 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: ' *'; @@ -211,10 +212,6 @@ select:-webkit-autofill { justify-content: center; } -.q-card__section[dense] { - padding: 0; -} - input[type='number'] { -moz-appearance: textfield; } @@ -229,12 +226,10 @@ input::-webkit-inner-spin-button { max-width: 100%; } -.remove-bg { - filter: brightness(1.1); - mix-blend-mode: multiply; -} - .q-table__container { + /* ===== Scrollbar CSS ===== / + / Firefox */ + * { scrollbar-width: auto; scrollbar-color: var(--vn-label-color) transparent; @@ -275,6 +270,8 @@ input::-webkit-inner-spin-button { font-size: 11pt; } td { + font-size: 11pt; + border-top: 1px solid var(--vn-page-color); border-collapse: collapse; } } @@ -318,6 +315,9 @@ 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; diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index 22c6d2b56d3..d6e99243769 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: #89be34; +$secondary: $primary; $positive: #c8e484; $negative: #fb5252; $info: #84d0e2; @@ -30,9 +30,7 @@ $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 002797af528..8fe8f383662 100644 --- a/src/filters/toDate.js +++ b/src/filters/toDate.js @@ -3,8 +3,6 @@ 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'; @@ -12,12 +10,7 @@ export default function (value, options = {}) { } const { locale } = useI18n(); - const newDate = new Date(value); + const date = new Date(value); - 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()); + return new Intl.DateTimeFormat(locale.value, options).format(date); } diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 9a60e9da138..7d0f3e0b24b 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -33,7 +33,6 @@ globals: reset: Reset close: Close cancel: Cancel - isSaveAndContinue: Save and continue clone: Clone confirm: Confirm assign: Assign @@ -157,7 +156,6 @@ globals: changeState: Change state raid: 'Raid {daysInForward} days' isVies: Vies - noData: No data available pageTitles: logIn: Login addressEdit: Update address @@ -170,7 +168,6 @@ globals: workCenters: Work centers modes: Modes zones: Zones - negative: Negative zonesList: List deliveryDays: Delivery days upcomingDeliveries: Upcoming deliveries @@ -178,7 +175,6 @@ globals: alias: Alias aliasUsers: Users subRoles: Subroles - myAccount: Mi cuenta inheritedRoles: Inherited Roles customers: Customers customerCreate: New customer @@ -337,13 +333,10 @@ globals: wasteRecalc: Waste recaclulate operator: Operator parking: Parking - vehicleList: Vehicles - vehicle: Vehicle unsavedPopup: title: Unsaved changes will be lost subtitle: Are you sure exit without saving? params: - description: Description clientFk: Client id salesPersonFk: Sales person warehouseFk: Warehouse @@ -366,13 +359,7 @@ globals: correctingFk: Rectificative daysOnward: Days onward countryFk: Country - countryCodeFk: Country companyFk: Company - model: Model - fuel: Fuel - active: Active - inactive: Inactive - deliveryPoint: Delivery point errors: statusUnauthorized: Access denied statusInternalServerError: An internal server error has ocurred @@ -411,106 +398,6 @@ 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 @@ -740,8 +627,6 @@ wagon: name: Name supplier: - search: Search supplier - searchInfo: Search supplier by id or name list: payMethod: Pay method account: Account @@ -831,8 +716,6 @@ 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 846c442ea51..7ca9e4b4cac 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -33,11 +33,9 @@ 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 @@ -50,7 +48,6 @@ 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 @@ -59,8 +56,8 @@ globals: today: Hoy yesterday: Ayer dateFormat: es-ES - noSelectedRows: No tienes ninguna línea seleccionada microsip: Abrir en MicroSIP + noSelectedRows: No tienes ninguna línea seleccionada downloadCSVSuccess: Descarga de CSV exitosa reference: Referencia agency: Agencia @@ -80,10 +77,8 @@ globals: requiredField: Campo obligatorio class: clase type: Tipo - reason: Motivo - removeSelection: Eliminar selección + reason: motivo noResults: Sin resultados - results: resultados system: Sistema notificationSent: Notificación enviada warehouse: Almacén @@ -161,7 +156,6 @@ globals: changeState: Cambiar estado raid: 'Redada {daysInForward} días' isVies: Vies - noData: Datos no disponibles pageTitles: logIn: Inicio de sesión addressEdit: Modificar consignatario @@ -173,7 +167,6 @@ globals: agency: Agencia workCenters: Centros de trabajo modes: Modos - negative: Tickets negativos zones: Zonas zonesList: Listado deliveryDays: Días de entrega @@ -294,9 +287,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 @@ -340,13 +333,10 @@ globals: wasteRecalc: Recalcular mermas operator: Operario parking: Parking - vehicleList: Vehículos - vehicle: Vehículo unsavedPopup: title: Los cambios que no haya guardado se perderán subtitle: ¿Seguro que quiere salir sin guardar? params: - description: Descripción clientFk: Id cliente salesPersonFk: Comercial warehouseFk: Almacén @@ -360,14 +350,13 @@ globals: from: Desde to: Hasta supplierFk: Proveedor - supplierRef: Nº factura + supplierRef: Ref. proveedor serial: Serie amount: Importe awbCode: AWB daysOnward: Días adelante packing: ITP countryFk: País - countryCodeFk: País companyFk: Empresa errors: statusUnauthorized: Acceso denegado @@ -405,87 +394,6 @@ 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 @@ -499,38 +407,6 @@ 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 @@ -577,48 +453,6 @@ ticket: consigneeStreet: Dirección create: address: Dirección -invoiceOut: - card: - issued: Fecha emisión - customerCard: Ficha del cliente - ticketList: Listado de tickets - summary: - issued: Fecha - dued: Fecha límite - booked: Contabilizada - taxBreakdown: Desglose impositivo - taxableBase: Base imp. - rate: Tarifa - 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 @@ -629,34 +463,15 @@ order: 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... + issued: Fecha + dued: Fecha límite + booked: Contabilizada + taxBreakdown: Desglose impositivo + taxableBase: Base imp. + rate: Tarifa + fee: Cuota + tickets: Tickets + totalWithVat: Importe department: chat: Chat bossDepartment: Jefe de departamento @@ -817,8 +632,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 @@ -826,8 +641,6 @@ wagon: volume: Volumen name: Nombre supplier: - search: Buscar proveedor - searchInfo: Buscar proveedor por id o nombre list: payMethod: Método de pago account: Cuenta @@ -918,8 +731,6 @@ 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) @@ -968,7 +779,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/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 3ad1c79bc6a..2a84e5aa154 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -2,7 +2,7 @@ import Navbar from 'src/components/NavBar.vue'; </script> <template> - <QLayout view="hHh LpR fFf"> + <QLayout view="hHh LpR fFf" v-shortcut> <Navbar /> <RouterView></RouterView> <QFooter v-if="$q.platform.is.mobile"></QFooter> diff --git a/src/layouts/OutLayout.vue b/src/layouts/OutLayout.vue index eba57c198e4..4ccc6bf9ee9 100644 --- a/src/layouts/OutLayout.vue +++ b/src/layouts/OutLayout.vue @@ -1,12 +1,12 @@ <script setup> import { Dark, Quasar } from 'quasar'; -import { computed, onMounted } from 'vue'; +import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { localeEquivalence } from 'src/i18n/index'; import quasarLang from 'src/utils/quasarLang'; -import { langs } from 'src/boot/defaults/constants.js'; const { t, locale } = useI18n(); + const userLocale = computed({ get() { return locale.value; @@ -28,6 +28,7 @@ const darkMode = computed({ Dark.set(value); }, }); +const langs = ['en', 'es']; </script> <template> diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index 19682286ce5..f6016fb6c18 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -3,7 +3,6 @@ import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import VnSection from 'src/components/common/VnSection.vue'; -import exprBuilder from './Alias/AliasExprBuilder'; const tableRef = ref(); const { t } = useI18n(); @@ -32,6 +31,15 @@ const columns = computed(() => [ create: true, }, ]); + +const exprBuilder = (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? { id: value } + : { alias: { like: `%${value}%` } }; + } +}; </script> <template> diff --git a/src/pages/Account/AccountExprBuilder.js b/src/pages/Account/AccountExprBuilder.js deleted file mode 100644 index 6497a9d30d0..00000000000 --- a/src/pages/Account/AccountExprBuilder.js +++ /dev/null @@ -1,18 +0,0 @@ -export default (param, value) => { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? { id: value } - : { - or: [ - { name: { like: `%${value}%` } }, - { nickname: { like: `%${value}%` } }, - ], - }; - case 'name': - case 'nickname': - return { [param]: { like: `%${value}%` } }; - case 'roleFk': - return { [param]: value }; - } -}; diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 976af1d19f9..ea8daba0d39 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -4,16 +4,15 @@ import { computed, ref } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import exprBuilder from './AccountExprBuilder.js'; -import filter from './Card/AccountFilter.js'; import VnSection from 'src/components/common/VnSection.vue'; import FetchData from 'src/components/FetchData.vue'; import VnInputPassword from 'src/components/common/VnInputPassword.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); -const tableRef = ref(); - +const filter = { + include: { relation: 'role', scope: { fields: ['id', 'name'] } }, +}; const dataKey = 'AccountList'; const roles = ref([]); const columns = computed(() => [ @@ -118,6 +117,25 @@ const columns = computed(() => [ ], }, ]); + +function exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? { id: value } + : { + or: [ + { name: { like: `%${value}%` } }, + { nickname: { like: `%${value}%` } }, + ], + }; + case 'name': + case 'nickname': + return { [param]: { like: `%${value}%` } }; + case 'roleFk': + return { [param]: value }; + } +} </script> <template> <FetchData url="VnRoles" @on-fetch="(data) => (roles = data)" auto-load /> diff --git a/src/pages/Account/Alias/AliasExprBuilder.js b/src/pages/Account/Alias/AliasExprBuilder.js deleted file mode 100644 index f7a5a104c07..00000000000 --- a/src/pages/Account/Alias/AliasExprBuilder.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (param, value) => { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? { id: value } - : { alias: { like: `%${value}%` } }; - } -}; diff --git a/src/pages/Account/Alias/Card/AliasCard.vue b/src/pages/Account/Alias/Card/AliasCard.vue index f37bd7d0f4b..3a814edc051 100644 --- a/src/pages/Account/Alias/Card/AliasCard.vue +++ b/src/pages/Account/Alias/Card/AliasCard.vue @@ -1,13 +1,21 @@ <script setup> +import { useI18n } from 'vue-i18n'; import VnCardBeta from 'components/common/VnCardBeta.vue'; import AliasDescriptor from './AliasDescriptor.vue'; +const { t } = useI18n(); </script> <template> <VnCardBeta data-key="Alias" - url="MailAliases" + base-url="MailAliases" :descriptor="AliasDescriptor" search-data-key="AccountAliasList" + :searchbar-props="{ + url: 'MailAliases', + info: t('mailAlias.searchInfo'), + label: t('mailAlias.search'), + searchUrl: 'table', + }" /> </template> diff --git a/src/pages/Account/Alias/Card/AliasDescriptor.vue b/src/pages/Account/Alias/Card/AliasDescriptor.vue index 671ef7fbcd0..2e01fad016e 100644 --- a/src/pages/Account/Alias/Card/AliasDescriptor.vue +++ b/src/pages/Account/Alias/Card/AliasDescriptor.vue @@ -7,6 +7,7 @@ import { useQuasar } from 'quasar'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -28,6 +29,9 @@ const entityId = computed(() => { return $props.id || route.params.id; }); +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.alias, entity.id)); + const removeAlias = () => { quasar .dialog({ @@ -51,8 +55,11 @@ const removeAlias = () => { <CardDescriptor ref="descriptor" :url="`MailAliases/${entityId}`" - data-key="Alias" - title="alias" + module="Alias" + @on-fetch="setData" + data-key="aliasData" + :title="data.title" + :subtitle="data.subtitle" > <template #menu> <QItem v-ripple clickable @click="removeAlias()"> diff --git a/src/pages/Account/Alias/Card/AliasSummary.vue b/src/pages/Account/Alias/Card/AliasSummary.vue index b4b9abd25a0..1f76fe7c25c 100644 --- a/src/pages/Account/Alias/Card/AliasSummary.vue +++ b/src/pages/Account/Alias/Card/AliasSummary.vue @@ -1,11 +1,13 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import { useArrayData } from 'src/composables/useArrayData'; + const route = useRoute(); const { t } = useI18n(); @@ -16,15 +18,20 @@ const $props = defineProps({ }, }); +const { store } = useArrayData('Alias'); +const alias = ref(store.data); const entityId = computed(() => $props.id || route.params.id); </script> <template> - <CardSummary ref="summary" :url="`MailAliases/${entityId}`" data-key="Alias"> - <template #header="{ entity: alias }"> - {{ alias.id }} - {{ alias.alias }} - </template> - <template #body="{ entity: alias }"> + <CardSummary + ref="summary" + :url="`MailAliases/${entityId}`" + @on-fetch="(data) => (alias = data)" + data-key="MailAliasesSummary" + > + <template #header> {{ alias.id }} - {{ alias.alias }} </template> + <template #body> <QCard class="vn-one"> <QCardSection class="q-pa-none"> <router-link diff --git a/src/pages/Account/Card/AccountBasicData.vue b/src/pages/Account/Card/AccountBasicData.vue index 393f9eb801d..e6c9da6fe74 100644 --- a/src/pages/Account/Card/AccountBasicData.vue +++ b/src/pages/Account/Card/AccountBasicData.vue @@ -1,20 +1,46 @@ <script setup> +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import FormModel from 'components/FormModel.vue'; import VnInput from 'src/components/common/VnInput.vue'; +import { ref, watch } from 'vue'; + +const route = useRoute(); +const { t } = useI18n(); +const formModelRef = ref(null); + +const accountFilter = { + where: { id: route.params.id }, + fields: ['id', 'email', 'nickname', 'name', 'accountStateFk', 'packages', 'pickup'], + include: [], +}; + +watch( + () => route.params.id, + () => formModelRef.value.reset() +); </script> <template> - <FormModel :url-update="`VnUsers/${$route.params.id}/update-user`" model="Account"> + <FormModel + ref="formModelRef" + url="VnUsers/preview" + :url-update="`VnUsers/${route.params.id}/update-user`" + :filter="accountFilter" + model="Accounts" + auto-load + @on-data-saved="formModelRef.fetch()" + > <template #form="{ data }"> <div class="q-gutter-y-sm"> - <VnInput v-model="data.name" :label="$t('account.card.nickname')" /> - <VnInput v-model="data.nickname" :label="$t('account.card.alias')" /> - <VnInput v-model="data.email" :label="$t('globals.params.email')" /> + <VnInput v-model="data.name" :label="t('account.card.nickname')" /> + <VnInput v-model="data.nickname" :label="t('account.card.alias')" /> + <VnInput v-model="data.email" :label="t('globals.params.email')" /> <VnSelect url="Languages" v-model="data.lang" - :label="$t('account.card.lang')" + :label="t('account.card.lang')" option-value="code" option-label="code" /> @@ -23,7 +49,7 @@ import VnInput from 'src/components/common/VnInput.vue'; table="user" column="twoFactor" v-model="data.twoFactor" - :label="$t('account.card.twoFactor')" + :label="t('account.card.twoFactor')" option-value="code" option-label="code" /> diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index a5037e30136..35ff7e73229 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -1,14 +1,8 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import AccountDescriptor from './AccountDescriptor.vue'; -import filter from './AccountFilter.js'; </script> + <template> - <VnCardBeta - url="VnUsers/preview" - :id-in-where="true" - data-key="Account" - :descriptor="AccountDescriptor" - :filter="filter" - /> + <VnCardBeta data-key="AccountId" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 49328fe87eb..4e5328de66c 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -1,18 +1,36 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; import VnImg from 'src/components/ui/VnImg.vue'; -import filter from './AccountFilter.js'; import useHasAccount from 'src/composables/useHasAccount.js'; -const $props = defineProps({ id: { type: Number, default: null } }); +const $props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); const route = useRoute(); -const entityId = computed(() => $props.id || route.params.id); +const { t } = useI18n(); +const entityId = computed(() => { + return $props.id || route.params.id; +}); +const data = ref(useCardDescription()); const hasAccount = ref(); +const setData = (entity) => (data.value = useCardDescription(entity.nickname, entity.id)); + +const filter = { + where: { id: entityId }, + fields: ['id', 'nickname', 'name', 'role'], + include: { relation: 'role', scope: { fields: ['id', 'name'] } }, +}; onMounted(async () => { hasAccount.value = await useHasAccount(entityId.value); @@ -23,9 +41,12 @@ onMounted(async () => { <CardDescriptor ref="descriptor" :url="`VnUsers/preview`" - :filter="{ ...filter, where: { id: entityId } }" - data-key="Account" - title="nickname" + :filter="filter" + module="Account" + @on-fetch="setData" + data-key="AccountId" + :title="data.title" + :subtitle="data.subtitle" > <template #menu> <AccountDescriptorMenu :entity-id="entityId" /> @@ -41,7 +62,7 @@ onMounted(async () => { <QIcon name="vn:claims" /> </div> <div class="text-grey-5" style="opacity: 0.4"> - {{ $t('account.imageNotFound') }} + {{ t('account.imageNotFound') }} </div> </div> </div> @@ -49,8 +70,8 @@ onMounted(async () => { </VnImg> </template> <template #body="{ entity }"> - <VnLv :label="$t('account.card.nickname')" :value="entity.name" /> - <VnLv :label="$t('account.card.role')" :value="entity.role?.name" /> + <VnLv :label="t('account.card.nickname')" :value="entity.name" /> + <VnLv :label="t('account.card.role')" :value="entity.role.name" /> </template> <template #actions="{ entity }"> <QCardActions class="q-gutter-x-md"> @@ -63,7 +84,7 @@ onMounted(async () => { size="sm" class="fill-icon" > - <QTooltip>{{ $t('account.card.deactivated') }}</QTooltip> + <QTooltip>{{ t('account.card.deactivated') }}</QTooltip> </QIcon> <QIcon color="primary" @@ -74,7 +95,7 @@ onMounted(async () => { size="sm" class="fill-icon" > - <QTooltip>{{ $t('account.card.enabled') }}</QTooltip> + <QTooltip>{{ t('account.card.enabled') }}</QTooltip> </QIcon> </QCardActions> </template> diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index 30584c61f46..961323d3a73 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -12,7 +12,6 @@ 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: { @@ -30,7 +29,7 @@ const router = useRouter(); const state = useState(); const user = state.getUser(); const { notify } = useQuasar(); -const account = computed(() => useArrayData('Account').store.data[0]); +const account = computed(() => useArrayData('AccountId').store.data[0]); account.value.hasAccount = hasAccount.value; const entityId = computed(() => +route.params.id); const hasitManagementAccess = ref(); @@ -125,14 +124,18 @@ onMounted(() => { :promise="sync" > <template #customHTML> - <VnCheckbox - v-model="shouldSyncPassword" + {{ shouldSyncPassword }} + <QCheckbox :label="t('account.card.actions.sync.checkbox')" - :info="t('account.card.actions.sync.tooltip')" + v-model="shouldSyncPassword" + class="full-width" clearable clear-icon="close" - color="primary" - /> + > + <QIcon style="padding-left: 10px" color="primary" name="info" size="sm"> + <QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip> + </QIcon></QCheckbox + > <VnInputPassword v-if="shouldSyncPassword" :label="t('login.password')" @@ -152,7 +155,7 @@ onMounted(() => { openConfirmationModal( t('account.card.actions.disableAccount.title'), t('account.card.actions.disableAccount.subtitle'), - () => deleteAccount(), + () => deleteAccount() ) " > @@ -171,7 +174,7 @@ onMounted(() => { openConfirmationModal( t('account.card.actions.enableAccount.title'), t('account.card.actions.enableAccount.subtitle'), - () => updateStatusAccount(true), + () => updateStatusAccount(true) ) " > @@ -185,7 +188,7 @@ onMounted(() => { openConfirmationModal( t('account.card.actions.disableAccount.title'), t('account.card.actions.disableAccount.subtitle'), - () => updateStatusAccount(false), + () => updateStatusAccount(false) ) " > @@ -200,7 +203,7 @@ onMounted(() => { openConfirmationModal( t('account.card.actions.activateUser.title'), t('account.card.actions.activateUser.title'), - () => updateStatusUser(true), + () => updateStatusUser(true) ) " > @@ -214,7 +217,7 @@ onMounted(() => { openConfirmationModal( t('account.card.actions.deactivateUser.title'), t('account.card.actions.deactivateUser.title'), - () => updateStatusUser(false), + () => updateStatusUser(false) ) " > diff --git a/src/pages/Account/Card/AccountFilter.js b/src/pages/Account/Card/AccountFilter.js deleted file mode 100644 index 01787656436..00000000000 --- a/src/pages/Account/Card/AccountFilter.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - include: { relation: 'role', scope: { fields: ['id', 'name'] } }, -}; diff --git a/src/pages/Account/Card/AccountMailAlias.vue b/src/pages/Account/Card/AccountMailAlias.vue index 7a060cff180..ef1707cf2ee 100644 --- a/src/pages/Account/Card/AccountMailAlias.vue +++ b/src/pages/Account/Card/AccountMailAlias.vue @@ -86,7 +86,7 @@ watch( () => route.params.id, () => { getAccountData(); - }, + } ); onMounted(async () => await getAccountData(false)); @@ -130,8 +130,7 @@ onMounted(async () => await getAccountData(false)); openConfirmationModal( t('User will be removed from alias'), t('¿Seguro que quieres continuar?'), - () => - deleteMailAlias(row, rows, rowIndex), + () => deleteMailAlias(row, rows, rowIndex) ) " > @@ -158,7 +157,7 @@ onMounted(async () => await getAccountData(false)); icon="add" color="primary" @click="openCreateMailAliasForm()" - v-shortcut="'+'" + shortcut="+" > <QTooltip>{{ t('warehouses.add') }}</QTooltip> </QBtn> diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index f7a16e8c355..ca17c7975bb 100644 --- a/src/pages/Account/Card/AccountSummary.vue +++ b/src/pages/Account/Card/AccountSummary.vue @@ -1,41 +1,58 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; -import filter from './AccountFilter.js'; + +import { useArrayData } from 'src/composables/useArrayData'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; -const $props = defineProps({ id: { type: Number, default: 0 } }); - const route = useRoute(); +const { t } = useI18n(); + +const $props = defineProps({ + id: { + type: Number, + default: 0, + }, +}); +const { store } = useArrayData('Account'); +const account = ref(store.data); + const entityId = computed(() => $props.id || route.params.id); +const filter = { + where: { id: entityId }, + fields: ['id', 'nickname', 'name', 'role'], + include: { relation: 'role', scope: { fields: ['id', 'name'] } }, +}; </script> <template> <CardSummary - data-key="Account" - ref="AccountSummary" + data-key="AccountId" url="VnUsers/preview" :filter="filter" + @on-fetch="(data) => (account = data)" > - <template #header="{ entity }">{{ entity.id }} - {{ entity.nickname }}</template> - <template #menu> + <template #header>{{ account.id }} - {{ account.nickname }}</template> + <template #menu=""> <AccountDescriptorMenu :entity-id="entityId" /> </template> - <template #body="{ entity }"> + <template #body> <QCard class="vn-one"> <QCardSection class="q-pa-none"> <router-link :to="{ name: 'AccountBasicData', params: { id: entityId } }" class="header header-link" > - {{ $t('globals.pageTitles.basicData') }} + {{ t('globals.pageTitles.basicData') }} <QIcon name="open_in_new" /> </router-link> </QCardSection> - <VnLv :label="$t('account.card.nickname')" :value="entity.name" /> - <VnLv :label="$t('account.card.role')" :value="entity.role?.name" /> + <VnLv :label="t('account.card.nickname')" :value="account.name" /> + <VnLv :label="t('account.card.role')" :value="account.role.name" /> </QCard> </template> </CardSummary> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 02f5400c628..3c3d6b243fc 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -5,7 +5,6 @@ import VnTable from 'components/VnTable/VnTable.vue'; import { useRoute } from 'vue-router'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; -import exprBuilder from './RoleExprBuilder.js'; import VnSection from 'src/components/common/VnSection.vue'; const route = useRoute(); @@ -67,7 +66,24 @@ const columns = computed(() => [ ], }, ]); +const exprBuilder = (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? { id: value } + : { + or: [ + { name: { like: `%${value}%` } }, + { nickname: { like: `%${value}%` } }, + ], + }; + case 'name': + case 'description': + return { [param]: { like: `%${value}%` } }; + } +}; </script> + <template> <VnSection :data-key="dataKey" diff --git a/src/pages/Account/Role/Card/RoleBasicData.vue b/src/pages/Account/Role/Card/RoleBasicData.vue index de70b0fb695..1de9ff3876f 100644 --- a/src/pages/Account/Role/Card/RoleBasicData.vue +++ b/src/pages/Account/Role/Card/RoleBasicData.vue @@ -1,16 +1,24 @@ <script setup> +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; +const route = useRoute(); +const { t } = useI18n(); </script> <template> - <FormModel model="Role" auto-load> + <FormModel :url="`VnRoles/${route.params.id}`" model="VnRole" auto-load> <template #form="{ data }"> <VnRow> - <VnInput v-model="data.name" :label="$t('globals.name')" /> + <div class="col"> + <VnInput v-model="data.name" :label="t('globals.name')" /> + </div> </VnRow> <VnRow> - <VnInput v-model="data.description" :label="$t('role.description')" /> + <div class="col"> + <VnInput v-model="data.description" :label="t('role.description')" /> + </div> </VnRow> </template> </FormModel> diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue index ef5b9db0491..7664deca8da 100644 --- a/src/pages/Account/Role/Card/RoleCard.vue +++ b/src/pages/Account/Role/Card/RoleCard.vue @@ -3,10 +3,5 @@ import VnCardBeta from 'components/common/VnCardBeta.vue'; import RoleDescriptor from './RoleDescriptor.vue'; </script> <template> - <VnCardBeta - url="VnRoles" - data-key="Role" - :id-in-where="true" - :descriptor="RoleDescriptor" - /> + <VnCardBeta data-key="Role" :descriptor="RoleDescriptor" /> </template> diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index 517517af0dd..0a555346d9f 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -1,9 +1,10 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; const $props = defineProps({ @@ -25,6 +26,11 @@ const { t } = useI18n(); const entityId = computed(() => { return $props.id || route.params.id; }); +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.name, entity.id)); +const filter = { + where: { id: entityId }, +}; const removeRole = async () => { await axios.delete(`VnRoles/${entityId.value}`); notify(t('Role removed'), 'positive'); @@ -33,9 +39,13 @@ const removeRole = async () => { <template> <CardDescriptor - url="VnRoles" - :filter="{ where: { id: entityId } }" + :url="`VnRoles/${entityId}`" + :filter="filter" + module="Role" + @on-fetch="setData" data-key="Role" + :title="data.title" + :subtitle="data.subtitle" :summary="$props.summary" > <template #menu> diff --git a/src/pages/Account/Role/Card/RoleSummary.vue b/src/pages/Account/Role/Card/RoleSummary.vue index 410f90b17cc..f0daa77fb8f 100644 --- a/src/pages/Account/Role/Card/RoleSummary.vue +++ b/src/pages/Account/Role/Card/RoleSummary.vue @@ -1,9 +1,10 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import { useArrayData } from 'src/composables/useArrayData'; const route = useRoute(); const { t } = useI18n(); @@ -15,18 +16,24 @@ const $props = defineProps({ }, }); +const { store } = useArrayData('Role'); +const role = ref(store.data); const entityId = computed(() => $props.id || route.params.id); +const filter = { + where: { id: entityId }, +}; </script> <template> <CardSummary ref="summary" - url="VnRoles" - :filter="{ where: { id: entityId } }" + :url="`VnRoles/${entityId}`" + :filter="filter" + @on-fetch="(data) => (role = data)" data-key="Role" > - <template #header="{ entity }"> {{ entity.id }} - {{ entity.name }} </template> - <template #body="{ entity }"> + <template #header> {{ role.id }} - {{ role.name }} </template> + <template #body> <QCard class="vn-one"> <QCardSection class="q-pa-none"> <a @@ -37,9 +44,9 @@ const entityId = computed(() => $props.id || route.params.id); <QIcon name="open_in_new" /> </a> </QCardSection> - <VnLv :label="t('role.id')" :value="entity.id" /> - <VnLv :label="t('globals.name')" :value="entity.name" /> - <VnLv :label="t('role.description')" :value="entity.description" /> + <VnLv :label="t('role.id')" :value="role.id" /> + <VnLv :label="t('globals.name')" :value="role.name" /> + <VnLv :label="t('role.description')" :value="role.description" /> </QCard> </template> </CardSummary> diff --git a/src/pages/Account/Role/Card/SubRoles.vue b/src/pages/Account/Role/Card/SubRoles.vue index 99cf5e8f0e0..0077f12b06a 100644 --- a/src/pages/Account/Role/Card/SubRoles.vue +++ b/src/pages/Account/Role/Card/SubRoles.vue @@ -63,7 +63,7 @@ watch( store.url = urlPath.value; store.filter = filter.value; fetchSubRoles(); - }, + } ); const fetchSubRoles = () => paginateRef.value.fetch(); @@ -109,7 +109,7 @@ const redirectToRoleSummary = (id) => openConfirmationModal( t('El rol va a ser eliminado'), t('¿Seguro que quieres continuar?'), - () => deleteSubRole(row, rows, rowIndex), + () => deleteSubRole(row, rows, rowIndex) ) " > @@ -131,7 +131,7 @@ const redirectToRoleSummary = (id) => <QBtn fab icon="add" - v-shortcut="'+'" + shortcut="+" color="primary" @click="openCreateSubRoleForm()" > diff --git a/src/pages/Account/Role/RoleExprBuilder.js b/src/pages/Account/Role/RoleExprBuilder.js deleted file mode 100644 index cc4fab39983..00000000000 --- a/src/pages/Account/Role/RoleExprBuilder.js +++ /dev/null @@ -1,16 +0,0 @@ -export default (param, value) => { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? { id: value } - : { - or: [ - { name: { like: `%${value}%` } }, - { nickname: { like: `%${value}%` } }, - ], - }; - case 'name': - case 'description': - return { [param]: { like: `%${value}%` } }; - } -}; diff --git a/src/pages/Claim/Card/ClaimBasicData.vue b/src/pages/Claim/Card/ClaimBasicData.vue index 67034da1af8..63b0b7c0d73 100644 --- a/src/pages/Claim/Card/ClaimBasicData.vue +++ b/src/pages/Claim/Card/ClaimBasicData.vue @@ -28,6 +28,7 @@ const workersOptions = ref([]); model="Claim" :url-update="`Claims/updateClaim/${route.params.id}`" auto-load + :reload="true" > <template #form="{ data, validate }"> <VnRow> diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 05f3b53a860..e1e00081571 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -4,11 +4,10 @@ import ClaimDescriptor from './ClaimDescriptor.vue'; import filter from './ClaimFilter.js'; </script> <template> - <VnCardBeta - data-key="Claim" - url="Claims" - :descriptor="ClaimDescriptor" - search-data-key="ClaimList" + <VnCardBeta + data-key="Claim" + base-url="Claims" + :descriptor="ClaimDescriptor" :filter="filter" /> </template> diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index 4551c58fe33..02b63dd8e7e 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -3,10 +3,12 @@ import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { toDateHourMinSec, toPercentage } from 'src/filters'; +import { useState } from 'src/composables/useState'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import { getUrl } from 'src/composables/getUrl'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; @@ -21,6 +23,7 @@ const $props = defineProps({ }); const route = useRoute(); +const state = useState(); const { t } = useI18n(); const salixUrl = ref(); const entityId = computed(() => { @@ -36,7 +39,12 @@ const STATE_COLOR = { function stateColor(code) { return STATE_COLOR[code]; } - +const data = ref(useCardDescription()); +const setData = (entity) => { + if (!entity) return; + data.value = useCardDescription(entity?.client?.name, entity.id); + state.set('ClaimDescriptor', entity); +}; onMounted(async () => { salixUrl.value = await getUrl(''); }); @@ -46,7 +54,9 @@ onMounted(async () => { <CardDescriptor :url="`Claims/${entityId}`" :filter="filter" + module="Claim" title="client.name" + @on-fetch="setData" data-key="Claim" > <template #menu="{ entity }"> @@ -85,7 +95,7 @@ onMounted(async () => { /> </template> </VnLv> - <VnLv v-if="entity.ticket?.zone?.id" :label="t('claim.zone')"> + <VnLv :label="t('claim.zone')"> <template #value> <span class="link"> {{ entity.ticket?.zone?.name }} @@ -97,10 +107,11 @@ onMounted(async () => { :label="t('claim.province')" :value="entity.ticket?.address?.province?.name" /> - <VnLv v-if="entity.ticketFk" :label="t('claim.ticketId')"> + <VnLv :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 dee03b95d4f..33fadd02016 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -317,13 +317,7 @@ async function saveWhenHasChanges() { </div> <QPageSticky position="bottom-right" :offset="[25, 25]"> - <QBtn - fab - color="primary" - v-shortcut="'+'" - icon="add" - @click="showImportDialog()" - /> + <QBtn fab color="primary" shortcut="+" icon="add" @click="showImportDialog()" /> </QPageSticky> </template> diff --git a/src/pages/Claim/Card/ClaimNotes.vue b/src/pages/Claim/Card/ClaimNotes.vue index cc6e33779e3..134ee33abcc 100644 --- a/src/pages/Claim/Card/ClaimNotes.vue +++ b/src/pages/Claim/Card/ClaimNotes.vue @@ -1,5 +1,5 @@ <script setup> -import { computed, useAttrs } from 'vue'; +import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useState } from 'src/composables/useState'; import VnNotes from 'src/components/ui/VnNotes.vue'; @@ -7,7 +7,6 @@ import VnNotes from 'src/components/ui/VnNotes.vue'; const route = useRoute(); const state = useState(); const user = state.getUser(); -const $attrs = useAttrs(); const $props = defineProps({ id: { type: [Number, String], default: null }, diff --git a/src/pages/Claim/Card/ClaimPhoto.vue b/src/pages/Claim/Card/ClaimPhoto.vue index d4acc9bbe27..d4321d8eb82 100644 --- a/src/pages/Claim/Card/ClaimPhoto.vue +++ b/src/pages/Claim/Card/ClaimPhoto.vue @@ -61,7 +61,7 @@ watch( () => { claimDmsFilter.value.where.id = router.currentRoute.value.params.id; claimDmsRef.value.fetch(); - }, + } ); function openDialog(dmsId) { @@ -248,7 +248,7 @@ function onDrag() { <QBtn fab @click="inputFile.nativeEl.click()" - v-shortcut="'+'" + shortcut="+" icon="add" color="primary" > diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index 41d0c559819..63fd035daa5 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -132,7 +132,7 @@ const STATE_COLOR = { prefix="claim" :array-data-props="{ url: 'Claims/filter', - order: 'cs.priority ASC, created ASC', + order: ['cs.priority ASC', 'created ASC'], }" > <template #advanced-menu> diff --git a/src/pages/Customer/Card/CustomerAddress.vue b/src/pages/Customer/Card/CustomerAddress.vue index f1799d0cc93..1b0d1dde11a 100644 --- a/src/pages/Customer/Card/CustomerAddress.vue +++ b/src/pages/Customer/Card/CustomerAddress.vue @@ -61,7 +61,7 @@ watch( (newValue) => { if (!newValue) return; getClientData(newValue); - }, + } ); const getClientData = async (id) => { @@ -137,7 +137,7 @@ const toCustomerAddressEdit = (addressId) => { <QIcon :style="{ 'font-variation-settings': `'FILL' ${isDefaultAddress( - item, + item )}`, }" color="primary" @@ -150,7 +150,7 @@ const toCustomerAddressEdit = (addressId) => { t( isDefaultAddress(item) ? 'Default address' - : 'Set as default', + : 'Set as default' ) }} </QTooltip> @@ -216,7 +216,7 @@ const toCustomerAddressEdit = (addressId) => { color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" /> <QTooltip> {{ t('New consignee') }} diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue index 11db92eab94..04ef5f882ff 100644 --- a/src/pages/Customer/Card/CustomerBalance.vue +++ b/src/pages/Customer/Card/CustomerBalance.vue @@ -158,7 +158,7 @@ const columns = computed(() => [ openConfirmationModal( t('Send compensation'), t('Do you want to report compensation to the client by mail?'), - () => sendEmail(`Receipts/${id}/balance-compensation-email`), + () => sendEmail(`Receipts/${id}/balance-compensation-email`) ), }, ], @@ -291,7 +291,7 @@ const showBalancePdf = ({ id }) => { color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" /> <QTooltip> {{ t('New payment') }} diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue index 36ec4763e87..e9a349e0b94 100644 --- a/src/pages/Customer/Card/CustomerBasicData.vue +++ b/src/pages/Customer/Card/CustomerBasicData.vue @@ -54,10 +54,10 @@ function onBeforeSave(formData, originalData) { auto-load /> <FormModel - :url-update="`Clients/${route.params.id}`" + :url="`Clients/${route.params.id}`" auto-load + model="customer" :mapper="onBeforeSave" - model="Customer" > <template #form="{ data, validate }"> <VnRow> diff --git a/src/pages/Customer/Card/CustomerBillingData.vue b/src/pages/Customer/Card/CustomerBillingData.vue index cc894d01e64..f1e78d9e564 100644 --- a/src/pages/Customer/Card/CustomerBillingData.vue +++ b/src/pages/Customer/Card/CustomerBillingData.vue @@ -27,7 +27,7 @@ const getBankEntities = (data, formData) => { </script> <template> - <FormModel :url-update="`Clients/${route.params.id}`" auto-load model="Customer"> + <FormModel :url-update="`Clients/${route.params.id}`" auto-load model="customer"> <template #form="{ data, validate }"> <VnRow> <VnSelect diff --git a/src/pages/Customer/Card/CustomerCard.vue b/src/pages/Customer/Card/CustomerCard.vue index 75fcb98fa19..f46884834d7 100644 --- a/src/pages/Customer/Card/CustomerCard.vue +++ b/src/pages/Customer/Card/CustomerCard.vue @@ -5,8 +5,8 @@ import CustomerDescriptor from './CustomerDescriptor.vue'; <template> <VnCardBeta - data-key="Customer" - :url="`Clients/${$route.params.id}/getCard`" + data-key="Client" + base-url="Clients" :descriptor="CustomerDescriptor" /> </template> diff --git a/src/pages/Customer/Card/CustomerConsumption.vue b/src/pages/Customer/Card/CustomerConsumption.vue index f3949bb32d7..f0d8dea47ee 100644 --- a/src/pages/Customer/Card/CustomerConsumption.vue +++ b/src/pages/Customer/Card/CustomerConsumption.vue @@ -61,23 +61,6 @@ 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', @@ -91,7 +74,6 @@ const columns = computed(() => [ name: 'quantity', label: t('globals.quantity'), cardVisible: true, - visible: true, columnFilter: { inWhere: true, }, @@ -137,7 +119,7 @@ const openSendEmailDialog = async () => { openConfirmationModal( t('The consumption report will be sent'), t('Please, confirm'), - () => sendCampaignMetricsEmail({ address: arrayData.store.data.email }), + () => sendCampaignMetricsEmail({ address: arrayData.store.data.email }) ); }; const sendCampaignMetricsEmail = ({ address }) => { @@ -156,11 +138,11 @@ const updateDateParams = (value, params) => { const campaign = campaignList.value.find((c) => c.id === value); if (!campaign) return; - const { dated, scopeDays } = campaign; - const from = new Date(dated); - from.setDate(from.getDate() - scopeDays); - params.from = from; - params.to = dated; + 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(); return params; }; </script> @@ -170,7 +152,7 @@ const updateDateParams = (value, params) => { v-if="campaignList" data-key="CustomerConsumption" url="Clients/consumption" - :order="['itemTypeFk', 'itemName', 'itemSize', 'description']" + :order="['itemTypeFk', 'itemName', 'itemSize', 'description']" :filter="{ where: { clientFk: route.params.id } }" :columns="columns" search-url="consumption" @@ -218,60 +200,29 @@ const updateDateParams = (value, params) => { <div v-if="row.subName" class="subName"> {{ row.subName }} </div> - <FetchedTags :item="row" /> + <FetchedTags :item="row" :max-length="3" /> </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" - :option-label="(opt) => t(opt.code)" - :fields="['id', 'code', 'dated', 'scopeDays']" - @update:model-value="(data) => updateDateParams(data, params)" dense + option-label="code" + @update:model-value="(data) => updateDateParams(data, params)" > <template #option="scope"> <QItem v-bind="scope.itemProps"> <QItemSection> - <QItemLabel> {{ t(scope.opt?.code) }} </QItemLabel> - <QItemLabel caption> - {{ new Date(scope.opt?.dated).getFullYear() }} - </QItemLabel> + <QItemLabel> + {{ scope.opt?.code }} + {{ + new Date(scope.opt?.dated).getFullYear() + }}</QItemLabel + > </QItemSection> </QItem> </template> @@ -296,21 +247,7 @@ 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/CustomerContacts.vue b/src/pages/Customer/Card/CustomerContacts.vue index d03f7124466..c420f650e85 100644 --- a/src/pages/Customer/Card/CustomerContacts.vue +++ b/src/pages/Customer/Card/CustomerContacts.vue @@ -62,7 +62,7 @@ const customerContactsRef = ref(null); color="primary" flat icon="add" - v-shortcut="'+'" + shortcut="+" > <QTooltip> {{ t('Add contact') }} diff --git a/src/pages/Customer/Card/CustomerCreditContracts.vue b/src/pages/Customer/Card/CustomerCreditContracts.vue index a49faeb8dc5..7dc53db723f 100644 --- a/src/pages/Customer/Card/CustomerCreditContracts.vue +++ b/src/pages/Customer/Card/CustomerCreditContracts.vue @@ -195,7 +195,7 @@ const updateData = () => { color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" /> <QTooltip> {{ t('New contract') }} diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 89f9d94494f..d7a8a59a140 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; @@ -11,15 +11,6 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; -import { useState } from 'src/composables/useState'; -const state = useState(); - -const customer = ref(); - -onMounted(async () => { - customer.value = state.get('Customer'); - if (customer.value) customer.value.webAccess = data.value?.account?.isActive; -}); const customerDebt = ref(); const customerCredit = ref(); @@ -55,10 +46,13 @@ const debtWarning = computed(() => { <template> <CardDescriptor + module="Customer" :url="`Clients/${entityId}/getCard`" - :summary="$props.summary" - data-key="Customer" + :title="data.title" + :subtitle="data.subtitle" @on-fetch="setData" + :summary="$props.summary" + data-key="customer" width="lg-width" > <template #menu="{ entity }"> @@ -67,7 +61,7 @@ const debtWarning = computed(() => { <template #body="{ entity }"> <VnLv :label="t('customer.summary.payMethod')" - :value="entity.payMethod?.name" + :value="entity.payMethod.name" /> <VnLv @@ -96,7 +90,7 @@ const debtWarning = computed(() => { </VnLv> <VnLv :label="t('customer.extendedList.tableVisibleColumns.businessTypeFk')" - :value="entity.businessType?.description" + :value="entity.businessType.description" /> </template> <template #icons="{ entity }"> @@ -109,21 +103,7 @@ const debtWarning = computed(() => { > <QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip> </QIcon> - - <QIcon - v-if="entity?.substitutionAllowed" - name="help" - size="xs" - color="primary" - > - <QTooltip>{{ t('Allowed substitution') }}</QTooltip> - </QIcon> - <QIcon - v-if="customer?.isFreezed" - name="vn:frozen" - size="xs" - color="primary" - > + <QIcon v-if="entity.isFreezed" name="vn:frozen" size="xs" color="primary"> <QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip> </QIcon> <QIcon @@ -163,13 +143,13 @@ const debtWarning = computed(() => { <br /> {{ t('unpaidDated', { - dated: toDate(customer.unpaid?.dated), + dated: toDate(customer.unpaid.dated), }) }} <br /> {{ t('unpaidAmount', { - amount: toCurrency(customer.unpaid?.amount), + amount: toCurrency(customer.unpaid.amount), }) }} </QTooltip> diff --git a/src/pages/Customer/Card/CustomerDescriptorMenu.vue b/src/pages/Customer/Card/CustomerDescriptorMenu.vue index aea45721c7a..fb78eab69cc 100644 --- a/src/pages/Customer/Card/CustomerDescriptorMenu.vue +++ b/src/pages/Customer/Card/CustomerDescriptorMenu.vue @@ -61,16 +61,6 @@ const openCreateForm = (type) => { .join('&'); useOpenURL(`/#/${type}/list?${params}`); }; -const updateSubstitutionAllowed = async () => { - try { - await axios.patch(`Clients/${route.params.id}`, { - substitutionAllowed: !$props.customer.substitutionAllowed, - }); - notify('globals.notificationSent', 'positive'); - } catch (error) { - notify(error.message, 'positive'); - } -}; </script> <template> @@ -79,13 +69,6 @@ const updateSubstitutionAllowed = async () => { {{ t('globals.pageTitles.createTicket') }} </QItemSection> </QItem> - <QItem v-ripple clickable> - <QItemSection @click="updateSubstitutionAllowed()">{{ - $props.customer.substitutionAllowed - ? t('Disable substitution') - : t('Allow substitution') - }}</QItemSection> - </QItem> <QItem v-ripple clickable> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> </QItem> diff --git a/src/pages/Customer/Card/CustomerFileManagement.vue b/src/pages/Customer/Card/CustomerFileManagement.vue index b565db6e72c..134d8dbd68a 100644 --- a/src/pages/Customer/Card/CustomerFileManagement.vue +++ b/src/pages/Customer/Card/CustomerFileManagement.vue @@ -236,7 +236,7 @@ const toCustomerFileManagementCreate = () => { @click.stop="toCustomerFileManagementCreate()" color="primary" fab - v-shortcut="'+'" + shortcut="+" icon="add" /> <QTooltip> diff --git a/src/pages/Customer/Card/CustomerFiscalData.vue b/src/pages/Customer/Card/CustomerFiscalData.vue index 93909eb9c75..ceeb70bb679 100644 --- a/src/pages/Customer/Card/CustomerFiscalData.vue +++ b/src/pages/Customer/Card/CustomerFiscalData.vue @@ -12,7 +12,6 @@ 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'; @@ -74,7 +73,7 @@ async function acceptPropagate({ isEqualizated }) { <FormModel :url-update="`Clients/${route.params.id}/updateFiscalData`" auto-load - model="Customer" + model="customer" :mapper="onBeforeSave" observe-form-changes @on-data-saved="checkEtChanges" @@ -152,11 +151,14 @@ async function acceptPropagate({ isEqualizated }) { </VnRow> <VnRow> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> - <VnCheckbox - v-model="data.isVies" - :label="t('globals.isVies')" - :info="t('whenActivatingIt')" - /> + <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> </VnRow> <VnRow> @@ -168,11 +170,17 @@ async function acceptPropagate({ isEqualizated }) { </VnRow> <VnRow> - <VnCheckbox - v-model="data.isEqualizated" - :label="t('Is equalizated')" - :info="t('inOrderToInvoice')" - /> + <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> <QCheckbox :label="t('Daily invoice')" v-model="data.hasDailyInvoice" /> </VnRow> diff --git a/src/pages/Customer/Card/CustomerNotes.vue b/src/pages/Customer/Card/CustomerNotes.vue index 189b5990420..b851746963d 100644 --- a/src/pages/Customer/Card/CustomerNotes.vue +++ b/src/pages/Customer/Card/CustomerNotes.vue @@ -23,6 +23,5 @@ const noteFilter = computed(() => { :body="{ clientFk: route.params.id }" style="overflow-y: auto" :select-type="true" - required /> </template> diff --git a/src/pages/Customer/Card/CustomerSamples.vue b/src/pages/Customer/Card/CustomerSamples.vue index 19a7f87594f..f1269111237 100644 --- a/src/pages/Customer/Card/CustomerSamples.vue +++ b/src/pages/Customer/Card/CustomerSamples.vue @@ -104,7 +104,7 @@ const tableRef = ref(); color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" /> <QTooltip> {{ t('Send sample') }} diff --git a/src/pages/Customer/Card/CustomerWebAccess.vue b/src/pages/Customer/Card/CustomerWebAccess.vue index 809f1091831..3c4106846e9 100644 --- a/src/pages/Customer/Card/CustomerWebAccess.vue +++ b/src/pages/Customer/Card/CustomerWebAccess.vue @@ -27,7 +27,7 @@ async function hasCustomerRole() { <FormModel :url-update="`Clients/${route.params.id}/updateUser`" :filter="filter" - model="Customer" + model="customer" :mapper=" ({ account }) => { const { name, email, active } = account; diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 1c5a08304ef..9b883daad31 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -51,7 +51,11 @@ const exprBuilder = (param, value) => { </QItem> <QItem class="q-mb-sm"> <QItemSection> - <VnInput :label="t('Name')" v-model="params.name" is-outlined /> + <VnInput + :label="t('globals.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 0bfca7910a3..2f2dd59789e 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -274,7 +274,6 @@ const columns = computed(() => [ align: 'left', name: 'isActive', label: t('customer.summary.isActive'), - component: 'checkbox', chip: { color: null, condition: (value) => !value, @@ -313,7 +312,6 @@ const columns = computed(() => [ align: 'left', name: 'isFreezed', label: t('customer.extendedList.tableVisibleColumns.isFreezed'), - component: 'checkbox', chip: { color: null, condition: (value) => value, @@ -431,7 +429,7 @@ function handleLocation(data, location) { <VnTable ref="tableRef" :data-key="dataKey" - url="Clients/extendedListFilter" + url="Clients/filter" :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 dc4ac9162a2..eca2ad59637 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/Worker/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import { useArrayData } from 'src/composables/useArrayData'; diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index f852c160ac2..d650bbbda32 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -233,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> @@ -336,7 +336,7 @@ function handleLocation(data, location) { class="cursor-pointer add-icon q-mt-md" flat icon="add" - v-shortcut="'+'" + shortcut="+" > <QTooltip> {{ t('Add note') }} diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index 8f61bac89ec..c2c38b55a74 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 + :prevent-submit="true" > <template #form="{ data, validate }"> <span ref="closeButton" class="row justify-end close-icon" v-close-popup> diff --git a/src/pages/Customer/components/CustomerSamplesCreate.vue b/src/pages/Customer/components/CustomerSamplesCreate.vue index 1294a5d2554..754693672d3 100644 --- a/src/pages/Customer/components/CustomerSamplesCreate.vue +++ b/src/pages/Customer/components/CustomerSamplesCreate.vue @@ -18,7 +18,6 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import CustomerSamplesPreview from 'src/pages/Customer/components/CustomerSamplesPreview.vue'; import FormPopup from 'src/components/FormPopup.vue'; -import { useArrayData } from 'src/composables/useArrayData'; const { dialogRef, onDialogOK } = useDialogPluginComponent(); @@ -40,7 +39,7 @@ const optionsSamplesVisible = ref([]); const sampleType = ref({ hasPreview: false }); const initialData = reactive({}); const entityId = computed(() => route.params.id); -const customer = computed(() => useArrayData('Customer').store?.data); +const customer = computed(() => state.get('customer')); const filterEmailUsers = { where: { userFk: user.value.id } }; const filterClientsAddresses = { include: [ @@ -66,9 +65,9 @@ const filterSamplesVisible = { defineEmits(['confirm', ...useDialogPluginComponent.emits]); onBeforeMount(async () => { - initialData.clientFk = customer.value?.id; - initialData.recipient = customer.value?.email; - initialData.recipientId = customer.value?.id; + initialData.clientFk = customer.value.id; + initialData.recipient = customer.value.email; + initialData.recipientId = customer.value.id; }); const setEmailUser = (data) => { diff --git a/src/pages/Customer/locale/en.yml b/src/pages/Customer/locale/en.yml index b6d495335f1..118f04a311f 100644 --- a/src/pages/Customer/locale/en.yml +++ b/src/pages/Customer/locale/en.yml @@ -107,9 +107,6 @@ 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 f50d049dad1..7c33ffee80f 100644 --- a/src/pages/Customer/locale/es.yml +++ b/src/pages/Customer/locale/es.yml @@ -108,9 +108,6 @@ 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/Worker/Department/Card/DepartmentBasicData.vue b/src/pages/Department/Card/DepartmentBasicData.vue similarity index 73% rename from src/pages/Worker/Department/Card/DepartmentBasicData.vue rename to src/pages/Department/Card/DepartmentBasicData.vue index 66210be7b34..b13aed2d3f4 100644 --- a/src/pages/Worker/Department/Card/DepartmentBasicData.vue +++ b/src/pages/Department/Card/DepartmentBasicData.vue @@ -1,16 +1,27 @@ <script setup> +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; + +const route = useRoute(); +const { t } = useI18n(); </script> <template> - <FormModel model="Department" auto-load class="full-width"> + <FormModel + :url="`Departments/${route.params.id}`" + model="department" + auto-load + class="full-width" + > <template #form="{ data, validate }"> <VnRow> <VnInput - :label="$t('globals.name')" + :label="t('globals.name')" v-model="data.name" :rules="validate('globals.name')" clearable @@ -18,33 +29,33 @@ import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; /> <VnInput v-model="data.code" - :label="$t('globals.code')" + :label="t('globals.code')" :rules="validate('globals.code')" clearable /> </VnRow> <VnRow> <VnInput - :label="$t('department.chat')" + :label="t('department.chat')" v-model="data.chatName" :rules="validate('department.chat')" clearable /> <VnInput v-model="data.notificationEmail" - :label="$t('globals.params.email')" + :label="t('globals.params.email')" :rules="validate('globals.params.email')" clearable /> </VnRow> <VnRow> <VnSelectWorker - :label="$t('department.bossDepartment')" + :label="t('department.bossDepartment')" v-model="data.workerFk" :rules="validate('department.bossDepartment')" /> <VnSelect - :label="$t('department.selfConsumptionCustomer')" + :label="t('department.selfConsumptionCustomer')" v-model="data.clientFk" url="Clients" option-value="id" @@ -56,11 +67,11 @@ import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; </VnRow> <VnRow> <QCheckbox - :label="$t('department.telework')" + :label="t('department.telework')" v-model="data.isTeleworking" /> <QCheckbox - :label="$t('department.notifyOnErrors')" + :label="t('department.notifyOnErrors')" v-model="data.hasToMistake" :false-value="0" :true-value="1" @@ -68,17 +79,17 @@ import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; </VnRow> <VnRow> <QCheckbox - :label="$t('department.worksInProduction')" + :label="t('department.worksInProduction')" v-model="data.isProduction" /> <QCheckbox - :label="$t('department.hasToRefill')" + :label="t('department.hasToRefill')" v-model="data.hasToRefill" /> </VnRow> <VnRow> <QCheckbox - :label="$t('department.hasToSendMail')" + :label="t('department.hasToSendMail')" v-model="data.hasToSendMail" /> </VnRow> diff --git a/src/pages/Worker/Department/Card/DepartmentCard.vue b/src/pages/Department/Card/DepartmentCard.vue similarity index 70% rename from src/pages/Worker/Department/Card/DepartmentCard.vue rename to src/pages/Department/Card/DepartmentCard.vue index 2e3f1152148..4b9fe419cac 100644 --- a/src/pages/Worker/Department/Card/DepartmentCard.vue +++ b/src/pages/Department/Card/DepartmentCard.vue @@ -1,13 +1,13 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; -import DepartmentDescriptor from 'pages/Worker/Department/Card/DepartmentDescriptor.vue'; +import DepartmentDescriptor from 'pages/Department/Card/DepartmentDescriptor.vue'; </script> <template> <VnCardBeta class="q-pa-md column items-center" v-bind="{ ...$attrs }" data-key="Department" - url="Departments" + base-url="Departments" :descriptor="DepartmentDescriptor" /> </template> diff --git a/src/pages/Worker/Department/Card/DepartmentDescriptor.vue b/src/pages/Department/Card/DepartmentDescriptor.vue similarity index 84% rename from src/pages/Worker/Department/Card/DepartmentDescriptor.vue rename to src/pages/Department/Card/DepartmentDescriptor.vue index 4b7dfd9b80d..b219ccfe12c 100644 --- a/src/pages/Worker/Department/Card/DepartmentDescriptor.vue +++ b/src/pages/Department/Card/DepartmentDescriptor.vue @@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; import VnLv from 'src/components/ui/VnLv.vue'; import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; @@ -31,6 +32,15 @@ const entityId = computed(() => { return $props.id || route.params.id; }); +const department = ref(); + +const data = ref(useCardDescription()); + +const setData = (entity) => { + if (!entity) return; + data.value = useCardDescription(entity.name, entity.id); +}; + const removeDepartment = async () => { await axios.post(`/Departments/${entityId.value}/removeChild`, entityId.value); router.push({ name: 'WorkerDepartment' }); @@ -42,10 +52,19 @@ const { openConfirmationModal } = useVnConfirm(); <template> <CardDescriptor ref="DepartmentDescriptorRef" + module="Department" :url="`Departments/${entityId}`" + :title="data.title" + :subtitle="data.subtitle" :summary="$props.summary" :to-module="{ name: 'WorkerDepartment' }" - data-key="Department" + @on-fetch=" + (data) => { + department = data; + setData(data); + } + " + data-key="department" > <template #menu="{}"> <QItem @@ -55,7 +74,7 @@ const { openConfirmationModal } = useVnConfirm(); openConfirmationModal( t('Are you sure you want to delete it?'), t('Delete department'), - removeDepartment, + removeDepartment ) " > diff --git a/src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue b/src/pages/Department/Card/DepartmentDescriptorProxy.vue similarity index 100% rename from src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue rename to src/pages/Department/Card/DepartmentDescriptorProxy.vue diff --git a/src/pages/Worker/Department/Card/DepartmentSummary.vue b/src/pages/Department/Card/DepartmentSummary.vue similarity index 99% rename from src/pages/Worker/Department/Card/DepartmentSummary.vue rename to src/pages/Department/Card/DepartmentSummary.vue index 3719137e454..3d481601f23 100644 --- a/src/pages/Worker/Department/Card/DepartmentSummary.vue +++ b/src/pages/Department/Card/DepartmentSummary.vue @@ -27,7 +27,7 @@ onMounted(async () => { <template> <CardSummary - data-key="Department" + data-key="DepartmentSummary" ref="summary" :url="`Departments/${entityId}`" class="full-width" diff --git a/src/pages/Worker/Department/Card/DepartmentSummaryDialog.vue b/src/pages/Department/Card/DepartmentSummaryDialog.vue similarity index 100% rename from src/pages/Worker/Department/Card/DepartmentSummaryDialog.vue rename to src/pages/Department/Card/DepartmentSummaryDialog.vue diff --git a/src/pages/Entry/Card/EntryBasicData.vue b/src/pages/Entry/Card/EntryBasicData.vue index 6462ed24a99..689eea686ee 100644 --- a/src/pages/Entry/Card/EntryBasicData.vue +++ b/src/pages/Entry/Card/EntryBasicData.vue @@ -1,32 +1,30 @@ <script setup> -import { onMounted, ref } from 'vue'; +import { 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 VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; +import { toDate } from 'src/filters'; 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([]); -onMounted(() => { - checkEntryLock(route.params.id, user.id); -}); +const onFilterTravelSelected = (formData, id) => { + formData.travelFk = id; +}; </script> <template> @@ -54,24 +52,46 @@ onMounted(() => { > <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 @@ -93,7 +113,8 @@ onMounted(() => { <VnInputNumber :label="t('entry.summary.commission')" v-model="data.commission" - :step="1" + step="1" + autofocus :positive="false" /> <VnSelect @@ -140,7 +161,7 @@ onMounted(() => { :label="t('entry.summary.excludedFromAvailable')" /> <QCheckbox - :disable="!isAdministrative()" + v-if="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 81578c609e1..6194ce5b8b2 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -1,806 +1,478 @@ <script setup> -import { useStateStore } from 'stores/useStateStore'; -import { useRoute } from 'vue-router'; +import { ref, computed } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { onMounted, ref } from 'vue'; +import { QBtn } from 'quasar'; -import { useState } from 'src/composables/useState'; - -import FetchData from 'src/components/FetchData.vue'; -import VnTable from 'src/components/VnTable/VnTable.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 ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; -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 VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; + +import { useQuasar } from 'quasar'; +import { toCurrency } from 'src/filters'; import axios from 'axios'; -import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; -import { checkEntryLock } from 'src/composables/checkEntryLock'; +import useNotify from 'src/composables/useNotify.js'; -const $props = defineProps({ - id: { - type: Number, - default: null, - }, - editableMode: { - type: Boolean, - default: true, - }, - tableHeight: { - type: String, - default: null, - }, -}); - -const state = useState(); -const user = state.getUser().fn(); -const stateStore = useStateStore(); -const { t } = useI18n(); +const quasar = useQuasar(); 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, +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, }, - create: true, - width: '25px', + event: () => ({}), }, - { - label: t('Buyer'), - name: 'workerFk', - component: 'select', - attrs: { - url: 'Workers/search', - fields: ['id', 'nickname'], - optionLabel: 'nickname', - optionValue: 'id', + quantity: { + component: VnInput, + props: { + type: 'number', + min: 0, + class: 'input-number', + dense: true, }, - visible: false, + event: getInputEvents, }, - { - 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']; - }, - }, - width: '35px', - }, - { - align: 'center', - label: t('Bucket'), - name: 'packagingFk', - component: 'select', - attrs: { - url: 'packagings', + 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'], - optionLabel: 'id', - optionValue: 'id', + where: { freightItemFk: true }, + 'sort-by': 'id ASC', + dense: true, }, - create: true, - width: '40px', + event: getInputEvents, }, - { - align: 'center', - label: 'Kg', - name: 'weight', - component: 'number', - create: true, - width: '35px', - format: (row) => parseFloat(row['weight']).toFixed(1), + stickers: { + component: VnInput, + props: { + type: 'number', + min: 0, + class: 'input-number', + dense: true, + }, + event: getInputEvents, }, - { - 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)' }; + printedStickers: { + component: VnInput, + props: { + type: 'number', + min: 0, + class: 'input-number', + dense: true, }, + event: getInputEvents, }, - { - 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'; - } + weight: { + component: VnInput, + props: { + type: 'number', + min: 0, + dense: true, }, + event: getInputEvents, }, - { - 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)' }; + packing: { + component: VnInput, + props: { + type: 'number', + min: 0, + dense: true, }, + event: getInputEvents, }, - { - align: 'center', - label: t('Quantity'), - name: 'quantity', - component: 'number', - attrs: { - positive: false, + grouping: { + component: VnInput, + props: { + type: 'number', + min: 0, + dense: true, }, - cellEvent: { - 'update:modelValue': async (value, oldValue, row) => { - row['amount'] = value * row['buyingValue']; - }, - }, - width: '45px', - create: true, - style: getQuantityStyle, + event: getInputEvents, }, - { - align: 'center', - labelAbbreviation: t('Cost'), - label: t('Buying value'), - toolTip: t('Buying value'), - name: 'buyingValue', - create: true, - component: 'number', - attrs: { - positive: false, + buyingValue: { + component: VnInput, + props: { + type: 'number', + min: 0, + dense: true, }, - cellEvent: { - 'update:modelValue': async (value, oldValue, row) => { - row['amount'] = row['quantity'] * value; - }, + event: getInputEvents, + }, + price2: { + component: VnInput, + props: { + type: 'number', + min: 0, + dense: true, }, - width: '45px', - format: (row) => parseFloat(row['buyingValue']).toFixed(3), + event: getInputEvents, }, - { - align: 'center', - label: t('Amount'), - name: 'amount', - width: '45px', - component: 'number', - attrs: { - positive: false, + price3: { + component: VnInput, + props: { + type: 'number', + min: 0, + dense: true, }, - isEditable: false, - format: (row) => parseFloat(row['amount']).toFixed(2), - style: getAmountStyle, + event: getInputEvents, }, - { - 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), + import: { + component: 'span', + props: {}, + event: () => ({}), }, - { - 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)' }; - }, - }, -]; +})); -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)' }; -} - -async function beforeSave(data, getChanges) { - try { - const changes = data.updates; - if (!changes) return data; - const patchPromises = []; - - for (const change of changes) { - let patchData = {}; - - if ('hasMinPrice' in change.data) { - patchData.hasMinPrice = change.data?.hasMinPrice; - delete change.data.hasMinPrice; - } - if ('minPrice' in change.data) { - patchData.minPrice = change.data?.minPrice; - delete change.data.minPrice; - } - - if (Object.keys(patchData).length > 0) { - const promise = axios - .get('Buys/findOne', { - params: { - filter: { - fields: ['itemFk'], - where: { id: change.where.id }, - }, - }, - }) - .then((buy) => { - return axios.patch(`Items/${buy.data.itemFk}`, patchData); - }) - .catch((error) => { - console.error('Error processing change: ', change, error); - }); - - patchPromises.push(promise); - } - } - - await Promise.all(patchPromises); - - data.updates = changes.filter((change) => Object.keys(change.data).length > 0); - - return data; - } catch (error) { - console.error('Error in beforeSave:', error); - throw error; - } -} - -function invertQuantitySign(rows, sign) { - for (const row of rows) { - if (sign > 0) row.quantity = Math.abs(row.quantity); - 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 entriesTableColumns = computed(() => { + return [ + { + label: t('globals.item'), + field: 'itemFk', + name: 'item', + align: 'left', }, - }); - 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); + { + 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, + }, + }) + .onOk(async () => { + await deleteBuys(); + const notifyMessage = t( + `Buy${rowsSelected.value.length > 1 ? 's' : ''} deleted` + ); + notify(notifyMessage, 'positive'); + }); +}; + +const deleteBuys = async () => { + await axios.post('Buys/deleteBuys', { buys: rowsSelected.value }); + entryBuysPaginateRef.value.fetch(); +}; + +const importBuys = () => { + router.push({ name: 'EntryBuysImport' }); +}; + +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; +}; + +const lockIconType = (groupingMode, mode) => { + if (mode === 'packing') { + return groupingMode === 'packing' ? 'lock' : 'lock_open'; + } else { + return groupingMode === 'grouping' ? 'lock' : 'lock_open'; + } +}; </script> + <template> - <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" - > - <QList> - <QItem> - <QItemSection> - <QBtn - flat - @click="invertQuantitySign(selectedRows, -1)" - data-cy="set-negative-quantity" - > - <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" + <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/${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'" + :url="`Entries/${route.params.id}/getBuys`" + @on-fetch="copyOriginalRowsData($event)" auto-load - footer - data-cy="entry-buys" > - <template #column-hex="{ row }"> - <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> - </template> - <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 #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')" > - <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 #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 + ) + " + > + <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> - </VnSelect> + <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> </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> + </VnPaginate> + + <QPageSticky :offset="[20, 20]"> + <QBtn fab icon="upload" color="primary" @click="importBuys()" /> + <QTooltip class="text-no-wrap"> + {{ t('Import buys') }} + </QTooltip> + </QPageSticky> </template> -<i18n> -es: - 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%; +.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? +</i18n> diff --git a/src/pages/Entry/Card/EntryCard.vue b/src/pages/Entry/Card/EntryCard.vue index be82289f4a5..e00623a21d0 100644 --- a/src/pages/Entry/Card/EntryCard.vue +++ b/src/pages/Entry/Card/EntryCard.vue @@ -1,13 +1,13 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import EntryDescriptor from './EntryDescriptor.vue'; -import filter from './EntryFilter.js'; +import filter from './EntryFilter.js' </script> <template> <VnCardBeta data-key="Entry" - url="Entries" + base-url="Entries" :descriptor="EntryDescriptor" - :filter="filter" + :user-filter="filter" /> </template> diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 69b300cb2a6..19d13e51a50 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -1,19 +1,12 @@ <script setup> import { ref, computed, onMounted } from 'vue'; -import { useRoute, useRouter } from 'vue-router'; +import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { toDate } from 'src/filters'; -import { getUrl } from 'src/composables/getUrl'; -import { useQuasar } from 'quasar'; -import { usePrintService } from 'composables/usePrintService'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import 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(); +import { toDate } from 'src/filters'; +import { getUrl } from 'src/composables/getUrl'; +import EntryDescriptorMenu from './EntryDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -90,63 +83,12 @@ 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" @@ -154,56 +96,15 @@ async function deleteEntry() { width="lg-width" > <template #menu="{ entity }"> - <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> + <EntryDescriptorMenu :id="entity.id" /> </template> <template #body="{ entity }"> - <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.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('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" + :label="t('globals.warehouseOut')" + :value="entity.travel?.warehouseOut?.name" /> </template> <template #icons="{ entity }"> @@ -230,14 +131,6 @@ async function deleteEntry() { }}</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 }"> @@ -250,6 +143,21 @@ async function deleteEntry() { > <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', @@ -269,24 +177,10 @@ async function deleteEntry() { </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/EntryFilter.js b/src/pages/Entry/Card/EntryFilter.js index d9fd1c2be9c..3ff62cf273c 100644 --- a/src/pages/Entry/Card/EntryFilter.js +++ b/src/pages/Entry/Card/EntryFilter.js @@ -9,7 +9,6 @@ export default { 'shipped', 'agencyModeFk', 'warehouseOutFk', - 'warehouseInFk', 'daysInForward', ], include: [ @@ -22,13 +21,13 @@ export default { { relation: 'warehouseOut', scope: { - fields: ['name', 'code'], + fields: ['name'], }, }, { relation: 'warehouseIn', scope: { - fields: ['name', 'code'], + fields: ['name'], }, }, ], @@ -40,17 +39,5 @@ export default { fields: ['id', 'nickname'], }, }, - { - relation: 'currency', - scope: { - fields: ['id', 'code'], - }, - }, - { - relation: 'entryType', - scope: { - fields: ['code', 'description'], - }, - }, ], }; diff --git a/src/pages/Entry/Card/EntryNotes.vue b/src/pages/Entry/Card/EntryNotes.vue index 459c3b069ac..55cac043792 100644 --- a/src/pages/Entry/Card/EntryNotes.vue +++ b/src/pages/Entry/Card/EntryNotes.vue @@ -17,7 +17,7 @@ const selected = ref([]); const sortEntryObservationOptions = (data) => { entryObservationsOptions.value = [...data].sort((a, b) => - a.description.localeCompare(b.description), + a.description.localeCompare(b.description) ); }; @@ -142,7 +142,7 @@ const columns = computed(() => [ fab color="primary" icon="add" - v-shortcut="'+'" + shortcut="+" @click="entryObservationsRef.insert()" /> </QPageSticky> diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index c40e2ba46f6..8c46fb6e67e 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -2,17 +2,19 @@ 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 EntryBuys from './EntryBuys.vue'; -import VnTitle from 'src/components/common/VnTitle.vue'; -import VnCheckbox from 'src/components/common/VnCheckbox.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 VnTitle from 'src/components/common/VnTitle.vue'; const route = useRoute(); const { t } = useI18n(); @@ -31,6 +33,117 @@ 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(); @@ -40,18 +153,14 @@ const fetchEntryBuys = async () => { const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`); if (data) entryBuys.value = data; }; - -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 @@ -64,154 +173,159 @@ onMounted(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')" /> - <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" v-if="entry?.travelFk"> - <VnTitle - :url="`#/travel/${entry.travel.id}/summary`" - :text="t('Travel')" + <VnLv :label="t('entry.summary.commission')" :value="entry.commission" /> + <VnLv + :label="t('entry.summary.currency')" + :value="entry.currency?.name" /> - <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> + <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)" + /> + </QCard> + <QCard class="vn-one"> + <VnTitle + :url="`#/entry/${entityId}/basic-data`" + :text="t('globals.summary.basicData')" + /> + <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> </QCard> <QCard class="vn-max"> <VnTitle :url="`#/entry/${entityId}/buys`" :text="t('entry.summary.buys')" /> - <EntryBuys - v-if="entityId" - :id="Number(entityId)" - :editable-mode="false" - table-height="49vh" - /> + <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> </QCard> </template> </CardSummary> </template> + <style lang="scss" scoped> -.card-group { - display: flex; - flex-direction: column; -} - -.card-content { - display: flex; - flex-direction: column; - text-overflow: ellipsis; - > div { - max-height: 24px; - } -} - -@media (min-width: 1010px) { - .card-group { - flex-direction: row; - } - .card-content { - flex: 1; - margin-right: 16px; - } +.separation-row { + background-color: var(--vn-section-color) !important; } </style> + <i18n> es: - Travel: Envío - InvoiceIn data: Datos factura + Travel data: Datos envío </i18n> diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index 8c60918a836..0f632c0ef3b 100644 --- a/src/pages/Entry/EntryFilter.vue +++ b/src/pages/Entry/EntryFilter.vue @@ -19,7 +19,6 @@ const props = defineProps({ const currenciesOptions = ref([]); const companiesOptions = ref([]); -const entryFilterPanel = ref(); </script> <template> @@ -39,7 +38,7 @@ const entryFilterPanel = ref(); @on-fetch="(data) => (currenciesOptions = data)" auto-load /> - <VnFilterPanel ref="entryFilterPanel" :data-key="props.dataKey" :search-button="true"> + <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`entryFilter.params.${tag.label}`) }}: </strong> @@ -49,65 +48,70 @@ const entryFilterPanel = ref(); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <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> - <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> - <VnInputDate - :label="t('params.landed')" - v-model="params.landed" - @update:model-value="searchFn()" + <VnInput + v-model="params.search" + :label="t('entryFilter.params.search')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput v-model="params.id" label="Id" is-outlined /> + <VnInput + v-model="params.reference" + :label="t('entryFilter.params.reference')" + is-outlined + /> + </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" + @update:model-value="searchFn()" + :options="companiesOptions" + option-value="id" + option-label="code" + hide-selected + dense + outlined + rounded + /> + </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 + /> </QItemSection> </QItem> <QItem> @@ -121,165 +125,62 @@ const entryFilterPanel = ref(); rounded /> </QItemSection> - <QItemSection> - <VnInput - v-model="params.invoiceNumber" - :label="t('params.invoiceNumber')" - is-outlined - /> - </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.reference" - :label="t('entry.list.tableVisibleColumns.reference')" - is-outlined - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnSelect - :label="t('params.agencyModeId')" - v-model="params.agencyModeId" + <VnInputDate + :label="t('entryFilter.params.created')" + v-model="params.created" @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> - <VnSelect - :label="t('params.warehouseOutFk')" - v-model="params.warehouseOutFk" + <VnInputDate + :label="t('entryFilter.params.from')" + v-model="params.from" @update:model-value="searchFn()" - url="Warehouses" - :fields="['id', 'name']" - hide-selected - dense - outlined - rounded - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <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" + <VnInputDate + :label="t('entryFilter.params.to')" + v-model="params.to" @update:model-value="searchFn()" - url="EntryTypes" - :fields="['code', 'description']" - option-value="code" - option-label="description" - hide-selected - dense - outlined - rounded + is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.evaNotes" - :label="t('params.evaNotes')" - is-outlined + <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 + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <QCheckbox + :label="t('entryFilter.params.isOrdered')" + v-model="params.isOrdered" + toggle-indeterminate /> </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/EntryList.vue b/src/pages/Entry/EntryList.vue index 3c96a230286..3172c6d0e9b 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -1,25 +1,21 @@ <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 VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; -import { toDate } from 'src/filters'; +import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); const tableRef = ref(); -const defaultEntry = ref({}); -const state = useState(); -const user = state.getUser(); const dataKey = 'EntryList'; -const entryQueryFilter = { +const { viewSummary } = useSummaryDialog(); +const entryFilter = { include: [ { relation: 'suppliers', @@ -44,58 +40,44 @@ const entryQueryFilter = { const columns = computed(() => [ { - labelAbbreviation: 'Ex', - label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), - toolTip: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), - name: 'isExcludedFromAvailable', - component: 'checkbox', - width: '35px', + name: 'status', + columnFilter: false, }, { - 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.id'), + name: 'id', + isId: true, + chip: { + condition: () => 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('globals.reference'), + name: 'reference', + isTitle: true, + component: 'input', + columnField: { + component: null, + }, + 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', + align: 'left', + label: t('entry.list.tableVisibleColumns.created'), + name: 'created', + create: true, + cardVisible: true, component: 'date', columnField: { component: null, }, - format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)), - width: '105px', - }, - { - label: t('globals.id'), - name: 'id', - isId: true, - component: 'number', - chip: { - condition: () => true, - }, - width: '50px', + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.created)), }, { + align: 'left', label: t('entry.list.tableVisibleColumns.supplierFk'), name: 'supplierFk', create: true, @@ -104,213 +86,165 @@ const columns = computed(() => [ attrs: { url: 'suppliers', fields: ['id', 'name'], - where: { order: 'name DESC' }, + }, + columnField: { + component: null, }, format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), - width: '110px', }, { align: 'left', - 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, - }, + label: t('entry.list.tableVisibleColumns.isBooked'), + name: 'isBooked', cardVisible: true, + create: true, + component: 'checkbox', }, { align: 'left', - label: 'AWB', - name: 'awbCode', - component: 'input', - width: '100px', - }, - { - align: 'left', - label: t('entry.list.tableVisibleColumns.agencyModeId'), - name: 'agencyModeId', + label: t('entry.list.tableVisibleColumns.isConfirmed'), + name: 'isConfirmed', cardVisible: true, - component: 'select', - attrs: { - url: 'agencyModes', - fields: ['id', 'name'], - }, - columnField: { - component: null, - }, - format: (row, dashIfEmpty) => dashIfEmpty(row.agencyModeName), + create: true, + component: 'checkbox', }, { align: 'left', - label: t('entry.list.tableVisibleColumns.evaNotes'), - name: 'evaNotes', - component: 'input', - }, - { - align: 'left', - label: t('entry.list.tableVisibleColumns.warehouseOutFk'), - name: 'warehouseOutFk', + label: t('entry.list.tableVisibleColumns.isOrdered'), + name: 'isOrdered', cardVisible: true, - component: 'select', - attrs: { - url: 'warehouses', - fields: ['id', 'name'], - }, - columnField: { - component: null, - }, - format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseOutName), - width: '65px', + create: true, + component: 'checkbox', }, { align: 'left', - 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, + name: 'companyFk', component: 'select', attrs: { - optionValue: 'id', + url: 'companies', + fields: ['id', 'code'], optionLabel: 'code', - url: 'Companies', + optionValue: 'id', + }, + 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, + }, + 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, }, }, { - name: 'travelFk', - label: t('entry.list.tableVisibleColumns.travelFk'), - cardVisible: false, - visible: false, - create: 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: 'landed DESC', - userFilter: EntryFilter, + order: 'id DESC', + userFilter: entryFilter, }" > <template #advanced-menu> - <EntryFilter :data-key="dataKey" /> + <EntryFilter data-key="EntryList" /> </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('Create entry'), + title: t('entry.list.newEntry'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { - supplierFk: defaultEntry.defaultSupplierFk, - dated: Date.vnNew(), - companyFk: user?.companyFk, - }, + formInitialData: {}, }" :columns="columns" redirect="entry" :right-search="false" > - <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 #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> <template #column-supplierFk="{ row }"> <span class="link" @click.stop> @@ -318,27 +252,13 @@ onBeforeMount(async () => { <SupplierDescriptorProxy :id="row.supplierFk" /> </span> </template> - <template #column-create-travelFk="{ data }"> - <VnSelectTravelExtended - :data="data" - v-model="data.travelFk" - :onFilterTravelSelected=" - (data, result) => (data.travelFk = result) - " - data-cy="entry-travel-select" - /> + <template #column-travelFk="{ row }"> + <span class="link" @click.stop> + {{ row.travelRef }} + <TravelDescriptorProxy :id="row.travelFk" /> + </span> </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 4bd0fe6408d..fa0bdc12e2f 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -34,20 +34,18 @@ const columns = computed(() => [ label: t('entryStockBought.buyer'), isTitle: true, component: 'select', - isEditable: false, cardVisible: true, create: true, attrs: { url: 'Workers/activeWithInheritedRole', - fields: ['id', 'name', 'nickname'], + fields: ['id', 'name'], where: { role: 'buyer' }, optionFilter: 'firstName', - optionLabel: 'nickname', + optionLabel: 'name', optionValue: 'id', useLike: false, }, columnFilter: false, - width: '70px', }, { align: 'center', @@ -57,7 +55,6 @@ const columns = computed(() => [ create: true, component: 'number', summation: true, - width: '50px', }, { align: 'center', @@ -81,7 +78,6 @@ const columns = computed(() => [ actions: [ { title: t('entryStockBought.viewMoreDetails'), - name: 'searchBtn', icon: 'search', isPrimary: true, action: (row) => { @@ -95,7 +91,6 @@ const columns = computed(() => [ }, }, ], - 'data-cy': 'table-actions', }, ]); @@ -163,7 +158,7 @@ function round(value) { @on-fetch=" (data) => { travel = data.find( - (data) => data.warehouseIn?.code.toLowerCase() === 'vnh', + (data) => data.warehouseIn?.code.toLowerCase() === 'vnh' ); } " @@ -184,7 +179,6 @@ function round(value) { @click="openDialog()" :title="t('entryStockBought.editTravel')" color="primary" - data-cy="edit-travel" /> </div> </VnRow> @@ -245,11 +239,10 @@ 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?.nickname }} + {{ row?.worker?.user?.name }} <WorkerDescriptorProxy :id="row?.workerFk" /> </span> </template> @@ -286,11 +279,10 @@ 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 1a37994d908..81217182575 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: 'right', + align: 'left', label: t('Entry'), name: 'entryFk', isTitle: true, @@ -29,7 +29,7 @@ const columns = [ columnFilter: false, }, { - align: 'right', + align: 'left', name: 'itemFk', label: t('Item'), columnFilter: false, @@ -44,21 +44,21 @@ const columns = [ cardVisible: true, }, { - align: 'right', + align: 'left', name: 'volume', label: t('Volume'), columnFilter: false, cardVisible: true, }, { - align: 'right', + align: 'left', label: t('Packaging'), name: 'packagingFk', columnFilter: false, cardVisible: true, }, { - align: 'right', + align: 'left', label: 'Packing', name: 'packing', columnFilter: false, @@ -73,14 +73,12 @@ const columns = [ ref="tableRef" data-key="StockBoughtsDetail" :url="customUrl" - order="volume DESC" + order="itemName 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 }"> @@ -101,14 +99,16 @@ const columns = [ </template> <style lang="css" scoped> .container { - max-width: 100%; - width: 50%; + max-width: 50vw; overflow: auto; justify-content: center; align-items: center; margin: auto; background-color: var(--vn-section-color); - padding: 2%; + padding: 4px; +} +.container > div > div > .q-table__top.relative-position.row.items-center { + background-color: red !important; } </style> <i18n> diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index 88b16cb03ba..80f3491a837 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -1,36 +1,21 @@ 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: - isExcludedFromAvailable: Exclude from inventory - isOrdered: Ordered - isConfirmed: Ready to label - isReceived: Received - isRaid: Raid - landed: Date + created: Creation supplierFk: Supplier - reference: Ref/Alb/Guide - invoiceNumber: Invoice - agencyModeId: Agency isBooked: Booked + isConfirmed: Confirmed + isOrdered: Ordered companyFk: Company - evaNotes: Notes - warehouseOutFk: Origin - warehouseInFk: Destiny - entryTypeDescription: Entry type - invoiceAmount: Import travelFk: Travel - dated: Dated + isExcludedFromAvailable: Inventory + invoiceAmount: Import inventoryEntry: Inventory entry summary: commission: Commission currency: Currency invoiceNumber: Invoice number - invoiceAmount: Invoice amount ordered: Ordered booked: Booked excludedFromAvailable: Inventory @@ -48,7 +33,6 @@ entry: buyingValue: Buying value import: Import pvp: PVP - entryType: Entry type basicData: travel: Travel currency: Currency @@ -85,55 +69,17 @@ entry: landing: Landing isExcludedFromAvailable: Es inventory params: - 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 + toShipped: To + fromShipped: From + daysOnward: Days onward + daysAgo: Days ago + warehouseInFk: Warehouse in 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 @@ -145,16 +91,8 @@ 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 3025d64cbc3..a5b968016f9 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -1,36 +1,21 @@ 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: - isExcludedFromAvailable: Excluir del inventario - isOrdered: Pedida - isConfirmed: Lista para etiquetar - isReceived: Recibida - isRaid: Redada - landed: Fecha + created: Creación supplierFk: Proveedor - invoiceNumber: Nº Factura - reference: Ref/Alb/Guía - agencyModeId: Agencia isBooked: Asentado + isConfirmed: Confirmado + isOrdered: Pedida companyFk: Empresa travelFk: Envio - evaNotes: Notas - warehouseOutFk: Origen - warehouseInFk: Destino - entryTypeDescription: Tipo entrada + isExcludedFromAvailable: Inventario 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 @@ -49,13 +34,12 @@ entry: buyingValue: Coste import: Importe pvp: PVP - entryType: Tipo entrada basicData: travel: Envío currency: Moneda observation: Observación commission: Comisión - booked: Contabilizada + booked: Asentado excludedFromAvailable: Inventario initialTemperature: Ini °C finalTemperature: Fin °C @@ -85,70 +69,31 @@ 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: - 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 + travelFk: Envío + companyFk: Empresa + currencyFk: Moneda + supplierFk: Proveedor + from: Desde + to: Hasta + created: Fecha creación + isBooked: Asentado + isConfirmed: Confirmado + isOrdered: Pedida + search: Búsqueda general + reference: Referencia myEntries: id: ID landed: F. llegada diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index 905ddebb2b0..c01ec4ab4a2 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('invoiceIn.supplierRef')" + :label="t('Supplier ref')" v-model="data.supplierRef" /> </VnRow> @@ -149,7 +149,6 @@ function deleteFile(dmsFk) { option-value="id" option-label="id" :filter-options="['id', 'name']" - data-cy="UnDeductibleVatSelect" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -216,7 +215,7 @@ function deleteFile(dmsFk) { v-else icon="add_circle" round - v-shortcut="'+'" + shortcut="+" padding="xs" @click=" () => { @@ -311,6 +310,7 @@ 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/InvoiceInCard.vue b/src/pages/InvoiceIn/Card/InvoiceInCard.vue index 34cc26437a5..8aa35f4d86b 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInCard.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInCard.vue @@ -1,18 +1,47 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import InvoiceInDescriptor from './InvoiceInDescriptor.vue'; -import { onBeforeRouteUpdate } from 'vue-router'; -import { setRectificative } from '../composables/setRectificative'; -import filter from './InvoiceInFilter.js'; -onBeforeRouteUpdate(async (to) => await setRectificative(to)); +const filter = { + include: [ + { + relation: 'supplier', + scope: { + include: { + relation: 'contacts', + scope: { where: { email: { neq: null } } }, + }, + }, + }, + { relation: 'invoiceInDueDay' }, + { relation: 'company' }, + { relation: 'currency' }, + { + relation: 'dms', + scope: { + fields: [ + 'dmsTypeFk', + 'reference', + 'hardCopyNumber', + 'workerFk', + 'description', + 'hasFile', + 'file', + 'created', + 'companyFk', + 'warehouseFk', + ], + }, + }, + ], +}; </script> <template> <VnCardBeta data-key="InvoiceIn" - url="InvoiceIns" + base-url="InvoiceIns" :descriptor="InvoiceInDescriptor" - :filter="filter" + :user-filter="filter" /> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index 3843f5bf713..da7bd4426b5 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -7,7 +7,6 @@ import { toCurrency, toDate } from 'src/filters'; import VnLv from 'src/components/ui/VnLv.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; -import filter from './InvoiceInFilter.js'; import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; const $props = defineProps({ id: { type: Number, default: null } }); @@ -17,10 +16,33 @@ const { t } = useI18n(); const cardDescriptorRef = ref(); const entityId = computed(() => $props.id || +currentRoute.value.params.id); const totalAmount = ref(); -const config = ref(); -const cplusRectificationTypes = ref([]); -const siiTypeInvoiceIns = ref([]); -const invoiceCorrectionTypes = ref([]); + +const filter = { + include: [ + { + relation: 'supplier', + scope: { + include: { + relation: 'contacts', + scope: { + where: { + email: { neq: null }, + }, + }, + }, + }, + }, + { + relation: 'invoiceInDueDay', + }, + { + relation: 'company', + }, + { + relation: 'currency', + }, + ], +}; const invoiceInCorrection = reactive({ correcting: [], corrected: null }); const routes = reactive({ getSupplier: (id) => { @@ -90,6 +112,7 @@ 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 8b039ec2726..c3ab635c849 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.book') }}</QItemSection> + <QItemSection>{{ t('invoiceIn.descriptorMenu.toBook') }}</QItemSection> </QItem> </template> </InvoiceInToBook> @@ -197,7 +197,7 @@ const createInvoiceInCorrection = async () => { @click="triggerMenu('unbook')" > <QItemSection> - {{ t('invoiceIn.descriptorMenu.unbook') }} + {{ t('invoiceIn.descriptorMenu.toUnbook') }} </QItemSection> </QItem> <QItem diff --git a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue index 20cc1cc719b..23387ff7425 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, onBeforeMount } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import axios from 'axios'; @@ -11,7 +11,6 @@ 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(); @@ -25,7 +24,7 @@ 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', @@ -64,8 +63,6 @@ const columns = computed(() => [ }, ]); -const totalAmount = computed(() => getTotal(invoiceInFormRef.value.formData, 'amount')); - const isNotEuro = (code) => code != 'EUR'; async function insert() { @@ -73,10 +70,6 @@ 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> <CrudModel @@ -151,7 +144,7 @@ onBeforeMount(async () => { <QTd /> <QTd /> <QTd> - {{ toCurrency(totalAmount) }} + {{ getTotal(rows, 'amount', { currency: 'default' }) }} </QTd> <QTd> <template v-if="isNotEuro(invoiceIn.currency.code)"> @@ -229,19 +222,10 @@ onBeforeMount(async () => { <QBtn color="primary" icon="add" - v-shortcut="'+'" + shortcut="+" size="lg" round - @click=" - () => { - if (!areRows) insert(); - else - invoiceInFormRef.insert({ - amount: (totals.totalTaxableBase - totalAmount).toFixed(2), - invoiceInFk: invoiceId, - }); - } - " + @click="!areRows ? insert() : invoiceInFormRef.insert()" /> </QPageSticky> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInFilter.js b/src/pages/InvoiceIn/Card/InvoiceInFilter.js deleted file mode 100644 index 6df8b583045..00000000000 --- a/src/pages/InvoiceIn/Card/InvoiceInFilter.js +++ /dev/null @@ -1,33 +0,0 @@ -export default { - include: [ - { - relation: 'supplier', - scope: { - include: { - relation: 'contacts', - scope: { where: { email: { neq: null } } }, - }, - }, - }, - { relation: 'invoiceInDueDay' }, - { relation: 'company' }, - { relation: 'currency' }, - { - relation: 'dms', - scope: { - fields: [ - 'dmsTypeFk', - 'reference', - 'hardCopyNumber', - 'workerFk', - 'description', - 'hasFile', - 'file', - 'created', - 'companyFk', - 'warehouseFk', - ], - }, - }, - ], -}; diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index 6f8642313ae..e529ea6cd06 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -218,7 +218,7 @@ const columns = computed(() => [ <QBtn color="primary" icon="add" - v-shortcut="'+'" + shortcut="+" size="lg" round @click="invoiceInFormRef.insert()" diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index d358601d316..e546638f2de 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('Book')" + :label="t('To book')" color="orange-11" text-color="black" @click="book(entityId)" @@ -224,7 +224,10 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </span> </template> </VnLv> - <VnLv :label="t('invoiceIn.supplierRef')" :value="entity.supplierRef" /> + <VnLv + :label="t('invoiceIn.list.supplierRef')" + :value="entity.supplierRef" + /> <VnLv :label="t('invoiceIn.summary.currency')" :value="entity.currency?.code" @@ -354,7 +357,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; entity.totals.totalTaxableBaseForeignValue && toCurrency( entity.totals.totalTaxableBaseForeignValue, - currency, + currency ) }}</QTd> </QTr> @@ -389,7 +392,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; entity.totals.totalDueDayForeignValue && toCurrency( entity.totals.totalDueDayForeignValue, - currency, + currency ) }} </QTd> @@ -469,5 +472,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 - Book: Contabilizar + To book: Contabilizar </i18n> diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index e77453bc084..f99e060b8f1 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, nextTick } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; @@ -25,6 +25,7 @@ const sageTaxTypes = ref([]); const sageTransactionTypes = ref([]); const rowsSelected = ref([]); const invoiceInFormRef = ref(); +const expenseRef = ref(); defineProps({ actionIcon: { @@ -96,20 +97,6 @@ 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', @@ -130,7 +117,7 @@ const isNotEuro = (code) => code != 'EUR'; function taxRate(invoiceInTax) { const sageTaxTypeId = invoiceInTax.taxTypeSageFk; const taxRateSelection = sageTaxTypes.value.find( - (transaction) => transaction.id == sageTaxTypeId, + (transaction) => transaction.id == sageTaxTypeId ); const taxTypeSage = taxRateSelection?.rate ?? 0; const taxableBase = invoiceInTax?.taxableBase ?? 0; @@ -138,26 +125,35 @@ function taxRate(invoiceInTax) { return ((taxTypeSage / 100) * taxableBase).toFixed(2); } -function autocompleteExpense(evt, row, col, ref) { +function autocompleteExpense(evt, row, col) { const val = evt.target.value; if (!val) return; const param = isNaN(val) ? row[col.model] : val; const lookup = expenses.value.find( - ({ id }) => id == useAccountShortToStandard(param), + ({ id }) => id == useAccountShortToStandard(param) ); - ref.vnSelectDialogRef.vnSelectRef.toggleOption(lookup); + expenseRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup); } -function setCursor(ref) { - nextTick(() => { - const select = ref.vnSelectDialogRef - ? ref.vnSelectDialogRef.vnSelectRef - : ref.vnSelectRef; - select.$el.querySelector('input').setSelectionRange(0, 0); +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; +}); + + + </script> <template> <FetchData @@ -195,24 +191,14 @@ function setCursor(ref) { <template #body-cell-expense="{ row, col }"> <QTd> <VnSelectDialog - :ref="`expenseRef-${row.$index}`" + ref="expenseRef" 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, - $refs[`expenseRef-${row.$index}`], - ) - " - @update:model-value=" - setCursor($refs[`expenseRef-${row.$index}`]) - " + @keydown.tab="autocompleteExpense($event, row, col)" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -228,7 +214,7 @@ function setCursor(ref) { </QTd> </template> <template #body-cell-taxablebase="{ row }"> - <QTd shrink> + <QTd> <VnInputNumber clear-icon="close" v-model="row.taxableBase" @@ -239,16 +225,12 @@ function setCursor(ref) { <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"> @@ -266,15 +248,11 @@ function setCursor(ref) { <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"> @@ -292,7 +270,7 @@ function setCursor(ref) { </QTd> </template> <template #body-cell-foreignvalue="{ row }"> - <QTd shrink> + <QTd> <VnInputNumber :class="{ 'no-pointer-events': !isNotEuro(currency), @@ -305,7 +283,7 @@ function setCursor(ref) { row.taxableBase = await getExchange( val, row.currencyFk, - invoiceIn.issued, + invoiceIn.issued ); } " @@ -448,7 +426,7 @@ function setCursor(ref) { color="primary" icon="add" size="lg" - v-shortcut="'+'" + shortcut="+" round @click="invoiceInFormRef.insert()" > diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 0960d0d6cef..e1723e3b17a 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -29,7 +29,6 @@ const cols = computed(() => [ name: 'isBooked', label: t('invoiceIn.isBooked'), columnFilter: false, - component: 'checkbox', }, { align: 'left', @@ -57,7 +56,7 @@ const cols = computed(() => [ { align: 'left', name: 'supplierRef', - label: t('invoiceIn.supplierRef'), + label: t('invoiceIn.list.supplierRef'), }, { align: 'left', @@ -178,7 +177,7 @@ const cols = computed(() => [ :required="true" /> <VnInput - :label="t('invoiceIn.supplierRef')" + :label="t('invoiceIn.list.supplierRef')" v-model="data.supplierRef" /> <VnSelect diff --git a/src/pages/InvoiceIn/InvoiceInToBook.vue b/src/pages/InvoiceIn/InvoiceInToBook.vue index 5bdbe197b99..95ce8155a26 100644 --- a/src/pages/InvoiceIn/InvoiceInToBook.vue +++ b/src/pages/InvoiceIn/InvoiceInToBook.vue @@ -4,7 +4,6 @@ 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(); @@ -13,51 +12,29 @@ defineExpose({ checkToBook }); const { store } = useArrayData(); async function checkToBook(id) { - 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')); + let directBooking = true; const { data: totals } = await axios.get(`InvoiceIns/${id}/getTotals`); const taxableBaseNotEqualDueDay = totals.totalDueDay != totals.totalTaxableBase; const vatNotEqualDueDay = totals.totalDueDay != totals.totalVat; - if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) - messages.push(t('The sum of the taxable bases does not match the due dates')); + if (taxableBaseNotEqualDueDay && vatNotEqualDueDay) directBooking = false; - const dueDaysCount = ( - await axios.get('InvoiceInDueDays/count', { - params: { - where: JSON.stringify({ - invoiceInFk: id, - dueDated: { gte: Date.vnNew() }, - }), - }, - }) - ).data?.count; + const { data: dueDaysCount } = await axios.get('InvoiceInDueDays/count', { + where: { + invoiceInFk: id, + dueDated: { gte: Date.vnNew() }, + }, + }); - if (dueDaysCount) messages.push(t('Some due dates are less than or equal to today')); + if (dueDaysCount) directBooking = false; - 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)); + 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)); } async function toBook(id) { @@ -82,7 +59,4 @@ 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 548e6c2010b..6b21b316bad 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,6 +19,8 @@ invoiceIn: unbook: Unbook delete: Delete clone: Clone + toBook: To book + toUnbook: To unbook deleteInvoice: Delete invoice invoiceDeleted: invoice deleted cloneInvoice: Clone invoice @@ -68,3 +70,4 @@ 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 142d95f929c..3f27c895c51 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,10 +15,12 @@ invoiceIn: descriptor: ticketList: Listado de tickets descriptorMenu: - book: Contabilizar - unbook: Descontabilizar + book: Asentar + unbook: Desasentar delete: Eliminar clone: Clonar + toBook: Contabilizar + toUnbook: Descontabilizar deleteInvoice: Eliminar factura invoiceDeleted: Factura eliminada cloneInvoice: Clonar factura @@ -66,3 +68,4 @@ invoiceIn: isBooked: Contabilizada account: Cuenta contable correctingFk: Rectificativa + diff --git a/src/pages/InvoiceOut/Card/InvoiceOutCard.vue b/src/pages/InvoiceOut/Card/InvoiceOutCard.vue index a50c9d24732..93e3fe0420a 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutCard.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutCard.vue @@ -1,13 +1,11 @@ <script setup> import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue'; -import filter from './InvoiceOutFilter.js'; </script> <template> <VnCardBeta data-key="InvoiceOut" - url="InvoiceOuts" - :filter="filter" + base-url="InvoiceOuts" :descriptor="InvoiceOutDescriptor" /> </template> diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue index dfaf6c10988..209f1531e1b 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -8,8 +8,8 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy import VnLv from 'src/components/ui/VnLv.vue'; import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import { toCurrency, toDate } from 'src/filters'; -import filter from './InvoiceOutFilter.js'; const $props = defineProps({ id: { @@ -26,20 +26,42 @@ const entityId = computed(() => { return $props.id || route.params.id; }); +const filter = { + include: [ + { + relation: 'company', + scope: { + fields: ['id', 'code'], + }, + }, + { + relation: 'client', + scope: { + fields: ['id', 'name', 'email'], + }, + }, + ], +}; + const descriptor = ref(); function ticketFilter(invoice) { return JSON.stringify({ refFk: invoice.ref }); } +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.id)); </script> <template> <CardDescriptor ref="descriptor" + module="InvoiceOut" :url="`InvoiceOuts/${entityId}`" :filter="filter" - title="ref" - data-key="InvoiceOut" + :title="data.title" + :subtitle="data.subtitle" + @on-fetch="setData" + data-key="invoiceOutData" width="lg-width" > <template #menu="{ entity, menuRef }"> diff --git a/src/pages/InvoiceOut/Card/InvoiceOutFilter.js b/src/pages/InvoiceOut/Card/InvoiceOutFilter.js deleted file mode 100644 index 48b20faf698..00000000000 --- a/src/pages/InvoiceOut/Card/InvoiceOutFilter.js +++ /dev/null @@ -1,16 +0,0 @@ -export default { - include: [ - { - relation: 'company', - scope: { - fields: ['id', 'code'], - }, - }, - { - relation: 'client', - scope: { - fields: ['id', 'name', 'email'], - }, - }, - ], -}; diff --git a/src/pages/Item/components/CreateGenusForm.vue b/src/pages/Item/Card/CreateGenusForm.vue similarity index 100% rename from src/pages/Item/components/CreateGenusForm.vue rename to src/pages/Item/Card/CreateGenusForm.vue diff --git a/src/pages/Item/components/CreateSpecieForm.vue b/src/pages/Item/Card/CreateSpecieForm.vue similarity index 100% rename from src/pages/Item/components/CreateSpecieForm.vue rename to src/pages/Item/Card/CreateSpecieForm.vue diff --git a/src/pages/Item/Card/ItemBarcode.vue b/src/pages/Item/Card/ItemBarcode.vue index 590b524cd84..6db5943c77b 100644 --- a/src/pages/Item/Card/ItemBarcode.vue +++ b/src/pages/Item/Card/ItemBarcode.vue @@ -92,7 +92,7 @@ const submit = async (rows) => { class="cursor-pointer fill-icon-on-hover" color="primary" icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat > <QTooltip> diff --git a/src/pages/Item/Card/ItemBasicData.vue b/src/pages/Item/Card/ItemBasicData.vue index df7e71684c6..4c96401f3b9 100644 --- a/src/pages/Item/Card/ItemBasicData.vue +++ b/src/pages/Item/Card/ItemBasicData.vue @@ -11,7 +11,6 @@ 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(); @@ -55,8 +54,9 @@ const onIntrastatCreated = (response, formData) => { auto-load /> <FormModel + :url="`Items/${route.params.id}`" :url-update="`Items/${route.params.id}`" - model="Item" + model="item" auto-load :clear-store-on-unmount="false" > @@ -209,20 +209,30 @@ const onIntrastatCreated = (response, formData) => { /> </VnRow> <VnRow class="row q-gutter-md q-mb-md"> - <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" - /> + <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> </VnRow> <VnRow> <VnInput diff --git a/src/pages/Item/Card/ItemBotanical.vue b/src/pages/Item/Card/ItemBotanical.vue index a40d8158987..4894d94fcd3 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 '../components/CreateGenusForm.vue'; -import CreateSpecieForm from '../components/CreateSpecieForm.vue'; +import CreateGenusForm from './CreateGenusForm.vue'; +import CreateSpecieForm from './CreateSpecieForm.vue'; const route = useRoute(); const { t } = useI18n(); diff --git a/src/pages/Item/Card/ItemCard.vue b/src/pages/Item/Card/ItemCard.vue index 610b77a02ae..2546982eb7b 100644 --- a/src/pages/Item/Card/ItemCard.vue +++ b/src/pages/Item/Card/ItemCard.vue @@ -5,7 +5,7 @@ import ItemDescriptor from './ItemDescriptor.vue'; <template> <VnCardBeta data-key="Item" - :url="`Items/${$route.params.id}/getCard`" + base-url="Items" :descriptor="ItemDescriptor" /> </template> diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue index a4c58ef4b2c..c6fee8540ef 100644 --- a/src/pages/Item/Card/ItemDescriptor.vue +++ b/src/pages/Item/Card/ItemDescriptor.vue @@ -7,6 +7,7 @@ import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import axios from 'axios'; import { dashIfEmpty } from 'src/filters'; import { useArrayData } from 'src/composables/useArrayData'; @@ -34,10 +35,6 @@ const $props = defineProps({ type: Number, default: null, }, - proxyRender: { - type: Boolean, - default: false, - }, }); const route = useRoute(); @@ -58,8 +55,10 @@ onMounted(async () => { mounted.value = true; }); +const data = ref(useCardDescription()); const setData = async (entity) => { if (!entity) return; + data.value = useCardDescription(entity.name, entity.id); await updateStock(); }; @@ -91,7 +90,10 @@ const updateStock = async () => { <template> <CardDescriptor - data-key="Item" + data-key="ItemData" + module="Item" + :title="data.title" + :subtitle="data.subtitle" :summary="$props.summary" :url="`Items/${entityId}/getCard`" @on-fetch="setData" @@ -115,7 +117,7 @@ const updateStock = async () => { <template #value> <span class="link"> {{ entity.itemType?.worker?.user?.name }} - <WorkerDescriptorProxy :id="entity.itemType?.worker?.id ?? NaN" /> + <WorkerDescriptorProxy :id="entity.itemType?.worker?.id" /> </span> </template> </VnLv> @@ -150,7 +152,7 @@ const updateStock = async () => { </QCardActions> </template> <template #actions="{}"> - <QCardActions class="row justify-center" v-if="proxyRender"> + <QCardActions class="row justify-center"> <QBtn :to="{ name: 'ItemDiary', @@ -163,16 +165,6 @@ 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 f686e822115..2ffc9080f1a 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, String], + type: Number, required: true, }, dated: { @@ -21,8 +21,9 @@ const $props = defineProps({ }, }); </script> + <template> - <QPopupProxy style="max-width: 10px"> + <QPopupProxy> <ItemDescriptor v-if="$props.id" :id="$props.id" @@ -30,7 +31,6 @@ 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 b29e2a2a5a9..7ad60c9e0c6 100644 --- a/src/pages/Item/Card/ItemShelving.vue +++ b/src/pages/Item/Card/ItemShelving.vue @@ -110,16 +110,10 @@ 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 () => { @@ -163,7 +157,7 @@ watchEffect(selectedRows); openConfirmationModal( t('shelvings.removeConfirmTitle'), t('shelvings.removeConfirmSubtitle'), - removeLines, + removeLines ) " > diff --git a/src/pages/Item/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue index ab26b9caea5..5a7d7f818de 100644 --- a/src/pages/Item/Card/ItemTags.vue +++ b/src/pages/Item/Card/ItemTags.vue @@ -178,7 +178,7 @@ const insertTag = (rows) => { @click="insertTag(rows)" color="primary" icon="add" - v-shortcut="'+'" + shortcut="+" fab data-cy="createNewTag" > diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index fdfa1d3d1d7..1c4382fbd2a 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -65,19 +65,10 @@ 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', @@ -85,6 +76,7 @@ const columns = computed(() => [ }, { label: t('item.fixedPrice.packingPrice'), + field: 'rate3', name: 'rate3', ...defaultColumnAttrs, component: 'input', @@ -93,6 +85,7 @@ const columns = computed(() => [ { label: t('item.fixedPrice.minPrice'), + field: 'minPrice', name: 'minPrice', ...defaultColumnAttrs, component: 'input', @@ -115,6 +108,7 @@ const columns = computed(() => [ }, { label: t('item.fixedPrice.ended'), + field: 'ended', name: 'ended', ...defaultColumnAttrs, columnField: { @@ -130,6 +124,7 @@ const columns = computed(() => [ { label: t('globals.warehouse'), + field: 'warehouseFk', name: 'warehouseFk', ...defaultColumnAttrs, columnClass: 'shrink', @@ -420,6 +415,7 @@ function handleOnDataSave({ CrudModelRef }) { 'row-key': 'id', selection: 'multiple', }" + :use-model="true" v-model:selected="rowsSelected" :create-as-dialog="false" :create="{ diff --git a/src/pages/Item/ItemType/Card/ItemTypeBasicData.vue b/src/pages/Item/ItemType/Card/ItemTypeBasicData.vue index 475dffd8b9a..b4032ff8a5b 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeBasicData.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeBasicData.vue @@ -40,7 +40,12 @@ const itemPackingTypesOptions = ref([]); }" auto-load /> - <FormModel :url-update="`ItemTypes/${route.params.id}`" model="ItemType" auto-load> + <FormModel + :url="`ItemTypes/${route.params.id}`" + :url-update="`ItemTypes/${route.params.id}`" + model="itemTypeBasicData" + auto-load + > <template #form="{ data }"> <VnRow> <VnInput v-model="data.code" :label="t('itemType.shared.code')" /> diff --git a/src/pages/Item/ItemType/Card/ItemTypeCard.vue b/src/pages/Item/ItemType/Card/ItemTypeCard.vue index 84e810de513..fa51e428e0a 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeCard.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeCard.vue @@ -1,14 +1,12 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue'; -import filter from './ItemTypeFilter.js'; </script> <template> <VnCardBeta - data-key="ItemType" - url="ItemTypes" - :filter="filter" + data-key="ItemTypeSummary" + base-url="ItemTypes" :descriptor="ItemTypeDescriptor" /> </template> diff --git a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue index 725fb30aa60..09d3dbce575 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue @@ -1,11 +1,12 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; -import filter from './ItemTypeFilter.js'; +import useCardDescription from 'src/composables/useCardDescription'; const $props = defineProps({ id: { @@ -19,31 +20,46 @@ const $props = defineProps({ }); const route = useRoute(); +const { t } = useI18n(); const entityId = computed(() => { return $props.id || route.params.id; }); + +const itemTypeFilter = { + include: [ + { relation: 'worker' }, + { relation: 'category' }, + { relation: 'itemPackingType' }, + { relation: 'temperature' }, + ], +}; + +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); </script> + <template> <CardDescriptor + module="ItemType" :url="`ItemTypes/${entityId}`" - :filter="filter" - title="code" - data-key="ItemType" + :filter="itemTypeFilter" + :title="data.title" + :subtitle="data.subtitle" + data-key="itemTypeDescriptor" + @on-fetch="setData" > <template #body="{ entity }"> - <VnLv :label="$t('itemType.shared.code')" :value="entity.code" /> - <VnLv :label="$t('itemType.shared.name')" :value="entity.name" /> - <VnLv :label="$t('itemType.shared.worker')"> + <VnLv :label="t('itemType.shared.code')" :value="entity.code" /> + <VnLv :label="t('itemType.shared.name')" :value="entity.name" /> + <VnLv :label="t('itemType.shared.worker')"> <template #value> <span class="link">{{ entity.worker?.firstName }}</span> <WorkerDescriptorProxy :id="entity.worker?.id" /> </template> </VnLv> - <VnLv - :label="$t('itemType.shared.category')" - :value="entity.category?.name" - /> + <VnLv :label="t('itemType.shared.category')" :value="entity.category?.name" /> </template> </CardDescriptor> </template> + diff --git a/src/pages/Item/ItemType/Card/ItemTypeFilter.js b/src/pages/Item/ItemType/Card/ItemTypeFilter.js deleted file mode 100644 index 5651d368d83..00000000000 --- a/src/pages/Item/ItemType/Card/ItemTypeFilter.js +++ /dev/null @@ -1,8 +0,0 @@ -export default { - include: [ - { relation: 'worker' }, - { relation: 'category' }, - { relation: 'itemPackingType' }, - { relation: 'temperature' }, - ], -}; diff --git a/src/pages/Item/ItemType/Card/ItemTypeSummary.vue b/src/pages/Item/ItemType/Card/ItemTypeSummary.vue index 3b63c4b63ed..9ba774ca462 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeSummary.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeSummary.vue @@ -3,7 +3,7 @@ import { ref, computed, onUpdated } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; -import filter from './ItemTypeFilter.js'; + import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; @@ -21,6 +21,15 @@ const $props = defineProps({ }, }); +const itemTypeFilter = { + include: [ + { relation: 'worker' }, + { relation: 'category' }, + { relation: 'itemPackingType' }, + { relation: 'temperature' }, + ], +}; + const entityId = computed(() => $props.id || route.params.id); const summaryRef = ref(); const itemType = ref(); @@ -34,8 +43,8 @@ async function setItemTypeData(data) { <CardSummary ref="summaryRef" :url="`ItemTypes/${entityId}`" - data-key="ItemType" - :filter="filter" + data-key="ItemTypeSummary" + :filter="itemTypeFilter" @on-fetch="(data) => setItemTypeData(data)" class="full-width" > diff --git a/src/pages/Item/components/ItemProposal.vue b/src/pages/Item/components/ItemProposal.vue deleted file mode 100644 index d2dbea7b320..00000000000 --- a/src/pages/Item/components/ItemProposal.vue +++ /dev/null @@ -1,332 +0,0 @@ -<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 deleted file mode 100644 index 7da0ce398fd..00000000000 --- a/src/pages/Item/components/ItemProposalProxy.vue +++ /dev/null @@ -1,56 +0,0 @@ -<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 9d27fc96e2a..bc73abb1220 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -112,7 +112,6 @@ 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 @@ -131,7 +130,6 @@ item: origin: Orig. userName: Buyer weight: Weight - color: Color weightByPiece: Weight/stem stemMultiplier: Multiplier producer: Producer @@ -217,24 +215,4 @@ item: specie: Specie search: 'Search item' searchInfo: 'You can search by id' - 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 + regularizeStock: Regularize stock \ No newline at end of file diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 935f5160bf1..dd5074f5fc4 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -118,7 +118,6 @@ 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 @@ -136,7 +135,6 @@ item: size: Medida origin: Orig. weight: Peso - color: Color weightByPiece: Peso/tallo userName: Comprador stemMultiplier: Multiplicador @@ -222,30 +220,5 @@ item: achieved: 'Conseguido' concept: 'Concepto' state: 'Estado' -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' + search: 'Buscar artículo' + searchInfo: 'Puedes buscar por id' diff --git a/src/pages/Monitor/MonitorOrders.vue b/src/pages/Monitor/MonitorOrders.vue index 873f8abb4ee..4efab56fbe5 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/locale/en.yml b/src/pages/Monitor/locale/en.yml index 496c8761a16..21324087c42 100644 --- a/src/pages/Monitor/locale/en.yml +++ b/src/pages/Monitor/locale/en.yml @@ -38,7 +38,6 @@ salesTicketsTable: payMethod: Pay method department: Department packing: ITP - hasItemLost: Item lost searchBar: label: Search tickets info: Search tickets by id or alias diff --git a/src/pages/Monitor/locale/es.yml b/src/pages/Monitor/locale/es.yml index f6a29879f08..30afb1904ba 100644 --- a/src/pages/Monitor/locale/es.yml +++ b/src/pages/Monitor/locale/es.yml @@ -39,7 +39,6 @@ salesTicketsTable: payMethod: Método de pago department: Departamento packing: ITP - hasItemLost: Artículo perdido searchBar: label: Buscar tickets info: Buscar tickets por identificador o alias diff --git a/src/pages/Order/Card/CatalogFilterValueDialog.vue b/src/pages/Order/Card/CatalogFilterValueDialog.vue index d1bd48c9e90..b91e7d229bf 100644 --- a/src/pages/Order/Card/CatalogFilterValueDialog.vue +++ b/src/pages/Order/Card/CatalogFilterValueDialog.vue @@ -110,7 +110,7 @@ const getSelectedTagValues = async (tag) => { </div> <QBtn icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat class="filter-icon q-mb-md" size="md" diff --git a/src/pages/Order/Card/OrderBasicData.vue b/src/pages/Order/Card/OrderBasicData.vue index 9c02d7494b5..8594a05f459 100644 --- a/src/pages/Order/Card/OrderBasicData.vue +++ b/src/pages/Order/Card/OrderBasicData.vue @@ -14,6 +14,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; const { t } = useI18n(); const route = useRoute(); const state = useState(); +const ORDER_MODEL = 'order'; const isNew = Boolean(!route.params.id); const clientList = ref([]); @@ -31,7 +32,7 @@ const fetchAddressList = async (addressId) => { }); addressList.value = data; if (addressList.value?.length === 1) { - state.get('Order').addressFk = addressList.value[0].id; + state.get(ORDER_MODEL).addressFk = addressList.value[0].id; } }; @@ -90,8 +91,9 @@ const onClientChange = async (clientId) => { <VnSubToolbar v-if="isNew" /> <div class="q-pa-md"> <FormModel + :url="`Orders/${route.params.id}`" :url-update="`Orders/${route.params.id}/updateBasicData`" - model="Order" + :model="ORDER_MODEL" :filter="orderFilter" @on-fetch="fetchOrderDetails" auto-load diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue index ad5c73a878c..823815f59b9 100644 --- a/src/pages/Order/Card/OrderCard.vue +++ b/src/pages/Order/Card/OrderCard.vue @@ -1,14 +1,12 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue'; -import filter from './OrderFilter.js'; </script> <template> <VnCardBeta data-key="Order" - url="Orders" - :filter="filter" + base-url="Orders" :descriptor="OrderDescriptor" /> </template> diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue index 76e60898343..262f503fd11 100644 --- a/src/pages/Order/Card/OrderCatalogFilter.vue +++ b/src/pages/Order/Card/OrderCatalogFilter.vue @@ -184,7 +184,7 @@ function addOrder(value, field, params) { {{ t( categoryList.find((c) => c.id == customTag.value)?.name || - '', + '' ) }} </strong> @@ -296,7 +296,7 @@ function addOrder(value, field, params) { <template #append> <QBtn icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat color="primary" size="md" diff --git a/src/pages/Order/Card/OrderCatalogItemDialog.vue b/src/pages/Order/Card/OrderCatalogItemDialog.vue index 766945e4ddf..77f6a840548 100644 --- a/src/pages/Order/Card/OrderCatalogItemDialog.vue +++ b/src/pages/Order/Card/OrderCatalogItemDialog.vue @@ -20,7 +20,7 @@ const props = defineProps({ }); const state = useState(); -const orderData = computed(() => state.get('Order')); +const orderData = computed(() => state.get('orderData')); const prices = ref((props.item.prices || []).map((item) => ({ ...item, quantity: 0 }))); const isLoading = ref(false); @@ -39,11 +39,11 @@ 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); - state.set('Order', { + state.set('orderData', { ...orderData.value, items, }); @@ -56,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 0d18864dc86..0d5f0146f7b 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -4,7 +4,8 @@ import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { toCurrency, toDate } from 'src/filters'; import { useState } from 'src/composables/useState'; -import filter from './OrderFilter.js'; +import useCardDescription from 'src/composables/useCardDescription'; + import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import FetchData from 'components/FetchData.vue'; @@ -23,15 +24,44 @@ const $props = defineProps({ const route = useRoute(); const state = useState(); const { t } = useI18n(); +const data = ref(useCardDescription()); const getTotalRef = ref(); const entityId = computed(() => { return $props.id || route.params.id; }); +const filter = { + include: [ + { relation: 'agencyMode', scope: { fields: ['name'] } }, + { + relation: 'address', + scope: { fields: ['nickname'] }, + }, + { relation: 'rows', scope: { fields: ['id'] } }, + { + relation: 'client', + scope: { + fields: [ + 'salesPersonFk', + 'name', + 'isActive', + 'isFreezed', + 'isTaxDataChecked', + ], + include: { + relation: 'salesPersonUser', + scope: { fields: ['id', 'name'] }, + }, + }, + }, + ], +}; + const setData = (entity) => { if (!entity) return; getTotalRef.value && getTotalRef.value.fetch(); + data.value = useCardDescription(entity?.client?.name, entity?.id); state.set('orderTotal', total); }; @@ -57,9 +87,11 @@ const total = ref(0); ref="descriptor" :url="`Orders/${entityId}`" :filter="filter" - title="client.name" + module="Order" + :title="data.title" + :subtitle="data.subtitle" @on-fetch="setData" - data-key="Order" + data-key="orderData" > <template #body="{ entity }"> <VnLv diff --git a/src/pages/Order/Card/OrderFilter.js b/src/pages/Order/Card/OrderFilter.js deleted file mode 100644 index 3e521b92c48..00000000000 --- a/src/pages/Order/Card/OrderFilter.js +++ /dev/null @@ -1,26 +0,0 @@ -export default { - include: [ - { relation: 'agencyMode', scope: { fields: ['name'] } }, - { - relation: 'address', - scope: { fields: ['nickname'] }, - }, - { relation: 'rows', scope: { fields: ['id'] } }, - { - relation: 'client', - scope: { - fields: [ - 'salesPersonFk', - 'name', - 'isActive', - 'isFreezed', - 'isTaxDataChecked', - ], - include: { - relation: 'salesPersonUser', - scope: { fields: ['id', 'name'] }, - }, - }, - }, - ], -}; diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue index 1b864de6f88..cf219a244f8 100644 --- a/src/pages/Order/Card/OrderLines.vue +++ b/src/pages/Order/Card/OrderLines.vue @@ -21,7 +21,7 @@ const router = useRouter(); const route = useRoute(); const { t } = useI18n(); const quasar = useQuasar(); -const descriptorData = useArrayData('Order'); +const descriptorData = useArrayData('orderData'); const componentKey = ref(0); const tableLinesRef = ref(); const order = ref(); @@ -238,7 +238,7 @@ watch( lineFilter.value.where.orderFk = router.currentRoute.value.params.id; tableLinesRef.value.reload(); - }, + } ); </script> diff --git a/src/pages/Order/Card/OrderSummary.vue b/src/pages/Order/Card/OrderSummary.vue index a4bdb288102..a289688e47e 100644 --- a/src/pages/Order/Card/OrderSummary.vue +++ b/src/pages/Order/Card/OrderSummary.vue @@ -27,7 +27,7 @@ const $props = defineProps({ const entityId = computed(() => $props.id || route.params.id); const summary = ref(); const quasar = useQuasar(); -const descriptorData = useArrayData('Order'); +const descriptorData = useArrayData('orderData'); const detailsColumns = ref([ { name: 'item', diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 40990f32906..21cb5ed7e99 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -71,9 +71,8 @@ const columns = computed(() => [ format: (row) => row?.name, }, { - align: 'center', + align: 'left', name: 'isConfirmed', - component: 'checkbox', label: t('module.isConfirmed'), }, { @@ -96,9 +95,7 @@ const columns = computed(() => [ columnField: { component: null, }, - style: () => { - return { color: 'positive' }; - }, + style: 'color="positive"', }, { align: 'left', diff --git a/src/pages/Shelving/Parking/Card/ParkingBasicData.vue b/src/pages/Parking/Card/ParkingBasicData.vue similarity index 68% rename from src/pages/Shelving/Parking/Card/ParkingBasicData.vue rename to src/pages/Parking/Card/ParkingBasicData.vue index 3de35800218..550a0684e81 100644 --- a/src/pages/Shelving/Parking/Card/ParkingBasicData.vue +++ b/src/pages/Parking/Card/ParkingBasicData.vue @@ -1,11 +1,16 @@ <script setup> -import { ref } from 'vue'; +import { ref, computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import VnRow from 'components/ui/VnRow.vue'; import FetchData from 'src/components/FetchData.vue'; import VnInput from 'src/components/common/VnInput.vue'; import FormModel from 'components/FormModel.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +const { t } = useI18n(); +const route = useRoute(); +const parkingId = computed(() => route.params?.id || null); const sectors = ref([]); const sectorFilter = { fields: ['id', 'description'] }; @@ -22,21 +27,18 @@ const filter = { @on-fetch="(data) => (sectors = data)" auto-load /> - <FormModel model="Parking" auto-load> + <FormModel :url="`Parkings/${parkingId}`" model="parking" :filter="filter" auto-load> <template #form="{ data }"> <VnRow> - <VnInput v-model="data.code" :label="$t('globals.code')" /> - <VnInput - v-model="data.pickingOrder" - :label="$t('parking.pickingOrder')" - /> + <VnInput v-model="data.code" :label="t('globals.code')" /> + <VnInput v-model="data.pickingOrder" :label="t('parking.pickingOrder')" /> </VnRow> <VnRow> <VnSelect v-model="data.sectorFk" option-value="id" option-label="description" - :label="$t('parking.sector')" + :label="t('parking.sector')" :options="sectors" use-input input-debounce="0" diff --git a/src/pages/Shelving/Parking/Card/ParkingCard.vue b/src/pages/Parking/Card/ParkingCard.vue similarity index 53% rename from src/pages/Shelving/Parking/Card/ParkingCard.vue rename to src/pages/Parking/Card/ParkingCard.vue index b32c1b7d335..1cd2df7b743 100644 --- a/src/pages/Shelving/Parking/Card/ParkingCard.vue +++ b/src/pages/Parking/Card/ParkingCard.vue @@ -1,14 +1,12 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; -import ParkingDescriptor from 'pages/Shelving/Parking/Card/ParkingDescriptor.vue'; -import filter from './ParkingFilter.js'; +import ParkingDescriptor from 'pages/Parking/Card/ParkingDescriptor.vue'; </script> <template> <VnCardBeta data-key="Parking" - url="Parkings" - :filter="filter" + base-url="Parkings" :descriptor="ParkingDescriptor" /> </template> diff --git a/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue b/src/pages/Parking/Card/ParkingDescriptor.vue similarity index 58% rename from src/pages/Shelving/Parking/Card/ParkingDescriptor.vue rename to src/pages/Parking/Card/ParkingDescriptor.vue index 46c9f8ea0a8..d36ea16fc22 100644 --- a/src/pages/Shelving/Parking/Card/ParkingDescriptor.vue +++ b/src/pages/Parking/Card/ParkingDescriptor.vue @@ -1,9 +1,10 @@ <script setup> import { computed } from 'vue'; +import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; -import filter from './ParkingFilter.js'; + const props = defineProps({ id: { type: Number, @@ -12,11 +13,18 @@ const props = defineProps({ }, }); +const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => props.id || route.params.id); + +const filter = { + fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'], + include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }], +}; </script> <template> <CardDescriptor + module="Parking" data-key="Parking" :url="`Parkings/${entityId}`" title="code" @@ -24,9 +32,9 @@ const entityId = computed(() => props.id || route.params.id); :to-module="{ name: 'ParkingList' }" > <template #body="{ entity }"> - <VnLv :label="$t('globals.code')" :value="entity.code" /> - <VnLv :label="$t('parking.pickingOrder')" :value="entity.pickingOrder" /> - <VnLv :label="$t('parking.sector')" :value="entity.sector?.description" /> + <VnLv :label="t('globals.code')" :value="entity.code" /> + <VnLv :label="t('parking.pickingOrder')" :value="entity.pickingOrder" /> + <VnLv :label="t('parking.sector')" :value="entity.sector?.description" /> </template> </CardDescriptor> </template> diff --git a/src/pages/Shelving/Parking/Card/ParkingLog.vue b/src/pages/Parking/Card/ParkingLog.vue similarity index 100% rename from src/pages/Shelving/Parking/Card/ParkingLog.vue rename to src/pages/Parking/Card/ParkingLog.vue diff --git a/src/pages/Shelving/Parking/Card/ParkingSummary.vue b/src/pages/Parking/Card/ParkingSummary.vue similarity index 100% rename from src/pages/Shelving/Parking/Card/ParkingSummary.vue rename to src/pages/Parking/Card/ParkingSummary.vue diff --git a/src/pages/Shelving/Parking/ParkingFilter.vue b/src/pages/Parking/ParkingFilter.vue similarity index 100% rename from src/pages/Shelving/Parking/ParkingFilter.vue rename to src/pages/Parking/ParkingFilter.vue diff --git a/src/pages/Shelving/Parking/ParkingList.vue b/src/pages/Parking/ParkingList.vue similarity index 90% rename from src/pages/Shelving/Parking/ParkingList.vue rename to src/pages/Parking/ParkingList.vue index fe6c93ba538..bce87126ead 100644 --- a/src/pages/Shelving/Parking/ParkingList.vue +++ b/src/pages/Parking/ParkingList.vue @@ -9,7 +9,6 @@ import CardList from 'components/ui/CardList.vue'; import VnLv from 'components/ui/VnLv.vue'; import ParkingFilter from './ParkingFilter.vue'; import ParkingSummary from './Card/ParkingSummary.vue'; -import exprBuilder from './ParkingExprBuilder.js'; import VnSection from 'src/components/common/VnSection.vue'; const stateStore = useStateStore(); @@ -24,7 +23,19 @@ onUnmounted(() => (stateStore.rightDrawer = false)); const filter = { fields: ['id', 'sectorFk', 'code', 'pickingOrder'], }; + +function exprBuilder(param, value) { + switch (param) { + case 'code': + return { [param]: { like: `%${value}%` } }; + case 'sectorFk': + return { [param]: value }; + case 'search': + return { or: [{ code: { like: `%${value}%` } }, { id: value }] }; + } +} </script> + <template> <VnSection :data-key="dataKey" diff --git a/src/pages/Shelving/Parking/locale/en.yml b/src/pages/Parking/locale/en.yml similarity index 100% rename from src/pages/Shelving/Parking/locale/en.yml rename to src/pages/Parking/locale/en.yml diff --git a/src/pages/Shelving/Parking/locale/es.yml b/src/pages/Parking/locale/es.yml similarity index 100% rename from src/pages/Shelving/Parking/locale/es.yml rename to src/pages/Parking/locale/es.yml diff --git a/src/pages/Route/Agency/AgencyList.vue b/src/pages/Route/Agency/AgencyList.vue index 5c2904bf3a4..4322b9bc808 100644 --- a/src/pages/Route/Agency/AgencyList.vue +++ b/src/pages/Route/Agency/AgencyList.vue @@ -51,6 +51,7 @@ const columns = computed(() => [ name: 'isAnyVolumeAllowed', component: 'checkbox', cardVisible: true, + disable: true, }, { align: 'right', @@ -71,7 +72,7 @@ const columns = computed(() => [ :data-key :columns="columns" prefix="agency" - :right-filter="true" + :right-filter="false" :array-data-props="{ url: 'Agencies', order: 'name', @@ -82,7 +83,6 @@ 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/AgencyBasicData.vue b/src/pages/Route/Agency/Card/AgencyBasicData.vue index 4270b136c44..599058b3e21 100644 --- a/src/pages/Route/Agency/Card/AgencyBasicData.vue +++ b/src/pages/Route/Agency/Card/AgencyBasicData.vue @@ -21,7 +21,7 @@ const warehouses = ref([]); @on-fetch="(data) => (warehouses = data)" auto-load /> - <FormModel :update-url="`Agencies/${routeId}`" model="Agency" auto-load> + <FormModel :url="`Agencies/${routeId}`" model="agency" auto-load> <template #form="{ data }"> <VnRow> <VnInput v-model="data.name" :label="t('globals.name')" /> diff --git a/src/pages/Route/Agency/Card/AgencyCard.vue b/src/pages/Route/Agency/Card/AgencyCard.vue index 7dc31f8ba35..35685790a60 100644 --- a/src/pages/Route/Agency/Card/AgencyCard.vue +++ b/src/pages/Route/Agency/Card/AgencyCard.vue @@ -3,5 +3,5 @@ import AgencyDescriptor from 'pages/Route/Agency/Card/AgencyDescriptor.vue'; import VnCardBeta from 'src/components/common/VnCardBeta.vue'; </script> <template> - <VnCardBeta data-key="Agency" url="Agencies" :descriptor="AgencyDescriptor" /> + <VnCardBeta data-key="Agency" base-url="Agencies" :descriptor="AgencyDescriptor" /> </template> diff --git a/src/pages/Route/Agency/Card/AgencyDescriptor.vue b/src/pages/Route/Agency/Card/AgencyDescriptor.vue index a0472c6c3c8..b9772037c76 100644 --- a/src/pages/Route/Agency/Card/AgencyDescriptor.vue +++ b/src/pages/Route/Agency/Card/AgencyDescriptor.vue @@ -22,6 +22,7 @@ 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/Agency/Card/AgencyWorkcenter.vue b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue index 9a921386877..7cabf396d0e 100644 --- a/src/pages/Route/Agency/Card/AgencyWorkcenter.vue +++ b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue @@ -88,7 +88,7 @@ async function deleteWorCenter(id) { </VnPaginate> </div> <QPageSticky :offset="[18, 18]"> - <QBtn @click.stop="dialog.show()" color="primary" fab v-shortcut="'+'" icon="add"> + <QBtn @click.stop="dialog.show()" color="primary" fab shortcut="+" icon="add"> <QDialog ref="dialog"> <FormModelPopup :title="t('Add work center')" diff --git a/src/pages/Route/Card/RouteCard.vue b/src/pages/Route/Card/RouteCard.vue index c178dc6bf34..81b6cfa169a 100644 --- a/src/pages/Route/Card/RouteCard.vue +++ b/src/pages/Route/Card/RouteCard.vue @@ -1,13 +1,12 @@ <script setup> import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue'; import VnCardBeta from 'src/components/common/VnCardBeta.vue'; -import filter from './RouteFilter.js'; </script> <template> <VnCardBeta data-key="Route" - url="Routes" - :filter="filter" + base-url="Routes" + custom-url="Routes/filter" :descriptor="RouteDescriptor" /> </template> diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index 503cd1941f4..68c08b8210e 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -1,14 +1,13 @@ <script setup> import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; +import useCardDescription from 'composables/useCardDescription'; 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: { type: Number, @@ -18,6 +17,7 @@ const $props = defineProps({ }); const route = useRoute(); +const { t } = useI18n(); const zone = ref(); const zoneId = ref(); const entityId = computed(() => { @@ -36,31 +36,81 @@ const getZone = async () => { const { data: zoneData } = await axios.get(`Zones/${zoneId.value}`); zone.value = zoneData.name; }; + +const filter = { + fields: [ + 'id', + 'workerFk', + 'agencyModeFk', + 'dated', + 'm3', + 'warehouseFk', + 'description', + 'vehicleFk', + 'kmStart', + 'kmEnd', + 'started', + 'finished', + 'cost', + 'isOk', + ], + include: [ + { relation: 'agencyMode', scope: { fields: ['id', 'name'] } }, + { + relation: 'vehicle', + scope: { fields: ['id', 'm3'] }, + }, + { + relation: 'ticket', + scope: { + fields: ['id', 'name', 'zoneFk'], + include: { relation: 'zone', scope: { fields: ['id', 'name'] } }, + }, + }, + { + relation: 'worker', + scope: { + fields: ['id'], + include: { + relation: 'user', + scope: { + fields: ['id'], + include: { relation: 'emailUser', scope: { fields: ['email'] } }, + }, + }, + }, + }, + ], +}; 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" - data-key="Route" + :title="data.title" + :subtitle="data.subtitle" + data-key="routeData" + @on-fetch="setData" width="lg-width" > <template #body="{ entity }"> - <VnLv :label="$t('Date')" :value="toDate(entity?.dated)" /> - <VnLv :label="$t('Agency')" :value="entity?.agencyMode?.name" /> - <VnLv :label="$t('Zone')" :value="zone" /> + <VnLv :label="t('Date')" :value="toDate(entity?.dated)" /> + <VnLv :label="t('Agency')" :value="entity?.agencyMode?.name" /> + <VnLv :label="t('Zone')" :value="zone" /> <VnLv - :label="$t('Volume')" + :label="t('Volume')" :value="`${dashIfEmpty(entity?.m3)} / ${dashIfEmpty( entity?.vehicle?.m3, )} m³`" /> - <VnLv :label="$t('Description')" :value="entity?.description" /> + <VnLv :label="t('Description')" :value="entity?.description" /> </template> <template #menu="{ entity }"> <RouteDescriptorMenu :route="entity" /> diff --git a/src/pages/Route/Card/RouteFilter.js b/src/pages/Route/Card/RouteFilter.js deleted file mode 100644 index 90ee71bf77c..00000000000 --- a/src/pages/Route/Card/RouteFilter.js +++ /dev/null @@ -1,39 +0,0 @@ -export default { - fields: [ - 'code', - 'id', - 'workerFk', - 'agencyModeFk', - 'created', - 'm3', - 'warehouseFk', - 'description', - 'vehicleFk', - 'kmStart', - 'kmEnd', - 'started', - 'finished', - 'cost', - 'isOk', - ], - include: [ - { relation: 'agencyMode', scope: { fields: ['id', 'name'] } }, - { - relation: 'vehicle', - scope: { fields: ['id', 'm3'] }, - }, - { - relation: 'worker', - scope: { - fields: ['id'], - include: { - relation: 'user', - scope: { - fields: ['id'], - include: { relation: 'emailUser', scope: { fields: ['email'] } }, - }, - }, - }, - }, - ], -}; diff --git a/src/pages/Route/Card/RouteFilter.vue b/src/pages/Route/Card/RouteFilter.vue index 21858102be2..72bfed1da7e 100644 --- a/src/pages/Route/Card/RouteFilter.vue +++ b/src/pages/Route/Card/RouteFilter.vue @@ -100,7 +100,7 @@ const emit = defineEmits(['search']); <VnSelect :label="t('Vehicle')" v-model="params.vehicleFk" - url="Vehicles/active" + url="Vehicles" sort-by="numberPlate ASC" option-value="id" option-label="numberPlate" diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue index 667204b15ac..633ff44bcb0 100644 --- a/src/pages/Route/Card/RouteForm.vue +++ b/src/pages/Route/Card/RouteForm.vue @@ -11,7 +11,6 @@ import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; import axios from 'axios'; import VnInputTime from 'components/common/VnInputTime.vue'; -import filter from './RouteFilter.js'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); @@ -28,6 +27,52 @@ const defaultInitialData = { isOk: false, }; const maxDistance = ref(); + +const routeFilter = { + fields: [ + 'id', + 'workerFk', + 'agencyModeFk', + 'dated', + 'm3', + 'warehouseFk', + 'description', + 'vehicleFk', + 'kmStart', + 'kmEnd', + 'started', + 'finished', + 'cost', + 'isOk', + ], + include: [ + { relation: 'agencyMode', scope: { fields: ['id', 'name'] } }, + { + relation: 'vehicle', + scope: { fields: ['id', 'm3'] }, + }, + { + relation: 'ticket', + scope: { + fields: ['id', 'name', 'zoneFk'], + include: { relation: 'zone', scope: { fields: ['id', 'name'] } }, + }, + }, + { + relation: 'worker', + scope: { + fields: ['id'], + include: { + relation: 'user', + scope: { + fields: ['id'], + include: { relation: 'emailUser', scope: { fields: ['email'] } }, + }, + }, + }, + }, + ], +}; const onSave = (data, response) => { if (isNew) { axios.post(`Routes/${response?.id}/updateWorkCenter`); @@ -44,10 +89,11 @@ const onSave = (data, response) => { sort-by="id ASC" /> <FormModel + :url="isNew ? null : `Routes/${route.params?.id}`" :url-create="isNew ? 'Routes' : null" :observe-form-changes="!isNew" - :filter="filter" - model="Route" + :filter="routeFilter" + model="route" :auto-load="!isNew" :form-initial-data="isNew ? defaultInitialData : null" @on-data-saved="onSave" @@ -58,7 +104,7 @@ const onSave = (data, response) => { <VnSelect :label="t('Vehicle')" v-model="data.vehicleFk" - url="Vehicles/active" + url="Vehicles" sort-by="numberPlate ASC" option-value="id" option-label="numberPlate" diff --git a/src/pages/Route/Roadmap/RoadmapBasicData.vue b/src/pages/Route/Roadmap/RoadmapBasicData.vue index a9e6059c337..2fe80536294 100644 --- a/src/pages/Route/Roadmap/RoadmapBasicData.vue +++ b/src/pages/Route/Roadmap/RoadmapBasicData.vue @@ -11,16 +11,17 @@ import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; const { t } = useI18n(); const router = useRouter(); +const filter = { include: [{ relation: 'supplier' }] }; const onSave = (data, response) => { router.push({ name: 'RoadmapSummary', params: { id: response?.id } }); }; </script> <template> <FormModel - :update-url="`Roadmaps/${$route.params?.id}`" :url="`Roadmaps/${$route.params?.id}`" observe-form-changes - model="Roadmap" + :filter="filter" + model="roadmap" auto-load @on-data-saved="onSave" > diff --git a/src/pages/Route/Roadmap/RoadmapCard.vue b/src/pages/Route/Roadmap/RoadmapCard.vue index 48ba516a114..0b81de6737d 100644 --- a/src/pages/Route/Roadmap/RoadmapCard.vue +++ b/src/pages/Route/Roadmap/RoadmapCard.vue @@ -3,5 +3,5 @@ import VnCardBeta from 'components/common/VnCardBeta.vue'; import RoadmapDescriptor from 'pages/Route/Roadmap/RoadmapDescriptor.vue'; </script> <template> - <VnCardBeta data-key="Roadmap" url="Roadmaps" :descriptor="RoadmapDescriptor" /> + <VnCardBeta data-key="Roadmap" base-url="Roadmaps" :descriptor="RoadmapDescriptor" /> </template> diff --git a/src/pages/Route/Roadmap/RoadmapDescriptor.vue b/src/pages/Route/Roadmap/RoadmapDescriptor.vue index baa864a150f..788173688cb 100644 --- a/src/pages/Route/Roadmap/RoadmapDescriptor.vue +++ b/src/pages/Route/Roadmap/RoadmapDescriptor.vue @@ -1,13 +1,13 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; +import useCardDescription from 'composables/useCardDescription'; import { dashIfEmpty, toDateHourMin } from 'src/filters'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; import RoadmapDescriptorMenu from 'pages/Route/Roadmap/RoadmapDescriptorMenu.vue'; -import filter from 'pages/Route/Roadmap/RoadmapFilter.js'; const $props = defineProps({ id: { @@ -23,10 +23,22 @@ const { t } = useI18n(); const entityId = computed(() => { return $props.id || route.params.id; }); + +const filter = { include: [{ relation: 'supplier' }] }; +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); </script> <template> - <CardDescriptor :url="`Roadmaps/${entityId}`" :filter="filter" data-key="Roadmap"> + <CardDescriptor + module="Roadmap" + :url="`Roadmaps/${entityId}`" + :filter="filter" + :title="data.title" + :subtitle="data.subtitle" + data-key="Roadmap" + @on-fetch="setData" + > <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.js b/src/pages/Route/Roadmap/RoadmapFilter.js deleted file mode 100644 index 0ae890363ae..00000000000 --- a/src/pages/Route/Roadmap/RoadmapFilter.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - include: [{ relation: 'supplier' }], -}; diff --git a/src/pages/Route/Roadmap/RoadmapStops.vue b/src/pages/Route/Roadmap/RoadmapStops.vue index e4085d57295..d8215ea4984 100644 --- a/src/pages/Route/Roadmap/RoadmapStops.vue +++ b/src/pages/Route/Roadmap/RoadmapStops.vue @@ -68,7 +68,7 @@ const updateDefaultStop = (data) => { <QBtn flat icon="add" - v-shortcut="'+'" + shortcut="+" class="cursor-pointer" color="primary" @click="roadmapStopsCrudRef.insert()" diff --git a/src/pages/Route/Roadmap/RoadmapSummary.vue b/src/pages/Route/Roadmap/RoadmapSummary.vue index 0c1c2b90398..1fbb1897d5b 100644 --- a/src/pages/Route/Roadmap/RoadmapSummary.vue +++ b/src/pages/Route/Roadmap/RoadmapSummary.vue @@ -67,6 +67,7 @@ const filter = { }, }, ], + where: { id: entityId }, }; </script> @@ -75,7 +76,7 @@ const filter = { <CardSummary data-key="RoadmapSummary" ref="summary" - :url="`Roadmaps/${entityId}`" + :url="`Roadmaps`" :filter="filter" > <template #header-left> diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue index 46bc1a69080..221fc47545a 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 { dashIfEmpty, toDate, toHour } from 'src/filters'; +import { toDate } from 'src/filters'; import { useRouter } from 'vue-router'; import { usePrintService } from 'src/composables/usePrintService'; @@ -38,7 +38,7 @@ const routeFilter = { }; const columns = computed(() => [ { - align: 'center', + align: 'left', name: 'id', label: 'Id', chip: { @@ -48,7 +48,7 @@ const columns = computed(() => [ columnFilter: false, }, { - align: 'center', + align: 'left', name: 'workerFk', label: t('route.Worker'), create: true, @@ -68,10 +68,10 @@ const columns = computed(() => [ }, useLike: false, cardVisible: true, - format: (row, dashIfEmpty) => dashIfEmpty(row.workerUserName), + format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), }, { - align: 'center', + align: 'left', name: 'agencyModeFk', label: t('route.Agency'), isTitle: true, @@ -87,17 +87,17 @@ const columns = computed(() => [ }, }, columnClass: 'expand', - format: (row, dashIfEmpty) => dashIfEmpty(row.agencyName), }, { - align: 'center', + align: 'left', name: 'vehicleFk', label: t('route.Vehicle'), cardVisible: true, create: true, component: 'select', attrs: { - url: 'vehicles/active', + url: 'vehicles', + fields: ['id', 'numberPlate'], optionLabel: 'numberPlate', optionFilterValue: 'numberPlate', find: { @@ -108,31 +108,29 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, - format: (row, dashIfEmpty) => dashIfEmpty(row.vehiclePlateNumber), }, { - align: 'center', + align: 'left', name: 'dated', label: t('route.Date'), columnFilter: false, cardVisible: true, create: true, component: 'date', - format: ({ dated }, dashIfEmpty) => - dated === '0000-00-00' ? dashIfEmpty(null) : toDate(dated), + format: ({ date }) => toDate(date), }, { - align: 'center', + align: 'left', name: 'from', label: t('route.From'), visible: false, cardVisible: true, create: true, component: 'date', - format: ({ from }) => toDate(from), + format: ({ date }) => toDate(date), }, { - align: 'center', + align: 'left', name: 'to', label: t('route.To'), visible: false, @@ -149,20 +147,18 @@ const columns = computed(() => [ columnClass: 'shrink', }, { - align: 'center', + align: 'left', name: 'started', label: t('route.hourStarted'), component: 'time', columnFilter: false, - format: ({ started }) => toHour(started), }, { - align: 'center', + align: 'left', name: 'finished', label: t('route.hourFinished'), component: 'time', columnFilter: false, - format: ({ finished }) => toHour(finished), }, { align: 'center', @@ -181,7 +177,7 @@ const columns = computed(() => [ visible: false, }, { - align: 'center', + align: 'left', name: 'description', label: t('route.Description'), isTitle: true, @@ -190,7 +186,7 @@ const columns = computed(() => [ field: 'description', }, { - align: 'center', + align: 'left', name: 'isOk', label: t('route.Served'), component: 'checkbox', @@ -304,62 +300,60 @@ const openTicketsDialog = (id) => { <RouteFilter data-key="RouteList" /> </template> </RightMenu> - <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" - 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> + <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> </template> diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index 9dad8ba22b5..bc3227f6c86 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -38,17 +38,6 @@ 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), @@ -59,15 +48,6 @@ 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, @@ -77,17 +57,6 @@ 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/RouteTickets.vue b/src/pages/Route/RouteTickets.vue index adc7dfdaaad..1416f77ce1e 100644 --- a/src/pages/Route/RouteTickets.vue +++ b/src/pages/Route/RouteTickets.vue @@ -120,8 +120,8 @@ const deletePriorities = async () => { try { await Promise.all( selectedRows.value.map((ticket) => - axios.patch(`Tickets/${ticket?.id}/`, { priority: null }), - ), + axios.patch(`Tickets/${ticket?.id}/`, { priority: null }) + ) ); } finally { refreshKey.value++; @@ -132,8 +132,8 @@ const setOrderedPriority = async () => { try { await Promise.all( ticketList.value.map((ticket, index) => - axios.patch(`Tickets/${ticket?.id}/`, { priority: index + 1 }), - ), + axios.patch(`Tickets/${ticket?.id}/`, { priority: index + 1 }) + ) ); } finally { refreshKey.value++; @@ -162,7 +162,7 @@ const setHighestPriority = async (ticket, ticketList) => { const goToBuscaman = async (ticket = null) => { await openBuscaman( routeEntity.value?.vehicleFk, - ticket ? [ticket] : selectedRows.value, + ticket ? [ticket] : selectedRows.value ); }; @@ -393,13 +393,7 @@ const openSmsDialog = async () => { </VnPaginate> </div> <QPageSticky :offset="[20, 20]"> - <QBtn - fab - icon="add" - v-shortcut="'+'" - color="primary" - @click="openTicketsDialog" - > + <QBtn fab icon="add" shortcut="+" color="primary" @click="openTicketsDialog"> <QTooltip> {{ t('Add ticket') }} </QTooltip> diff --git a/src/pages/Route/Vehicle/Card/VehicleBasicData.vue b/src/pages/Route/Vehicle/Card/VehicleBasicData.vue deleted file mode 100644 index e78bc6edd2c..00000000000 --- a/src/pages/Route/Vehicle/Card/VehicleBasicData.vue +++ /dev/null @@ -1,162 +0,0 @@ -<script setup> -import { ref } from 'vue'; -import FormModel from 'components/FormModel.vue'; -import FetchData from 'src/components/FetchData.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import VnRow from 'components/ui/VnRow.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnInputNumber from 'src/components/common/VnInputNumber.vue'; - -const warehouses = ref([]); -const companies = ref([]); -const countries = ref([]); -const fuelTypes = ref([]); -const bankPolicies = ref([]); -const deliveryPoints = ref([]); -</script> -<template> - <FetchData - url="Warehouses" - :filter="{ fields: ['id', 'name'] }" - @on-fetch="(data) => (warehouses = data)" - auto-load - /> - <FetchData - url="Companies" - :filter="{ fields: ['id', 'code'] }" - @on-fetch="(data) => (companies = data)" - auto-load - /> - <FetchData - url="Countries" - :filter="{ fields: ['code'] }" - @on-fetch="(data) => (countries = data)" - auto-load - /> - <FetchData - url="FuelTypes" - :filter="{ fields: ['id', 'name'] }" - @on-fetch="(data) => (fuelTypes = data)" - auto-load - /> - <FetchData - url="DeliveryPoints" - :filter="{ fields: ['id', 'name'] }" - @on-fetch="(data) => (deliveryPoints = data)" - auto-load - /> - <FormModel model="Vehicle" :url-update="`Vehicles/${$route.params.id}`"> - <template #form="{ data }"> - <VnRow> - <VnInput v-model="data.description" :label="$t('globals.description')" /> - <VnInput v-model="data.numberPlate" :label="$t('vehicle.numberPlate')" /> - </VnRow> - <VnRow> - <VnInput - v-model="data.model" - :label="$t('globals.model')" - :required="true" - /> - <VnSelect - url="VehicleTypes" - v-model="data.vehicleTypeFk" - :label="$t('globals.type')" - /> - </VnRow> - <VnRow> - <VnInput - v-model="data.tradeMark" - :label="$t('vehicle.tradeMark')" - :required="true" - /> - <VnInput v-model="data.chassis" :label="$t('vehicle.chassis')" /> - </VnRow> - <VnRow> - <VnSelect - v-model="data.fuelTypeFk" - :label="$t('globals.fuel')" - :options="fuelTypes" - /> - <VnSelect - v-model="data.deliveryPointFk" - :label="$t('globals.deliveryPoint')" - :options="deliveryPoints" - /> - </VnRow> - <VnRow> - <VnSelect - v-model="data.companyFk" - :label="$t('globals.company')" - :options="companies" - option-label="code" - /> - <VnSelect - v-model="data.warehouseFk" - :label="$t('globals.warehouse')" - :options="warehouses" - /> - </VnRow> - <VnRow> - <VnSelect - url="Suppliers" - :filter="{ fields: ['id', 'name'] }" - v-model="data.supplierFk" - :label="$t('globals.supplier')" - /> - <VnSelect - url="Suppliers" - :filter="{ fields: ['id', 'name'] }" - v-model="data.supplierCoolerFk" - :label="$t('vehicle.supplierCooler')" - /> - </VnRow> - <VnRow> - <VnSelect - url="BankPolicies" - :filter="{ fields: ['id', 'ref'] }" - v-model="data.bankPolicyFk" - :label="$t('vehicle.leasing')" - :options="bankPolicies" - option-label="ref" - option-value="id" - /> - <VnInput v-model="data.leasing" :label="$t('vehicle.nLeasing')" /> - </VnRow> - <VnRow> - <VnInputNumber v-model="data.import" :label="$t('globals.amount')" /> - <VnInputNumber - v-model="data.importCooler" - :label="$t('vehicle.amountCooler')" - /> - </VnRow> - <VnRow> - <VnSelect - url="Ppes" - option-label="id" - v-model="data.ppeFk" - :label="$t('vehicle.ppe')" - /> - <VnSelect - v-model="data.countryCodeFk" - :label="$t('globals.country')" - :options="countries" - option-label="code" - option-value="code" - /> - </VnRow> - <VnRow> - <VnInput v-model="data.vin" :label="$t('vehicle.vin')" /> - <span :style="{ 'align-self': $q.screen.gt.xs ? 'end' : 'unset' }"> - <QCheckbox - v-model="data.isActive" - :label="$t('vehicle.isActive')" - :false-value="0" - :true-value="1" - dense - class="q-mt-sm" - /> - </span> - </VnRow> - </template> - </FormModel> -</template> diff --git a/src/pages/Route/Vehicle/Card/VehicleCard.vue b/src/pages/Route/Vehicle/Card/VehicleCard.vue deleted file mode 100644 index f59420aa24b..00000000000 --- a/src/pages/Route/Vehicle/Card/VehicleCard.vue +++ /dev/null @@ -1,13 +0,0 @@ -<script setup> -import VnCardBeta from 'components/common/VnCardBeta.vue'; -import VehicleDescriptor from './VehicleDescriptor.vue'; -import VehicleFilter from '../VehicleFilter.js'; -</script> -<template> - <VnCardBeta - data-key="Vehicle" - url="Vehicles" - :filter="VehicleFilter" - :descriptor="VehicleDescriptor" - /> -</template> diff --git a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue b/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue deleted file mode 100644 index d9a2434ab3c..00000000000 --- a/src/pages/Route/Vehicle/Card/VehicleDescriptor.vue +++ /dev/null @@ -1,49 +0,0 @@ -<script setup> -import VnLv from 'src/components/ui/VnLv.vue'; -import CardDescriptor from 'components/ui/CardDescriptor.vue'; -import axios from 'axios'; -import useNotify from 'src/composables/useNotify.js'; - -const { notify } = useNotify(); -</script> -<template> - <CardDescriptor - :url="`Vehicles/${$route.params.id}`" - data-key="Vehicle" - title="numberPlate" - :to-module="{ name: 'VehicleList' }" - > - <template #menu="{ entity }"> - <QItem - data-cy="delete" - v-ripple - clickable - @click=" - async () => { - try { - await axios.delete(`Vehicles/${entity.id}`); - notify('vehicle.remove', 'positive'); - $router.push({ name: 'VehicleList' }); - } catch (e) { - throw e; - } - } - " - > - <QItemSection> - {{ $t('vehicle.delete') }} - </QItemSection> - </QItem> - </template> - <template #body="{ entity }"> - <VnLv :label="$t('vehicle.numberPlate')" :value="entity.numberPlate" /> - <VnLv :label="$t('vehicle.tradeMark')" :value="entity.tradeMark" /> - <VnLv :label="$t('globals.model')" :value="entity.model" /> - <VnLv :label="$t('globals.country')" :value="entity.countryCodeFk" /> - </template> - </CardDescriptor> -</template> -<i18n> -es: - Vehicle removed: Vehículo eliminado -</i18n> diff --git a/src/pages/Route/Vehicle/Card/VehicleSummary.vue b/src/pages/Route/Vehicle/Card/VehicleSummary.vue deleted file mode 100644 index 981870cb29b..00000000000 --- a/src/pages/Route/Vehicle/Card/VehicleSummary.vue +++ /dev/null @@ -1,127 +0,0 @@ -<script setup> -import { computed } from 'vue'; -import { useRoute } from 'vue-router'; -import CardSummary from 'components/ui/CardSummary.vue'; -import VnLv from 'src/components/ui/VnLv.vue'; -import VnTitle from 'src/components/common/VnTitle.vue'; -import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; -import VehicleFilter from '../VehicleFilter.js'; -import { downloadFile } from 'src/composables/downloadFile'; -import { dashIfEmpty } from 'src/filters'; - -const props = defineProps({ id: { type: [Number, String], default: null } }); - -const route = useRoute(); -const entityId = computed(() => props.id || +route.params.id); -const links = { - 'basic-data': `#/vehicle/${entityId.value}/basic-data`, - notes: `#/vehicle/${entityId.value}/notes`, - dms: `#/vehicle/${entityId.value}/dms`, - 'invoice-in': `#/vehicle/${entityId.value}/invoice-in`, - events: `#/vehicle/${entityId.value}/events`, -}; -</script> -<template> - <CardSummary data-key="Vehicle" :url="`Vehicles/${entityId}`" :filter="VehicleFilter"> - <template #header="{ entity }"> - <div>{{ entity.id }} - {{ entity.numberPlate }}</div> - </template> - <template #body="{ entity }"> - <QCard class="vn-one"> - <QCardSection dense> - <VnTitle - :url="links['basic-data']" - :text="$t('globals.pageTitles.basicData')" - /> - </QCardSection> - <QCardSection content> - <QList dense> - <VnLv - :label="$t('globals.description')" - :value="entity.description" - /> - <VnLv - :label="$t('vehicle.tradeMark')" - :value="entity.tradeMark" - /> - <VnLv :label="$t('globals.model')" :value="entity.model" /> - <VnLv :label="$t('globals.supplier')"> - <template #value> - <span class="link"> - {{ entity.supplier?.name }} - <SupplierDescriptorProxy :id="entity.supplierFk" /> - </span> - </template> - </VnLv> - <VnLv :label="$t('vehicle.supplierCooler')"> - <template #value> - <span class="link"> - {{ entity.supplierCooler?.name }} - <SupplierDescriptorProxy - :id="entity.supplierCoolerFk" - /> - </span> - </template> - </VnLv> - <VnLv :label="$t('vehicle.vin')" :value="entity.vin" /> - </QList> - <QList dense> - <VnLv :label="$t('vehicle.chassis')" :value="entity.chassis" /> - <VnLv - :label="$t('globals.fuel')" - :value="entity.fuelType?.name" - /> - <VnLv :label="$t('vehicle.ppe')" :value="entity.ppeFk" /> - <VnLv :label="$t('vehicle.nLeasing')" :value="entity.leasing" /> - <VnLv - :label="$t('vehicle.leasing')" - :value="entity.bankPolicy?.ref" - > - <template #value> - <span v-text="dashIfEmpty(entity.bankPolicy?.name)" /> - <QBtn - v-if="entity.bankPolicy?.dmsFk" - class="q-ml-xs" - color="primary" - flat - dense - icon="cloud_download" - @click="downloadFile(entity.bankPolicy?.dmsFk)" - > - <QTooltip>{{ $t('globals.download') }}</QTooltip> - </QBtn> - </template> - </VnLv> - <VnLv :label="$t('globals.amount')" :value="entity.import" /> - </QList> - <QList dense> - <VnLv - :label="$t('globals.warehouse')" - :value="entity.warehouse?.name" - /> - <VnLv - :label="$t('globals.company')" - :value="entity.company?.code" - /> - <VnLv - :label="$t('globals.deliveryPoint')" - :value="entity.deliveryPoint?.name" - /> - <VnLv - :label="$t('globals.country')" - :value="entity.countryCodeFk" - /> - <VnLv - :label="$t('vehicle.isKmTruckRate')" - :value="!!entity.isKmTruckRate" - /> - <VnLv - :label="$t('vehicle.isActive')" - :value="!!entity.isActive" - /> - </QList> - </QCardSection> - </QCard> - </template> - </CardSummary> -</template> diff --git a/src/pages/Route/Vehicle/VehicleFilter.js b/src/pages/Route/Vehicle/VehicleFilter.js deleted file mode 100644 index cbf5cc621c1..00000000000 --- a/src/pages/Route/Vehicle/VehicleFilter.js +++ /dev/null @@ -1,76 +0,0 @@ -export default { - fields: [ - 'id', - 'description', - 'isActive', - 'isKmTruckRate', - 'warehouseFk', - 'companyFk', - 'numberPlate', - 'chassis', - 'supplierFk', - 'supplierCoolerFk', - 'tradeMark', - 'fuelTypeFk', - 'import', - 'importCooler', - 'vin', - 'model', - 'ppeFk', - 'countryCodeFk', - 'leasing', - 'bankPolicyFk', - 'vehicleTypeFk', - 'deliveryPointFk', - ], - include: [ - { - relation: 'warehouse', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'company', - scope: { - fields: ['id', 'code'], - }, - }, - { - relation: 'supplier', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'supplierCooler', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'fuelType', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'bankPolicy', - scope: { - fields: ['id', 'ref', 'dmsFk'], - }, - }, - { - relation: 'ppe', - scope: { - fields: ['id'], - }, - }, - { - relation: 'deliveryPoint', - scope: { - fields: ['id', 'name'], - }, - }, - ], -}; diff --git a/src/pages/Route/Vehicle/VehicleList.vue b/src/pages/Route/Vehicle/VehicleList.vue deleted file mode 100644 index e5b94501005..00000000000 --- a/src/pages/Route/Vehicle/VehicleList.vue +++ /dev/null @@ -1,224 +0,0 @@ -<script setup> -import { ref, computed } from 'vue'; -import { useI18n } from 'vue-i18n'; -import VnTable from 'components/VnTable/VnTable.vue'; -import FetchData from 'src/components/FetchData.vue'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import VehicleSummary from 'src/pages/Route/Vehicle/Card/VehicleSummary.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import VnSection from 'src/components/common/VnSection.vue'; - -const { t } = useI18n(); -const { viewSummary } = useSummaryDialog(); -const warehouses = ref([]); -const companies = ref([]); -const countries = ref([]); -const vehicleStates = ref([]); -const vehicleTypes = ref([]); - -const columns = computed(() => [ - { - name: 'isActive', - columnFilter: false, - align: 'center', - }, - { - name: 'id', - label: t('globals.id'), - isId: true, - chip: { - condition: () => true, - }, - }, - { - name: 'description', - label: t('globals.description'), - }, - { - name: 'tradeMark', - label: t('vehicle.tradeMark'), - cardVisible: true, - }, - { - name: 'numberPlate', - label: t('vehicle.numberPlate'), - isTitle: true, - }, - { - name: 'vehicleTypeFk', - label: t('globals.type'), - format: (row) => row.type, - columnFilter: { - component: 'select', - name: 'vehicleTypeFk', - options: vehicleTypes.value, - }, - cardVisible: true, - }, - { - name: 'vehicleStateFk', - label: t('globals.state'), - columnFilter: { - component: 'select', - name: 'vehicleStateFk', - optionLabel: 'state', - options: vehicleStates.value, - }, - format: (row, dashIfEmpty) => dashIfEmpty(row.state), - }, - { - name: 'chassis', - label: t('vehicle.chassis'), - }, - { - name: 'leasing', - label: t('vehicle.leasing'), - }, - { - name: 'warehouseFk', - label: t('globals.warehouse'), - format: (row, dashIfEmpty) => dashIfEmpty(row.warehouse), - columnFilter: { - component: 'select', - name: 'warehouseFk', - options: warehouses.value, - }, - cardVisible: true, - }, - { - name: 'companyFk', - label: t('globals.company'), - format: (row, dashIfEmpty) => dashIfEmpty(row.company), - columnFilter: { - component: 'select', - name: 'companyFk', - optionLabel: 'code', - options: companies.value, - }, - }, - { - name: 'countryCodeFk', - label: t('globals.country'), - columnFilter: { - component: 'select', - name: 'countryCodeFk', - optionValue: 'code', - optionLabel: 'code', - options: countries.value, - }, - }, - { - align: 'right', - name: 'tableActions', - actions: [ - { - title: t('components.smartCard.openSummary'), - icon: 'preview', - action: (row) => viewSummary(row.id, VehicleSummary), - }, - ], - }, -]); -</script> -<template> - <FetchData - url="Warehouses" - :filter="{ fields: ['id', 'name'] }" - @on-fetch="(data) => (warehouses = data)" - auto-load - /> - <FetchData - url="Companies" - :filter="{ fields: ['id', 'code'] }" - @on-fetch="(data) => (companies = data)" - auto-load - /> - <FetchData - url="Countries" - :filter="{ fields: ['name', 'code'] }" - @on-fetch="(data) => (countries = data)" - auto-load - /> - <FetchData - url="VehicleStates" - :filter="{ fields: ['id', 'state'] }" - @on-fetch="(data) => (vehicleStates = data)" - auto-load - /> - <FetchData - url="VehicleTypes" - :filter="{ fields: ['id', 'name'] }" - @on-fetch="(data) => (vehicleTypes = data)" - auto-load - /> - <VnSection - data-key="VehicleList" - :columns="columns" - prefix="vehicle" - :array-data-props="{ - url: 'Vehicles/filter', - }" - > - <template #body> - <VnTable - ref="tableRef" - data-key="VehicleList" - :columns="columns" - redirect="route/vehicle" - :create="{ - urlCreate: 'Vehicles', - title: t('vehicle.create'), - onDataSaved: ({ id }) => $refs.tableRef.redirect(id), - formInitialData: { isActive: true, isKmTruckRate: false }, - }" - :use-model="true" - :right-search="false" - > - <template #column-isActive="{ row }"> - <span> - <QIcon - v-if="!row.isActive" - name="vn:inactive-car" - color="primary" - size="xs" - > - <QTooltip>{{ $t('globals.inactive') }}</QTooltip> - </QIcon> - </span> - </template> - <template #more-create-dialog="{ data }"> - <VnInput - v-model="data.numberPlate" - :label="$t('vehicle.numberPlate')" - :uppercase="true" - /> - <VnInput v-model="data.tradeMark" :label="$t('vehicle.tradeMark')" /> - <VnInput v-model="data.model" :label="$t('globals.model')" /> - <VnSelect - v-model="data.vehicleTypeFk" - :label="$t('globals.type')" - :options="vehicleTypes" - /> - <VnSelect - v-model="data.warehouseFk" - :label="$t('globals.warehouse')" - :options="warehouses" - /> - <VnSelect - v-model="data.countryCodeFk" - :label="$t('globals.country')" - option-value="code" - option-label="name" - :options="countries" - /> - <VnInput - v-model="data.description" - :label="$t('globals.description')" - /> - <QCheckbox to v-model="data.isActive" :label="$t('globals.active')" /> - </template> - </VnTable> - </template> - </VnSection> -</template> diff --git a/src/pages/Route/Vehicle/locale/en.yml b/src/pages/Route/Vehicle/locale/en.yml deleted file mode 100644 index c92022f9d29..00000000000 --- a/src/pages/Route/Vehicle/locale/en.yml +++ /dev/null @@ -1,20 +0,0 @@ -vehicle: - tradeMark: Trade Mark - numberPlate: Nº Plate - chassis: Chassis - leasing: Leasing - isKmTruckRate: Trailer - delete: Delete Vehicle - supplierCooler: Supplier Cooler - vin: VIN - ppe: Ppe - isActive: Active - nLeasing: Nº Leasing - create: Create Vehicle - amountCooler: Amount cooler - remove: Vehicle removed - search: Search Vehicle - searchInfo: Search by id or number plate - params: - vehicleTypeFk: Type - vehicleStateFk: State diff --git a/src/pages/Route/Vehicle/locale/es.yml b/src/pages/Route/Vehicle/locale/es.yml deleted file mode 100644 index c878f97ac56..00000000000 --- a/src/pages/Route/Vehicle/locale/es.yml +++ /dev/null @@ -1,20 +0,0 @@ -vehicle: - tradeMark: Marca - numberPlate: Matrícula - chassis: Nº de bastidor - leasing: Leasing - isKmTruckRate: Trailer - delete: Eliminar vehículo - supplierCooler: Proveedor Frío - vin: VIN - ppe: Nº Inmovilizado - create: Crear vehículo - amountCooler: Importe frío - isActive: Activo - nLeasing: Nº leasing - remove: Vehículo eliminado - search: Buscar Vehículo - searchInfo: Buscar por id o matrícula - params: - vehicleTypeFk: Tipo - vehicleStateFk: Estado diff --git a/src/pages/Shelving/Card/ShelvingCard.vue b/src/pages/Shelving/Card/ShelvingCard.vue index 9e0ac8ad2ce..41a0db33cb8 100644 --- a/src/pages/Shelving/Card/ShelvingCard.vue +++ b/src/pages/Shelving/Card/ShelvingCard.vue @@ -1,14 +1,12 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue'; -import filter from './ShelvingFilter.js'; </script> <template> <VnCardBeta data-key="Shelving" - url="Shelvings" - :filter="filter" + base-url="Shelvings" :descriptor="ShelvingDescriptor" /> </template> diff --git a/src/pages/Shelving/Card/ShelvingDescriptor.vue b/src/pages/Shelving/Card/ShelvingDescriptor.vue index 5e618aa7f46..b1ff4a8aedf 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptor.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptor.vue @@ -1,12 +1,12 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; +import useCardDescription from 'composables/useCardDescription'; import ShelvingDescriptorMenu from 'pages/Shelving/Card/ShelvingDescriptorMenu.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; -import filter from './ShelvingFilter.js'; const $props = defineProps({ id: { @@ -22,13 +22,35 @@ const { t } = useI18n(); const entityId = computed(() => { return $props.id || route.params.id; }); + +const filter = { + include: [ + { + relation: 'worker', + scope: { + fields: ['id'], + include: { + relation: 'user', + scope: { fields: ['nickname'] }, + }, + }, + }, + { relation: 'parking' }, + ], +}; +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); </script> + <template> <CardDescriptor + module="Shelving" :url="`Shelvings/${entityId}`" :filter="filter" - title="code" - data-key="Shelving" + :title="data.title" + :subtitle="data.subtitle" + data-key="Shelvings" + @on-fetch="setData" > <template #body="{ entity }"> <VnLv :label="t('globals.code')" :value="entity.code" /> diff --git a/src/pages/Shelving/Card/ShelvingFilter.js b/src/pages/Shelving/Card/ShelvingFilter.js deleted file mode 100644 index e302e1b9cb4..00000000000 --- a/src/pages/Shelving/Card/ShelvingFilter.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - include: [ - { - relation: 'worker', - scope: { - fields: ['id'], - include: { - relation: 'user', - scope: { fields: ['nickname'] }, - }, - }, - }, - { relation: 'parking' }, - ], -}; diff --git a/src/pages/Shelving/Card/ShelvingForm.vue b/src/pages/Shelving/Card/ShelvingForm.vue index 0780583427d..3bbd94a0a54 100644 --- a/src/pages/Shelving/Card/ShelvingForm.vue +++ b/src/pages/Shelving/Card/ShelvingForm.vue @@ -1,4 +1,5 @@ <script setup> +import { useI18n } from 'vue-i18n'; import { computed } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import VnRow from 'components/ui/VnRow.vue'; @@ -6,8 +7,8 @@ import FormModel from 'components/FormModel.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -import filter from './ShelvingFilter.js'; +const { t } = useI18n(); const route = useRoute(); const router = useRouter(); const entityId = computed(() => route.params.id ?? null); @@ -19,6 +20,22 @@ const defaultInitialData = { isRecyclable: false, }; +const shelvingFilter = { + include: [ + { + relation: 'worker', + scope: { + fields: ['id'], + include: { + relation: 'user', + scope: { fields: ['nickname'] }, + }, + }, + }, + { relation: 'parking' }, + ], +}; + const onSave = (shelving, newShelving) => { if (isNew) { router.push({ name: 'ShelvingBasicData', params: { id: newShelving?.id } }); @@ -28,10 +45,11 @@ const onSave = (shelving, newShelving) => { <template> <VnSubToolbar v-if="isNew" /> <FormModel + :url="isNew ? null : `Shelvings/${entityId}`" :url-create="isNew ? 'Shelvings' : null" :observe-form-changes="!isNew" - :filter="filter" - model="Shelving" + :filter="shelvingFilter" + model="shelving" :auto-load="!isNew" :form-initial-data="isNew ? defaultInitialData : null" @on-data-saved="onSave" @@ -40,7 +58,7 @@ const onSave = (shelving, newShelving) => { <VnRow> <VnInput v-model="data.code" - :label="$t('globals.code')" + :label="t('globals.code')" :rules="validate('Shelving.code')" /> <VnSelect @@ -50,7 +68,7 @@ const onSave = (shelving, newShelving) => { option-label="code" :filter-options="['id', 'code']" :fields="['id', 'code']" - :label="$t('shelving.list.parking')" + :label="t('shelving.list.parking')" :rules="validate('Shelving.parkingFk')" /> </VnRow> @@ -58,12 +76,12 @@ const onSave = (shelving, newShelving) => { <VnInput v-model="data.priority" type="number" - :label="$t('shelving.list.priority')" + :label="t('shelving.list.priority')" :rules="validate('Shelving.priority')" /> <QCheckbox v-model="data.isRecyclable" - :label="$t('shelving.summary.recyclable')" + :label="t('shelving.summary.recyclable')" :rules="validate('Shelving.isRecyclable')" /> </VnRow> diff --git a/src/pages/Shelving/Card/ShelvingSearchbar.vue b/src/pages/Shelving/Card/ShelvingSearchbar.vue index 741b1166305..bfc8ad4f50a 100644 --- a/src/pages/Shelving/Card/ShelvingSearchbar.vue +++ b/src/pages/Shelving/Card/ShelvingSearchbar.vue @@ -1,15 +1,15 @@ <script setup> import VnSearchbar from 'components/ui/VnSearchbar.vue'; -import exprBuilder from '../ShelvingExprBuilder.js'; +import {useI18n} from "vue-i18n"; +const { t } = useI18n(); </script> <template> <VnSearchbar data-key="ShelvingList" url="Shelvings" - :label="$t('Search shelving')" - :info="$t('You can search by shelving reference')" - :expr-builder="exprBuilder" + :label="t('Search shelving')" + :info="t('You can search by shelving reference')" /> </template> diff --git a/src/pages/Shelving/Card/ShelvingSummary.vue b/src/pages/Shelving/Card/ShelvingSummary.vue index f89ff4d788e..39fa4639f23 100644 --- a/src/pages/Shelving/Card/ShelvingSummary.vue +++ b/src/pages/Shelving/Card/ShelvingSummary.vue @@ -1,10 +1,10 @@ <script setup> import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnUserLink from 'components/ui/VnUserLink.vue'; -import filter from './ShelvingFilter.js'; import ShelvingDescriptorMenu from './ShelvingDescriptorMenu.vue'; const $props = defineProps({ @@ -14,9 +14,25 @@ const $props = defineProps({ }, }); const route = useRoute(); - +const { t } = useI18n(); const summary = ref({}); const entityId = computed(() => $props.id || route.params.id); + +const filter = { + include: [ + { + relation: 'worker', + scope: { + fields: ['id'], + include: { + relation: 'user', + scope: { fields: ['nickname'] }, + }, + }, + }, + { relation: 'parking' }, + ], +}; </script> <template> @@ -25,7 +41,7 @@ const entityId = computed(() => $props.id || route.params.id); ref="summary" :url="`Shelvings/${entityId}`" :filter="filter" - data-key="Shelving" + data-key="ShelvingSummary" > <template #header="{ entity }"> <div>{{ entity.code }}</div> @@ -42,19 +58,16 @@ const entityId = computed(() => $props.id || route.params.id); class="header header-link" :to="{ name: 'ShelvingBasicData', params: { id: entityId } }" > - {{ $t('globals.pageTitles.basicData') }} + {{ t('globals.pageTitles.basicData') }} <QIcon name="open_in_new" /> </RouterLink> - <VnLv :label="$t('globals.code')" :value="entity.code" /> + <VnLv :label="t('globals.code')" :value="entity.code" /> <VnLv - :label="$t('shelving.list.parking')" + :label="t('shelving.list.parking')" :value="entity.parking?.code" /> - <VnLv - :label="$t('shelving.list.priority')" - :value="entity.priority" - /> - <VnLv v-if="entity.worker" :label="$t('globals.worker')"> + <VnLv :label="t('shelving.list.priority')" :value="entity.priority" /> + <VnLv v-if="entity.worker" :label="t('globals.worker')"> <template #value> <VnUserLink :name="entity.worker?.user?.nickname" @@ -63,7 +76,7 @@ const entityId = computed(() => $props.id || route.params.id); </template> </VnLv> <VnLv - :label="$t('shelving.summary.recyclable')" + :label="t('shelving.summary.recyclable')" :value="entity.isRecyclable" /> </QCard> diff --git a/src/pages/Shelving/Parking/Card/ParkingFilter.js b/src/pages/Shelving/Parking/Card/ParkingFilter.js deleted file mode 100644 index fd1855c4535..00000000000 --- a/src/pages/Shelving/Parking/Card/ParkingFilter.js +++ /dev/null @@ -1,4 +0,0 @@ -export default { - fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'], - include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }], -}; diff --git a/src/pages/Shelving/Parking/ParkingExprBuilder.js b/src/pages/Shelving/Parking/ParkingExprBuilder.js deleted file mode 100644 index 16d2262c8fd..00000000000 --- a/src/pages/Shelving/Parking/ParkingExprBuilder.js +++ /dev/null @@ -1,10 +0,0 @@ -export default (param, value) => { - switch (param) { - case 'code': - return { [param]: { like: `%${value}%` } }; - case 'sectorFk': - return { [param]: value }; - case 'search': - return { or: [{ code: { like: `%${value}%` } }, { id: value }] }; - } -}; diff --git a/src/pages/Shelving/ShelvingExprBuilder.js b/src/pages/Shelving/ShelvingExprBuilder.js deleted file mode 100644 index b9aad8a711e..00000000000 --- a/src/pages/Shelving/ShelvingExprBuilder.js +++ /dev/null @@ -1,10 +0,0 @@ -export default (param, value) => { - switch (param) { - case 'search': - return { code: { like: `%${value}%` } }; - case 'parkingFk': - case 'userFk': - case 'isRecyclable': - return { [param]: value }; - } -}; diff --git a/src/pages/Shelving/ShelvingList.vue b/src/pages/Shelving/ShelvingList.vue index 4e0c21100e8..cf158e76b17 100644 --- a/src/pages/Shelving/ShelvingList.vue +++ b/src/pages/Shelving/ShelvingList.vue @@ -1,5 +1,6 @@ <script setup> import VnPaginate from 'components/ui/VnPaginate.vue'; +import { useI18n } from 'vue-i18n'; import CardList from 'components/ui/CardList.vue'; import VnLv from 'components/ui/VnLv.vue'; import { useRouter } from 'vue-router'; @@ -7,9 +8,9 @@ import ShelvingFilter from 'pages/Shelving/Card/ShelvingFilter.vue'; import ShelvingSummary from 'pages/Shelving/Card/ShelvingSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnSection from 'src/components/common/VnSection.vue'; -import exprBuilder from './ShelvingExprBuilder.js'; const router = useRouter(); +const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const dataKey = 'ShelvingList'; @@ -20,6 +21,17 @@ const filter = { function navigate(id) { router.push({ path: `/shelving/${id}` }); } + +function exprBuilder(param, value) { + switch (param) { + case 'search': + return { code: { like: `%${value}%` } }; + case 'parkingFk': + case 'userFk': + case 'isRecyclable': + return { [param]: value }; + } +} </script> <template> @@ -50,18 +62,18 @@ function navigate(id) { > <template #list-items> <VnLv - :label="$t('shelving.list.parking')" - :title-label="$t('shelving.list.parking')" + :label="t('shelving.list.parking')" + :title-label="t('shelving.list.parking')" :value="row.parking?.code" /> <VnLv - :label="$t('shelving.list.priority')" + :label="t('shelving.list.priority')" :value="row?.priority" /> </template> <template #actions> <QBtn - :label="$t('components.smartCard.openSummary')" + :label="t('components.smartCard.openSummary')" @click.stop="viewSummary(row.id, ShelvingSummary)" color="primary" /> @@ -72,9 +84,9 @@ function navigate(id) { </div> <QPageSticky :offset="[20, 20]"> <RouterLink :to="{ name: 'ShelvingCreate' }"> - <QBtn fab icon="add" color="primary" v-shortcut="'+'" /> + <QBtn fab icon="add" color="primary" shortcut="+" /> <QTooltip> - {{ $t('shelving.list.newShelving') }} + {{ t('shelving.list.newShelving') }} </QTooltip> </RouterLink> </QPageSticky> diff --git a/src/pages/Supplier/Card/SupplierAccounts.vue b/src/pages/Supplier/Card/SupplierAccounts.vue index 365eb67a141..4a6901d1d13 100644 --- a/src/pages/Supplier/Card/SupplierAccounts.vue +++ b/src/pages/Supplier/Card/SupplierAccounts.vue @@ -71,7 +71,7 @@ function bankEntityFilter(val, update) { filteredBankEntitiesOptions.value = bankEntitiesOptions.value.filter( (bank) => bank.bic.toLowerCase().startsWith(needle) || - bank.name.toLowerCase().includes(needle), + bank.name.toLowerCase().includes(needle) ); }); } @@ -170,7 +170,7 @@ function bankEntityFilter(val, update) { <QIcon name="info" class="cursor-pointer"> <QTooltip>{{ t( - 'Name of the bank account holder if different from the provider', + 'Name of the bank account holder if different from the provider' ) }}</QTooltip> </QIcon> @@ -194,7 +194,7 @@ function bankEntityFilter(val, update) { <QBtn flat icon="add" - v-shortcut + shortcut="+" class="cursor-pointer" color="primary" @click="supplierAccountRef.insert()" diff --git a/src/pages/Supplier/Card/SupplierAddresses.vue b/src/pages/Supplier/Card/SupplierAddresses.vue index c4c0ab7bef6..e568962ff28 100644 --- a/src/pages/Supplier/Card/SupplierAddresses.vue +++ b/src/pages/Supplier/Card/SupplierAddresses.vue @@ -89,7 +89,7 @@ const redirectToUpdateView = (addressData) => { icon="add" color="primary" @click="redirectToCreateView()" - v-shortcut="'+'" + shortcut="+" /> <QTooltip> {{ t('New address') }} diff --git a/src/pages/Supplier/Card/SupplierAgencyTerm.vue b/src/pages/Supplier/Card/SupplierAgencyTerm.vue index ab21f1f76fb..99b672cc4c5 100644 --- a/src/pages/Supplier/Card/SupplierAgencyTerm.vue +++ b/src/pages/Supplier/Card/SupplierAgencyTerm.vue @@ -114,7 +114,7 @@ const redirectToCreateView = () => { icon="add" color="primary" @click="redirectToCreateView()" - v-shortcut="'+'" + shortcut="+" /> <QTooltip> {{ t('supplier.agencyTerms.addRow') }} diff --git a/src/pages/Supplier/Card/SupplierBasicData.vue b/src/pages/Supplier/Card/SupplierBasicData.vue index 631700a4aab..f6c13b7afcd 100644 --- a/src/pages/Supplier/Card/SupplierBasicData.vue +++ b/src/pages/Supplier/Card/SupplierBasicData.vue @@ -19,8 +19,9 @@ const companySizes = [ </script> <template> <FormModel + :url="`Suppliers/${route.params.id}`" :url-update="`Suppliers/${route.params.id}`" - model="Supplier" + model="supplier" auto-load :clear-store-on-unmount="false" @on-data-saved="arrayData.fetch({})" diff --git a/src/pages/Supplier/Card/SupplierCard.vue b/src/pages/Supplier/Card/SupplierCard.vue index e30f79f962b..594026d18e9 100644 --- a/src/pages/Supplier/Card/SupplierCard.vue +++ b/src/pages/Supplier/Card/SupplierCard.vue @@ -1,13 +1,19 @@ <script setup> +import VnCard from 'components/common/VnCard.vue'; import SupplierDescriptor from './SupplierDescriptor.vue'; -import VnCardBeta from 'src/components/common/VnCardBeta.vue'; -import filter from './SupplierFilter.js'; +import SupplierListFilter from '../SupplierListFilter.vue'; </script> <template> - <VnCardBeta + <VnCard data-key="Supplier" - url="Suppliers" + base-url="Suppliers" :descriptor="SupplierDescriptor" - :filter="filter" + :filter-panel="SupplierListFilter" + search-data-key="SupplierList" + :searchbar-props="{ + url: 'Suppliers/filter', + searchUrl: 'table', + label: 'Search suppliers', + }" /> </template> diff --git a/src/pages/Supplier/Card/SupplierConsumption.vue b/src/pages/Supplier/Card/SupplierConsumption.vue index 718de95ddf4..8a7021fb341 100644 --- a/src/pages/Supplier/Card/SupplierConsumption.vue +++ b/src/pages/Supplier/Card/SupplierConsumption.vue @@ -16,7 +16,6 @@ import axios from 'axios'; import { useStateStore } from 'stores/useStateStore'; import { useState } from 'src/composables/useState'; import { useArrayData } from 'composables/useArrayData'; -import RightMenu from 'src/components/common/RightMenu.vue'; const state = useState(); const stateStore = useStateStore(); @@ -174,59 +173,59 @@ onMounted(async () => { </div> </div> </Teleport> - <RightMenu> - <template #right-panel> + <QPage class="column items-center q-pa-md"> + <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <SupplierConsumptionFilter data-key="SupplierConsumption" /> - </template> - </RightMenu> - <QTable - :rows="rows" - row-key="id" - hide-header - class="full-width q-mt-md" - :no-data-label="t('No results')" - > - <template #body="{ row }"> - <QTr> - <QTd no-hover> - <span class="label">{{ t('supplier.consumption.entry') }}: </span> - <span>{{ row.id }}</span> - </QTd> - <QTd no-hover> - <span class="label">{{ t('globals.date') }}: </span> - <span>{{ toDate(row.shipped) }}</span></QTd - > - <QTd colspan="6" no-hover> - <span class="label">{{ t('globals.reference') }}: </span> - <span>{{ row.invoiceNumber }}</span> - </QTd> - </QTr> - <QTr v-for="(buy, index) in row.buys" :key="index"> - <QTd no-hover> - <QBtn flat color="blue" dense no-caps>{{ buy.itemName }}</QBtn> - <ItemDescriptorProxy :id="buy.itemFk" /> - </QTd> + </Teleport> + <QTable + :rows="rows" + row-key="id" + hide-header + class="full-width q-mt-md" + :no-data-label="t('No results')" + > + <template #body="{ row }"> + <QTr> + <QTd no-hover> + <span class="label">{{ t('supplier.consumption.entry') }}: </span> + <span>{{ row.id }}</span> + </QTd> + <QTd no-hover> + <span class="label">{{ t('globals.date') }}: </span> + <span>{{ toDate(row.shipped) }}</span></QTd + > + <QTd colspan="6" no-hover> + <span class="label">{{ t('globals.reference') }}: </span> + <span>{{ row.invoiceNumber }}</span> + </QTd> + </QTr> + <QTr v-for="(buy, index) in row.buys" :key="index"> + <QTd no-hover> + <QBtn flat color="blue" dense no-caps>{{ buy.itemName }}</QBtn> + <ItemDescriptorProxy :id="buy.itemFk" /> + </QTd> - <QTd no-hover> - <span>{{ buy.subName }}</span> - <FetchedTags :item="buy" /> - </QTd> - <QTd no-hover> {{ dashIfEmpty(buy.quantity) }}</QTd> - <QTd no-hover> {{ dashIfEmpty(buy.price) }}</QTd> - <QTd colspan="2" no-hover> {{ dashIfEmpty(buy.total) }}</QTd> - </QTr> - <QTr> - <QTd colspan="5" no-hover> - <span class="label">{{ t('Total entry') }}: </span> - <span>{{ row.total }} €</span> - </QTd> - <QTd no-hover> - <span class="label">{{ t('Total stems') }}: </span> - <span>{{ row.quantity }}</span> - </QTd> - </QTr> - </template> - </QTable> + <QTd no-hover> + <span>{{ buy.subName }}</span> + <FetchedTags :item="buy" /> + </QTd> + <QTd no-hover> {{ dashIfEmpty(buy.quantity) }}</QTd> + <QTd no-hover> {{ dashIfEmpty(buy.price) }}</QTd> + <QTd colspan="2" no-hover> {{ dashIfEmpty(buy.total) }}</QTd> + </QTr> + <QTr> + <QTd colspan="5" no-hover> + <span class="label">{{ t('Total entry') }}: </span> + <span>{{ row.total }} €</span> + </QTd> + <QTd no-hover> + <span class="label">{{ t('Total stems') }}: </span> + <span>{{ row.quantity }}</span> + </QTd> + </QTr> + </template> + </QTable> + </QPage> </template> <style scoped lang="scss"> diff --git a/src/pages/Supplier/Card/SupplierContacts.vue b/src/pages/Supplier/Card/SupplierContacts.vue index f96d92ab1db..6781c8d34ae 100644 --- a/src/pages/Supplier/Card/SupplierContacts.vue +++ b/src/pages/Supplier/Card/SupplierContacts.vue @@ -78,7 +78,7 @@ const insertRow = () => { <QBtn flat icon="add" - v-shortcut="'+'" + shortcut="+" class="cursor-pointer" color="primary" @click="insertRow()" diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 462bdf853ae..37c9c1cff23 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -7,8 +7,8 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toDateString } from 'src/filters'; +import useCardDescription from 'src/composables/useCardDescription'; import { getUrl } from 'src/composables/getUrl'; -import filter from './SupplierFilter.js'; import { useArrayData } from 'src/composables/useArrayData'; const $props = defineProps({ @@ -28,6 +28,42 @@ const { t } = useI18n(); const url = ref(); const arrayData = useArrayData(); +const filter = { + fields: [ + 'id', + 'name', + 'nickname', + 'nif', + 'payMethodFk', + 'payDemFk', + 'payDay', + 'isActive', + 'isReal', + 'isTrucker', + 'account', + ], + include: [ + { + relation: 'payMethod', + scope: { + fields: ['id', 'name'], + }, + }, + { + relation: 'payDem', + scope: { + fields: ['id', 'payDem'], + }, + }, + { + relation: 'client', + scope: { + fields: ['id', 'fi'], + }, + }, + ], +}; + onMounted(async () => { url.value = await getUrl(''); }); @@ -36,6 +72,11 @@ const entityId = computed(() => { return $props.id || route.params.id; }); +const data = ref(useCardDescription()); +const setData = (entity) => { + data.value = useCardDescription(entity.ref, entity.id); +}; + const supplier = computed(() => arrayData.store.data); const getEntryQueryParams = (supplier) => { @@ -62,9 +103,13 @@ const getEntryQueryParams = (supplier) => { <template> <CardDescriptor + module="Supplier" :url="`Suppliers/${entityId}`" + :title="data.title" + :subtitle="data.subtitle" :filter="filter" - data-key="Supplier" + @on-fetch="setData" + data-key="supplierDescriptor" :summary="$props.summary" > <template #body="{ entity }"> diff --git a/src/pages/Supplier/Card/SupplierFilter.js b/src/pages/Supplier/Card/SupplierFilter.js deleted file mode 100644 index 3ce5c3de29e..00000000000 --- a/src/pages/Supplier/Card/SupplierFilter.js +++ /dev/null @@ -1,35 +0,0 @@ -export default { - fields: [ - 'id', - 'name', - 'nickname', - 'nif', - 'payMethodFk', - 'payDemFk', - 'payDay', - 'isActive', - 'isSerious', - 'isTrucker', - 'account', - ], - include: [ - { - relation: 'payMethod', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'payDem', - scope: { - fields: ['id', 'payDem'], - }, - }, - { - relation: 'client', - scope: { - fields: ['id', 'fi'], - }, - }, - ], -}; diff --git a/src/pages/Supplier/Card/SupplierFiscalData.vue b/src/pages/Supplier/Card/SupplierFiscalData.vue index ecee5b76b09..e569eb23609 100644 --- a/src/pages/Supplier/Card/SupplierFiscalData.vue +++ b/src/pages/Supplier/Card/SupplierFiscalData.vue @@ -10,7 +10,6 @@ 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(); @@ -183,11 +182,18 @@ function handleLocation(data, location) { v-model="data.isTrucker" :label="t('supplier.fiscalData.isTrucker')" /> - <VnCheckbox - v-model="data.isVies" - :label="t('globals.isVies')" - :info="t('whenActivatingIt')" - /> + <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> </div> </VnRow> </template> @@ -195,8 +201,6 @@ function handleLocation(data, location) { </template> <i18n> -en: - whenActivatingIt: When activating it, do not enter the country code in the ID field. es: - whenActivatingIt: Al activarlo, no informar el código del país en el campo nif. + 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 </i18n> diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index 600790745eb..85cc11857c4 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -2,15 +2,14 @@ import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSection from 'src/components/common/VnSection.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; +import SupplierListFilter from './SupplierListFilter.vue'; import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import FetchData from 'src/components/FetchData.vue'; const { t } = useI18n(); const tableRef = ref(); -const dataKey = 'SupplierList'; -const provincesOptions = ref([]); + const columns = computed(() => [ { align: 'left', @@ -105,62 +104,38 @@ const columns = computed(() => [ }, ]); </script> + <template> - <FetchData - url="Provinces" - :filter="{ fields: ['id', 'name'], order: 'name ASC' }" - @on-fetch="(data) => (provincesOptions = data)" - auto-load - /> - <VnSection - :data-key="dataKey" - :columns="columns" - prefix="supplier" - :array-data-props="{ - url: 'Suppliers/filter', - order: 'id ASC', + <VnSearchbar data-key="SuppliersList" :limit="20" :label="t('Search suppliers')" /> + <RightMenu> + <template #right-panel> + <SupplierListFilter data-key="SuppliersList" /> + </template> + </RightMenu> + <VnTable + ref="tableRef" + data-key="SuppliersList" + url="Suppliers/filter" + redirect="supplier" + :create="{ + urlCreate: 'Suppliers/newSupplier', + title: t('Create Supplier'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: {}, + mapper: (data) => { + data.name = data.socialName; + + return data; + }, }" + :right-search="false" + order="id ASC" + :columns="columns" > - <template #body> - <VnTable - ref="tableRef" - :data-key="dataKey" - :create="{ - urlCreate: 'Suppliers/newSupplier', - title: t('Create Supplier'), - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, - mapper: (data) => { - data.name = data.socialName; - delete data.socialName; - return data; - }, - }" - :columns="columns" - redirect="supplier" - :right-search="false" - > - <template #more-create-dialog="{ data }"> - <VnInput - :label="t('globals.name')" - v-model="data.socialName" - :uppercase="true" - /> - </template> - </VnTable> - </template> - <template #moreFilterPanel="{ params, searchFn }"> - <VnSelect - :label="t('globals.params.provinceFk')" - v-model="params.provinceFk" - @update:model-value="searchFn()" - :options="provincesOptions" - filled - dense - class="q-px-sm q-pr-lg" - /> - </template> - </VnSection> + <template #more-create-dialog="{ data }"> + <VnInput :label="t('globals.name')" v-model="data.socialName" :uppercase="true" /> + </template> + </VnTable> </template> <i18n> diff --git a/src/pages/Supplier/SupplierListFilter.vue b/src/pages/Supplier/SupplierListFilter.vue new file mode 100644 index 00000000000..b170a35cc8b --- /dev/null +++ b/src/pages/Supplier/SupplierListFilter.vue @@ -0,0 +1,122 @@ +<script setup> +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; + +import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import FetchData from 'components/FetchData.vue'; + +const props = defineProps({ + dataKey: { + type: String, + required: true, + }, +}); + +const { t } = useI18n(); + +const provincesOptions = ref([]); +const countriesOptions = ref([]); +</script> + +<template> + <FetchData + url="Provinces" + :filter="{ fields: ['id', 'name'], order: 'name ASC'}" + @on-fetch="(data) => (provincesOptions = data)" + auto-load + /> + <FetchData + url="countries" + :filter="{ fields: ['id', 'name'], order: 'name ASC'}" + @on-fetch="(data) => (countriesOptions = data)" + auto-load + /> + <VnFilterPanel + :data-key="props.dataKey" + :search-button="true" + :unremovable-params="['supplierFk']" + > + <template #tags="{ tag, formatFn }"> + <div class="q-gutter-x-xs"> + <strong>{{ t(`params.${tag.label}`) }}: </strong> + <span>{{ formatFn(tag.value) }}</span> + </div> + </template> + <template #body="{ params, searchFn }"> + <QItem> + <QItemSection> + <VnInput + v-model="params.search" + :label="t('params.search')" + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.nickname" + :label="t('params.nickname')" + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnInput v-model="params.nif" :label="t('params.nif')" is-outlined /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + :label="t('params.provinceFk')" + v-model="params.provinceFk" + @update:model-value="searchFn()" + :options="provincesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + :label="t('params.countryFk')" + v-model="params.countryFk" + @update:model-value="searchFn()" + :options="countriesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + </template> + </VnFilterPanel> +</template> + +<i18n> +en: + params: + search: General search + nickname: Alias + nif: Tax number + provinceFk: Province + countryFk: Country +es: + params: + search: Búsqueda general + nickname: Alias + nif: NIF/CIF + provinceFk: Provincia + countryFk: País +</i18n> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue index 055c9a0ffd4..c6a85c287d4 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -9,9 +9,8 @@ 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 haveNegatives = defineModel('haveNegatives', { type: Boolean, required: true }); const formData = defineModel({ type: Object, required: true }); const stateStore = useStateStore(); @@ -183,19 +182,22 @@ onMounted(async () => { </QCard> <QCard v-if="haveNegatives" - class="q-pa-xs q-mb-md q-ma-md color-vn-text" + class="q-pa-md q-mb-md q-ma-md color-vn-text" bordered flat style="border-color: black" > <QCardSection horizontal class="flex row items-center"> - <VnCheckbox - v-model="formData.withoutNegatives" + <QCheckbox :label="t('basicData.withoutNegatives')" - :info="t('basicData.withoutNegativesInfo')" + v-model="formData.withoutNegatives" :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 9d70fea38a8..cf44815374b 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 class="row q-gutter-md q-mb-md no-wrap"> + <VnRow> <VnSelect :label="t('ticketList.client')" v-model="clientId" @@ -296,7 +296,7 @@ async function getZone(options) { :rules="validate('ticketList.warehouse')" /> </VnRow> - <VnRow class="row q-gutter-md q-mb-md no-wrap"> + <VnRow> <VnSelect :label="t('basicData.address')" v-model="addressId" diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue index ef2eb75d6e0..89249b899d0 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue @@ -1,7 +1,7 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, onBeforeMount } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; +import { useRoute, useRouter } from 'vue-router'; import TicketBasicData from './TicketBasicData.vue'; import TicketBasicDataForm from './TicketBasicDataForm.vue'; @@ -9,69 +9,104 @@ import { useVnConfirm } from 'composables/useVnConfirm'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; -import { useArrayData } from 'src/composables/useArrayData'; const { notify } = useNotify(); +const route = useRoute(); const router = useRouter(); const { t } = useI18n(); const stepperRef = ref(null); const { openConfirmationModal } = useVnConfirm(); const step = ref(1); -const haveNegatives = ref(true); +const formData = ref({}); +const initialDataLoaded = ref(false); +const haveNegatives = ref(false); -const ticket = computed(() => useArrayData('Ticket').store?.data); +const ticketFilter = { + include: [ + { relation: 'address' }, + { + relation: 'client', + scope: { + fields: [ + 'salesPersonFk', + 'name', + 'isActive', + 'isFreezed', + 'isTaxDataChecked', + 'credit', + 'email', + 'phone', + 'mobile', + 'hasElectronicInvoice', + ], + include: { + relation: 'salesPersonUser', + scope: { fields: ['id', 'name'] }, + }, + }, + }, + { relation: 'invoiceOut' }, + ], +}; + +const getTicketData = async () => { + const params = { filter: JSON.stringify(ticketFilter) }; + const { data } = await axios.get(`tickets/${route.params.id}`, { params }); + formData.value = data; + initialDataLoaded.value = true; +}; const isFormInvalid = () => { return ( - !ticket.value.clientFk || - !ticket.value.addressFk || - !ticket.value.agencyModeFk || - !ticket.value.companyFk || - !ticket.value.shipped || - !ticket.value.landed || - !ticket.value.zoneFk + !formData.value.clientFk || + !formData.value.addressFk || + !formData.value.agencyModeFk || + !formData.value.companyFk || + !formData.value.shipped || + !formData.value.landed || + !formData.value.zoneFk ); }; const getPriceDifference = async () => { const params = { - landed: ticket.value.landed, - addressId: ticket.value.addressFk, - agencyModeId: ticket.value.agencyModeFk, - zoneId: ticket.value.zoneFk, - warehouseId: ticket.value.warehouseFk, - shipped: ticket.value.shipped, + landed: formData.value.landed, + addressId: formData.value.addressFk, + agencyModeId: formData.value.agencyModeFk, + zoneId: formData.value.zoneFk, + warehouseId: formData.value.warehouseFk, + shipped: formData.value.shipped, }; const { data } = await axios.post( - `tickets/${ticket.value.id}/priceDifference`, + `tickets/${formData.value.id}/priceDifference`, params ); - ticket.value.sale = data; + formData.value.sale = data; }; const submit = async () => { - if (!ticket.value.option) return notify(t('basicData.chooseAnOption'), 'negative'); + if (!formData.value.option) return notify(t('basicData.chooseAnOption'), 'negative'); const params = { - clientFk: ticket.value.clientFk, - nickname: ticket.value.nickname, - agencyModeFk: ticket.value.agencyModeFk, - addressFk: ticket.value.addressFk, - zoneFk: ticket.value.zoneFk, - warehouseFk: ticket.value.warehouseFk, - companyFk: ticket.value.companyFk, - shipped: ticket.value.shipped, - landed: ticket.value.landed, - isDeleted: ticket.value.isDeleted, - option: ticket.value.option, - isWithoutNegatives: ticket.value.withoutNegatives, - withWarningAccept: ticket.value.withWarningAccept, + clientFk: formData.value.clientFk, + nickname: formData.value.nickname, + agencyModeFk: formData.value.agencyModeFk, + addressFk: formData.value.addressFk, + zoneFk: formData.value.zoneFk, + warehouseFk: formData.value.warehouseFk, + companyFk: formData.value.companyFk, + shipped: formData.value.shipped, + landed: formData.value.landed, + isDeleted: formData.value.isDeleted, + option: formData.value.option, + isWithoutNegatives: formData.value.withoutNegatives, + withWarningAccept: formData.value.withWarningAccept, keepPrice: false, }; const { data } = await axios.post( - `tickets/${ticket.value.id}/componentUpdate`, + `tickets/${formData.value.id}/componentUpdate`, params ); @@ -83,7 +118,7 @@ const submit = async () => { }; const submitWithNegatives = async () => { - ticket.value.withWarningAccept = true; + formData.value.withWarningAccept = true; submit(); }; @@ -95,7 +130,7 @@ const onNextStep = async () => { await getPriceDifference(); stepperRef.value.next(); } else if (step.value === 2) { - if (haveNegatives.value && !ticket.value.withoutNegatives) + if (haveNegatives.value && !formData.value.withoutNegatives) openConfirmationModal( t('basicData.negativesConfirmTitle'), t('basicData.negativesConfirmMessage'), @@ -104,10 +139,11 @@ const onNextStep = async () => { else submit(); } }; + +onBeforeMount(async () => await getTicketData()); </script> <template> <QStepper - v-if="ticket" v-model="step" ref="stepperRef" color="primary" @@ -119,10 +155,10 @@ const onNextStep = async () => { }" > <QStep :name="1" :title="t('globals.pageTitles.basicData')" :done="step > 1"> - <TicketBasicDataForm v-model="ticket" /> + <TicketBasicDataForm v-if="initialDataLoaded" v-model="formData" /> </QStep> <QStep :name="2" :title="t('basicData.priceDifference')"> - <TicketBasicData v-model="ticket" v-model:have-negatives="haveNegatives" /> + <TicketBasicData v-model="formData" v-model:have-negatives="haveNegatives" /> </QStep> <template #navigation> <QStepperNavigation class="flex justify-between"> diff --git a/src/pages/Ticket/Card/TicketCard.vue b/src/pages/Ticket/Card/TicketCard.vue index e22d5799ab1..6886a8e577a 100644 --- a/src/pages/Ticket/Card/TicketCard.vue +++ b/src/pages/Ticket/Card/TicketCard.vue @@ -1,13 +1,7 @@ <script setup> import VnCardBeta from 'components/common/VnCardBeta.vue'; import TicketDescriptor from './TicketDescriptor.vue'; -import filter from './TicketFilter.js'; </script> <template> - <VnCardBeta - data-key="Ticket" - url="Tickets" - :descriptor="TicketDescriptor" - :filter="filter" - /> + <VnCardBeta data-key="Ticket" base-url="Tickets" :descriptor="TicketDescriptor" /> </template> diff --git a/src/pages/Ticket/Card/TicketComponents.vue b/src/pages/Ticket/Card/TicketComponents.vue index 5936ffc2851..842607e0c20 100644 --- a/src/pages/Ticket/Card/TicketComponents.vue +++ b/src/pages/Ticket/Card/TicketComponents.vue @@ -19,7 +19,7 @@ import RightMenu from 'src/components/common/RightMenu.vue'; const route = useRoute(); const { t } = useI18n(); const salesRef = ref(null); -const arrayData = useArrayData('Ticket'); +const arrayData = useArrayData('ticketData'); const { store } = arrayData; const ticketData = computed(() => store.data); diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index c5f3233b1de..c9849d631fb 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -6,11 +6,9 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy import CardDescriptor from 'components/ui/CardDescriptor.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import { toDateTimeFormat } from 'src/filters/date'; -import filter from './TicketFilter.js'; -import FetchData from 'src/components/FetchData.vue'; -import TicketProblems from 'src/components/TicketProblems.vue'; const $props = defineProps({ id: { @@ -30,24 +28,100 @@ const { t } = useI18n(); const entityId = computed(() => { return $props.id || route.params.id; }); -const problems = ref({}); + +const filter = { + include: [ + { + relation: 'address', + scope: { + fields: ['id', 'name', 'mobile', 'phone', 'incotermsFk'], + }, + }, + { + relation: 'client', + scope: { + fields: [ + 'id', + 'name', + 'salesPersonFk', + 'phone', + 'mobile', + 'email', + 'isActive', + 'isFreezed', + 'isTaxDataChecked', + 'hasElectronicInvoice', + ], + include: [ + { + relation: 'user', + scope: { + fields: ['id', 'lang'], + }, + }, + { relation: 'salesPersonUser' }, + ], + }, + }, + { + relation: 'ticketState', + scope: { + include: { relation: 'state' }, + }, + }, + { + relation: 'warehouse', + scope: { + fields: ['id', 'name'], + }, + }, + { + relation: 'agencyMode', + scope: { + fields: ['id', 'name'], + }, + }, + { + relation: 'zone', + scope: { + fields: [ + 'agencyModeFk', + 'bonus', + 'hour', + 'id', + 'isVolumetric', + 'itemMaxSize', + 'm3Max', + 'name', + 'price', + 'travelingDays', + ], + }, + }, + ], +}; + +const data = ref(useCardDescription()); function ticketFilter(ticket) { return JSON.stringify({ clientFk: ticket.clientFk }); } + +const setData = (entity) => { + data.value = useCardDescription(entity.ref, entity.id); +}; </script> <template> - <FetchData - :url="`Tickets/${entityId}/getTicketProblems`" - auto-load - @on-fetch="(data) => ([problems] = data)" - /> <CardDescriptor + module="Ticket" :url="`Tickets/${entityId}`" :filter="filter" - data-key="Ticket" + :title="data.title" + :subtitle="data.subtitle" + @on-fetch="setData" :summary="$props.summary" + data-key="ticketData" width="lg-width" > <template #menu="{ entity }"> @@ -93,9 +167,48 @@ function ticketFilter(ticket) { <VnLv :label="t('globals.warehouse')" :value="entity.warehouse?.name" /> <VnLv :label="t('globals.alias')" :value="entity.nickname" /> </template> - <template #icons> - <QCardActions class="q-gutter-x-xs"> - <TicketProblems :row="problems" /> + <template #icons="{ entity }"> + <QCardActions class="q-gutter-x-md"> + <QIcon + v-if="entity.client.isActive == false" + name="vn:disabled" + size="xs" + color="primary" + > + <QTooltip>{{ t('Client inactive') }}</QTooltip> + </QIcon> + <QIcon + v-if="entity.client.isFreezed == true" + name="vn:frozen" + size="xs" + color="primary" + > + <QTooltip>{{ t('Client Frozen') }}</QTooltip> + </QIcon> + <QIcon + v-if="entity?.problem?.includes('hasRisk')" + name="vn:risk" + size="xs" + color="primary" + > + <QTooltip>{{ t('Client has debt') }}</QTooltip> + </QIcon> + <QIcon + v-if="entity.client.isTaxDataChecked == false" + name="vn:no036" + size="xs" + color="primary" + > + <QTooltip>{{ t('Client not checked') }}</QTooltip> + </QIcon> + <QIcon + v-if="entity.isDeleted == true" + name="vn:deletedTicket" + size="xs" + color="primary" + > + <QTooltip>{{ t('This ticket is deleted') }}</QTooltip> + </QIcon> </QCardActions> </template> <template #actions="{ entity }"> diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue index f8084ff2f62..166e86978ab 100644 --- a/src/pages/Ticket/Card/TicketExpedition.vue +++ b/src/pages/Ticket/Card/TicketExpedition.vue @@ -40,7 +40,7 @@ const expeditionsFilter = computed(() => ({ order: ['created DESC'], })); -const ticketArrayData = useArrayData('Ticket'); +const ticketArrayData = useArrayData('ticketData'); const ticketStore = ticketArrayData.store; const ticketData = computed(() => ticketStore.data); diff --git a/src/pages/Ticket/Card/TicketFilter.js b/src/pages/Ticket/Card/TicketFilter.js deleted file mode 100644 index 7846f16584e..00000000000 --- a/src/pages/Ticket/Card/TicketFilter.js +++ /dev/null @@ -1,72 +0,0 @@ -export default { - include: [ - { - relation: 'address', - scope: { - fields: ['id', 'name', 'mobile', 'phone', 'incotermsFk'], - }, - }, - { - relation: 'client', - scope: { - fields: [ - 'id', - 'name', - 'salesPersonFk', - 'phone', - 'mobile', - 'email', - 'isActive', - 'isFreezed', - 'isTaxDataChecked', - 'hasElectronicInvoice', - 'credit', - ], - include: [ - { - relation: 'user', - scope: { - fields: ['id', 'lang'], - }, - }, - { relation: 'salesPersonUser' }, - ], - }, - }, - { - relation: 'ticketState', - scope: { - include: { relation: 'state' }, - }, - }, - { - relation: 'warehouse', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'agencyMode', - scope: { - fields: ['id', 'name'], - }, - }, - { - relation: 'zone', - scope: { - fields: [ - 'agencyModeFk', - 'bonus', - 'hour', - 'id', - 'isVolumetric', - 'itemMaxSize', - 'm3Max', - 'name', - 'price', - 'travelingDays', - ], - }, - }, - ], -}; diff --git a/src/pages/Ticket/Card/TicketNotes.vue b/src/pages/Ticket/Card/TicketNotes.vue index feb88bf8438..f558b71cc2b 100644 --- a/src/pages/Ticket/Card/TicketNotes.vue +++ b/src/pages/Ticket/Card/TicketNotes.vue @@ -32,7 +32,7 @@ watch( crudModelFilter.where.ticketFk = route.params.id; store.filter = crudModelFilter; await ticketNotesCrudRef.value.reload(); - }, + } ); function handleDelete(row) { ticketNotesCrudRef.value.remove([row]); @@ -105,7 +105,7 @@ async function handleSave() { <VnRow v-if="observationTypes.length > rows.length"> <QBtn icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat class="fill-icon-on-hover q-ml-md" color="primary" diff --git a/src/pages/Ticket/Card/TicketPackage.vue b/src/pages/Ticket/Card/TicketPackage.vue index 5fbf4c80017..8ebdb44017a 100644 --- a/src/pages/Ticket/Card/TicketPackage.vue +++ b/src/pages/Ticket/Card/TicketPackage.vue @@ -41,7 +41,7 @@ watch( crudModelFilter.where.ticketFk = route.params.id; store.filter = crudModelFilter; await ticketPackagingsCrudRef.value.reload(); - }, + } ); </script> @@ -118,7 +118,7 @@ watch( <VnRow> <QBtn icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat class="fill-icon-on-hover q-ml-md" color="primary" diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 6f02a2ce6aa..f5fb50ecff8 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 TicketTransferProxy from './TicketTransferProxy.vue'; +import TicketTransfer from './TicketTransfer.vue'; import { toCurrency, toPercentage } from 'src/filters'; import { useArrayData } from 'composables/useArrayData'; @@ -23,7 +23,6 @@ import useNotify from 'src/composables/useNotify.js'; import axios from 'axios'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; -import TicketProblems from 'src/components/TicketProblems.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; const route = useRoute(); @@ -35,7 +34,7 @@ const editPriceProxyRef = ref(null); const editManaProxyRef = ref(null); const stateBtnDropdownRef = ref(null); const quasar = useQuasar(); -const arrayData = useArrayData('Ticket'); +const arrayData = useArrayData('ticketData'); const { store } = arrayData; const selectedRows = ref([]); const hasSelectedRows = computed(() => selectedRows.value.length > 0); @@ -627,9 +626,8 @@ watch( @click="setTransferParams()" data-cy="ticketSaleTransferBtn" > - <QTooltip>{{ t('ticketSale.transferLines') }}</QTooltip> - <TicketTransferProxy - class="full-width" + <QTooltip>{{ t('Transfer lines') }}</QTooltip> + <TicketTransfer :transfer="transfer" :ticket="store.data" @refresh-data="resetChanges()" @@ -699,7 +697,53 @@ watch( :disabled-attr="isTicketEditable" > <template #column-statusIcons="{ row }"> - <TicketProblems :row="row" /> + <router-link + v-if="row.claim?.claimFk" + :to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }" + > + <QIcon color="primary" name="vn:claims" size="xs"> + <QTooltip> + {{ t('ticketSale.claim') }}: + {{ row.claim?.claimFk }} + </QTooltip> + </QIcon> + </router-link> + <QIcon v-if="row.visible < 0" color="primary" name="warning" size="xs"> + <QTooltip> + {{ t('ticketSale.visible') }}: {{ row.visible || 0 }} + </QTooltip> + </QIcon> + <QIcon + v-if="row.reserved" + color="primary" + name="vn:reserva" + size="xs" + data-cy="ticketSaleReservedIcon" + > + <QTooltip> + {{ t('ticketSale.reserved') }} + </QTooltip> + </QIcon> + <QIcon + v-if="row.itemShortage" + color="primary" + name="vn:unavailable" + size="xs" + > + <QTooltip> + {{ t('ticketSale.noVisible') }} + </QTooltip> + </QIcon> + <QIcon + v-if="row.hasComponentLack" + color="primary" + name="vn:components" + size="xs" + > + <QTooltip> + {{ t('ticketSale.hasComponentLack') }} + </QTooltip> + </QIcon> </template> <template #body-cell-picture="{ row }"> <QTd> @@ -837,7 +881,7 @@ watch( color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" data-cy="ticketSaleAddToBasketBtn" /> <QTooltip class="text-no-wrap"> diff --git a/src/pages/Ticket/Card/TicketService.vue b/src/pages/Ticket/Card/TicketService.vue index 6ce69a6aa40..d045eadee87 100644 --- a/src/pages/Ticket/Card/TicketService.vue +++ b/src/pages/Ticket/Card/TicketService.vue @@ -40,7 +40,7 @@ watch( async () => { store.filter = crudModelFilter.value; await ticketServiceCrudRef.value.reload(); - }, + } ); onMounted(async () => await getDefaultTaxClass()); @@ -59,7 +59,7 @@ const createRefund = async () => { t('service.createRefundSuccess', { ticketId: refundTicket.id, }), - 'positive', + 'positive' ); router.push({ name: 'TicketSale', params: { id: refundTicket.id } }); }; @@ -225,7 +225,7 @@ async function handleSave() { color="primary" icon="add" @click="ticketServiceCrudRef.insert()" - v-shortcut="'+'" + shortcut="+" /> </QPageSticky> </template> diff --git a/src/pages/Ticket/Card/TicketSplit.vue b/src/pages/Ticket/Card/TicketSplit.vue deleted file mode 100644 index e7905726669..00000000000 --- a/src/pages/Ticket/Card/TicketSplit.vue +++ /dev/null @@ -1,37 +0,0 @@ -<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/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index 5838efa8803..8cb518823f3 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -20,7 +20,6 @@ import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; -import TicketProblems from 'src/components/TicketProblems.vue'; const route = useRoute(); const { notify } = useNotify(); @@ -41,7 +40,7 @@ const editableStates = ref([]); const ticketUrl = ref(); const grafanaUrl = 'https://grafana.verdnatura.es'; const stateBtnDropdownRef = ref(); -const descriptorData = useArrayData('Ticket'); +const descriptorData = useArrayData('ticketData'); onMounted(async () => { ticketUrl.value = (await getUrl('ticket/')) + entityId.value + '/'; @@ -321,7 +320,83 @@ onMounted(async () => { <template #body="props"> <QTr :props="props"> <QTd class="q-gutter-x-xs"> - <TicketProblems :row="props.row" /> + <QBtn + flat + round + icon="vn:claims" + v-if="props.row.claim" + color="primary" + :to="{ + name: 'ClaimCard', + params: { + id: props.row.claim.claimFk, + }, + }" + > + <QTooltip> + {{ t('ticket.summary.claim') }}: + {{ props.row.claim.claimFk }} + </QTooltip> + </QBtn> + <QBtn + flat + round + icon="vn:claims" + v-if="props.row.claimBeginning" + color="primary" + :to="{ + name: 'ClaimCard', + params: { + id: props.row.claimBeginning.claimFk, + }, + }" + > + <QTooltip> + {{ t('ticket.summary.claim') }}: + {{ props.row.claimBeginning.claimFk }} + </QTooltip> + </QBtn> + <QIcon + name="warning" + v-show="props.row.visible < 0" + color="primary" + size="xs" + > + <QTooltip> + {{ t('globals.visible') }}: + {{ props.row.visible }} + </QTooltip> + </QIcon> + <QIcon + name="vn:reserved" + v-show="props.row.reserved" + color="primary" + size="xs" + > + <QTooltip> + {{ t('ticket.summary.reserved') }} + </QTooltip> + </QIcon> + <QIcon + name="vn:unavailable" + v-show="props.row.itemShortage" + color="primary" + size="xs" + > + <QTooltip> + {{ t('ticket.summary.itemShortage') }} + </QTooltip> + </QIcon> + <QIcon + name="vn:components" + v-show="props.row.hasComponentLack" + color="primary" + size="xs" + > + <QTooltip> + {{ t('ticket.summary.hasComponentLack') }} + </QTooltip> + </QIcon> </QTd> <QTd> <QBtn class="link" flat> diff --git a/src/pages/Ticket/Card/TicketTracking.vue b/src/pages/Ticket/Card/TicketTracking.vue index acf464fb1e1..f4b8544d375 100644 --- a/src/pages/Ticket/Card/TicketTracking.vue +++ b/src/pages/Ticket/Card/TicketTracking.vue @@ -19,7 +19,7 @@ watch( async (val) => { paginateFilter.where.ticketFk = val; paginateRef.value.fetch(); - }, + } ); const paginateFilter = reactive({ @@ -119,7 +119,7 @@ const openCreateModal = () => createTrackingDialogRef.value.show(); color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" /> <QTooltip class="text-no-wrap"> {{ t('tracking.addState') }} diff --git a/src/pages/Ticket/Card/TicketTransfer.vue b/src/pages/Ticket/Card/TicketTransfer.vue index ffa964c920e..005d74a0e4a 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,15 +21,16 @@ const $props = defineProps({ default: () => {}, }, ticket: { - type: [Array, Object], + type: 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'), @@ -85,74 +86,76 @@ const handleRowClick = (row) => { transferFormRef.value.transferSales(ticketId); } }; + +onMounted(() => (_transfer.value = $props.transfer)); </script> <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> + <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> - <template #no-data> - <TicketTransferForm ref="transferFormRef" v-bind="$props" /> - </template> - <template #bottom> - <TicketTransferForm ref="transferFormRef" v-bind="$props" /> - </template> - </QTable> + <template #no-data> + <TicketTransferForm ref="transferFormRef" v-bind="$props" /> + </template> + <template #bottom> + <TicketTransferForm ref="transferFormRef" v-bind="$props" /> + </template> + </QTable> + </QCard> + </QPopupProxy> </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 deleted file mode 100644 index 3f3f018df3f..00000000000 --- a/src/pages/Ticket/Card/TicketTransferProxy.vue +++ /dev/null @@ -1,54 +0,0 @@ -<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 deleted file mode 100644 index afa1d5cd6ef..00000000000 --- a/src/pages/Ticket/Card/components/split.js +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index dcf835d0305..00000000000 --- a/src/pages/Ticket/Negative/TicketLackDetail.vue +++ /dev/null @@ -1,198 +0,0 @@ -<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 deleted file mode 100644 index 3762f453d87..00000000000 --- a/src/pages/Ticket/Negative/TicketLackFilter.vue +++ /dev/null @@ -1,175 +0,0 @@ -<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 deleted file mode 100644 index d1e8b823ab1..00000000000 --- a/src/pages/Ticket/Negative/TicketLackList.vue +++ /dev/null @@ -1,227 +0,0 @@ -<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 deleted file mode 100644 index 176e8f7adf9..00000000000 --- a/src/pages/Ticket/Negative/TicketLackTable.vue +++ /dev/null @@ -1,356 +0,0 @@ -<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 deleted file mode 100644 index e419b85c080..00000000000 --- a/src/pages/Ticket/Negative/components/ChangeItemDialog.vue +++ /dev/null @@ -1,90 +0,0 @@ -<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 deleted file mode 100644 index 2e9aac4f0ab..00000000000 --- a/src/pages/Ticket/Negative/components/ChangeQuantityDialog.vue +++ /dev/null @@ -1,84 +0,0 @@ -<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 deleted file mode 100644 index 1acc7e0ef64..00000000000 --- a/src/pages/Ticket/Negative/components/ChangeStateDialog.vue +++ /dev/null @@ -1,91 +0,0 @@ -<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 92911cd2549..0d216bed4fb 100644 --- a/src/pages/Ticket/TicketFuture.vue +++ b/src/pages/Ticket/TicketFuture.vue @@ -1,22 +1,24 @@ <script setup> -import { ref, computed, reactive, watch } from 'vue'; +import { onMounted, ref, computed, reactive } 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(); @@ -24,126 +26,214 @@ const { openConfirmationModal } = useVnConfirm(); const { notify } = useNotify(); const user = state.getUser(); +const itemPackingTypesOptions = ref([]); const selectedTickets = ref([]); -const vnTableRef = ref({}); -const originElRef = ref(null); -const destinationElRef = ref(null); + +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 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: '', + label: t('futureTickets.problems'), name: 'problems', - headerClass: 'horizontal-separator', align: 'left', - columnFilter: false, + columnFilter: null, }, { label: t('advanceTickets.ticketId'), - name: 'id', + name: 'ticketId', align: 'center', - headerClass: 'horizontal-separator', + sortable: true, + columnFilter: { + component: VnInput, + type: 'text', + filterValue: null, + filterParamKey: 'id', + event: getInputEvents, + attrs: { + dense: true, + }, + }, }, { label: t('futureTickets.shipped'), name: 'shipped', align: 'left', - columnFilter: false, - headerClass: 'horizontal-separator', + sortable: true, + columnFilter: null, }, { - align: 'center', - class: 'shrink', label: t('advanceTickets.ipt'), name: 'ipt', + field: 'ipt', + align: 'left', + sortable: true, columnFilter: { - component: 'select', + component: VnSelect, + filterParamKey: 'iptColFilter', + type: 'select', + filterValue: null, + event: getInputEvents, attrs: { - url: 'itemPackingTypes', - fields: ['code', 'description'], - where: { isActive: true }, - optionValue: 'code', - optionLabel: 'description', - inWhere: false, + options: itemPackingTypesOptions.value, + 'option-value': 'code', + 'option-label': 'description', + dense: true, }, }, - format: (row, dashIfEmpty) => dashIfEmpty(row.ipt), - headerClass: 'horizontal-separator', + format: (val) => dashIfEmpty(val), }, { label: t('ticketList.state'), name: 'state', align: 'left', - columnFilter: false, - headerClass: 'horizontal-separator', + sortable: true, + columnFilter: null, }, { label: t('advanceTickets.liters'), name: 'liters', + field: 'liters', align: 'left', - headerClass: 'horizontal-separator', + sortable: true, + columnFilter: { + component: VnInput, + type: 'text', + filterValue: null, + event: getInputEvents, + attrs: { + dense: true, + }, + }, }, { label: t('advanceTickets.import'), + field: 'import', name: 'import', align: 'left', - headerClass: 'horizontal-separator', - columnFilter: false, - format: (row) => toCurrency(row.totalWithVat), + sortable: true, }, { label: t('futureTickets.availableLines'), name: 'lines', field: 'lines', align: 'center', - headerClass: 'horizontal-separator', - format: (row, dashIfEmpty) => dashIfEmpty(row.lines), + sortable: true, + columnFilter: { + component: VnInput, + type: 'text', + filterValue: null, + event: getInputEvents, + attrs: { + dense: true, + }, + }, + format: (val) => dashIfEmpty(val), }, { label: t('advanceTickets.futureId'), name: 'futureId', - align: 'center', - headerClass: 'horizontal-separator vertical-separator ', - columnClass: 'vertical-separator', + align: 'left', + sortable: true, + columnFilter: { + component: VnInput, + type: 'text', + filterValue: null, + filterParamKey: 'futureId', + event: getInputEvents, + attrs: { + dense: true, + }, + }, }, { label: t('futureTickets.futureShipped'), name: 'futureShipped', align: 'left', - headerClass: 'horizontal-separator', - columnFilter: false, - format: (row) => toDateTimeFormat(row.futureShipped), + sortable: true, + columnFilter: null, + format: (val) => dashIfEmpty(val), }, + { - align: 'center', label: t('advanceTickets.futureIpt'), - class: 'shrink', name: 'futureIpt', + field: 'futureIpt', + align: 'left', + sortable: true, columnFilter: { - component: 'select', + component: VnSelect, + filterParamKey: 'futureIptColFilter', + type: 'select', + filterValue: null, + event: getInputEvents, attrs: { - url: 'itemPackingTypes', - fields: ['code', 'description'], - where: { isActive: true }, - optionValue: 'code', - optionLabel: 'description', + options: itemPackingTypesOptions.value, + 'option-value': 'code', + 'option-label': 'description', + dense: true, }, }, - headerClass: 'horizontal-separator', - format: (row, dashIfEmpty) => dashIfEmpty(row.futureIpt), + format: (val) => dashIfEmpty(val), }, { label: t('advanceTickets.futureState'), name: 'futureState', align: 'right', - headerClass: 'horizontal-separator', - class: 'expand', - columnFilter: false, - format: (row, dashIfEmpty) => dashIfEmpty(row.futureState), + sortable: true, + columnFilter: null, + format: (val) => dashIfEmpty(val), }, ]); @@ -168,51 +258,26 @@ const moveTicketsFuture = async () => { await axios.post('Tickets/merge', params); notify(t('advanceTickets.moveTicketSuccess'), 'positive'); selectedTickets.value = []; - vnTableRef.value.reload(); + 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 }, -); +onMounted(async () => { + await arrayData.fetch({ append: false }); +}); </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="futureTicket" + data-key="FutureTickets" :label="t('Search ticket')" :info="t('futureTickets.searchInfo')" /> @@ -228,7 +293,7 @@ watch( t(`futureTickets.moveTicketDialogSubtitle`, { selectedTickets: selectedTickets.length, }), - moveTicketsFuture, + moveTicketsFuture ) " > @@ -240,135 +305,235 @@ watch( </VnSubToolbar> <RightMenu> <template #right-panel> - <TicketFutureFilter data-key="futureTickets" /> + <TicketFutureFilter data-key="FutureTickets" /> </template> </RightMenu> <QPage class="column items-center q-pa-md"> - <VnTable - data-key="futureTickets" - ref="vnTableRef" - url="Tickets/getTicketsFuture" - search-url="futureTickets" - :user-params="userParams" - :limit="0" + <QTable + :rows="tickets" :columns="ticketColumns" - :table="{ - 'row-key': '$index', - selection: 'multiple', - }" + row-key="id" + selection="multiple" v-model:selected="selectedTickets" - :right-search="false" - auto-load - :disable-option="{ card: true }" + :pagination="{ rowsPerPage: 0 }" + :no-data-label="t('globals.noResults')" + style="max-width: 99%" > - <template #column-problems="{ row }"> - <span class="q-gutter-x-xs"> - <QIcon - v-if="row.futureAgencyFk !== row.agencyFk && row.agencyFk" - color="primary" - name="vn:agency-term" - size="xs" - class="q-mr-xs" + <template #header="props"> + <QTr> + <QTh class="horizontal-separator" /> + <QTh + class="horizontal-separator text-uppercase color-vn-label" + colspan="8" + translate > - <QTooltip class="column"> - <span> - {{ - t('advanceTickets.originAgency', { - agency: row.futureAgency, - }) - }} - </span> - <span> - {{ - t('advanceTickets.destinationAgency', { - agency: row.agency, - }) - }} - </span> + {{ 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"> + <QIcon + v-if="row.isTaxDataChecked === 0" + color="primary" + name="vn:no036" + size="xs" + > + <QTooltip> + {{ t('futureTickets.noVerified') }} </QTooltip> </QIcon> - <TicketProblems :row /> - </span> + <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> </template> - <template #column-id="{ row }"> - <QBtn flat class="link" @click.stop dense> - {{ row.id }} - <TicketDescriptorProxy :id="row.id" /> - </QBtn> + <template #body-cell-ticketId="{ row }"> + <QTd> + <QBtn flat class="link"> + {{ row.id }} + <TicketDescriptorProxy :id="row.id" /> + </QBtn> + </QTd> </template> - <template #column-shipped="{ row }"> - <QBadge - text-color="black" - :color="getDateQBadgeColor(row.shipped)" - class="q-ma-none" - > - {{ toDateTimeFormat(row.shipped) }} - </QBadge> + <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> - <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 #body-cell-state="{ row }"> + <QTd> + <QBadge + text-color="black" + :color="row.classColor" + class="q-ma-none" + dense + > + {{ row.state }} + </QBadge> + </QTd> </template> - <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 #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> - <template #column-futureId="{ row }"> - <QBtn flat class="link" @click.stop dense> - {{ row.futureId }} - <TicketDescriptorProxy :id="row.futureId" /> - </QBtn> + <template #body-cell-futureId="{ row }"> + <QTd class="vertical-separator"> + <QBtn flat class="link" dense> + {{ row.futureId }} + <TicketDescriptorProxy :id="row.futureId" /> + </QBtn> + </QTd> </template> - <template #column-futureShipped="{ row }"> - <QBadge - text-color="black" - :color="getDateQBadgeColor(row.futureShipped)" - class="q-ma-none" - > - {{ toDateTimeFormat(row.futureShipped) }} - </QBadge> + <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> - <template #column-futureState="{ row }"> - <QBadge - text-color="black" - :color="row.futureClassColor" - class="q-mr-xs" - dense - > - {{ row.futureState }} - </QBadge> + <template #body-cell-futureState="{ row }"> + <QTd> + <QBadge + text-color="black" + :color="row.futureClassColor" + class="q-ma-none" + dense + > + {{ row.futureState }} + </QBadge> + </QTd> </template> - </VnTable> + </QTable> </QPage> </template> <style scoped lang="scss"> -:deep(.vertical-separator) { +.shipped { + min-width: 132px; +} +.vertical-separator { border-left: 4px solid white !important; } -:deep(.horizontal-separator) { - border-top: 4px solid white !important; -} -:deep(.horizontal-bottom-separator) { +.horizontal-separator { border-bottom: 4px solid white !important; } </style> diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue index 64e060a3921..d28b0af71e9 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(); -defineProps({ +const props = defineProps({ dataKey: { type: String, required: true, @@ -58,7 +58,7 @@ onMounted(async () => { auto-load /> <VnFilterPanel - :data-key + :data-key="props.dataKey" :un-removable-params="['warehouseFk', 'originScopeDays ', 'futureScopeDays']" > <template #tags="{ tag, formatFn }"> diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index cdbb22d9be6..f11b32c3a5e 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -23,8 +23,6 @@ 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 @@ -190,6 +188,7 @@ 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 @@ -203,89 +202,9 @@ ticketList: creditCard: Credit card transfers: Transfers province: Province + warehouse: Warehouse + hour: Hour closure: Closure toLines: Go to lines addressNickname: Address nickname ref: Reference - rounding: Rounding - noVerifiedData: No verified data - purchaseRequest: Purchase request - 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 75d3c6a2b7b..945da8367f8 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -127,8 +127,6 @@ 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 @@ -215,84 +213,3 @@ 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 - notVisible: No visible - clientFrozen: Cliente congelado - componentLack: Faltan componentes diff --git a/src/pages/Travel/Card/TravelBasicData.vue b/src/pages/Travel/Card/TravelBasicData.vue index b1adc812622..4b9aa28ed06 100644 --- a/src/pages/Travel/Card/TravelBasicData.vue +++ b/src/pages/Travel/Card/TravelBasicData.vue @@ -9,7 +9,6 @@ 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(); @@ -54,16 +53,7 @@ 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')" @@ -111,3 +101,10 @@ 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/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index cb09eafd680..445675b902f 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -1,13 +1,43 @@ <script setup> import TravelDescriptor from './TravelDescriptor.vue'; import VnCardBeta from 'src/components/common/VnCardBeta.vue'; -import filter from './TravelFilter.js'; + +const userFilter = { + fields: [ + 'id', + 'ref', + 'shipped', + 'landed', + 'totalEntries', + 'warehouseInFk', + 'warehouseOutFk', + 'cargoSupplierFk', + 'agencyModeFk', + 'isRaid', + 'isDelivered', + 'isReceived', + ], + include: [ + { + relation: 'warehouseIn', + scope: { + fields: ['name'], + }, + }, + { + relation: 'warehouseOut', + scope: { + fields: ['name'], + }, + }, + ], +}; </script> <template> <VnCardBeta data-key="Travel" - url="Travels" + base-url="Travels" :descriptor="TravelDescriptor" - :filter="filter" + :user-filter="userFilter" /> </template> diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue index 922f89f333f..72acf91b843 100644 --- a/src/pages/Travel/Card/TravelDescriptor.vue +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -32,6 +32,7 @@ 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 05436834ffb..f5f4520fd49 100644 --- a/src/pages/Travel/Card/TravelFilter.js +++ b/src/pages/Travel/Card/TravelFilter.js @@ -11,7 +11,6 @@ export default { 'agencyModeFk', 'isRaid', 'daysInForward', - 'availabled', ], include: [ { diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index 9f955261181..16d42f10424 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -10,8 +10,6 @@ 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'; @@ -335,12 +333,6 @@ 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/TravelThermographs.vue b/src/pages/Travel/Card/TravelThermographs.vue index 2376bd6d253..2946c8814bc 100644 --- a/src/pages/Travel/Card/TravelThermographs.vue +++ b/src/pages/Travel/Card/TravelThermographs.vue @@ -217,7 +217,7 @@ const removeThermograph = async (id) => { icon="add" color="primary" @click="redirectToThermographForm('create')" - v-shortcut="'+'" + shortcut="+" /> <QTooltip class="text-no-wrap"> {{ t('Add thermograph') }} diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index b22574632fc..b903aeabffb 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -113,7 +113,7 @@ warehouses(); <template #append> <QBtn icon="add" - v-shortcut="'+'" + shortcut="+" flat dense size="12px" diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index b227afcb213..e90c01be237 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -10,9 +10,6 @@ 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(); @@ -170,17 +167,6 @@ 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: '', @@ -283,16 +269,6 @@ 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/Wagon/Card/WagonCard.vue b/src/pages/Wagon/Card/WagonCard.vue index 644a30ffa33..ed6c837781b 100644 --- a/src/pages/Wagon/Card/WagonCard.vue +++ b/src/pages/Wagon/Card/WagonCard.vue @@ -2,5 +2,5 @@ import VnCard from 'components/common/VnCard.vue'; </script> <template> - <VnCard data-key="Wagon" url="Wagons" /> + <VnCard data-key="Wagon" base-url="Wagons" /> </template> diff --git a/src/pages/Wagon/Type/WagonTypeList.vue b/src/pages/Wagon/Type/WagonTypeList.vue index 4c0b078a730..c0943c58e80 100644 --- a/src/pages/Wagon/Type/WagonTypeList.vue +++ b/src/pages/Wagon/Type/WagonTypeList.vue @@ -96,13 +96,7 @@ async function remove(row) { > </VnTable> <QPageSticky :offset="[18, 18]"> - <QBtn - @click.stop="dialog.show()" - color="primary" - fab - icon="add" - v-shortcut="'+'" - > + <QBtn @click.stop="dialog.show()" color="primary" fab icon="add" shortcut="+"> <QDialog ref="dialog"> <FormModelPopup :title="t('Create new Wagon type')" diff --git a/src/pages/Worker/Card/WorkerBasicData.vue b/src/pages/Worker/Card/WorkerBasicData.vue index fcf0f0369e2..6a13e3f398a 100644 --- a/src/pages/Worker/Card/WorkerBasicData.vue +++ b/src/pages/Worker/Card/WorkerBasicData.vue @@ -1,5 +1,6 @@ <script setup> -import { ref } from 'vue'; +import { ref, onBeforeMount } from 'vue'; +import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import FetchData from 'components/FetchData.vue'; @@ -10,13 +11,18 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import { useAdvancedSummary } from 'src/composables/useAdvancedSummary'; const { t } = useI18n(); -const form = ref(); const educationLevels = ref([]); const countries = ref([]); const maritalStatus = [ { code: 'M', name: t('Married') }, { code: 'S', name: t('Single') }, ]; +const advancedSummary = ref({}); + +onBeforeMount(async () => { + advancedSummary.value = + (await useAdvancedSummary('Workers', +useRoute().params.id)) ?? {}; +}); </script> <template> <FetchData @@ -32,15 +38,14 @@ const maritalStatus = [ auto-load /> <FormModel - ref="form" + :filter="{ where: { id: +$route.params.id } }" + url="Workers/summary" :url-update="`Workers/${$route.params.id}`" auto-load model="Worker" @on-fetch=" async (data) => { - Object.assign(data, (await useAdvancedSummary('Workers', data.id)) ?? {}); - await $nextTick(); - if (form) form.hasChanges = false; + Object.assign(data, advancedSummary); } " > diff --git a/src/pages/Worker/Card/WorkerCalendar.vue b/src/pages/Worker/Card/WorkerCalendar.vue index df4616011f4..5ca95a1a40f 100644 --- a/src/pages/Worker/Card/WorkerCalendar.vue +++ b/src/pages/Worker/Card/WorkerCalendar.vue @@ -1,8 +1,7 @@ <script setup> -import { nextTick, ref, watch, computed } from 'vue'; +import { nextTick, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; -import { useAcl } from 'src/composables/useAcl'; import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue'; import FetchData from 'components/FetchData.vue'; @@ -10,17 +9,10 @@ import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; import axios from 'axios'; -import VnNotes from 'src/components/ui/VnNotes.vue'; -import { useStateStore } from 'src/stores/useStateStore'; -const stateStore = useStateStore(); const router = useRouter(); const route = useRoute(); const { t } = useI18n(); -const acl = useAcl(); -const canSeeNotes = computed(() => - acl.hasAny([{ model: 'Worker', props: '__get__business', accessType: 'READ' }]), -); const workerIsFreelance = ref(); const WorkerFreelanceRef = ref(); const workerCalendarFilterRef = ref(null); @@ -34,10 +26,6 @@ const contractHolidays = ref(null); const yearHolidays = ref(null); const eventsMap = ref({}); const festiveEventsMap = ref({}); -const saveUrl = ref(); -const body = { - workerFk: route.params.id, -}; const onFetchActiveContract = (data) => { if (!data) return; @@ -79,7 +67,7 @@ const onFetchAbsences = (data) => { name: holidayName, isFestive: true, }, - true, + true ); }); } @@ -158,7 +146,7 @@ watch( async () => { await nextTick(); await activeContractRef.value.fetch(); - }, + } ); watch([year, businessFk], () => refreshData()); </script> @@ -193,20 +181,6 @@ watch([year, businessFk], () => refreshData()); /> </template> </RightMenu> - <Teleport to="#st-data" v-if="stateStore.isSubToolbarShown() && canSeeNotes"> - <VnNotes - :just-input="true" - :url="`Workers/${route.params.id}/business`" - :filter="{ fields: ['id', 'notes', 'workerFk'] }" - :save-url="saveUrl" - @on-fetch=" - (data) => { - saveUrl = `Businesses/${data.id}`; - } - " - :body="body" - /> - </Teleport> <QPage class="column items-center"> <QCard v-if="workerIsFreelance"> <QCardSection class="text-center"> diff --git a/src/pages/Worker/Card/WorkerCalendarFilter.vue b/src/pages/Worker/Card/WorkerCalendarFilter.vue index 48fc4094b8f..67b7df907be 100644 --- a/src/pages/Worker/Card/WorkerCalendarFilter.vue +++ b/src/pages/Worker/Card/WorkerCalendarFilter.vue @@ -180,6 +180,8 @@ const yearList = ref(generateYears()); :is-clearable="false" /> </QItemSection> + </QItem> + <QItem> <QItemSection> <VnSelect :label="t('Contract')" diff --git a/src/pages/Worker/Card/WorkerCard.vue b/src/pages/Worker/Card/WorkerCard.vue index 3b7a62025ae..1ada15a3386 100644 --- a/src/pages/Worker/Card/WorkerCard.vue +++ b/src/pages/Worker/Card/WorkerCard.vue @@ -3,10 +3,5 @@ import WorkerDescriptor from './WorkerDescriptor.vue'; import VnCardBeta from 'src/components/common/VnCardBeta.vue'; </script> <template> - <VnCardBeta - data-key="Worker" - url="Workers/summary" - :id-in-where="true" - :descriptor="WorkerDescriptor" - /> + <VnCardBeta data-key="Worker" custom-url="Workers/summary" :descriptor="WorkerDescriptor" /> </template> diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index de3f634e2c0..d87fd4a54ac 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/Worker/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; const $props = defineProps({ id: { @@ -21,7 +21,7 @@ const $props = defineProps({ dataKey: { type: String, required: false, - default: 'Worker', + default: 'workerData', }, }); const image = ref(null); @@ -50,8 +50,9 @@ const handlePhotoUpdated = (evt = false) => { <template> <CardDescriptor ref="cardDescriptorRef" + module="Worker" :data-key="dataKey" - url="Workers/summary" + url="Workers/descriptor" :filter="{ where: { id: entityId } }" title="user.nickname" @on-fetch="getIsExcluded" @@ -151,7 +152,7 @@ const handlePhotoUpdated = (evt = false) => { <QBtn :to="{ name: 'AccountCard', - params: { id: entity.user?.id }, + params: { id: entity.user.id }, }" size="md" icon="face" diff --git a/src/pages/Worker/Card/WorkerDescriptorProxy.vue b/src/pages/Worker/Card/WorkerDescriptorProxy.vue index a142570f9ca..43deb7821a0 100644 --- a/src/pages/Worker/Card/WorkerDescriptorProxy.vue +++ b/src/pages/Worker/Card/WorkerDescriptorProxy.vue @@ -12,6 +12,11 @@ const $props = defineProps({ <template> <QPopupProxy> - <WorkerDescriptor v-if="$props.id" :id="$props.id" :summary="WorkerSummary" /> + <WorkerDescriptor + v-if="$props.id" + :id="$props.id" + :summary="WorkerSummary" + data-key="workerDescriptorProxy" + /> </QPopupProxy> </template> diff --git a/src/pages/Worker/Card/WorkerFormation.vue b/src/pages/Worker/Card/WorkerFormation.vue index e8680f7dd84..6fd5a4eae01 100644 --- a/src/pages/Worker/Card/WorkerFormation.vue +++ b/src/pages/Worker/Card/WorkerFormation.vue @@ -94,7 +94,6 @@ const columns = computed(() => [ align: 'left', name: 'hasDiploma', label: t('worker.formation.tableVisibleColumns.hasDiploma'), - component: 'checkbox', create: true, }, { @@ -119,7 +118,7 @@ const columns = computed(() => [ :url="`Workers/${entityId}/trainingCourse`" :url-create="`Workers/${entityId}/trainingCourse`" save-url="TrainingCourses/crud" - :user-filter="courseFilter" + :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 c04f6496b9f..c220df76a2c 100644 --- a/src/pages/Worker/Card/WorkerMedical.vue +++ b/src/pages/Worker/Card/WorkerMedical.vue @@ -3,23 +3,11 @@ 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', @@ -48,9 +36,6 @@ const columns = [ url: 'medicalCenters', fields: ['id', 'name'], }, - format: (row, dashIfEmpty) => { - return dashIfEmpty(row.center?.name); - }, }, { align: 'left', @@ -99,7 +84,6 @@ 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/WorkerOperator.vue b/src/pages/Worker/Card/WorkerOperator.vue index 6faeefe6704..cdacc72c05b 100644 --- a/src/pages/Worker/Card/WorkerOperator.vue +++ b/src/pages/Worker/Card/WorkerOperator.vue @@ -1,7 +1,7 @@ <script setup> import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; -import { ref, computed, watch } from 'vue'; +import { ref, computed } from 'vue'; import FetchData from 'components/FetchData.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; @@ -19,7 +19,6 @@ const trainsData = ref([]); const machinesData = ref([]); const route = useRoute(); const routeId = computed(() => route.params.id); -const selected = ref([]); const initialData = computed(() => { return { @@ -42,21 +41,6 @@ async function insert() { await axios.post('Operators', initialData.value); crudModelRef.value.reload(); } - -watch( - () => crudModelRef.value?.formData, - (formData) => { - if (formData && formData.length) { - if (JSON.stringify(selected.value) !== JSON.stringify(formData)) { - selected.value = formData; - } - } else if (selected.value.length > 0) { - selected.value = []; - } - }, - { immediate: true, deep: true } -); - </script> <template> @@ -83,7 +67,6 @@ watch( :data-required="{ workerFk: route.params.id }" ref="crudModelRef" search-url="operator" - :selected="selected" auto-load > <template #body="{ rows }"> diff --git a/src/pages/Worker/Card/WorkerPda.vue b/src/pages/Worker/Card/WorkerPda.vue index 47e13cf6d53..f6cb92aacd6 100644 --- a/src/pages/Worker/Card/WorkerPda.vue +++ b/src/pages/Worker/Card/WorkerPda.vue @@ -101,7 +101,7 @@ function reloadData() { openConfirmationModal( t(`Remove PDA`), t('Do you want to remove this PDA?'), - () => deallocatePDA(row.deviceProductionFk), + () => deallocatePDA(row.deviceProductionFk) ) " > @@ -114,13 +114,7 @@ function reloadData() { </template> </VnPaginate> <QPageSticky :offset="[18, 18]"> - <QBtn - @click.stop="dialog.show()" - color="primary" - fab - icon="add" - v-shortcut="'+'" - > + <QBtn @click.stop="dialog.show()" color="primary" fab icon="add" shortcut="+"> <QDialog ref="dialog"> <FormModelPopup :title="t('Add new device')" diff --git a/src/pages/Worker/Card/WorkerPit.vue b/src/pages/Worker/Card/WorkerPit.vue index 40e814452ea..79cf1a04fb1 100644 --- a/src/pages/Worker/Card/WorkerPit.vue +++ b/src/pages/Worker/Card/WorkerPit.vue @@ -221,7 +221,7 @@ const deleteRelative = async (id) => { color="primary" flat icon="add" - v-shortcut="'+'" + shortcut="+" style="flex: 0" data-cy="addRelative" /> diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 78c5dfd82b3..992f6ec7182 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/Worker/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; import { useAdvancedSummary } from 'src/composables/useAdvancedSummary'; import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 7def6e94cf5..c580e5202de 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -64,17 +64,17 @@ const selectedCalendarDates = ref([]); // Date formateada para bindear al componente QDate const selectedDateFormatted = ref(toDateString(defaultDate.value)); -const arrayData = useArrayData('Worker'); +const arrayData = useArrayData('workerData'); const acl = useAcl(); const selectedDateYear = computed(() => moment(selectedDate.value).isoWeekYear()); const worker = computed(() => arrayData.store?.data); const canSend = computed(() => - acl.hasAny([{ model: 'WorkerTimeControl', props: 'sendMail', accessType: 'WRITE' }]), + acl.hasAny([{ model: 'WorkerTimeControl', props: 'sendMail', accessType: 'WRITE' }]) ); const canUpdate = computed(() => acl.hasAny([ { model: 'WorkerTimeControl', props: 'updateMailState', accessType: 'WRITE' }, - ]), + ]) ); const isHimself = computed(() => user.value.id === Number(route.params.id)); @@ -100,7 +100,7 @@ const getHeaderFormattedDate = (date) => { }; const formattedWeekTotalHours = computed(() => - secondsToHoursMinutes(weekTotalHours.value), + secondsToHoursMinutes(weekTotalHours.value) ); const onInputChange = async (date) => { @@ -320,7 +320,7 @@ const getFinishTime = () => { today.setHours(0, 0, 0, 0); let todayInWeek = weekDays.value.find( - (day) => day.dated.getTime() === today.getTime(), + (day) => day.dated.getTime() === today.getTime() ); if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) { @@ -472,7 +472,7 @@ onMounted(async () => { openConfirmationModal( t('Send time control email'), t('Are you sure you want to send it?'), - resendEmail, + resendEmail ) " > @@ -561,7 +561,7 @@ onMounted(async () => { @show-worker-time-form=" showWorkerTimeForm( { id: hour.id, entryCode: hour.direction }, - 'edit', + 'edit' ) " class="hour-chip" @@ -577,7 +577,7 @@ onMounted(async () => { </span> <QBtn icon="add_circle" - v-shortcut="'+'" + shortcut="+" flat color="primary" class="fill-icon cursor-pointer" diff --git a/src/pages/Worker/WorkerDepartmentTree.vue b/src/pages/Worker/WorkerDepartmentTree.vue index 9baf5ee571a..9abf4e3127e 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/Worker/Department/Card/DepartmentDescriptorProxy.vue'; +import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; import CreateDepartmentChild from './CreateDepartmentChild.vue'; import axios from 'axios'; import { useRouter } from 'vue-router'; @@ -173,7 +173,7 @@ function handleEvent(type, event, node) { color="primary" flat icon="add" - v-shortcut="'+'" + shortcut="+" class="cursor-pointer" @click.stop="showCreateNodeForm(node.id)" > diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 03013f011b9..cbeeff2e9cb 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -1,7 +1,5 @@ <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'; @@ -9,23 +7,10 @@ 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> - <FetchData - url="RoadmapAddresses" - auto-load - @on-fetch="(data) => (validAddresses = data)" - /> - <FetchData url="Addresses" auto-load @on-fetch="setFilteredAddresses" /> - <FormModel auto-load model="Zone"> + <FormModel :url="`Zones/${$route.params.id}`" auto-load model="zone"> <template #form="{ data, validate }"> <VnRow> <VnInput @@ -33,15 +18,15 @@ const setFilteredAddresses = (data) => { :label="t('Name')" clearable v-model="data.name" - :required="true" /> </VnRow> + <VnRow> <VnSelect v-model="data.agencyModeFk" :rules="validate('zone.agencyModeFk')" - url="AgencyModes/isActive" - :fields="['id', 'name']" + url="AgencyModes/isActive" + :fields="['id', 'name']" :label="t('Agency')" emit-value map-options @@ -84,7 +69,7 @@ const setFilteredAddresses = (data) => { type="number" min="0" /> - <VnInputTime v-model="data.hour" :label="t('Closing')" :required="true" /> + <VnInputTime v-model="data.hour" :label="t('Closing')" /> </VnRow> <VnRow> @@ -93,7 +78,7 @@ const setFilteredAddresses = (data) => { :label="t('Price')" type="number" min="0" - :required="true" + required="true" clearable /> <VnInput @@ -101,7 +86,7 @@ const setFilteredAddresses = (data) => { :label="t('Price optimum')" type="number" min="0" - :required="true" + required="true" clearable /> </VnRow> @@ -118,14 +103,12 @@ const setFilteredAddresses = (data) => { v-model="data.addressFk" option-value="id" option-label="nickname" - :options="addresses" + url="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/ZoneCard.vue b/src/pages/Zone/Card/ZoneCard.vue index 41daff5c0b9..a470cd5bdb8 100644 --- a/src/pages/Zone/Card/ZoneCard.vue +++ b/src/pages/Zone/Card/ZoneCard.vue @@ -1,12 +1,13 @@ <script setup> +import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import { computed } from 'vue'; import VnCard from 'components/common/VnCard.vue'; import ZoneDescriptor from './ZoneDescriptor.vue'; import ZoneFilterPanel from '../ZoneFilterPanel.vue'; -import filter from './ZoneFilter.js'; +const { t } = useI18n(); const route = useRoute(); const routeName = computed(() => route.name); @@ -18,16 +19,15 @@ function notIsLocations(ifIsFalse, ifIsTrue) { <template> <VnCard - data-key="Zone" - :url="notIsLocations('Zones', undefined)" + data-key="zone" + :base-url="notIsLocations('Zones', undefined)" :descriptor="ZoneDescriptor" - :filter="filter" :filter-panel="notIsLocations(ZoneFilterPanel, undefined)" :search-data-key="notIsLocations('ZoneList', undefined)" :searchbar-props="{ url: notIsLocations('Zones', 'ZoneLocations'), - label: notIsLocations($t('list.searchZone'), $t('list.searchLocation')), - info: $t('list.searchInfo'), + label: notIsLocations(t('list.searchZone'), t('list.searchLocation')), + info: t('list.searchInfo'), whereFilter: notIsLocations((value) => { return /^\d+$/.test(value) ? { id: value } diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue index 27676212e65..8355c219e55 100644 --- a/src/pages/Zone/Card/ZoneDescriptor.vue +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -1,14 +1,15 @@ <script setup> -import { computed } from 'vue'; +import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toTimeFormat } from 'src/filters/date'; import { toCurrency } from 'filters/index'; +import useCardDescription from 'src/composables/useCardDescription'; import ZoneDescriptorMenuItems from './ZoneDescriptorMenuItems.vue'; -import filter from './ZoneFilter.js'; const $props = defineProps({ id: { @@ -19,22 +20,49 @@ const $props = defineProps({ }); const route = useRoute(); +const { t } = useI18n(); + +const filter = { + include: [ + { + relation: 'agencyMode', + scope: { + fields: ['name', 'id'], + }, + }, + ], +}; + const entityId = computed(() => { return $props.id || route.params.id; }); + +const data = ref(useCardDescription()); +const setData = (entity) => { + data.value = useCardDescription(entity.ref, entity.id); +}; </script> <template> - <CardDescriptor :url="`Zones/${entityId}`" :filter="filter" data-key="Zone"> + <CardDescriptor + module="Zone" + :url="`Zones/${entityId}`" + :title="data.title" + :subtitle="data.subtitle" + :filter="filter" + @on-fetch="setData" + data-key="zoneData" + > <template #menu="{ entity }"> <ZoneDescriptorMenuItems :zone="entity" /> </template> <template #body="{ entity }"> - <VnLv :label="$t('list.agency')" :value="entity.agencyMode?.name" /> - <VnLv :label="$t('zone.closing')" :value="toTimeFormat(entity.hour)" /> - <VnLv :label="$t('zone.travelingDays')" :value="entity.travelingDays" /> - <VnLv :label="$t('list.price')" :value="toCurrency(entity.price)" /> - <VnLv :label="$t('zone.bonus')" :value="toCurrency(entity.bonus)" /> + <VnLv :label="t('list.agency')" :value="entity.agencyMode.name" /> + <VnLv :label="t('zone.closing')" :value="toTimeFormat(entity.hour)" /> + <VnLv :label="t('zone.travelingDays')" :value="entity.travelingDays" /> + <VnLv :label="t('list.price')" :value="toCurrency(entity.price)" /> + <VnLv :label="t('zone.bonus')" :value="toCurrency(entity.bonus)" /> </template> </CardDescriptor> </template> + diff --git a/src/pages/Zone/Card/ZoneEvents.vue b/src/pages/Zone/Card/ZoneEvents.vue index 1e6debd25df..a5806bab9ff 100644 --- a/src/pages/Zone/Card/ZoneEvents.vue +++ b/src/pages/Zone/Card/ZoneEvents.vue @@ -78,13 +78,13 @@ const onZoneEventFormClose = () => { { isNewMode: true, }, - true, + true ) " color="primary" fab icon="add" - v-shortcut="'+'" + shortcut="+" /> <QTooltip class="text-no-wrap"> {{ t('eventsInclusionForm.addEvent') }} diff --git a/src/pages/Zone/Card/ZoneFilter.js b/src/pages/Zone/Card/ZoneFilter.js deleted file mode 100644 index 3298c7c8a8f..00000000000 --- a/src/pages/Zone/Card/ZoneFilter.js +++ /dev/null @@ -1,10 +0,0 @@ -export default { - include: [ - { - relation: 'agencyMode', - scope: { - fields: ['name', 'id'], - }, - }, - ], -}; diff --git a/src/pages/Zone/Card/ZoneSearchbar.vue b/src/pages/Zone/Card/ZoneSearchbar.vue index d1188a1e8b6..f7a59e97fe0 100644 --- a/src/pages/Zone/Card/ZoneSearchbar.vue +++ b/src/pages/Zone/Card/ZoneSearchbar.vue @@ -22,50 +22,15 @@ 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="tableFilter" + :filter="{ + include: { relation: 'agencyMode', scope: { fields: ['name'] } }, + }" :expr-builder="exprBuilder" :label="t('list.searchZone')" :info="t('list.searchInfo')" diff --git a/src/pages/Zone/Card/ZoneSummary.vue b/src/pages/Zone/Card/ZoneSummary.vue index 5b29b495b06..12480263321 100644 --- a/src/pages/Zone/Card/ZoneSummary.vue +++ b/src/pages/Zone/Card/ZoneSummary.vue @@ -11,7 +11,6 @@ import { getUrl } from 'src/composables/getUrl'; import { toCurrency } from 'filters/index'; import { toTimeFormat } from 'src/filters/date'; import axios from 'axios'; -import filter from './ZoneFilter.js'; import ZoneDescriptorMenuItems from './ZoneDescriptorMenuItems.vue'; const route = useRoute(); @@ -27,6 +26,19 @@ const $props = defineProps({ const entityId = computed(() => $props.id || route.params.id); const zoneUrl = ref(); +const filter = computed(() => { + const filter = { + include: { + relation: 'agencyMode', + fields: ['name'], + }, + where: { + id: entityId, + }, + }; + return filter; +}); + const columns = computed(() => [ { label: t('list.name'), @@ -60,9 +72,9 @@ onMounted(async () => { <template> <CardSummary - data-key="Zone" + data-key="ZoneSummary" ref="summary" - :url="`Zones/${entityId}`" + url="Zones/findOne" :filter="filter" > <template #header="{ entity }"> diff --git a/src/pages/Zone/Card/ZoneWarehouses.vue b/src/pages/Zone/Card/ZoneWarehouses.vue index 165e9c84042..c96735697b5 100644 --- a/src/pages/Zone/Card/ZoneWarehouses.vue +++ b/src/pages/Zone/Card/ZoneWarehouses.vue @@ -109,7 +109,7 @@ const openCreateWarehouseForm = () => createWarehouseDialogRef.value.show(); icon="add" color="primary" @click="openCreateWarehouseForm()" - v-shortcut="'+'" + shortcut="+" > <QTooltip>{{ t('warehouses.add') }}</QTooltip> </QBtn> diff --git a/src/pages/Zone/Delivery/ZoneDeliveryList.vue b/src/pages/Zone/Delivery/ZoneDeliveryList.vue index e3ec8cb2d4e..975cbdb67b4 100644 --- a/src/pages/Zone/Delivery/ZoneDeliveryList.vue +++ b/src/pages/Zone/Delivery/ZoneDeliveryList.vue @@ -74,7 +74,7 @@ async function remove(row) { </VnPaginate> </div> <QPageSticky position="bottom-right" :offset="[18, 18]"> - <QBtn @click="create" fab icon="add" v-shortcut="'+'" color="primary" /> + <QBtn @click="create" fab icon="add" shortcut="+" color="primary" /> </QPageSticky> </QPage> </template> diff --git a/src/pages/Zone/Upcoming/ZoneUpcomingList.vue b/src/pages/Zone/Upcoming/ZoneUpcomingList.vue index 7b5c2ddbc80..5a7f0bb4c42 100644 --- a/src/pages/Zone/Upcoming/ZoneUpcomingList.vue +++ b/src/pages/Zone/Upcoming/ZoneUpcomingList.vue @@ -74,7 +74,7 @@ async function remove(row) { </VnPaginate> </div> <QPageSticky position="bottom-right" :offset="[18, 18]"> - <QBtn @click="create" fab icon="add" v-shortcut="'+'" color="primary" /> + <QBtn @click="create" fab icon="add" shortcut="+" color="primary" /> </QPageSticky> </QPage> </template> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index 4df84e4bdd7..e4a1774fe38 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 { dashIfEmpty, toCurrency } from 'src/filters'; +import { toCurrency } from 'src/filters'; import { toTimeFormat } from 'src/filters/date'; import { useVnConfirm } from 'composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; @@ -17,6 +17,7 @@ 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(); @@ -25,6 +26,7 @@ const { viewSummary } = useSummaryDialog(); const { openConfirmationModal } = useVnConfirm(); const tableRef = ref(); const warehouseOptions = ref([]); +const validAddresses = ref([]); const tableFilter = { include: [ @@ -129,7 +131,6 @@ const columns = computed(() => [ label: t('list.addressFk'), cardVisible: true, columnFilter: false, - columnClass: 'expand', }, { align: 'right', @@ -160,18 +161,30 @@ const handleClone = (id) => { openConfirmationModal( t('list.confirmCloneTitle'), t('list.confirmCloneSubtitle'), - () => clone(id), + () => clone(id) ); }; -function formatRow(row) { - if (!row?.address) return '-'; - return dashIfEmpty(`${row?.address?.nickname}, - ${row?.address?.postcode?.town?.name} (${row?.address?.province?.name})`); +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 '-'; } </script> <template> + <FetchData + url="RoadmapAddresses" + auto-load + @on-fetch="(data) => (validAddresses = data)" + /> <ZoneSearchbar /> <RightMenu> <template #right-panel> @@ -194,7 +207,7 @@ function formatRow(row) { :right-search="false" > <template #column-addressFk="{ row }"> - {{ dashIfEmpty(formatRow(row)) }} + {{ showValidAddresses(row) }} </template> <template #more-create-dialog="{ data }"> <VnSelect diff --git a/src/router/modules/account/aliasCard.js b/src/router/modules/account/aliasCard.js index a5b00f44b3a..cbbd31e5103 100644 --- a/src/router/modules/account/aliasCard.js +++ b/src/router/modules/account/aliasCard.js @@ -3,7 +3,7 @@ export default { path: ':id', component: () => import('src/pages/Account/Alias/Card/AliasCard.vue'), redirect: { name: 'AliasSummary' }, - meta: { moduleName: 'Alias', menu: ['AliasBasicData', 'AliasUsers'] }, + meta: { menu: ['AliasBasicData', 'AliasUsers'] }, children: [ { name: 'AliasSummary', diff --git a/src/router/modules/account/roleCard.js b/src/router/modules/account/roleCard.js index f8100071fbf..c36ce71b9da 100644 --- a/src/router/modules/account/roleCard.js +++ b/src/router/modules/account/roleCard.js @@ -4,7 +4,6 @@ export default { component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), redirect: { name: 'RoleSummary' }, meta: { - moduleName: 'Role', menu: ['RoleBasicData', 'SubRoles', 'InheritedRoles', 'RoleLog'], }, children: [ diff --git a/src/router/modules/entry.js b/src/router/modules/entry.js index b5656dc5ffb..f362c76533c 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -6,7 +6,13 @@ 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: [ { @@ -85,7 +91,7 @@ export default { 'EntryLatestBuys', 'EntryStockBought', 'EntryWasteRecalc', - ], + ] }, component: RouterView, redirect: { name: 'EntryMain' }, @@ -97,7 +103,7 @@ export default { redirect: { name: 'EntryIndexMain' }, children: [ { - path: '', + path:'', name: 'EntryIndexMain', redirect: { name: 'EntryList' }, component: () => import('src/pages/Entry/EntryList.vue'), @@ -109,7 +115,6 @@ export default { title: 'list', icon: 'view_list', }, - component: () => import('src/pages/Entry/EntryList.vue'), }, entryCard, ], @@ -122,7 +127,7 @@ export default { icon: 'add', }, component: () => import('src/pages/Entry/EntryCreate.vue'), - }, + }, { path: 'my', name: 'MyEntries', @@ -162,4 +167,4 @@ export default { ], }, ], -}; +}; \ No newline at end of file diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 835324d20b1..946ad3e15c0 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -160,36 +160,6 @@ const roadmapCard = { ], }; -const vehicleCard = { - path: ':id', - name: 'VehicleCard', - component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'), - redirect: { name: 'VehicleSummary' }, - meta: { - menu: ['VehicleBasicData'], - }, - children: [ - { - name: 'VehicleSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'view_list', - }, - component: () => import('src/pages/Route/Vehicle/Card/VehicleSummary.vue'), - }, - { - name: 'VehicleBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => import('src/pages/Route/Vehicle/Card/VehicleBasicData.vue'), - }, - ], -}; - export default { name: 'Route', path: '/route', @@ -204,7 +174,6 @@ export default { 'RouteRoadmap', 'CmrList', 'AgencyList', - 'VehicleList', ], }, component: RouterView, @@ -311,27 +280,6 @@ export default { agencyCard, ], }, - { - path: 'vehicle', - name: 'RouteVehicle', - redirect: { name: 'VehicleList' }, - meta: { - title: 'vehicle', - icon: 'directions_car', - }, - component: () => import('src/pages/Route/Vehicle/VehicleList.vue'), - children: [ - { - path: 'list', - name: 'VehicleList', - meta: { - title: 'vehicleList', - icon: 'directions_car', - }, - }, - vehicleCard, - ], - }, ], }, ], diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js index c085dd8dc71..55fb04278c6 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/Shelving/Parking/Card/ParkingCard.vue'), + component: () => import('src/pages/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/Shelving/Parking/Card/ParkingSummary.vue'), + component: () => import('src/pages/Parking/Card/ParkingSummary.vue'), }, { path: 'basic-data', @@ -25,8 +25,7 @@ const parkingCard = { title: 'basicData', icon: 'vn:settings', }, - component: () => - import('src/pages/Shelving/Parking/Card/ParkingBasicData.vue'), + component: () => import('src/pages/Parking/Card/ParkingBasicData.vue'), }, { path: 'log', @@ -35,7 +34,7 @@ const parkingCard = { title: 'log', icon: 'history', }, - component: () => import('src/pages/Shelving/Parking/Card/ParkingLog.vue'), + component: () => import('src/pages/Parking/Card/ParkingLog.vue'), }, ], }; @@ -128,7 +127,7 @@ export default { title: 'parkingList', icon: 'view_list', }, - component: () => import('src/pages/Shelving/Parking/ParkingList.vue'), + component: () => import('src/pages/Parking/ParkingList.vue'), children: [ { path: 'list', diff --git a/src/router/modules/supplier.js b/src/router/modules/supplier.js index 19763cdf3b8..4ece4c78455 100644 --- a/src/router/modules/supplier.js +++ b/src/router/modules/supplier.js @@ -1,12 +1,19 @@ import { RouterView } from 'vue-router'; -const supplierCard = { - name: 'SupplierCard', - path: ':id', - component: () => import('src/pages/Supplier/Card/SupplierCard.vue'), - redirect: { name: 'SupplierSummary' }, +export default { + path: '/supplier', + name: 'Supplier', meta: { - menu: [ + title: 'suppliers', + icon: 'vn:supplier', + moduleName: 'Supplier', + keyBinding: 'p', + }, + component: RouterView, + redirect: { name: 'SupplierMain' }, + menus: { + main: ['SupplierList'], + card: [ 'SupplierBasicData', 'SupplierFiscalData', 'SupplierBillingData', @@ -20,165 +27,21 @@ const supplierCard = { 'SupplierDms', ], }, - children: [ - { - name: 'SupplierSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Supplier/Card/SupplierSummary.vue'), - }, - { - path: 'basic-data', - name: 'SupplierBasicData', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => import('src/pages/Supplier/Card/SupplierBasicData.vue'), - }, - { - path: 'fiscal-data', - name: 'SupplierFiscalData', - meta: { - title: 'fiscalData', - icon: 'vn:dfiscales', - }, - component: () => import('src/pages/Supplier/Card/SupplierFiscalData.vue'), - }, - { - path: 'billing-data', - name: 'SupplierBillingData', - meta: { - title: 'billingData', - icon: 'vn:payment', - }, - component: () => import('src/pages/Supplier/Card/SupplierBillingData.vue'), - }, - { - path: 'log', - name: 'SupplierLog', - meta: { - title: 'log', - icon: 'vn:History', - }, - component: () => import('src/pages/Supplier/Card/SupplierLog.vue'), - }, - { - path: 'account', - name: 'SupplierAccounts', - meta: { - title: 'accounts', - icon: 'vn:credit', - }, - component: () => import('src/pages/Supplier/Card/SupplierAccounts.vue'), - }, - { - path: 'contact', - name: 'SupplierContacts', - meta: { - title: 'contacts', - icon: 'contact_phone', - }, - component: () => import('src/pages/Supplier/Card/SupplierContacts.vue'), - }, - { - path: 'address', - name: 'SupplierAddresses', - meta: { - title: 'addresses', - icon: 'vn:delivery', - }, - component: () => import('src/pages/Supplier/Card/SupplierAddresses.vue'), - }, - { - path: 'address/create', - name: 'SupplierAddressesCreate', - component: () => - import('src/pages/Supplier/Card/SupplierAddressesCreate.vue'), - }, - { - path: 'balance', - name: 'SupplierBalance', - meta: { - title: 'balance', - icon: 'balance', - }, - component: () => import('src/pages/Supplier/Card/SupplierBalance.vue'), - }, - { - path: 'consumption', - name: 'SupplierConsumption', - meta: { - title: 'consumption', - icon: 'show_chart', - }, - component: () => import('src/pages/Supplier/Card/SupplierConsumption.vue'), - }, - { - path: 'agency-term', - name: 'SupplierAgencyTerm', - meta: { - title: 'agencyTerm', - icon: 'vn:agency-term', - }, - component: () => import('src/pages/Supplier/Card/SupplierAgencyTerm.vue'), - }, - { - path: 'dms', - name: 'SupplierDms', - meta: { - title: 'dms', - icon: 'smb_share', - }, - component: () => import('src/pages/Supplier/Card/SupplierDms.vue'), - }, - { - path: 'agency-term/create', - name: 'SupplierAgencyTermCreate', - component: () => - import('src/pages/Supplier/Card/SupplierAgencyTermCreate.vue'), - }, - ], -}; - -export default { - name: 'Supplier', - path: '/supplier', - meta: { - title: 'suppliers', - icon: 'vn:supplier', - moduleName: 'Supplier', - keyBinding: 'p', - menu: ['SupplierList'], - }, - component: RouterView, - redirect: { name: 'SupplierMain' }, children: [ { path: '', name: 'SupplierMain', component: () => import('src/components/common/VnModule.vue'), - redirect: { name: 'SupplierIndexMain' }, + redirect: { name: 'SupplierList' }, children: [ { - path: '', - name: 'SupplierIndexMain', - redirect: { name: 'SupplierList' }, + path: 'list', + name: 'SupplierList', + meta: { + title: 'list', + icon: 'view_list', + }, component: () => import('src/pages/Supplier/SupplierList.vue'), - children: [ - { - path: 'list', - name: 'SupplierList', - meta: { - title: 'list', - icon: 'view_list', - }, - }, - supplierCard, - ], }, { path: 'create', @@ -191,5 +54,143 @@ export default { }, ], }, + { + name: 'SupplierCard', + path: ':id', + component: () => import('src/pages/Supplier/Card/SupplierCard.vue'), + redirect: { name: 'SupplierSummary' }, + children: [ + { + name: 'SupplierSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => + import('src/pages/Supplier/Card/SupplierSummary.vue'), + }, + { + path: 'basic-data', + name: 'SupplierBasicData', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => + import('src/pages/Supplier/Card/SupplierBasicData.vue'), + }, + { + path: 'fiscal-data', + name: 'SupplierFiscalData', + meta: { + title: 'fiscalData', + icon: 'vn:dfiscales', + }, + component: () => + import('src/pages/Supplier/Card/SupplierFiscalData.vue'), + }, + { + path: 'billing-data', + name: 'SupplierBillingData', + meta: { + title: 'billingData', + icon: 'vn:payment', + }, + component: () => + import('src/pages/Supplier/Card/SupplierBillingData.vue'), + }, + { + path: 'log', + name: 'SupplierLog', + meta: { + title: 'log', + icon: 'vn:History', + }, + component: () => import('src/pages/Supplier/Card/SupplierLog.vue'), + }, + { + path: 'account', + name: 'SupplierAccounts', + meta: { + title: 'accounts', + icon: 'vn:credit', + }, + component: () => + import('src/pages/Supplier/Card/SupplierAccounts.vue'), + }, + { + path: 'contact', + name: 'SupplierContacts', + meta: { + title: 'contacts', + icon: 'contact_phone', + }, + component: () => + import('src/pages/Supplier/Card/SupplierContacts.vue'), + }, + { + path: 'address', + name: 'SupplierAddresses', + meta: { + title: 'addresses', + icon: 'vn:delivery', + }, + component: () => + import('src/pages/Supplier/Card/SupplierAddresses.vue'), + }, + { + path: 'address/create', + name: 'SupplierAddressesCreate', + component: () => + import('src/pages/Supplier/Card/SupplierAddressesCreate.vue'), + }, + { + path: 'balance', + name: 'SupplierBalance', + meta: { + title: 'balance', + icon: 'balance', + }, + component: () => + import('src/pages/Supplier/Card/SupplierBalance.vue'), + }, + { + path: 'consumption', + name: 'SupplierConsumption', + meta: { + title: 'consumption', + icon: 'show_chart', + }, + component: () => + import('src/pages/Supplier/Card/SupplierConsumption.vue'), + }, + { + path: 'agency-term', + name: 'SupplierAgencyTerm', + meta: { + title: 'agencyTerm', + icon: 'vn:agency-term', + }, + component: () => + import('src/pages/Supplier/Card/SupplierAgencyTerm.vue'), + }, + { + path: 'dms', + name: 'SupplierDms', + meta: { + title: 'dms', + icon: 'smb_share', + }, + component: () => import('src/pages/Supplier/Card/SupplierDms.vue'), + }, + { + path: 'agency-term/create', + name: 'SupplierAgencyTermCreate', + component: () => + import('src/pages/Supplier/Card/SupplierAgencyTermCreate.vue'), + }, + ], + }, ], }; diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index bfcb78787e2..e5b423f64de 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -192,13 +192,7 @@ export default { icon: 'vn:ticket', moduleName: 'Ticket', keyBinding: 't', - menu: [ - 'TicketList', - 'TicketAdvance', - 'TicketWeekly', - 'TicketFuture', - 'TicketNegative', - ], + menu: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], }, component: RouterView, redirect: { name: 'TicketMain' }, @@ -235,32 +229,6 @@ 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 3eb95a96ee2..1d013c5964f 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -201,10 +201,9 @@ const workerCard = { const departmentCard = { name: 'DepartmentCard', path: ':id', - component: () => import('src/pages/Worker/Department/Card/DepartmentCard.vue'), + component: () => import('src/pages/Department/Card/DepartmentCard.vue'), redirect: { name: 'DepartmentSummary' }, meta: { - moduleName: 'Department', menu: ['DepartmentBasicData'], }, children: [ @@ -215,8 +214,7 @@ const departmentCard = { title: 'summary', icon: 'launch', }, - component: () => - import('src/pages/Worker/Department/Card/DepartmentSummary.vue'), + component: () => import('src/pages/Department/Card/DepartmentSummary.vue'), }, { path: 'basic-data', @@ -225,8 +223,7 @@ const departmentCard = { title: 'basicData', icon: 'vn:settings', }, - component: () => - import('src/pages/Worker/Department/Card/DepartmentBasicData.vue'), + component: () => import('src/pages/Department/Card/DepartmentBasicData.vue'), }, ], }; diff --git a/src/stores/__tests__/useNavigationStore.spec.js b/src/stores/__tests__/useNavigationStore.spec.js deleted file mode 100644 index c5df6157e5c..00000000000 --- a/src/stores/__tests__/useNavigationStore.spec.js +++ /dev/null @@ -1,153 +0,0 @@ -import { setActivePinia, createPinia } from 'pinia'; -import { describe, beforeEach, afterEach, it, expect, vi, beforeAll } from 'vitest'; -import { useNavigationStore } from '../useNavigationStore'; -import axios from 'axios'; - -let store; - -vi.mock('src/router/modules', () => [ - { name: 'Item', meta: {} }, - { name: 'Shelving', meta: {} }, - { name: 'Order', meta: {} }, -]); - -vi.mock('src/filters', () => ({ - toLowerCamel: vi.fn((name) => name.toLowerCase()), -})); - -const modulesMock = [ - { - name: 'Item', - children: null, - title: 'globals.pageTitles.undefined', - icon: undefined, - module: 'item', - isPinned: true, - }, - { - name: 'Shelving', - children: null, - title: 'globals.pageTitles.undefined', - icon: undefined, - module: 'shelving', - isPinned: false, - }, - { - name: 'Order', - children: null, - title: 'globals.pageTitles.undefined', - icon: undefined, - module: 'order', - isPinned: false, - }, -]; - -const pinnedModulesMock = [ - { - name: 'Item', - children: null, - title: 'globals.pageTitles.undefined', - icon: undefined, - module: 'item', - isPinned: true, - }, -]; - -describe('useNavigationStore', () => { - beforeEach(() => { - setActivePinia(createPinia()); - vi.spyOn(axios, 'get').mockResolvedValue({ data: true }); - store = useNavigationStore(); - store.getModules = vi.fn().mockReturnValue({ - value: modulesMock, - }); - store.getPinnedModules = vi.fn().mockReturnValue({ - value: pinnedModulesMock, - }); - }); - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should return modules with correct structure', () => { - const store = useNavigationStore(); - const modules = store.getModules(); - - expect(modules.value).toEqual(modulesMock); - }); - - it('should return pinned modules', () => { - const store = useNavigationStore(); - const pinnedModules = store.getPinnedModules(); - - expect(pinnedModules.value).toEqual(pinnedModulesMock); - }); - - it('should toggle pinned modules', () => { - const store = useNavigationStore(); - - store.togglePinned('item'); - store.togglePinned('shelving'); - expect(store.pinnedModules).toEqual(['item', 'shelving']); - - store.togglePinned('item'); - expect(store.pinnedModules).toEqual(['shelving']); - }); - - it('should fetch pinned modules', async () => { - vi.spyOn(axios, 'get').mockResolvedValue({ - data: [{ id: 1, workerFk: 9, moduleFk: 'order', position: 1 }], - }); - const store = useNavigationStore(); - await store.fetchPinned(); - - expect(store.pinnedModules).toEqual(['order']); - }); - - it('should add menu item correctly', () => { - const store = useNavigationStore(); - const module = 'customer'; - const parent = []; - const route = { - name: 'customer', - title: 'Customer', - icon: 'customer', - meta: { - keyBinding: 'ctrl+shift+c', - name: 'customer', - title: 'Customer', - icon: 'customer', - menu: 'customer', - menuChildren: [{ name: 'customer', title: 'Customer', icon: 'customer' }], - }, - }; - - const result = store.addMenuItem(module, route, parent); - const expectedItem = { - children: [ - { - icon: 'customer', - name: 'customer', - title: 'globals.pageTitles.Customer', - }, - ], - icon: 'customer', - keyBinding: 'ctrl+shift+c', - name: 'customer', - title: 'globals.pageTitles.Customer', - }; - expect(result).toEqual(expectedItem); - expect(parent.length).toBe(1); - expect(parent).toEqual([expectedItem]); - }); - - it('should not add menu item if condition is not met', () => { - const store = useNavigationStore(); - const module = 'testModule'; - const route = { meta: { hidden: true, menuchildren: {} } }; - const parent = []; - const result = store.addMenuItem(module, route, parent); - expect(result).toBeUndefined(); - expect(parent.length).toBe(0); - }); -}); diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index b3996d1e3e2..8d62fdb4a22 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -19,7 +19,6 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { page: 1, mapKey: 'id', keepData: false, - oneRecord: false, }; function get(key) { diff --git a/src/utils/notifyResults.js b/src/utils/notifyResults.js deleted file mode 100644 index e87ad6c6f7b..00000000000 --- a/src/utils/notifyResults.js +++ /dev/null @@ -1,19 +0,0 @@ -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 1770a6b56e9..cffc47f9150 100644 --- a/test/cypress/integration/Order/orderCatalog.spec.js +++ b/test/cypress/integration/Order/orderCatalog.spec.js @@ -45,6 +45,7 @@ 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/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js deleted file mode 100644 index 4f99f0cb6f1..00000000000 --- a/test/cypress/integration/entry/entryList.spec.js +++ /dev/null @@ -1,224 +0,0 @@ -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/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js index bc36156b4e4..078ad19cc28 100644 --- a/test/cypress/integration/entry/stockBought.spec.js +++ b/test/cypress/integration/entry/stockBought.spec.js @@ -6,7 +6,6 @@ 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'); @@ -16,35 +15,25 @@ 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('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('input[aria-label="Buyer"]').type('buyerboss{downarrow}{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); - - cy.get('[data-col-field="reserve"][data-row-index="1"]').click().clear(); - cy.get('[data-cy="searchBtn"]').eq(1).click(); - cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata') - .should('have.text', 'warningNo data available') - .type('{esc}'); - cy.get('[data-col-field="reserve"][data-row-index="1"]') - .click() - .type('{backspace}{enter}'); - cy.get('[data-cy="crudModelDefaultSaveBtn"]').should('be.enabled').click(); - cy.get('.q-notification__message').eq(1).should('have.text', 'Data saved'); }); it('Should check detail for the buyer', () => { - cy.get('[data-cy="searchBtn"]').eq(0).click(); + cy.get(':nth-child(1) > .sticky > .q-btn > .q-btn__content > .q-icon').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('[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 > .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('.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 11ca1bb59be..2016fca6df0 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,16 +11,13 @@ describe('InvoiceInBasicData', () => { }); it('should edit the provideer and supplier ref', () => { - cy.dataCy('UnDeductibleVatSelect').type('4751000000'); - cy.get('.q-menu .q-item').contains('4751000000').click(); - cy.get(resetBtn).click(); - - cy.waitForElement('#formModel').within(() => { - cy.dataCy('vnSupplierSelect').type('Bros nick'); - }) - cy.get('.q-menu .q-item').contains('Bros nick').click(); + cy.selectOption(firstFormSelect, 'Bros'); + cy.get('[title="Reset"]').click(); + cy.get(formInputs).eq(1).type('{selectall}4739'); cy.saveCard(); - cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Bros nick'); + + cy.get(`${firstFormSelect} input`).invoke('val').should('eq', 'Plants nick'); + cy.get(formInputs).eq(1).invoke('val').should('eq', '4739'); }); it('should edit, remove and create the dms data', () => { @@ -28,18 +25,18 @@ describe('InvoiceInBasicData', () => { const secondInput = "I don't know what posting here!"; //edit - cy.get(getDocumentBtns(2)).click(); + cy.get(documentBtns).eq(1).click(); cy.get(dialogInputs).eq(0).type(`{selectall}${firtsInput}`); cy.get('textarea').type(`{selectall}${secondInput}`); cy.get('[data-cy="FormModelPopup_save"]').click(); - cy.get(getDocumentBtns(2)).click(); + cy.get(documentBtns).eq(1).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(getDocumentBtns(3)).click(); + cy.get(documentBtns).eq(2).click(); cy.get('[data-cy="VnConfirm_confirm"]').click(); cy.checkNotification('Data saved'); @@ -49,7 +46,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 1e7ce1003b7..f8b403a458f 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('[data-cy="FormModelPopup_save"]').click(); + cy.get('button[type="submit"]').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 02b7fbb43ce..5f629df0b2c 100644 --- a/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js +++ b/test/cypress/integration/invoiceOut/invoiceOutNegativeBases.spec.js @@ -7,7 +7,9 @@ describe('InvoiceOut negative bases', () => { }); it('should filter and download as CSV', () => { - cy.get('input[name="ticketFk"]').type('23{enter}'); + 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('#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 deleted file mode 100644 index b3ba9f67645..00000000000 --- a/test/cypress/integration/item/ItemProposal.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -/// <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 425eaffe63b..17423bc5108 100644 --- a/test/cypress/integration/item/itemTag.spec.js +++ b/test/cypress/integration/item/itemTag.spec.js @@ -16,7 +16,10 @@ describe('Item tag', () => { cy.dataCy(newTag).should('be.visible').click().type('Genero{enter}'); cy.dataCy('tagGeneroValue').eq(1).should('be.visible'); cy.dataCy(saveBtn).click(); - cy.checkNotification("The tag or priority can't be repeated for an item"); + cy.get('.q-notification__message').should( + 'have.text', + "The tag or priority can't be repeated for an item", + ); }); it('should add a new tag', () => { diff --git a/test/cypress/integration/parking/parkingBasicData.spec.js b/test/cypress/integration/parking/parkingBasicData.spec.js index f64f23ec83e..0d130d335ab 100644 --- a/test/cypress/integration/parking/parkingBasicData.spec.js +++ b/test/cypress/integration/parking/parkingBasicData.spec.js @@ -13,11 +13,11 @@ describe('ParkingBasicData', () => { cy.get(sectorOpt).click(); cy.get(codeInput).eq(0).clear(); - cy.get(codeInput).eq(0).type('900-001'); + cy.get(codeInput).eq(0).type(123); cy.saveCard(); cy.get(sectorSelect).should('have.value', 'Second sector'); - cy.get(codeInput).should('have.value', '900-001'); + cy.get(codeInput).should('have.value', 123); }); }); diff --git a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js index 82ec6626da0..e28caea7c55 100644 --- a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js +++ b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js @@ -15,7 +15,6 @@ describe('AgencyWorkCenter', () => { // expect error when duplicate cy.get(createButton).click(); - cy.selectOption(workCenterCombobox, 'workCenterOne'); cy.get('[data-cy="FormModelPopup_save"]').click(); cy.checkNotification('This workCenter is already assigned to this agency'); cy.get('[data-cy="FormModelPopup_cancel"]').click(); diff --git a/test/cypress/integration/route/routeList.spec.js b/test/cypress/integration/route/routeList.spec.js index 976ce735204..4da43ce8e63 100644 --- a/test/cypress/integration/route/routeList.spec.js +++ b/test/cypress/integration/route/routeList.spec.js @@ -4,6 +4,9 @@ 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(); @@ -14,23 +17,15 @@ describe('Route', () => { it('Route list search and edit', () => { cy.get('#searchbar input').type('{enter}'); - cy.get('[data-col-field="description"][data-row-index="0"]') - .click() - .type('routeTestOne{enter}'); + cy.get('input[name="description"]').type('routeTestOne{enter}'); cy.get('.q-table tr') .its('length') .then((rowCount) => { expect(rowCount).to.be.greaterThan(0); }); - 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(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('button[title="Save"]').click(); cy.get('.q-notification__message').should('have.text', 'Data saved'); }); diff --git a/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js b/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js deleted file mode 100644 index 64b9ca0a000..00000000000 --- a/test/cypress/integration/route/vehicle/vehicleDescriptor.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -describe('Vehicle', () => { - beforeEach(() => { - cy.viewport(1920, 1080); - cy.login('deliveryAssistant'); - cy.visit(`/#/route/vehicle/7`); - }); - - it('should delete a vehicle', () => { - cy.openActionsDescriptor(); - cy.get('[data-cy="delete"]').click(); - cy.checkNotification('Vehicle removed'); - }); -}); diff --git a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js b/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js deleted file mode 100644 index 9ea1cff6311..00000000000 --- a/test/cypress/integration/ticket/negative/TicketLackDetail.spec.js +++ /dev/null @@ -1,147 +0,0 @@ -/// <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 deleted file mode 100644 index 01ab4f621d0..00000000000 --- a/test/cypress/integration/ticket/negative/TicketLackList.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -/// <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/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 593021e6e1b..2984a4ee493 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -53,29 +53,4 @@ describe('TicketList', () => { cy.checkNotification('Data created'); cy.url().should('match', /\/ticket\/\d+\/summary/); }); - - it('should show the corerct problems', () => { - cy.intercept('GET', '**/api/Tickets/filter*', (req) => { - req.headers['cache-control'] = 'no-cache'; - req.headers['pragma'] = 'no-cache'; - req.headers['expires'] = '0'; - - req.on('response', (res) => { - delete res.headers['if-none-match']; - delete res.headers['if-modified-since']; - }); - }).as('ticket'); - - cy.get('[data-cy="Warehouse_select"]').type('Warehouse Five'); - cy.get('.q-menu .q-item').contains('Warehouse Five').click(); - cy.wait('@ticket').then((interception) => { - const data = interception.response.body[1]; - expect(data.hasComponentLack).to.equal(1); - expect(data.isTooLittle).to.equal(1); - expect(data.hasItemShortage).to.equal(1); - }); - cy.get('.icon-components').should('exist'); - cy.get('.icon-unavailable').should('exist'); - cy.get('.icon-isTooLittle').should('exist'); - }); }); diff --git a/test/cypress/integration/vnComponent/VnShortcut.spec.js b/test/cypress/integration/vnComponent/VnShortcut.spec.js index e08c4463576..b49b4e964d5 100644 --- a/test/cypress/integration/vnComponent/VnShortcut.spec.js +++ b/test/cypress/integration/vnComponent/VnShortcut.spec.js @@ -28,17 +28,6 @@ describe('VnShortcuts', () => { }); cy.url().should('include', module); - if (['monitor', 'claim'].includes(module)) { - return; - } - cy.waitForElement('.q-page').should('exist'); - cy.dataCy('vnTableCreateBtn').should('exist'); - cy.get('.q-page').trigger('keydown', { - ctrlKey: true, - altKey: true, - key: '+', - }); - cy.get('#formModel').should('exist'); }); } }); diff --git a/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js index 2cd43984a5a..343c1c1271e 100644 --- a/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js +++ b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js @@ -9,7 +9,7 @@ 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('[data-cy="FormModelPopup_save"]').click(); + cy.get('button[type="submit"]').click(); cy.get('[title="Remove"] > .q-btn__content > .q-icon').first().click(); }); }); diff --git a/test/cypress/integration/zone/zoneBasicData.spec.js b/test/cypress/integration/zone/zoneBasicData.spec.js index 70ded3f7969..95a075fb336 100644 --- a/test/cypress/integration/zone/zoneBasicData.spec.js +++ b/test/cypress/integration/zone/zoneBasicData.spec.js @@ -1,6 +1,5 @@ describe('ZoneBasicData', () => { const priceBasicData = '[data-cy="Price_input"]'; - const saveBtn = '.q-btn-group > .q-btn--standard'; beforeEach(() => { cy.viewport(1280, 720); @@ -9,27 +8,20 @@ describe('ZoneBasicData', () => { }); it('should throw an error if the name is empty', () => { - cy.intercept('GET', /\/api\/Zones\/4./).as('zone'); - - cy.wait('@zone').then(() => { - cy.get('[data-cy="zone-basic-data-name"] input').type( - '{selectall}{backspace}', - ); - }); - - cy.get(saveBtn).click(); + cy.get('[data-cy="zone-basic-data-name"] input').type('{selectall}{backspace}'); + cy.get('.q-btn-group > .q-btn--standard').click(); cy.checkNotification("can't be blank"); }); it('should throw an error if the price is empty', () => { cy.get(priceBasicData).clear(); - cy.get(saveBtn).click(); + cy.get('.q-btn-group > .q-btn--standard').click(); cy.checkNotification('cannot be blank'); }); it("should edit the basicData's zone", () => { cy.get('.q-card > :nth-child(1)').type(' modified'); - cy.get(saveBtn).click(); + cy.get('.q-btn-group > .q-btn--standard').click(); cy.checkNotification('Data saved'); }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index aa4a1219e8e..2c93fbf844b 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -87,55 +87,36 @@ Cypress.Commands.add('getValue', (selector) => { }); // Fill Inputs -Cypress.Commands.add('selectOption', (selector, option, timeout = 2500) => { +Cypress.Commands.add('selectOption', (selector, option, timeout = 5000) => { cy.waitForElement(selector, timeout); - - 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)); + 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 }); -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 359f8643fd1..5fb47a2d82f 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 ); }