Compare commits

..

480 Commits

Author SHA1 Message Date
Jose Antonio Tubau 88ea1cbbba Merge pull request 'feat: refs #8685 add checkDescriptorProxy command to validate descriptor text in the UI' (!1813) from 8685-addCypressCommandCheckDescriptorProxy into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1813
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-22 05:16:38 +00:00
Alex Moreno 5ac7f1bf27 Merge branch 'dev' into 8685-addCypressCommandCheckDescriptorProxy
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-21 12:33:14 +00:00
Alex Moreno 2d61c9cb79 Merge pull request 'fix: refs #9021 improve event handling in VnFilter and remove unused blur emit in VnComponent' (!1826) from 9021-fix_keyup_enter into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1826
Reviewed-by: Pablo Natek <pablone@verdnatura.es>
2025-05-21 12:09:10 +00:00
Alex Moreno a052c68e67 fix: refs #9021 improve event handling in VnFilter and remove unused blur emit in VnComponent
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-21 12:56:37 +02:00
Alex Moreno 2f669b7d3a Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-21 07:43:37 +02:00
Alex Moreno ec5caef07c fix: refresh data after deleting an event in WorkerCalendar
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-21 07:43:22 +02:00
Alex Moreno 3fbfeeebbd Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-20 12:44:16 +02:00
Alex Moreno 6c18e6d40d Merge pull request 'fix: add 'dated' field to RouteFilter fields' (!1819) from hotFix_routeBasicDataDatedField into master
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1819
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-20 09:57:34 +00:00
Jose Antonio Tubau bc018289c3 Merge pull request 'feat: refs #8441 add VehicleInvoiceIn component with invoice management functionality' (!1567) from 8441-createVehicleInvoiceInSection into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1567
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2025-05-20 09:33:47 +00:00
Alex Moreno 284e1a2ae2 Merge branch 'master' into hotFix_routeBasicDataDatedField
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 09:31:38 +00:00
Alex Moreno d70ad13889 fix: update pull policy for services in docker-compose.yml
gitea/salix-front/pipeline/pr-master Something is wrong with the build of this commit Details
2025-05-20 11:30:49 +02:00
Jose Antonio Tubau 0dcbc49e35 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-20 09:27:34 +00:00
Jon Elias 7c2dac4e2c Merge pull request 'fix: no downloading file when clicking in the file field' (!1804) from Hotfix-OpenFilesInsteadOfDownloading into master
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1804
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
2025-05-20 07:42:38 +00:00
Jon Elias 752c004946 Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 07:30:06 +00:00
Pablo Natek 61cee39f6a Merge pull request '#8684 - itemRefactorAndE2e' (!1745) from 8684-itemRefactorAndE2e into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1745
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-20 07:28:06 +00:00
Javier Segarra 1f8a82468a Merge pull request 'Remove reset selected variable' (!1787) from hotfix_save_selection_after_reload into master
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1787
2025-05-20 07:24:46 +00:00
Javier Segarra 52fdc1f8de Merge pull request 'fix: minor issue and add test' (!1818) from warmfix_improve_newPaymentTicket_description into master
gitea/salix-front/pipeline/head Build queued... Details
Reviewed-on: #1818
2025-05-20 07:24:33 +00:00
Javier Segarra a8f6a31cef Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 07:16:43 +00:00
Javier Segarra ea77273d5b Merge branch 'master' into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 07:15:01 +00:00
Javier Segarra 3229f77148 Merge branch 'master' into warmfix_improve_newPaymentTicket_description
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 07:14:18 +00:00
Javier Segarra 807db25750 fix: update order in SupplierConsumption data fetching
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-20 09:12:30 +02:00
Javier Segarra fa31c49ebd Merge branch 'master' of https://gitea.verdnatura.es/verdnatura/salix-front 2025-05-20 09:10:41 +02:00
Javier Segarra 361183357b fix: update order in SupplierConsumption data fetching 2025-05-20 09:10:38 +02:00
Jon Elias 25f72fed31 Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 07:03:18 +00:00
Jon Elias 69a9cb25bd Merge pull request 'Hotfix[CustomerCredit]: Update descriptor when changing credit' (!1816) from Hotfix-CustomerCreditUpdateDescriptor into master
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1816
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-20 06:54:30 +00:00
Javier Segarra e6a73d7b42 test: skip ticketLackDetails 2025-05-20 08:49:49 +02:00
Jon Elias 1f716f104b Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master Something is wrong with the build of this commit Details
2025-05-20 06:47:54 +00:00
Jon Elias 9257e7ee07 Merge branch 'master' into Hotfix-CustomerCreditUpdateDescriptor
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 06:43:15 +00:00
Alex Moreno 6684c59473 Merge branch 'master' into hotFix_routeBasicDataDatedField
gitea/salix-front/pipeline/pr-master This commit is unstable Details
2025-05-20 06:40:44 +00:00
Pablo Natek 042fff67df Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-20 06:39:07 +00:00
Pablo Natek ca7d05f753 test: refs #8684 disable specific test cases for invoice details and item proposal
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-05-20 08:38:51 +02:00
Javier Segarra e9e9aea92d Merge branch 'master' into warmfix_improve_newPaymentTicket_description
gitea/salix-front/pipeline/pr-master This commit is unstable Details
2025-05-20 08:38:49 +02:00
Jose Antonio Tubau 00c74d8f53 fix: add 'dated' field to RouteFilter fields
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
2025-05-20 08:38:26 +02:00
Javier Segarra 33eb77864f test: minor changes 2025-05-20 08:38:24 +02:00
Javier Segarra a6d6ad92f6 test: skip entryBuys 2025-05-20 08:34:20 +02:00
Javier Segarra 13499f3226 Merge pull request 'refactor: enhance order filtering logic and improve code readability' (!1796) from hotFix_orderCatalogFilter_addTagsInOrderList into master
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1796
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
2025-05-20 06:33:41 +00:00
Javier Segarra 0221ca31e7 Merge branch 'master' into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-master This commit is unstable Details
2025-05-20 06:19:07 +00:00
Javier Segarra be9794fcd8 Merge branch 'master' into warmfix_improve_newPaymentTicket_description
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 06:17:01 +00:00
Alex Moreno 101298f681 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-20 08:04:30 +02:00
Alex Moreno fada268c16 fix: update tag identification logic in addOrder function
gitea/salix-front/pipeline/pr-master This commit looks good Details
2025-05-20 07:57:30 +02:00
Alex Moreno 07ed28e25d Merge branch 'master' of https://gitea.verdnatura.es/verdnatura/salix-front into hotFix_orderCatalogFilter_addTagsInOrderList 2025-05-20 07:54:49 +02:00
Pablo Natek 26bfec1c15 test: refs #8684 disable vehicle summary redirection tests and ticket quantity change tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-20 07:49:18 +02:00
Alex Moreno 1cfb03c3cb fix: update date management and input event handling in VnInputDate component
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-20 07:44:11 +02:00
Alex Moreno 7135aa6f40 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-20 07:29:09 +02:00
Pablo Natek cc016a4a8c Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-19 13:55:21 +00:00
Javier Segarra a39be108a3 Merge branch 'test' into warmfix_improve_newPaymentTicket_description
gitea/salix-front/pipeline/pr-test This commit looks good Details
gitea/salix-front/pipeline/pr-master Build queued... Details
2025-05-19 13:35:01 +00:00
Javier Segarra fc6dfc364b Merge branch 'hotfix_save_selection_after_reload' of https://gitea.verdnatura.es/verdnatura/salix-front into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-test This commit is unstable Details
gitea/salix-front/pipeline/pr-master Build queued... Details
2025-05-19 15:22:24 +02:00
Javier Segarra 3b4fc76651 Merge branch 'test' into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 13:08:47 +00:00
Javier Segarra 621eb92863 Merge branch 'test' into hotfix_save_selection_after_reload 2025-05-19 15:08:17 +02:00
Javier Segarra 9a7f4cdd8c fix: minor issue and add test
gitea/salix-front/pipeline/pr-test This commit looks good Details
2025-05-19 15:05:45 +02:00
Alex Moreno 0bcc54cbbe Merge pull request 'feat: refs #8227 Unable roadmap' (!1807) from 8227-roadmapUnable into test
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1807
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-19 13:00:35 +00:00
Alex Moreno bc6b369112 fix: refs #8227 skip InvoiceInVat test suite
gitea/salix-front/pipeline/pr-test This commit looks good Details
2025-05-19 14:58:56 +02:00
Alex Moreno 749fb7a4a8 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-19 14:39:34 +02:00
Pablo Natek e13c8457cc Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-19 12:30:05 +00:00
Guillermo Bonet 33b6d0fe89 Merge branch 'test' into 8227-roadmapUnable
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 12:24:46 +00:00
Javier Segarra 0ec8283ba5 test: fix test ticketSale
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 14:08:46 +02:00
Javier Segarra 67b51d7833 fix: improve ticketNewPayment
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-19 14:04:55 +02:00
Jon Elias 547d0a2895 feat: improved test
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
2025-05-19 13:22:24 +02:00
Jose Antonio Tubau ae122fb056 Merge pull request 'feat: refs #8930 add vehicle management to invoice summary and vehicle linking functionality' (!1736) from 8930-createVehicleSectionOnInvoiceIn into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1736
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2025-05-19 11:21:36 +00:00
Pablo Natek b677266f4c test: refs #8684 disable dms data modification tests in InvoiceInBasicData and InvoiceInDueDay
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-19 13:17:52 +02:00
Javier Segarra 0b5cc7d00f Merge pull request 'warmfix: New payment from ticket' (!1801) from warmfix_ticket_newPayment into test
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1801
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2025-05-19 11:17:05 +00:00
Jon Elias 8e5b5f15f1 fix: update descriptor when changing client's credit 2025-05-19 13:12:14 +02:00
Javier Segarra f76305fdac Merge branch 'test' into warmfix_ticket_newPayment
gitea/salix-front/pipeline/pr-test This commit looks good Details
2025-05-19 11:10:27 +00:00
Javier Segarra 58dd3ad787 Merge pull request 'warmfix: #8556 - excludeDates' (!1781) from warmfix_8556_excludeDates into test
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1781
Reviewed-by: Jon Elias <jon@verdnatura.es>
2025-05-19 11:10:17 +00:00
Jose Antonio Tubau cabee31109 Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 11:03:19 +00:00
Alex Moreno 66ab8b20ee fix: prevent default context menu behavior in VnTable component
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-19 12:45:40 +02:00
Jose Antonio Tubau 5c482cf738 Merge pull request 'fix: refs #7385 rename forecast column to estimated' (!1814) from 7385-fixColumnNameReference into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1814
Reviewed-by: Pablo Natek <pablone@verdnatura.es>
2025-05-19 10:39:48 +00:00
Jose Antonio Tubau acd9c97324 Merge branch 'dev' into 7385-fixColumnNameReference
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 10:20:00 +00:00
Jose Antonio Tubau df11584452 fix: refs #7385 rename forecast column to estimated
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 12:14:43 +02:00
Javier Segarra 1396758761 test: fix test
gitea/salix-front/pipeline/pr-test This commit looks good Details
2025-05-19 12:06:36 +02:00
Pablo Natek 2c11e3a524 Merge branch '8684-itemRefactorAndE2e' of https://gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-19 11:49:30 +02:00
Pablo Natek 124eca710c refactor: refs #8684 remove unused axios import and simplify keydown event handler 2025-05-19 11:49:27 +02:00
Pablo Natek f9ba48e410 Merge branch 'dev' of https: refs #8684//gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e 2025-05-19 11:46:41 +02:00
Jorge Penadés 86f1a5b9bb Merge pull request '#8564 throwErr' (!1793) from 8564-throwErr into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1793
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
Reviewed-by: Juan Ferrer <juan@verdnatura.es>
2025-05-19 09:40:04 +00:00
Javier Segarra 6c1926e371 Merge branch 'test' into warmfix_ticket_newPayment
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 09:37:07 +00:00
Javier Segarra 0505fb69b6 perf: change default prop
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 11:36:31 +02:00
Javier Segarra 2bdb73dfde feat: hide excludedDates chip
gitea/salix-front/pipeline/pr-test Build queued... Details
2025-05-19 11:33:01 +02:00
Jose Antonio Tubau 3c95b4c976 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 09:28:21 +00:00
Jose Antonio Tubau 0ae1027891 test: refs #8930 combine test cases for linking and unlinking a vehicle from the invoice
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 11:26:11 +02:00
Javier Segarra 4886b5459b Merge branch 'warmfix_8556_excludeDates' of https://gitea.verdnatura.es/verdnatura/salix-front into warmfix_8556_excludeDates
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 11:23:45 +02:00
Javier Segarra 6fd247badd Merge branch 'test' into warmfix_8556_excludeDates 2025-05-19 11:23:37 +02:00
Javier Segarra b8f38a4ae8 test: remove only 2025-05-19 11:23:23 +02:00
Javier Segarra a677abf35b test: fix test ticketLackDetail 2025-05-19 11:22:55 +02:00
Jorge Penadés f4d91701fa Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8564-throwErr
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 11:20:02 +02:00
Jorge Penadés df4a8a6818 Merge pull request '#8388 fineTunningInvoiceIn' (!1735) from 8388-fineTunningInvoiceIn into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1735
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-19 09:06:41 +00:00
Jorge Penadés 3ff508328b Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 10:45:28 +02:00
Jose Antonio Tubau f1f2c30c24 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 08:37:05 +00:00
Jose Antonio Tubau 9834c7cf93 fix: refs #8441 improve field filling and ensure visibility in route extended list tests
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 10:36:29 +02:00
Javier Segarra efb8a1a4ca Merge pull request 'feat: refs #7549 implement worker selection handling' (!1771) from 7549-autofillWorkerClaimDevelopment into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1771
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-19 08:32:15 +00:00
Javier Segarra f71ed84af7 Merge branch 'test' into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 08:31:49 +00:00
Jorge Penadés 5cba4773f1 Merge branch '8564-throwErr' of https://gitea.verdnatura.es/verdnatura/salix-front into 8564-throwErr
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 10:14:55 +02:00
Jorge Penadés e89cf14447 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8564-throwErr 2025-05-19 10:14:47 +02:00
Jorge Penadés b87730932e Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 10:13:08 +02:00
Jose Antonio Tubau 54f0c094f8 feat: refs #8685 add checkDescriptorProxy command to validate descriptor text in the UI
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 09:54:49 +02:00
Javier Segarra f532119a7f Merge branch 'dev' into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 07:44:24 +00:00
Guillermo Bonet 19feb140e2 Merge branch 'test' into 8227-roadmapUnable
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-19 06:45:47 +00:00
Jon Elias 5a6da51246 Merge pull request '#8944: Dev to test changes to match back' (!1812) from 8944-ChangesToTest into test
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1812
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-19 06:37:15 +00:00
Jon Elias 9e3c7430e7 fix: refs #8944 zone calendar e2e
gitea/salix-front/pipeline/pr-test This commit looks good Details
2025-05-19 07:57:25 +02:00
Jose Antonio Tubau 9e589cd48e Merge pull request 'feat: refs #7385 add delivery forecast and delivered fields to route summary and descriptor' (!1716) from 7385-addDeliveredAndForecastColumnsOnRouteTicketsList into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1716
Reviewed-by: Pablo Natek <pablone@verdnatura.es>
2025-05-19 05:35:56 +00:00
Jose Antonio Tubau 5559b5fdcf Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-19 05:23:39 +00:00
Jose Antonio Tubau e4dfd3d53a Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-19 05:23:09 +00:00
Javier Segarra f9e9bc5dde Merge branch 'test' into warmfix_8556_excludeDates
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-18 21:43:50 +00:00
Jon Elias 8f01bfdcb3 feat: refs #8944 dev to test changes to match back
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-18 14:43:15 +02:00
Jorge Penadés 07c078093f refactor: refs #8388 change beforeEach to before in InvoiceInIntrastat tests
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-16 17:24:38 +02:00
Jorge Penadés 08c67e3bcf Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 17:22:29 +02:00
Jose Antonio Tubau d4290a3cde Merge pull request 'test: refs #7069 add unit tests for VnAccountNumber component' (!1766) from 7069-testVnAccountNumber into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1766
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2025-05-16 13:29:07 +00:00
Jose Antonio Tubau 3a30853f6a Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 13:11:03 +00:00
Jose Antonio Tubau feef61bf32 Merge branch 'dev' into 7069-testVnAccountNumber
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-16 13:09:16 +00:00
Jose Antonio Tubau 68e41c3609 refactor: refs #7549 improve formatting in handleWorker function for better readability
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-16 15:07:25 +02:00
Javier Segarra 791e6fb2fa Merge branch 'test' into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-16 13:02:14 +00:00
Jose Antonio Tubau ae0e3eae20 Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList 2025-05-16 12:56:45 +00:00
Guillermo Bonet 1e29bbbd00 Merge branch 'test' into 8227-roadmapUnable
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-16 11:42:03 +00:00
Jorge Penadés 88229099d3 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 13:41:52 +02:00
Jorge Penadés 4fa143ca0e Merge pull request 'fix: refs #8602 skip EntryBuys test and add issue reference' (!1809) from 8602-skipTest into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1809
Reviewed-by: Pablo Natek <pablone@verdnatura.es>
2025-05-16 11:40:52 +00:00
Jorge Penadés 328fc50e40 fix: refs #8602 skip EntryBuys test and add issue reference
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-16 13:39:22 +02:00
Pablo Natek ca223ef8ca Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 11:36:42 +00:00
Jorge Penadés e04442efc6 Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 13:35:31 +02:00
Jorge Penadés cda333fabd Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-16 13:34:32 +02:00
Alex Moreno ed2f2dc273 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-16 13:33:27 +02:00
Jorge Penadés bfa8d6eec5 test: refs #8388 change beforeEach to before for login and visit in InvoiceInVat tests
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-16 13:33:07 +02:00
Pablo Natek c2d615bc6c Merge pull request 'fix: remove unnecessary blank line in VnColumn.vue' (!1808) from warmFixVnTableCardList into test
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1808
Reviewed-by: Jorge Penadés <jorgep@verdnatura.es>
2025-05-16 11:28:56 +00:00
Alex Moreno 2621f59a4c Merge pull request 'feat: refs #8224 add context menu forVnTable' (!1712) from 8224-contextMenu into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1712
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
2025-05-16 11:26:49 +00:00
Jorge Penadés cd59f880bb Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 13:09:45 +02:00
Alex Moreno 9bf30642b9 Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-16 11:01:10 +00:00
Alex Moreno 77846710f2 test: skip test for equalization check in client fiscal data
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-16 12:59:48 +02:00
Pablo Natek 441876d53e fix: remove unnecessary blank line in VnColumn.vue
gitea/salix-front/pipeline/pr-test This commit looks good Details
2025-05-16 12:59:24 +02:00
Javier Segarra bcfb4b1d39 Merge branch 'test' into warmfix_ticket_newPayment
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-16 10:57:45 +00:00
Pablo Natek a62d973c82 fix: refs #8684 update button title handling and improve test case for item request
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 12:49:59 +02:00
Alex Moreno aaa5d94a39 Merge branch 'test' into 8227-roadmapUnable
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-16 10:34:01 +00:00
Alex Moreno 924cc86bb7 fix: update Jenkinsfile to use the new syntax
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-16 12:31:35 +02:00
Jose Antonio Tubau 1ffb5eca15 Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-16 10:15:38 +00:00
Javier Segarra a7829ceab2 Merge branch 'test' into warmfix_ticket_newPayment
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-16 10:08:01 +00:00
Jose Antonio Tubau f4d6d4ad54 fix: refs #8441 update notification message for unassigning invoices
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 11:54:11 +02:00
Jon Elias c255b2ec44 Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
2025-05-16 09:10:37 +00:00
Guillermo Bonet 1c1ad34b18 feat: refs #8227 Unable roadmap
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-16 11:10:20 +02:00
Jose Antonio Tubau cf92ea8f47 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 09:02:02 +00:00
Jorge Penadés 13f461f878 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 10:47:17 +02:00
Alex Moreno c03562ed47 Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-16 10:26:38 +02:00
Jose Antonio Tubau 37a2adb0ad refactor: refs #7549 update worker handling logic to use responsible code instead of description
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-16 10:19:25 +02:00
Pau Rovira 8e816382c6 Merge pull request 'feat: #8825 - add functionality to terminate contract' (!1800) from 8825-bajaOpinionCredito into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1800
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
2025-05-16 08:09:26 +00:00
Javier Segarra 32bfede734 Merge branch 'test' into hotfix_save_selection_after_reload
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-16 09:38:43 +02:00
Javier Segarra fab57457d2 Merge branch 'dev' into 8825-bajaOpinionCredito
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-16 07:28:12 +00:00
Alex Moreno e714b38b63 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix-front into dev
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-16 08:03:55 +02:00
Alex Moreno 175a1ff681 Merge branch 'master' of https://gitea.verdnatura.es/verdnatura/salix-front into test
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-16 08:03:18 +02:00
Alex Moreno ba864eae09 fix: update filter parameters in VnLog and clean up unused fields in useArrayData
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-16 08:03:08 +02:00
Jose Antonio Tubau f5bac7d2d5 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-16 05:41:50 +00:00
Pau Rovira 2f8e8cf33a Merge branch 'dev' into 8825-bajaOpinionCredito
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-16 03:46:36 +00:00
Jorge Penadés 3f972991af Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 16:06:44 +02:00
Jorge Penadés 21d36a0d37 refactor: refs #8388 streamline InvoiceInVat tests by removing unused variables and improving element selectors
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 16:05:16 +02:00
Jon Elias e3936c63c6 Merge pull request '#8944: New date filter and make section use salix's back' (!1750) from 8944-FixedPriceChanges into dev
gitea/salix-front/pipeline/head This commit looks good Details
Reviewed-on: #1750
Reviewed-by: Pablo Natek <pablone@verdnatura.es>
2025-05-15 14:03:49 +00:00
Jon Elias 7b2b6a81eb Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
2025-05-15 12:57:51 +00:00
Jorge Penadés 7fcea1dfdc Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 14:46:32 +02:00
Jorge Penadés 0fbaa79563 Merge branch 'dev' into 8564-throwErr
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-15 12:44:20 +00:00
Pau Rovira 4057110a82 Merge branch 'dev' into 8825-bajaOpinionCredito
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-15 12:39:32 +00:00
Jorge Penadés cf64b7069b feat: refs #8388 add filter options for intrastat table columns and update tests for row operations
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 14:38:27 +02:00
Javier Segarra 2316b08c2a test: skip customerFiscalData
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-15 14:31:53 +02:00
Javier Segarra e6e4588d7c test: minor change 2025-05-15 14:27:08 +02:00
Javier Segarra f4171d8efa test: fix test 2025-05-15 14:08:39 +02:00
Pau Rovira 41db6ec043 refactor: refs #8825 remove unused code (again)
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 14:02:55 +02:00
Pau Rovira baa902f25e refactor: refs #8825 remove unused code
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 13:49:06 +02:00
Pau Rovira c95f4bb387 fix: refs #8825 fix filter not working properly when reloading
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 13:36:43 +02:00
Pau Rovira eeeeda0c68 Merge branch 'dev' into 8825-bajaOpinionCredito
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 11:31:43 +00:00
Jorge Penadés 79e87833b9 fix: refs #8388 reload on delete
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 13:29:40 +02:00
Pau Rovira 567ce5fe99 feat: refs #8825 use VnTable creation form
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-15 13:29:02 +02:00
Jon Elias 5a92f1a6cc fix: no downloading file when clicking in the file field
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
2025-05-15 13:16:26 +02:00
Jorge Penadés 67f8d718e8 Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 13:09:51 +02:00
Jorge Penadés 4befe90289 Merge branch '8388-fineTunningInvoiceIn' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 12:26:42 +02:00
Jorge Penadés 8e88c199e5 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn 2025-05-15 12:25:05 +02:00
Alex Moreno b44a397c81 Merge branch 'dev' into 8388-fineTunningInvoiceIn 2025-05-15 09:54:04 +00:00
Pablo Natek 0c701b23b6 Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-15 08:51:46 +00:00
Alex Moreno d87308844b fix: remove unused router import and update filter syntax in VnLog
gitea/salix-front/pipeline/head This commit looks good Details
2025-05-15 09:19:37 +02:00
Javier Segarra 044c06e963 fix: remove print variable
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-14 19:41:29 +02:00
Javier Segarra e595201501 fix: remove limit=0
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-14 19:29:56 +02:00
Javier Segarra 0b1b0b40a2 fix: remove limit
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-14 19:27:34 +02:00
Javier Segarra 4bbdd3f162 feat: use new ticket payment dialog
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-14 18:53:52 +02:00
Javier Segarra a80f6f9016 feat: new ticket payment dialog 2025-05-14 18:53:37 +02:00
Javier Segarra 3940ac3c82 feat: new vnInputMultipleDates
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-14 14:39:43 +02:00
Javier Segarra 534bb9f121 Merge branch 'test' into warmfix_8556_excludeDates 2025-05-14 14:29:14 +02:00
Javier Segarra ac59c79baf fix: update alert level code handling and binding in TicketLackTable 2025-05-14 14:27:34 +02:00
Javier Segarra 9f7994319b feat: remove vnsleect url inside column 2025-05-14 14:27:28 +02:00
Pau Rovira df6c5bde04 Merge branch 'dev' into 8825-bajaOpinionCredito
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-14 10:05:46 +00:00
Pau Rovira 327afe8311 feat: refs #8825 add functionality to terminate contract 2025-05-14 12:02:34 +02:00
Jose Antonio Tubau 2f29eaa843 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-14 07:59:17 +00:00
Jose Antonio Tubau e3e414e827 Merge branch 'dev' into 7069-testVnAccountNumber
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-14 05:29:35 +00:00
Jorge Penadés 464a739a6b refactor: refs #8388 compute totalAmount from fetched data instead of static reference
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 17:23:45 +02:00
Jorge Penadés 2d0cb72774 refactor: refs #8388 compute totalAmount dynamically from arrayData
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 16:37:44 +02:00
Jorge Penadés 07f58bbec0 fix: refs #8388 simplify navigation logic in save function
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 14:48:32 +02:00
Jorge Penadés 0a992094cf refactor: refs #8388 optimize expense fetching logic in columns computation
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 14:07:10 +02:00
Javier Segarra 5be5287267 fix: improve error handling in TicketList
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-13 13:51:33 +02:00
Javier Segarra 359f1cc4db fix: reset selected values on reload in VnTable and comment out selectedRows reset in TicketSale 2025-05-13 13:51:05 +02:00
Jorge Penadés d4337b32b4 fix: refs #8388 rollback
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 13:48:10 +02:00
Jorge Penadés 34a2306db2 fix: refs #8388 rollback
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 13:47:13 +02:00
Javier Segarra 7a3e70fc6e Merge branch 'master' into hotfix_save_selection_after_reload 2025-05-13 13:45:00 +02:00
Jorge Penadés 26507c6f6b fix: refs #8388 improve DMS check logic in preAccount function
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 13:42:11 +02:00
Jorge Penadés 1535be957f feat: refs #8388 enable auto-load
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 13:26:20 +02:00
Alex Moreno 3cf7aeae87 refactor: enhance order filtering logic and improve code readability
gitea/salix-front/pipeline/pr-master There was a failure building this commit Details
2025-05-13 13:22:00 +02:00
Jose Antonio Tubau ea93ed42d5 Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 11:14:28 +02:00
Jose Antonio Tubau c59b862057 fix: refs #8441 ensure date formatting is applied when managing date input 2025-05-13 11:14:26 +02:00
Jorge Penadés f020db7fe1 refactor: refs #8388 update VnSelectExpense to use computed for expenses and improve data fetching
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 11:13:14 +02:00
Jorge Penadés cc397f6b20 fix: refs #8388 ensure navigation occurs after data reload in save function
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 10:30:51 +02:00
Jose Antonio Tubau 3712fe4c4d Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 08:21:48 +00:00
Jorge Penadés 88a9299aa9 fix: refs #8388 enhance click handler to ignore dialog interactions
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 10:21:03 +02:00
Jose Antonio Tubau 8f6c2c1bfa Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 08:11:05 +00:00
Jose Antonio Tubau 9906b2a34d Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 07:52:49 +00:00
Jorge Penadés 3295c18847 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 09:50:46 +02:00
Benjamin Esteve 1e21abe978 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-13 09:11:43 +02:00
Alex Moreno 2f2942a23f Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details
2025-05-13 05:34:27 +00:00
Jorge Penadés 8764eeca92 fix: refs #8388 remove unused save-changes event
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 17:47:56 +02:00
Jorge Penadés 5882f47f5f fix: refs #8388 update CrudModel and VnTable to handle async reload and improve event handling
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 17:17:32 +02:00
Jorge Penadés 54a905d436 fix: refs #8388 remove unused save-changes event handler from VnTable component
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-05-12 17:08:02 +02:00
Pablo Natek 311e75e5ab Merge branch '8684-itemRefactorAndE2e' of https: refs #8684//gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 14:03:58 +02:00
Pablo Natek ce0936d11c refactor: refs #8684 change beforeEach to before and remove async from test cases in itemRequest.spec.js 2025-05-12 14:03:01 +02:00
Jorge Penadés 7b4534e7fd Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 14:00:45 +02:00
Jorge Penadés cda841b956 fix: refs #8388 remove unused insert-on-load attribute from InvoiceIn components
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 13:56:59 +02:00
Jorge Penadés 505489fae6 chore: refs #8388 drop useless code
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 13:19:30 +02:00
Jorge Penadés 7dbe5c24ec fix: refs #8388 update format functions to handle empty values and adjust column widths
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 13:17:07 +02:00
Pablo Natek ac7c64d66d Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e 2025-05-12 13:14:20 +02:00
Alex Moreno 4e0e84b703 Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 11:12:14 +00:00
Jose Antonio Tubau ad1bde3f43 Merge branch 'dev' into 7069-testVnAccountNumber
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-12 11:07:36 +00:00
Jose Antonio Tubau 5808aa6806 Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 11:06:40 +00:00
Benjamin Esteve bde19f0d4c Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-12 12:45:36 +02:00
Pablo Natek 7f6a385e1d Merge branch '8684-itemRefactorAndE2e' of https://gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 12:16:49 +02:00
Pablo Natek ead6766c45 fix: refs #8684 stop event propagation on Enter and Escape key actions in renderInput function 2025-05-12 12:16:42 +02:00
Jorge Penadés 30539dfed7 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-12 11:28:33 +02:00
Benjamin Esteve 9cf7e33afb Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-12 11:27:02 +02:00
Jorge Penadés ab542c1872 fix: refs #8564 remove unnecessary error rethrow in password change validation
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-12 11:20:16 +02:00
Jorge Penadés 9f56fc292b fix: refs #8564 improve error handling in password change validation 2025-05-12 11:18:04 +02:00
Pablo Natek dd59b950c5 Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 08:16:06 +00:00
Jorge Penadés 3d0191eb0c Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 10:13:56 +02:00
Pablo Natek ca4a822ce2 refactor: refs #8684 improve item request test by using constants for field selectors
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 09:49:04 +02:00
Pablo Natek 8120320a15 Merge branch '8684-itemRefactorAndE2e' of https://gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 09:43:47 +02:00
Pablo Natek 8de345f33b test: refs #8684 skip 'should add and remove new line' in ClaimDevelopment tests 2025-05-12 09:43:45 +02:00
Pablo Natek 0f4e7c95ab Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 06:39:15 +00:00
Pablo Natek 8b04dbc7ec test: refs #8684 enable test isolation for item type tests and improve element selection
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-05-12 08:38:50 +02:00
Jose Antonio Tubau e5b1fc1001 Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 06:33:50 +00:00
Jose Antonio Tubau def62c5e12 Merge branch 'dev' into 7069-testVnAccountNumber
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 05:13:46 +00:00
Jose Antonio Tubau b093e7398d Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-12 07:13:01 +02:00
Benjamin Esteve c3ae161eb0 refactor: refs #7549 optimize claim data retrieval in handleWorker function
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-09 12:38:41 +02:00
Javier Segarra 7dcfeb0fc7 test: try to fix entryBuys test
gitea/salix-front/pipeline/pr-master This commit is unstable Details
2025-05-09 11:56:18 +02:00
Benjamin Esteve c1b6d96aef Merge branch '7549-autofillWorkerClaimDevelopment' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-09 10:43:15 +02:00
Benjamin Esteve 640bc21022 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment 2025-05-09 10:43:11 +02:00
Benjamin Esteve f3b06c84a5 Merge branch 'dev' into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-09 08:40:07 +00:00
Alex Moreno 649686f128 refactor: refs #7069 remove keyup.enter and blur event emissions from VnInput component
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-09 09:53:42 +02:00
Alex Moreno 43258214e1 feat: refs #7069 add keyup.enter and blur event emissions to VnInput component
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-09 09:46:06 +02:00
Pablo Natek 8e02826b76 Merge branch 'dev' of https: refs #8684//gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-09 09:21:46 +02:00
Alex Moreno c723608d6b refactor: refs #7069 simplify VnInput emits and update event handling in tests
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-05-09 09:09:57 +02:00
Alex Moreno 2a8feaa5d1 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7069-testVnAccountNumber 2025-05-09 08:42:48 +02:00
Pablo Natek 8879b15e4e Merge branch 'dev' of https: refs #8684//gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-09 07:22:30 +02:00
Javier Segarra 1007a884b9 feat: remove reset selected variable
gitea/salix-front/pipeline/pr-master This commit is unstable Details
2025-05-08 12:25:59 +02:00
Benjamin Esteve 02a4960712 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-08 10:38:19 +02:00
Benjamin Esteve 254d5562dd Merge branch '7549-autofillWorkerClaimDevelopment' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-08 09:45:25 +02:00
Benjamin Esteve 40899c4c3d fix(ClaimDevelopment): refs #7549 move claim data fetch to after responsible check 2025-05-08 09:45:23 +02:00
Jose Antonio Tubau ffe502cd99 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-08 09:39:09 +02:00
Benjamin Esteve 191af3f917 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment 2025-05-08 09:08:54 +02:00
Pablo Natek bce37182ed fix: refs #8684 skip worker pop-ups test to prevent execution during CI
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-08 08:26:52 +02:00
Benjamin Esteve b9788bc097 Merge branch 'dev' into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 11:32:07 +00:00
Pablo Natek b3c418551c fix: refs #8684 update warehouse filter to include only destination warehouses
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 13:29:32 +02:00
Pablo Natek aacfb2a77d Merge branch 'dev' of https: refs #8684//gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 13:12:10 +02:00
Alex Moreno b8231c4878 Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 10:39:50 +00:00
Benjamin Esteve a63926ff48 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 11:38:03 +02:00
Benjamin Esteve b687c80559 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 10:24:39 +02:00
Alex Moreno d1fee6d692 Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 08:22:48 +00:00
Jose Antonio Tubau 936ee1b7f7 refactor: refs #8441 update localization for assigned invoices in English and Spanish
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 09:45:28 +02:00
Pablo Natek eb0e63441e refactor: refs #8684 skip VnShortcuts test suite in VnShortcut.spec.js
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 09:41:59 +02:00
Benjamin Esteve ad178825db fix: refs #7549 update handleWorker to check for commercial responsible by description
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 09:40:53 +02:00
Benjamin Esteve 8eb81fadf3 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 09:12:05 +02:00
Jose Antonio Tubau 5ff944e670 Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 08:53:58 +02:00
Jose Antonio Tubau c9e1e9a334 chore: refs #8441 update unlinkedInvoice to unassignedInvoice in vehicle ticket localization 2025-05-07 08:53:57 +02:00
Jose Antonio Tubau abb741ed1d Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-07 06:45:33 +00:00
Alex Moreno cd76a006a2 Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-07 07:35:36 +02:00
Javier Segarra e1be86774b Merge branch 'test' into warmfix_8556_excludeDates
gitea/salix-front/pipeline/pr-test This commit is unstable Details
2025-05-06 18:57:58 +02:00
Pablo Natek e382852349 refactor: refs #8684 update test descriptions and skip customer basic data
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-06 13:41:31 +02:00
Alex Moreno 95c58acb79 feat: refs #8224 update onDataSaved to handle async save and modify select component options
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-05-06 13:02:38 +02:00
Pablo Natek 8cf8b7d4ec refactor: refs #8684 skip 'View popup summary' test in entryList.spec.js for task #8638 2025-05-06 12:06:45 +02:00
Alex Moreno 59bb3c4ad2 Merge branch '8224-contextMenu' of https://gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-06 11:40:26 +02:00
Alex Moreno 1bfc2684f7 Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-05-06 09:35:16 +00:00
Alex Moreno 8fb7e5637b Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu 2025-05-06 11:34:49 +02:00
Benjamin Esteve f351734d84 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-06 11:31:57 +02:00
Benjamin Esteve a6a3d5a3d5 fix: refs #7549 handle null claimResponsibleFk in handleWorker function 2025-05-06 11:31:02 +02:00
Alex Moreno 81e572e66d Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-05-06 08:14:19 +00:00
Alex Moreno a56cdf8053 fix: refs #8224 handle object values in formatValue function
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-05-06 10:13:59 +02:00
Alex Moreno 0b40134cfe refactor: refs #8224 simplify valueIsObject computed property
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-06 09:38:10 +02:00
Benjamin Esteve 20ca8f8072 Merge branch 'dev' of https: refs #7549//gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-06 09:20:57 +02:00
Pablo Natek 9782288d97 refactor: refs #8684 remove 'only' from stepper test in ticketBasicData.spec.js
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-06 08:26:56 +02:00
Pablo Natek 70fdbb8210 refactor: refs #8684 remove unused image background color variables in app.scss
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-06 08:07:29 +02:00
Pablo Natek a9acc240e9 refactor: refs #8684 clean up imports and remove console logs in ItemRequest component
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-06 07:20:57 +02:00
Jose Antonio Tubau 9639c56ce0 Merge branch 'dev' into 7069-testVnAccountNumber
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-05 13:58:58 +00:00
Pablo Natek d64788732e refactor: refs #8684 improve input handling and rendering in VnTable enhance item request logic in ItemRequest
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-05 14:39:37 +02:00
Benjamin Esteve 9af63555c3 fix: refs #7549 update handleWorker function to correctly assign workerFk based on claimResponsibleFk
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-05 14:18:55 +02:00
Javier Segarra 76df3413b9 feat: solve minor bugs and styles 2025-05-05 12:26:37 +02:00
Benjamin Esteve 50cb0e243b Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7549-autofillWorkerClaimDevelopment
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-05 11:50:18 +02:00
Pablo Natek cb1fa3c7f5 refactor: refs #8684 improve input handling in VnTable and clean up ItemRequest component
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-05 10:49:37 +02:00
Jose Antonio Tubau 7b4f4880b5 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-05 06:59:10 +00:00
Pablo Natek 240b927a02 refactor: refs #8684 enhance editability checks in VnTable and improve item request handling
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-05-05 08:54:38 +02:00
Benjamin Esteve 41e4cc13b1 feat: refs #7549 implement worker selection handling
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-02 12:28:55 +02:00
Javier Segarra 22f77fc70c perf: minor changes 2025-05-02 10:09:47 +02:00
Javier Segarra adf33c14ae Merge branch 'test' into warmfix_8556_excludeDates 2025-05-02 09:28:21 +02:00
Jose Antonio Tubau 16a86282fc Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-05-02 06:09:22 +00:00
Jorge Penadés 65f77b41e6 fix: refs #8388 enhance fiscalCode formatting in EntryPreAccount
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 18:39:15 +02:00
Jorge Penadés 5383df6946 fix: refs #8388 changes
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 16:16:27 +02:00
Pablo Natek ab6dc5d2ca Merge branch '8684-itemRefactorAndE2e' of https://gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 15:44:35 +02:00
Pablo Natek f7280e6f25 refactor: refs #8684 clean up ItemRequest component and improve item descriptor rendering 2025-04-30 15:44:32 +02:00
Jorge Penadés f8c3bfe2cd fix: refs #8388 rollback
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 15:14:43 +02:00
Jorge Penadés ce69b5e274 fix: refs #8388 rollback 2025-04-30 15:14:15 +02:00
Jorge Penadés a0a5c13865 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-04-30 15:10:27 +02:00
Jorge Penadés 0dfb5a47b7 fix: refs #8388 enhance keydown event handling in VnTable and add expense fetching in InvoiceInVat
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 15:10:10 +02:00
Jose Antonio Tubau 78df8c877d Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 12:05:43 +00:00
Alex Moreno 5ef83fd53e Merge branch '8224-contextMenu' of https://gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 11:57:57 +02:00
Alex Moreno 23a13a0347 Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu 2025-04-30 11:57:55 +02:00
Jose Antonio Tubau d10549939b Merge branch 'dev' into 7069-testVnAccountNumber
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 09:29:43 +00:00
Alex Moreno 7e2f93d767 Merge branch 'dev' into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 09:19:45 +00:00
Jorge Penadés d45d84d707 fix: refs #8388 update handleHeaderSelection to use selection function for row selection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 11:07:38 +02:00
Alex Moreno 49bf269b4d Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 08:56:06 +00:00
Jorge Penadés ff38cd7590 Merge branch 'dev' of https: refs #8388//gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 10:42:50 +02:00
Jose Antonio Tubau b21c5752b8 test: refs #7069 add unit tests for VnAccountNumber component
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 10:41:49 +02:00
Alex Moreno 0af8b8af2c Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-30 08:41:14 +00:00
Alex Moreno 23dbd2a862 fix: refs #8224 update VnSelect component to handle nullish emit-value correctly
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-30 10:41:01 +02:00
Alex Moreno efa68e9489 fix: refs #8224 refine loading condition in VnSelect component
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-30 09:38:11 +02:00
Alex Moreno c2a470c25e Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-30 08:58:00 +02:00
Jose Antonio Tubau c89bfc488c Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-30 06:09:21 +00:00
Jose Antonio Tubau 103230e869 Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-30 06:06:15 +00:00
Jorge Penadés 9abd8a1841 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-29 17:17:06 +02:00
Jose Antonio Tubau b3285f6783 Merge branch '8930-createVehicleSectionOnInvoiceIn' of https://gitea.verdnatura.es/verdnatura/salix-front into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-29 09:43:44 +02:00
Jose Antonio Tubau 0c04c06024 refactor: refs #8930 remove unused template for create dialog in InvoiceInVehicle component 2025-04-29 09:43:43 +02:00
Jose Antonio Tubau eb8e0d1334 refactor: refs #8930 update amount column configuration 2025-04-29 09:43:29 +02:00
Alex Moreno d0f4f77640 Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-29 08:59:07 +02:00
Jorge Penadés bf79797024 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 16:57:27 +02:00
Jorge Penadés ca0af4b3f6 feat: refs #8388 add 'isOrdered' and 'isReceived' fields to TravelFilter
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 16:45:34 +02:00
Jorge Penadés d1f676e6ff feat: refs #8388 add ACL check for adding invoices in EntryPreAccount.vue
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 16:13:29 +02:00
Jorge Penadés d2452caef8 feat: refs #8388 update entry pre-accounting filter handling and enhance search info in localization files
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 15:56:19 +02:00
Jorge Penadés da02cc3622 feat: refs #8388 add VnSelectExpense component and integrate it into InvoiceInVat.vue
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 15:04:20 +02:00
Jorge Penadés b63ae45121 feat: refs #8388 update selection handling
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-28 13:30:17 +02:00
Alex Moreno b911211000 test: refs #8224 fix bad test
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 10:10:17 +02:00
Alex Moreno c6d4e2ffe5 test: refs #8224 fix e2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-28 09:55:05 +02:00
Alex Moreno 2f5ef1d0a0 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu 2025-04-28 09:25:19 +02:00
Alex Moreno 1511264aea test: refs #8224 adapt fillInForm
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-28 09:08:22 +02:00
Jose Antonio Tubau 8250660a32 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-28 07:05:10 +00:00
Jose Antonio Tubau 020cf18a73 Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-28 07:02:57 +00:00
Jose Antonio Tubau 22390d9aef refactor: refs #7385 remove unnecessary apostrophes
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-28 08:01:30 +02:00
Pablo Natek d81b8c7fc3 refactor: refs #8684 improve styling and accessibility in Item components, add data-cy attributes for testing
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-27 11:51:54 +02:00
Jorge Penadés 3e9d239902 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 18:04:33 +02:00
Alex Moreno d79d37dcaf test: refs #8224 fix fillInForm use q-select
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 14:28:09 +02:00
Jose Antonio Tubau 541b9f1b5f Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-25 12:25:58 +00:00
Jose Antonio Tubau 2f0f0c73cd fix: refs #8441 add TODO comment for skipped vehicleDms test
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-25 14:23:44 +02:00
Alex Moreno b1d637f382 test: refs #8224 fix e2e using select, use containts()
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-25 14:13:45 +02:00
Jose Antonio Tubau 2c7913e51e Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-25 14:04:03 +02:00
Jose Antonio Tubau adc4385f1e feat: refs #8441 add filter vehicles by 'isActive' field 2025-04-25 14:04:02 +02:00
Jose Antonio Tubau b2584c021b test: refs #8930 update invoice booking flow to use confirmation dialog
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-25 13:48:38 +02:00
Jorge Penadés dd3a7c7755 refactor: refs #8388 reorganize variable declarations and improve DMS handling in EntryPreAccount.vue
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 12:44:50 +02:00
Alex Moreno 24f8d1c2e5 fix(VnSelect): refs #8224 add hideSelected prop to VnSelect for improved focus handling
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 11:12:16 +02:00
Jose Antonio Tubau ee632880f9 Merge branch '8930-createVehicleSectionOnInvoiceIn' of https://gitea.verdnatura.es/verdnatura/salix-front into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-25 11:11:13 +02:00
Jose Antonio Tubau ab5d85f392 refactor: refs #8930 update title link redirection tests for clarity and consistency 2025-04-25 11:11:12 +02:00
Jose Antonio Tubau 2e61630f85 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-25 08:55:57 +00:00
Jose Antonio Tubau 18f43489a3 test: refs #8441 skip download DMS test
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-25 10:18:46 +02:00
Alex Moreno c900f17efa Merge branch '8224-contextMenu' of https://gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu 2025-04-25 10:07:15 +02:00
Jose Antonio Tubau 00ac4305a1 Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 07:52:34 +00:00
Jose Antonio Tubau 0251735c82 fix: refs #8441 change test to allow downloading DMS files and ensure proper interception
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 09:47:05 +02:00
Alex Moreno 1d568fa7df Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 07:41:18 +00:00
Alex Moreno 9418c9f897 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu 2025-04-25 09:41:05 +02:00
Jose Antonio Tubau b38c8cde0d Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 09:04:09 +02:00
Jose Antonio Tubau ce54da0039 fix: refs #8441 format code for improved readability and consistency in Vehicle DMS tests 2025-04-25 09:04:07 +02:00
Alex Moreno 00d1db03a8 Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-25 06:35:59 +00:00
Pablo Natek 1b660a15de refactor: refs #8684 update table styling and enhance EntryFilter component structure
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-25 08:25:35 +02:00
Jorge Penadés ce8127b723 feat: refs #8388 save & continue onEnter
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 17:25:21 +02:00
Jorge Penadés b2202d7843 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-04-24 17:06:37 +02:00
Jorge Penadés 92a282d71c refactor: refs #8388 clean up imports and improve variable naming in InvoiceInDueDay.vue
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 17:05:00 +02:00
Jose Antonio Tubau f5ba659214 Merge branch 'dev' into 8930-createVehicleSectionOnInvoiceIn
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-24 12:35:11 +00:00
Pablo Natek 21c7b2088b refactor: refs #8684 improve slot usage and clean up ItemTags and ItemListFilter components
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 13:57:36 +02:00
Pablo Natek 9c13cd921e Merge branch 'dev' of https: refs #8684//gitea.verdnatura.es/verdnatura/salix-front into 8684-itemRefactorAndE2e
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 13:32:07 +02:00
Pablo Natek 2cbbd92021 refactor: refs #8684 refs #8363 multiple refactor on item sections 2025-04-24 13:24:46 +02:00
Alex Moreno eeab1d228f Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-24 11:11:20 +00:00
Jorge Penadés ca596b418a Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 10:22:58 +02:00
Jose Antonio Tubau 99d2614f6d Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 08:18:55 +00:00
Jose Antonio Tubau ccbcaa13b7 Merge branch 'dev' into 7385-addDeliveredAndForecastColumnsOnRouteTicketsList
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-24 09:48:38 +02:00
Alex Moreno d65aa3e31c Merge branch 'dev' into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 07:25:23 +00:00
Alex Moreno 8a23bb8734 fix: refs #8224 change arrayData type from Array to Object
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-24 09:24:49 +02:00
Alex Moreno 807540b512 chore: refs #8224 merge dev
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-04-24 09:23:20 +02:00
Alex Moreno de5c62e481 Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu 2025-04-24 09:20:54 +02:00
Jose Antonio Tubau 3eeb23f89d fix: refs #8441 update button selectors in Vehicle DMS tests for improved consistency and functionality
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 09:01:39 +02:00
Jose Antonio Tubau dc7338ee8a fix: refs #8441 update vehicle DMS selectors to target first row elements for consistency
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 08:45:04 +02:00
Jose Antonio Tubau 4287efa700 refactor: refs #8930 update class names for readability
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-24 07:13:24 +02:00
Jorge Penadés 10548a00d2 fix: refs #8388 update layout
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 18:32:36 +02:00
Jorge Penadés eb373f10b3 fix: refs #8388 update VnTable integration in InvoiceInDueDay component and enhance input handling
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 18:29:53 +02:00
Jorge Penadés 50cf47436c fix: refs #8388 update title for creating Intrastat line in InvoiceInIntrastat component 2025-04-23 17:26:03 +02:00
Jorge Penadés 6c49c9ea82 fix: refs #8388 update VnSelect component to handle option rendering based on optionLabel type
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 16:38:34 +02:00
Alex Moreno 679b9f5d5b Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-23 15:01:12 +02:00
Alex Moreno f509616f86 fix(VnSelect): refs #8224 improve focus handling and update selected item template
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-23 14:59:41 +02:00
Jose Antonio Tubau db2a0b2685 refactor: refs #8930 update vehicle labels to use global translations and clean up unused keys in locale files
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 14:35:05 +02:00
Pablo Natek f01eb6c49c refactor: refs #8684 remove unused images and implement dark mode suffix for image handling 2025-04-23 13:40:13 +02:00
Alex Moreno 4ad9a3e613 feat: refs #8224 exclude filter
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-23 13:39:33 +02:00
Jose Antonio Tubau e4f0d13a9b Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-23 12:38:38 +02:00
Jorge Penadés 587f106818 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8388-fineTunningInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 12:29:41 +02:00
Jose Antonio Tubau f4ecf9ad51 refactor: refs #7385 add detailed summary and ticket fields in English and Spanish locales
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-23 11:44:54 +02:00
Jose Antonio Tubau c3f60c68bc feat: refs #7385 add delivered and estimated info in routeTicket 2025-04-23 11:44:33 +02:00
Jose Antonio Tubau 6122b64268 feat: refs #8930 add vehicle management to invoice summary and vehicle linking functionality
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 09:48:59 +02:00
Jose Antonio Tubau 905b15594c refactor: refs #8441 update vehicleCard menu order and reposition VehicleDms component
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-23 07:34:43 +02:00
Jose Antonio Tubau 5307becf31 Merge branch 'dev' into 8441-createVehicleInvoiceInSection 2025-04-23 07:34:32 +02:00
Jose Antonio Tubau 7d62f7161b refactor: refs #8441 update date input handling and rename action selectors in route tests
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-23 07:30:58 +02:00
Jorge Penadés 1c594173fe feat(invoice): refs #8388 update tax calculations and improve date filtering logic
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-22 18:18:42 +02:00
Pablo Natek d8bd78b5ce refactor: refs #8684 update prop types and clean up unused code in various components 2025-04-22 09:09:10 +02:00
Alex Moreno e8ba5761cc Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-22 08:40:18 +02:00
Jose Antonio Tubau 31e337c77b fix: refs #8441 handle blur and focusout events to format date input
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-22 07:49:32 +02:00
Jose Antonio Tubau 83ecfca6b8 refactor: refs #8441 simplify redirection tests using checkRedirectionFromPopUp utility
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-22 07:24:35 +02:00
Jose Antonio Tubau 39a5143953 refactor: refs #8441 update vehicleCard menu order 2025-04-22 07:23:37 +02:00
Jose Antonio Tubau 16c70af66a Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-22 07:12:10 +02:00
Javier Segarra 8e47a4134d fix: update label for excluded dates input in TicketLackFilter component 2025-04-17 20:16:20 +02:00
Javier Segarra f15865ee42 Merge branch 'test' into warmfix_8556_excludedDates 2025-04-17 12:57:57 +02:00
Javier Segarra 0ff6cc5dbe feat: add entry 2025-04-16 14:13:16 +02:00
Javier Segarra 6c0fcc38b0 feat: apply multiple 2025-04-16 14:13:08 +02:00
Javier Segarra 31f1850704 feat: add multiple attribute 2025-04-16 14:12:23 +02:00
Jose Antonio Tubau 2f20abf388 refactor: refs #7385 update ticket columns to use 'delivered' and 'forecast' fields in RouteSummary
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-15 15:40:03 +02:00
Jose Antonio Tubau b25bd19bb5 feat: refs #7385 add delivery forecast and delivered fields to route summary and descriptor
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-15 14:59:41 +02:00
Alex Moreno 197ebed39d feat: refs #8224 add context menu forVnTable
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-15 11:47:47 +02:00
Jose Antonio Tubau 7f3151b1a4 test: refs #8441 hide right menu before each it
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-14 13:40:41 +02:00
Jose Antonio Tubau b57b5f7cee Merge branch '8441-createVehicleInvoiceInSection' of https: refs #8441//gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-14 12:35:57 +02:00
Jose Antonio Tubau 693f6829f4 fix: refs #8441 remove unnecessary selector specificity for summary button in vehicle and route tests 2025-04-14 12:33:50 +02:00
Jose Antonio Tubau 33ea74d0a4 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-14 10:10:23 +00:00
Jose Antonio Tubau 5cb8a4a9dc Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-14 07:00:58 +00:00
Jose Antonio Tubau 3029265402 Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-11 10:40:01 +02:00
Jose Antonio Tubau d86fb54917 fix(vehicleList.spec): refs #8441 ensure URL includes vehicle ID after adding a new vehicle 2025-04-11 10:39:59 +02:00
Jose Antonio Tubau 84385730cd Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-11 07:03:42 +00:00
Jose Antonio Tubau 4b9e533ccd refactor: refs #8441 intercept api call instead .q-notification
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-08 14:16:33 +02:00
Jose Antonio Tubau ee150a6f26 Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-08 13:51:31 +02:00
Jose Antonio Tubau fc245fb6ac Merge branch 'dev' into 8441-createVehicleInvoiceInSection 2025-04-08 13:51:29 +02:00
Jose Antonio Tubau 89d7fccacd Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-08 08:24:37 +00:00
Jose Antonio Tubau e10dac995d Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-07 13:00:02 +00:00
Jose Antonio Tubau e8068c146d Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-07 12:07:52 +00:00
Jose Antonio Tubau b93e5d0e40 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-07 11:42:39 +00:00
Jose Antonio Tubau 20627c04f7 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-07 07:35:13 +00:00
Jose Antonio Tubau 31ac582c16 test: refs #8441 skip invoice assigned invoices pop-ups due to Redmine #8872
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-04-07 09:20:49 +02:00
Jose Antonio Tubau 9c3d92a244 test: refs #8441 update invoice pop-ups tests to ensure visibility of assigned invoices links
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-07 07:23:58 +02:00
Jose Antonio Tubau 2dd82a55e8 test: refs #8441 improve visibility checks and update invoice link selectors
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-04 12:02:27 +02:00
Jose Antonio Tubau 903345203b test: refs #8441 ensure menu visibility in vehicle summary tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-04 11:36:06 +02:00
Jose Antonio Tubau b650d2b438 test: refs #8441 skip invoice pop-ups test due to Redmine #8863
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-04 10:11:24 +02:00
Jose Antonio Tubau acd78fc859 test: refs #8441 update route extended list tests to include ID selector and improve summary redirection checks
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-04 09:42:28 +02:00
Jose Antonio Tubau 6d2fa8493c test: refs #8441 update URL assertions to use cy.location() and ensure menu visibility in vehicle invoice tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-04 08:49:10 +02:00
Jose Antonio Tubau fcba8c7fcd test: refs #8441 update invoice number in vehicle invoice test
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-03 15:28:30 +02:00
Jose Antonio Tubau 36de9e411e Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-03 15:20:08 +02:00
Jose Antonio Tubau c65a0f131c test: refs #8441 ensure buttons are visible before clicking in vehicle invoice tests 2025-04-03 15:20:06 +02:00
Jose Antonio Tubau 800530f943 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-03 10:25:02 +00:00
Jose Antonio Tubau 49dd8e2432 test: refs #8441 ensure buttons are visible before clicking in vehicle summary tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-03 11:59:18 +02:00
Jose Antonio Tubau b950b4845b refactor: refs #8441 improved tests to avoid intermittency and failures due to interference from other tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-04-03 09:09:40 +02:00
Jose Antonio Tubau 622307697f chore: refs #8441 update unassign invoice confirmation messages in English and Spanish locales
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-02 12:25:15 +02:00
Jose Antonio Tubau 737eb74117 chore: refs #8441 remove exclamation marks
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-02 12:20:09 +02:00
Jose Antonio Tubau 666d524553 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-02 12:11:59 +02:00
Jose Antonio Tubau 22293eb2f8 refactor: refs #8441 rename getSelector to getLinkSelector for clarity
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-02 06:52:45 +02:00
Jose Antonio Tubau 4e49a73173 chore: refs #8441 simplify error handling by rethrowing original errors
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-02 06:51:39 +02:00
Jose Antonio Tubau c57600b0f0 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-01 07:24:27 +02:00
Jose Antonio Tubau b92cf74d85 refactor: refs #8441 removed errors-related changes
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-04-01 07:09:37 +02:00
Jose Antonio Tubau 859ceeddf8 Merge branch 'dev' into 8441-createVehicleInvoiceInSection 2025-04-01 07:09:12 +02:00
Jose Antonio Tubau 99322a39e4 refactor: refs #8441 update notification messages for unlinking invoices
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-27 14:29:05 +01:00
Jose Antonio Tubau ea3a2c28cc Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-03-18 13:25:19 +01:00
Jose Antonio Tubau 20e99b5689 test: refs #8441 ensure visibility checks for elements in vehicle invoice tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 14:05:39 +01:00
Jose Antonio Tubau 138b3322b7 test: refs #8441 try add q.table check visbility
gitea/salix-front/pipeline/pr-dev Build queued... Details
2025-03-17 13:56:40 +01:00
Jose Antonio Tubau 7de71c96b4 Merge branch '8441-createVehicleInvoiceInSection' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 13:08:10 +01:00
Jose Antonio Tubau 040e642dd6 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection 2025-03-17 13:08:06 +01:00
Jose Antonio Tubau d33eae43f9 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 11:45:24 +00:00
Jose Antonio Tubau b0dd3879d0 test: refs #8441 ensure redirect completed before check summary title
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 12:16:13 +01:00
Jose Antonio Tubau a942b6648c test: refs #8441 update vehicle model name and ensure summary header visibility in vehicle list tests
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 11:33:08 +01:00
Jose Antonio Tubau 51e42a6dc8 test: refs #8441 skip EntryDms, Entry, and EntryStockBought test suites
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 11:12:38 +01:00
Jose Antonio Tubau 71d3d0830e Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection 2025-03-17 11:08:41 +01:00
Jose Antonio Tubau 128a271eba fix: refs #8441 intermittent test fail
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-17 10:29:38 +01:00
Jose Antonio Tubau 2eea5b453a fix: refs #8441 move component to parent instead of children
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-17 09:17:23 +01:00
Jose Antonio Tubau fa187614da fix: refs #8441 restore warehouseFk translation in Spanish locale for routes
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-14 14:52:30 +01:00
Jose Antonio Tubau 430cddb554 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-14 14:46:08 +01:00
Jose Antonio Tubau be36d0c864 fix: refs #8441 update selectors for summary buttons in vehicle invoice tests
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-14 14:39:48 +01:00
Jose Antonio Tubau f1f9a6ac16 refactor: refs #8441 update vehicle summary links to include module name in routes
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-14 14:27:23 +01:00
Jose Antonio Tubau 512611a309 refactor: refs #8441 add data-cy attributes for summary buttons and improve tests for vehicle invoice
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-14 12:45:02 +01:00
Jose Antonio Tubau b8be248af9 fix: refs #8441 update unassign invoice functionality and notifications
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-14 08:12:48 +01:00
Jose Antonio Tubau ef797a3675 test: refs #8441 add Cypress tests for vehicle summary functionality
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details
2025-03-11 11:35:50 +01:00
Jose Antonio Tubau 7ebf4cd38f test: refs #8441 update vehicle invoice in tests for delivery assistant login
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-11 09:49:16 +01:00
Jose Antonio Tubau 5332e3ab60 Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-11 08:55:57 +01:00
Jose Antonio Tubau 0fac79ea4d test: refs #8441 update vehicleInvoiceIn.spec.js to use 'developer' login and modify invoice data
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-10 09:58:45 +01:00
Jose Antonio Tubau 3961c4e276 refactor: refs #8441 add translation for assigned invoices in Vehicle locale
gitea/salix-front/pipeline/pr-dev Something is wrong with the build of this commit Details
2025-03-10 09:28:37 +01:00
Jose Antonio Tubau e2e34d2f2d feat: refs #8441 add assigned invoices section to VehicleSummary and update localization
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-10 09:26:05 +01:00
Jose Antonio Tubau 282014f873 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-10 08:56:49 +01:00
Jose Antonio Tubau 16a54bf02e refactor: refs #8441 add VnInputNumber component and update VnSelect options in VehicleInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-07 15:28:12 +01:00
Jose Antonio Tubau 59fdf0fd00 refactor: refs #8441 update VehicleInvoiceIn component and localizations
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-06 15:25:43 +01:00
Jose Antonio Tubau 08e68e47b8 test: refs #8441 add e2e tests for vehicleInvoiceIn
gitea/salix-front/pipeline/pr-dev This commit is unstable Details
2025-03-06 13:06:50 +01:00
Jose Antonio Tubau ce827722e3 feat: refs #8441 add VehicleInvoiceIn component with invoice management functionality
gitea/salix-front/pipeline/pr-dev This commit looks good Details
2025-03-06 13:04:10 +01:00
149 changed files with 4176 additions and 2866 deletions

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -1,227 +0,0 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* 1. DO NOT edit this file directly as it won't do anything.
* 2. EDIT the original quasar.config file INSTEAD.
* 3. DO NOT git commit this file. It should be ignored.
*
* This file is still here because there was an error in
* the original quasar.config file and this allows you to
* investigate the Node.js stack error.
*
* After you fix the original file, this file will be
* deleted automatically.
**/
// quasar.config.js
import { configure } from "quasar/wrappers";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import path from "path";
var __quasar_inject_dirname__ = "/home/jsegarra/Projects/salix-front";
var target = `http://${process.env.CI ? "back" : "localhost"}:3000`;
var quasar_config_default = configure(function() {
return {
eslint: {
// fix: true,
// include = [],
// exclude = [],
// rawOptions = {},
warnings: true,
errors: true
},
// https://v2.quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli/boot-files
boot: ["i18n", "axios", "vnDate", "validations", "quasar", "quasar.defaults"],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ["app.scss"],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
"roboto-font",
"material-icons-outlined",
"material-symbols-outlined"
],
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: {
target: {
browser: ["es2022", "edge88", "firefox78", "chrome87", "safari13.1"],
node: "node20"
},
vueRouterMode: "hash",
// available values: 'hash', 'history'
// vueRouterBase,
// vueDevtools,
// vueOptionsAPI: false,
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
// publicPath: '/',
// analyze: true,
// env: {},
rawDefine: {
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
},
// ignorePublicFolder: true,
// minify: false,
// polyfillModulePreload: true,
// distDir
extendViteConf(viteConf) {
delete viteConf.build.polyfillModulePreload;
viteConf.build.modulePreload = {
polyfill: false
};
},
// viteVuePluginOptions: {},
alias: {
composables: path.join(__quasar_inject_dirname__, "./src/composables"),
filters: path.join(__quasar_inject_dirname__, "./src/filters")
},
vitePlugins: [
[
VueI18nPlugin({
strictMessage: false,
runtimeOnly: false,
include: [
path.resolve(__quasar_inject_dirname__, "./src/i18n/locale/**"),
path.resolve(__quasar_inject_dirname__, "./src/pages/**/locale/**")
]
})
]
]
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
server: {
type: "http"
},
proxy: {
"/api": {
target,
logLevel: "debug",
changeOrigin: true,
secure: false
}
},
open: false,
allowedHosts: [
"front",
// Agrega este nombre de host
"localhost"
// Opcional, para pruebas locales
]
},
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: {
config: {
config: {
dark: "auto"
}
},
lang: "en-GB",
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: ["Notify", "Dialog"],
all: "auto",
autoImportComponentCase: "pascal"
},
// animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations
animations: [],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
// sourceFiles: {
// rootComponent: 'src/App.vue',
// router: 'src/router/index',
// store: 'src/store/index',
// registerServiceWorker: 'src-pwa/register-service-worker',
// serviceWorker: 'src-pwa/custom-service-worker',
// pwaManifestFile: 'src-pwa/manifest.json',
// electronMain: 'src-electron/electron-main',
// electronPreload: 'src-electron/electron-preload'
// },
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
prodPort: 3e3,
// The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime)
middlewares: [
"render"
// keep this as last one
]
},
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: {
workboxMode: "generateSW",
// or 'injectManifest'
injectPwaMetaTags: true,
swFilename: "sw.js",
manifestFilename: "manifest.json",
useCredentialsForManifestTag: false
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
inspectPort: 5858,
bundler: "packager",
// 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: "salix-frontend"
}
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: ["my-content-script"]
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
}
};
});
export {
quasar_config_default as default
};

View File

@ -131,11 +131,10 @@ async function fetch(data) {
const rows = keyData ? data[keyData] : data; const rows = keyData ? data[keyData] : data;
resetData(rows); resetData(rows);
emit('onFetch', rows); emit('onFetch', rows);
$props.insertOnLoad && await insert(); $props.insertOnLoad && (await insert());
return rows; return rows;
} }
function resetData(data) { function resetData(data) {
if (!data) return; if (!data) return;
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
@ -146,15 +145,22 @@ function resetData(data) {
formData.value = JSON.parse(JSON.stringify(data)); formData.value = JSON.parse(JSON.stringify(data));
if (watchChanges.value) watchChanges.value(); //destroy watcher if (watchChanges.value) watchChanges.value(); //destroy watcher
watchChanges.value = watch(formData, (nVal) => { watchChanges.value = watch(
hasChanges.value = false; formData,
const filteredNewData = nVal.filter(row => !isRowEmpty(row) || row[$props.primaryKey]); (nVal) => {
const filteredOriginal = originalData.value.filter(row => row[$props.primaryKey]); hasChanges.value = false;
const filteredNewData = nVal.filter(
(row) => !isRowEmpty(row) || row[$props.primaryKey],
);
const filteredOriginal = originalData.value.filter(
(row) => row[$props.primaryKey],
);
const changes = getDifferences(filteredOriginal, filteredNewData); const changes = getDifferences(filteredOriginal, filteredNewData);
hasChanges.value = !isEmpty(changes); hasChanges.value = !isEmpty(changes);
},
}, { deep: true }); { deep: true },
);
} }
async function reset() { async function reset() {
await fetch(originalData.value); await fetch(originalData.value);
@ -183,9 +189,8 @@ async function onSubmit() {
}); });
} }
isLoading.value = true; isLoading.value = true;
await saveChanges($props.saveFn ? formData.value : null); await saveChanges($props.saveFn ? formData.value : null);
} }
async function onSubmitAndGo() { async function onSubmitAndGo() {
@ -194,10 +199,10 @@ async function onSubmitAndGo() {
} }
async function saveChanges(data) { async function saveChanges(data) {
formData.value = formData.value.filter(row => formData.value = formData.value.filter(
row[$props.primaryKey] || !isRowEmpty(row) (row) => row[$props.primaryKey] || !isRowEmpty(row),
); );
if ($props.saveFn) { if ($props.saveFn) {
$props.saveFn(data, getChanges); $props.saveFn(data, getChanges);
isLoading.value = false; isLoading.value = false;
@ -228,31 +233,29 @@ async function saveChanges(data) {
} }
async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) { async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) {
formData.value = formData.value.filter(row => !isRowEmpty(row)); formData.value = formData.value.filter((row) => !isRowEmpty(row));
const lastRow = formData.value.at(-1); const lastRow = formData.value.at(-1);
const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false; const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false;
if (formData.value.length && isLastRowEmpty) return; if (formData.value.length && isLastRowEmpty) return;
const $index = formData.value.length ? formData.value.at(-1).$index + 1 : 0; const $index = formData.value.length ? formData.value.at(-1).$index + 1 : 0;
const nRow = Object.assign({ $index }, pushData); const nRow = Object.assign({ $index }, pushData);
formData.value.push(nRow); formData.value.push(nRow);
const hasChange = Object.keys(nRow).some(key => !isChange(nRow, key)); const hasChange = Object.keys(nRow).some((key) => !isChange(nRow, key));
if (hasChange) hasChanges.value = true; if (hasChange) hasChanges.value = true;
} }
function isRowEmpty(row) { function isRowEmpty(row) {
return Object.keys(row).every(key => isChange(row, key)); return Object.keys(row).every((key) => isChange(row, key));
} }
function isChange(row, key) {
function isChange(row,key){
return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key); return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key);
} }
async function remove(data) { async function remove(data) {
if (!data.length) if (!data.length)
return quasar.notify({ return quasar.notify({
@ -270,7 +273,9 @@ async function remove(data) {
(form) => !preRemove.some((index) => index == form.$index), (form) => !preRemove.some((index) => index == form.$index),
); );
formData.value = newData; formData.value = newData;
hasChanges.value = JSON.stringify(removeIndexField(formData.value)) !== JSON.stringify(removeIndexField(originalData.value)); hasChanges.value =
JSON.stringify(removeIndexField(formData.value)) !==
JSON.stringify(removeIndexField(originalData.value));
} }
if (ids.length) { if (ids.length) {
quasar quasar
@ -286,7 +291,7 @@ async function remove(data) {
}) })
.onOk(async () => { .onOk(async () => {
newData = newData.filter((form) => !ids.some((id) => id == form[pk])); newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
fetch(newData); await reload();
}); });
} }

View File

@ -254,17 +254,13 @@ async function save() {
old: originalData.value, old: originalData.value,
}); });
if ($props.reload) await arrayData.fetch({}); if ($props.reload) await arrayData.fetch({});
if ($props.goTo) push({ path: $props.goTo });
hasChanges.value = false; hasChanges.value = false;
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
} }
async function saveAndGo() {
await save();
push({ path: $props.goTo });
}
function reset() { function reset() {
formData.value = JSON.parse(JSON.stringify(originalData.value)); formData.value = JSON.parse(JSON.stringify(originalData.value));
updateAndEmit('onFetch', { val: originalData.value }); updateAndEmit('onFetch', { val: originalData.value });
@ -385,7 +381,7 @@ defineExpose({
<QBtnDropdown <QBtnDropdown
data-cy="saveAndContinueDefaultBtn" data-cy="saveAndContinueDefaultBtn"
v-if="$props.goTo" v-if="$props.goTo"
@click="saveAndGo" @click="submitForm"
:label=" :label="
tMobile('globals.saveAndContinue') + tMobile('globals.saveAndContinue') +
' ' + ' ' +
@ -405,7 +401,7 @@ defineExpose({
<QItem <QItem
clickable clickable
v-close-popup v-close-popup
@click="save" @click="submitForm"
:title="t('globals.save')" :title="t('globals.save')"
> >
<QItemSection> <QItemSection>

View File

@ -180,11 +180,12 @@ const col = computed(() => {
) )
newColumn.component = 'checkbox'; newColumn.component = 'checkbox';
if ($props.default && !newColumn.component) newColumn.component = $props.default; if ($props.default && !newColumn.component) newColumn.component = $props.default;
if (typeof newColumn.component !== 'string') { if (typeof newColumn.component !== 'string') {
newColumn.attrs = { ...newColumn.component.attrs, autofocus: $props.autofocus }; newColumn.attrs = { ...newColumn.component?.attrs, autofocus: $props.autofocus };
newColumn.event = { ...newColumn.component.event, ...$props?.eventHandlers }; newColumn.event = { ...newColumn.component?.event, ...$props?.eventHandlers };
} }
return newColumn; return newColumn;
}); });

View File

@ -0,0 +1,136 @@
<script setup>
import { ref } from 'vue';
import { useClipboard } from 'src/composables/useClipboard';
const { copyText } = useClipboard();
const target = ref();
const qmenuRef = ref();
const colField = ref();
let colValue = '';
let textValue = '';
defineExpose({ handler });
const arrayData = defineModel({
type: Object,
});
function handler(event) {
const clickedElement = event.target.closest('td');
if (!clickedElement) return;
event.preventDefault();
target.value = event.target;
qmenuRef.value.show();
colField.value = clickedElement.getAttribute('data-col-field');
colValue = isNaN(+clickedElement.getAttribute('data-col-value'))
? clickedElement.getAttribute('data-col-value')
: +clickedElement.getAttribute('data-col-value');
textValue = getDeepestText(clickedElement);
}
function getDeepestText(node) {
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, {
acceptNode: (textNode) => {
return textNode.nodeValue.trim()
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT;
},
});
let lastText = '';
while (walker.nextNode()) {
lastText = walker.currentNode.nodeValue.trim();
}
return lastText;
}
async function selectionFilter() {
await arrayData.value.addFilter({ params: { [colField.value]: colValue } });
}
async function selectionExclude() {
await arrayData.value.addFilter({
params: { [colField.value]: { neq: colValue } },
});
}
async function selectionRemoveFilter() {
await arrayData.value.addFilter({ params: { [colField.value]: undefined } });
}
async function removeAllFilters() {
await arrayData.value.applyFilter({ params: {} });
}
function copyValue() {
copyText(textValue, {
component: {
copyValue: textValue,
},
});
}
</script>
<template>
<QMenu
ref="qmenuRef"
:target
class="column q-pa-sm justify-left"
auto-close
no-parent-event
>
<QBtn
flat
icon="filter_list"
@click="selectionFilter()"
class="text-weight-regular"
align="left"
:label="$t('Filter by selection')"
no-caps
/>
<QBtn
flat
icon="dangerous"
@click="selectionExclude()"
class="text-weight-regular"
align="left"
:label="$t('Exclude selection')"
no-caps
/>
<QBtn
flat
icon="filter_list_off"
@click="selectionRemoveFilter()"
class="text-weight-regular"
align="left"
:label="$t('Remove filter')"
no-caps
/>
<QBtn
flat
icon="filter_list_off"
@click="removeAllFilters()"
class="text-weight-regular"
align="left"
:label="$t('Remove all filters')"
no-caps
/>
<QBtn
flat
icon="file_copy"
@click="copyValue()"
class="text-weight-regular"
align="left"
:label="$t('Copy value')"
no-caps
/>
</QMenu>
</template>
<i18n>
es:
Filter by selection: Filtro por selección
Exclude selection: Excluir selección
Remove filter: Quitar filtro por selección
Remove all filters: Eliminar todos los filtros
Copy value: Copiar valor
</i18n>

View File

@ -43,7 +43,9 @@ const columnFilter = computed(() => $props.column?.columnFilter);
const updateEvent = { 'update:modelValue': addFilter }; const updateEvent = { 'update:modelValue': addFilter };
const enterEvent = { const enterEvent = {
'keyup.enter': () => addFilter(model.value), keyup: ({ key }) => {
if (key === 'Enter') addFilter(model.value);
},
remove: () => addFilter(null), remove: () => addFilter(null),
}; };
@ -110,7 +112,6 @@ const components = {
component: markRaw(VnCheckbox), component: markRaw(VnCheckbox),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit',
'toggle-indeterminate': true, 'toggle-indeterminate': true,
size: 'sm', size: 'sm',
}, },
@ -136,6 +137,9 @@ async function addFilter(value, name) {
value = value === '' ? undefined : value; value = value === '' ? undefined : value;
let field = columnFilter.value?.name ?? $props.column.name ?? name; let field = columnFilter.value?.name ?? $props.column.name ?? name;
delete arrayData.store?.userParams?.[field];
delete arrayData.store?.filter?.where?.[field];
if (columnFilter.value?.inWhere) { if (columnFilter.value?.inWhere) {
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field; if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;
return await arrayData.addFilterWhere({ [field]: value }); return await arrayData.addFilterWhere({ [field]: value });

View File

@ -33,6 +33,7 @@ import VnTableOrder from 'src/components/VnTable/VnOrder.vue';
import VnTableFilter from './VnTableFilter.vue'; import VnTableFilter from './VnTableFilter.vue';
import { getColAlign } from 'src/composables/getColAlign'; import { getColAlign } from 'src/composables/getColAlign';
import RightMenu from '../common/RightMenu.vue'; import RightMenu from '../common/RightMenu.vue';
import VnContextMenu from './VnContextMenu.vue';
import VnScroll from '../common/VnScroll.vue'; import VnScroll from '../common/VnScroll.vue';
import VnCheckboxMenu from '../common/VnCheckboxMenu.vue'; import VnCheckboxMenu from '../common/VnCheckboxMenu.vue';
import VnCheckbox from '../common/VnCheckbox.vue'; import VnCheckbox from '../common/VnCheckbox.vue';
@ -150,6 +151,10 @@ const $props = defineProps({
type: String, type: String,
default: 'vnTable', default: 'vnTable',
}, },
selectionFn: {
type: Function,
default: null,
},
}); });
const { t } = useI18n(); const { t } = useI18n();
@ -178,8 +183,9 @@ const app = inject('app');
const tableHeight = useTableHeight(); const tableHeight = useTableHeight();
const vnScrollRef = ref(null); const vnScrollRef = ref(null);
const editingRow = ref(null); const editingRow = ref();
const editingField = ref(null); const editingField = ref();
const contextMenuRef = ref({});
const isTableMode = computed(() => mode.value == TABLE_MODE); const isTableMode = computed(() => mode.value == TABLE_MODE);
const selectRegex = /select/; const selectRegex = /select/;
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
@ -216,6 +222,7 @@ onBeforeMount(() => {
onMounted(async () => { onMounted(async () => {
if ($props.isEditable) document.addEventListener('click', clickHandler); if ($props.isEditable) document.addEventListener('click', clickHandler);
document.addEventListener('contextmenu', contextMenuRef.value.handler);
mode.value = mode.value =
quasar.platform.is.mobile && !$props.disableOption?.card quasar.platform.is.mobile && !$props.disableOption?.card
? CARD_MODE ? CARD_MODE
@ -239,6 +246,7 @@ onMounted(async () => {
onUnmounted(async () => { onUnmounted(async () => {
if ($props.isEditable) document.removeEventListener('click', clickHandler); if ($props.isEditable) document.removeEventListener('click', clickHandler);
document.removeEventListener('contextmenu', {});
}); });
watch( watch(
@ -331,10 +339,10 @@ function stopEventPropagation(event) {
event.stopPropagation(); event.stopPropagation();
} }
function reload(params) { async function reload(params) {
selected.value = []; selected.value = [];
selectAll.value = false; selectAll.value = false;
CrudModelRef.value.reload(params); await CrudModelRef.value.reload(params);
} }
function columnName(col) { function columnName(col) {
@ -375,28 +383,34 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
} }
} }
function isEditableColumn(column) { function isEditableColumn(column, row) {
const isEditableCol = column?.isEditable ?? true; const isEditableCol =
typeof column?.isEditable == 'function'
? column?.isEditable(row)
: (column?.isEditable ?? true);
const isVisible = column?.visible ?? true; const isVisible = column?.visible ?? true;
const hasComponent = column?.component; const hasComponent = column?.component;
return $props.isEditable && isVisible && hasComponent && isEditableCol; return $props.isEditable && isVisible && hasComponent && isEditableCol;
} }
function hasEditableFormat(column) { function hasEditableFormat(column, row) {
if (isEditableColumn(column)) return 'editable-text'; if (isEditableColumn(column, row)) return 'editable-text';
} }
const clickHandler = async (event) => { const clickHandler = async (event) => {
const clickedElement = event.target.closest('td'); const el = event.target;
const isDateElement = event.target.closest('.q-date'); const clickedElement = el.closest('td');
const isTimeElement = event.target.closest('.q-time'); const isDateElement = el.closest('.q-date');
const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon'); const isTimeElement = el.closest('.q-time');
const isQSelectDropDown = el.closest('.q-select__dropdown-icon');
const isDialog = el.closest('.q-dialog');
if (isDateElement || isTimeElement || isQSelectDropDown) return; if (isDateElement || isTimeElement || isQSelectDropDown || isDialog) return;
if (clickedElement === null) { if (clickedElement === null) {
await destroyInput(editingRow.value, editingField.value); destroyInput(editingRow.value, editingField.value);
return; return;
} }
const rowIndex = clickedElement.getAttribute('data-row-index'); const rowIndex = clickedElement.getAttribute('data-row-index');
@ -406,20 +420,25 @@ const clickHandler = async (event) => {
if (editingRow.value !== null && editingField.value !== null) { if (editingRow.value !== null && editingField.value !== null) {
if (editingRow.value == rowIndex && editingField.value == colField) return; if (editingRow.value == rowIndex && editingField.value == colField) return;
await destroyInput(editingRow.value, editingField.value); destroyInput(editingRow.value, editingField.value);
} }
if (isEditableColumn(column)) { if (
await renderInput(Number(rowIndex), colField, clickedElement); isEditableColumn(
column,
CrudModelRef.value.formData[rowIndex ?? editingRow.value],
)
) {
renderInput(Number(rowIndex), colField, clickedElement);
} }
}; };
async function handleTabKey(event, rowIndex, colField) { function handleTabKey(event, rowIndex, colField) {
if (editingRow.value == rowIndex && editingField.value == colField) if (editingRow.value == rowIndex && editingField.value == colField)
await destroyInput(editingRow.value, editingField.value); destroyInput(editingRow.value, editingField.value);
const direction = event.shiftKey ? -1 : 1; const direction = event.shiftKey ? -1 : 1;
const { nextRowIndex, nextColumnName } = await handleTabNavigation( const { nextRowIndex, nextColumnName } = handleTabNavigation(
rowIndex, rowIndex,
colField, colField,
direction, direction,
@ -428,10 +447,10 @@ async function handleTabKey(event, rowIndex, colField) {
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return; if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
event.preventDefault(); event.preventDefault();
await renderInput(nextRowIndex, nextColumnName, null); renderInput(nextRowIndex, nextColumnName, null);
} }
async function renderInput(rowId, field, clickedElement) { function renderInput(rowId, field, clickedElement) {
editingField.value = field; editingField.value = field;
editingRow.value = rowId; editingRow.value = rowId;
@ -440,6 +459,7 @@ async function renderInput(rowId, field, clickedElement) {
const row = CrudModelRef.value.formData[rowId]; const row = CrudModelRef.value.formData[rowId];
const oldValue = CrudModelRef.value.formData[rowId][column?.name]; const oldValue = CrudModelRef.value.formData[rowId][column?.name];
if (column.disable) return;
if (!clickedElement) if (!clickedElement)
clickedElement = document.querySelector( clickedElement = document.querySelector(
`[data-row-index="${rowId}"][data-col-field="${field}"]`, `[data-row-index="${rowId}"][data-col-field="${field}"]`,
@ -468,18 +488,22 @@ async function renderInput(rowId, field, clickedElement) {
} else row[column.name] = value; } else row[column.name] = value;
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row); await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
}, },
keyup: async (event) => { keyup: (event) => {
if (event.key === 'Enter') if (event.key === 'Enter') {
await destroyInput(rowId, field, clickedElement); destroyInput(rowId, field, clickedElement);
event.stopPropagation();
}
}, },
keydown: async (event) => { keydown: (event) => {
column?.cellEvent?.['keydown']?.(event, row);
switch (event.key) { switch (event.key) {
case 'Tab': case 'Tab':
await handleTabKey(event, rowId, field); handleTabKey(event, rowId, field);
event.stopPropagation(); event.stopPropagation();
break; break;
case 'Escape': case 'Escape':
await destroyInput(rowId, field, clickedElement); destroyInput(rowId, field, clickedElement);
event.stopPropagation();
break; break;
default: default:
break; break;
@ -512,25 +536,32 @@ async function updateSelectValue(value, column, row, oldValue) {
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row); await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
} }
async function destroyInput(rowIndex, field, clickedElement) { function destroyInput(rowIndex, field, clickedElement) {
if (!clickedElement) if (!clickedElement)
clickedElement = document.querySelector( clickedElement = document.querySelector(
`[data-row-index="${rowIndex}"][data-col-field="${field}"]`, `[data-row-index="${rowIndex}"][data-col-field="${field}"]`,
); );
if (clickedElement) { if (clickedElement) {
await nextTick(); const column = $props.columns.find((col) => col.name === field);
render(null, clickedElement); if (typeof column?.beforeDestroy === 'function')
Array.from(clickedElement.childNodes).forEach((child) => { column.beforeDestroy(CrudModelRef.value.formData[rowIndex]);
child.style.visibility = 'visible';
child.style.position = ''; nextTick().then(() => {
render(null, clickedElement);
Array.from(clickedElement.childNodes).forEach((child) => {
child.style.visibility = 'visible';
child.style.position = '';
});
}); });
} }
if (editingRow.value !== rowIndex || editingField.value !== field) return; if (editingRow.value !== rowIndex || editingField.value !== field) return;
editingRow.value = null; editingRow.value = null;
editingField.value = null; editingField.value = null;
} }
async function handleTabNavigation(rowIndex, colName, direction) { function handleTabNavigation(rowIndex, colName, direction) {
const columns = $props.columns; const columns = $props.columns;
const totalColumns = columns.length; const totalColumns = columns.length;
let currentColumnIndex = columns.findIndex((col) => col.name === colName); let currentColumnIndex = columns.findIndex((col) => col.name === colName);
@ -542,7 +573,13 @@ async function handleTabNavigation(rowIndex, colName, direction) {
iterations++; iterations++;
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns; newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
if (isEditableColumn(columns[newColumnIndex])) break; if (
isEditableColumn(
columns[newColumnIndex],
CrudModelRef.value.formData[rowIndex],
)
)
break;
} while (iterations < totalColumns); } while (iterations < totalColumns);
if (iterations >= totalColumns + 1) return; if (iterations >= totalColumns + 1) return;
@ -648,7 +685,9 @@ const rowCtrlClickFunction = computed(() => {
}); });
const handleHeaderSelection = (evt, data) => { const handleHeaderSelection = (evt, data) => {
if (evt === 'updateSelected' && selectAll.value) { if (evt === 'updateSelected' && selectAll.value) {
selected.value = tableRef.value.rows; const fn = $props.selectionFn;
const rows = tableRef.value.rows;
selected.value = fn ? fn(rows) : rows;
} else if (evt === 'selectAll') { } else if (evt === 'selectAll') {
selected.value = data; selected.value = data;
} else { } else {
@ -694,7 +733,6 @@ const handleHeaderSelection = (evt, data) => {
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="isTableMode" :disable-infinite-scroll="isTableMode"
:before-save-fn="removeTextValue" :before-save-fn="removeTextValue"
@save-changes="reload"
:has-sub-toolbar="$props.hasSubToolbar ?? isEditable" :has-sub-toolbar="$props.hasSubToolbar ?? isEditable"
:auto-load="hasParams || $attrs['auto-load']" :auto-load="hasParams || $attrs['auto-load']"
> >
@ -722,7 +760,15 @@ const handleHeaderSelection = (evt, data) => {
:virtual-scroll="isTableMode" :virtual-scroll="isTableMode"
@virtual-scroll="onVirtualScroll" @virtual-scroll="onVirtualScroll"
@row-click="(event, row) => handleRowClick(event, row)" @row-click="(event, row) => handleRowClick(event, row)"
@update:selected="emit('update:selected', $event)" @update:selected="
(evt) => {
if ($props.selectionFn) selected = $props.selectionFn(evt);
emit(
'update:selected',
selectionFn ? selectionFn(selected) : selected,
);
}
"
@selection="(details) => handleSelection(details, rows)" @selection="(details) => handleSelection(details, rows)"
:hide-selected-banner="true" :hide-selected-banner="true"
:data-cy :data-cy
@ -835,6 +881,7 @@ const handleHeaderSelection = (evt, data) => {
]" ]"
:data-row-index="rowIndex" :data-row-index="rowIndex"
:data-col-field="col?.name" :data-col-field="col?.name"
:data-col-value="row?.[col?.name]"
> >
<div <div
class="no-padding no-margin" class="no-padding no-margin"
@ -859,19 +906,19 @@ const handleHeaderSelection = (evt, data) => {
: getToggleIcon(row[col?.name]) : getToggleIcon(row[col?.name])
" "
style="color: var(--vn-text-color)" style="color: var(--vn-text-color)"
:class="hasEditableFormat(col)" :class="hasEditableFormat(col, row)"
size="14px" size="14px"
/> />
<QIcon <QIcon
v-else-if="col?.component === 'checkbox'" v-else-if="col?.component === 'checkbox'"
:name="getCheckboxIcon(row[col?.name])" :name="getCheckboxIcon(row[col?.name])"
style="color: var(--vn-text-color)" style="color: var(--vn-text-color)"
:class="hasEditableFormat(col)" :class="hasEditableFormat(col, row)"
size="14px" size="14px"
/> />
<span <span
v-else v-else
:class="hasEditableFormat(col)" :class="hasEditableFormat(col, row)"
:style=" :style="
typeof col?.style == 'function' typeof col?.style == 'function'
? col.style(row) ? col.style(row)
@ -897,7 +944,11 @@ const handleHeaderSelection = (evt, data) => {
v-for="(btn, index) of col.actions" v-for="(btn, index) of col.actions"
v-show="btn.show ? btn.show(row) : true" v-show="btn.show ? btn.show(row) : true"
:key="index" :key="index"
:title="btn.title" :title="
typeof btn.title === 'function'
? btn.title(row)
: btn.title
"
:icon="btn.icon" :icon="btn.icon"
class="q-pa-xs" class="q-pa-xs"
flat flat
@ -1145,6 +1196,7 @@ const handleHeaderSelection = (evt, data) => {
</template> </template>
</FormModelPopup> </FormModelPopup>
</QDialog> </QDialog>
<VnContextMenu ref="contextMenuRef" v-model="arrayData" />
<VnScroll <VnScroll
ref="vnScrollRef" ref="vnScrollRef"
v-if="isTableMode" v-if="isTableMode"
@ -1205,7 +1257,7 @@ es:
} }
.bg-header { .bg-header {
background-color: var(--vn-accent-color); background-color: var(--vn-section-color);
color: var(--vn-text-color); color: var(--vn-text-color);
} }

View File

@ -77,7 +77,12 @@ function columnName(col) {
<template #tags="{ tag, formatFn, getLocale }"> <template #tags="{ tag, formatFn, getLocale }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ getLocale(`${tag.label}`) }}: </strong> <strong>{{ getLocale(`${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span> <span
:class="{
'text-decoration-line-through': typeof chip === 'object',
}"
>{{ formatFn(tag) }}</span
>
</div> </div>
</template> </template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">

View File

@ -0,0 +1,43 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
describe('VnAccountNumber', () => {
let wrapper;
let input;
let vnInput;
let spyShort;
beforeEach(() => {
wrapper = createWrapper(VnAccountNumber);
wrapper = wrapper.wrapper;
input = wrapper.find('input');
vnInput = wrapper.findComponent({ name: 'VnInput' });
spyShort = vi.spyOn(wrapper.vm, 'useAccountShortToStandard');
});
it('should filter out non-numeric characters on input event', async () => {
await input.setValue('abc123.45!@#');
const emitted = wrapper.emitted('update:modelValue');
expect(emitted.pop()[0]).toBe('123.45');
expect(spyShort).not.toHaveBeenCalled();
});
it('should apply conversion on blur when valid short value is provided', async () => {
await input.setValue('123.45');
await vnInput.trigger('blur');
const emitted = wrapper.emitted('update:modelValue');
expect(emitted.pop()[0]).toBe('1230000045');
expect(spyShort).toHaveBeenCalled();
});
it('should not change value for invalid input values', async () => {
await input.setValue('123');
await vnInput.trigger('blur');
const emitted = wrapper.emitted('update:modelValue');
expect(emitted.pop()[0]).toBe('123');
expect(spyShort).toHaveBeenCalled();
});
});

View File

@ -36,8 +36,6 @@ const validate = async () => {
isLoading.value = true; isLoading.value = true;
await props.submitFn(newPassword, oldPassword); await props.submitFn(newPassword, oldPassword);
emit('onSubmit'); emit('onSubmit');
} catch (e) {
notify('errors.writeRequest', 'negative');
} finally { } finally {
changePassDialog.value.hide(); changePassDialog.value.hide();
isLoading.value = false; isLoading.value = false;

View File

@ -17,8 +17,6 @@ const $props = defineProps({
}, },
}); });
const emit = defineEmits(['blur']);
const componentArray = computed(() => { const componentArray = computed(() => {
if (typeof $props.prop === 'object') return [$props.prop]; if (typeof $props.prop === 'object') return [$props.prop];
return $props.prop; return $props.prop;
@ -57,7 +55,6 @@ function toValueAttrs(attrs) {
v-bind="mix(toComponent).attrs" v-bind="mix(toComponent).attrs"
v-on="mix(toComponent).event ?? {}" v-on="mix(toComponent).event ?? {}"
v-model="model" v-model="model"
@blur="emit('blur')"
/> />
</span> </span>
</template> </template>

View File

@ -7,7 +7,6 @@ import axios from 'axios';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
import { downloadFile } from 'src/composables/downloadFile';
import VnImg from 'components/ui/VnImg.vue'; import VnImg from 'components/ui/VnImg.vue';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnDms from 'src/components/common/VnDms.vue'; import VnDms from 'src/components/common/VnDms.vue';

View File

@ -6,13 +6,7 @@ import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs(); const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs); const { isRequired, requiredFieldRule } = useRequired($attrs);
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits([ const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
'update:modelValue',
'update:options',
'keyup.enter',
'remove',
'blur',
]);
const $props = defineProps({ const $props = defineProps({
modelValue: { modelValue: {
@ -136,8 +130,6 @@ const handleUppercase = () => {
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs }"
:type="$attrs.type" :type="$attrs.type"
:class="{ required: isRequired }" :class="{ required: isRequired }"
@keyup.enter="emit('keyup.enter')"
@blur="emit('blur')"
@keydown="handleKeydown" @keydown="handleKeydown"
:clearable="false" :clearable="false"
:rules="mixinRules" :rules="mixinRules"

View File

@ -51,6 +51,7 @@ const validateAndCleanInput = (value) => {
const manageDate = (date) => { const manageDate = (date) => {
inputValue.value = date.split('/').reverse().join('/'); inputValue.value = date.split('/').reverse().join('/');
formatDate();
isPopupOpen.value = false; isPopupOpen.value = false;
}; };
@ -170,7 +171,7 @@ const handleEnter = (event) => {
:input-style="{ color: textColor }" :input-style="{ color: textColor }"
@click="isPopupOpen = !isPopupOpen" @click="isPopupOpen = !isPopupOpen"
@keydown="isPopupOpen = false" @keydown="isPopupOpen = false"
@blur="formatDate" @focusout="formatDate"
@keydown.enter.prevent="handleEnter" @keydown.enter.prevent="handleEnter"
hide-bottom-space hide-bottom-space
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'" :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"

View File

@ -0,0 +1,184 @@
<script setup>
import { onMounted, watch, computed, ref, useAttrs } from 'vue';
import { date } from 'quasar';
import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const $props = defineProps({
isOutlined: {
type: Boolean,
default: false,
},
isPopupOpen: {
type: Boolean,
default: false,
},
multiple: {
type: Boolean,
default: true,
},
});
const model = defineModel({
type: [String, Date, Array],
default: null,
});
const vnInputDateRef = ref(null);
const dateFormat = 'DD/MM/YYYY';
const isPopupOpen = ref();
const hover = ref();
const mask = ref();
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const formattedDate = computed({
get() {
if (!model.value) return model.value;
if ($props.multiple) {
return model.value
.map((d) => date.formatDate(new Date(d), dateFormat))
.join(', ');
}
return date.formatDate(new Date(model.value), dateFormat);
},
set(value) {
if (value == model.value) return;
if ($props.multiple) return; // No permitir edición manual en modo múltiple
let newDate;
if (value) {
// parse input
if (value.includes('/') && value.length >= 10) {
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
value = date.formatDate(
new Date(value).toISOString(),
'YYYY-MM-DDTHH:mm:ss.SSSZ',
);
}
const [year, month, day] = value.split('-').map((e) => parseInt(e));
newDate = new Date(year, month - 1, day);
if (model.value && !$props.multiple) {
const orgDate =
model.value instanceof Date ? model.value : new Date(model.value);
newDate.setHours(
orgDate.getHours(),
orgDate.getMinutes(),
orgDate.getSeconds(),
orgDate.getMilliseconds(),
);
}
}
if (!isNaN(newDate)) model.value = newDate.toISOString();
},
});
const popupDate = computed(() => {
if (!model.value) return model.value;
if ($props.multiple) {
return model.value.map((d) => date.formatDate(new Date(d), 'YYYY/MM/DD'));
}
return date.formatDate(new Date(model.value), 'YYYY/MM/DD');
});
onMounted(() => {
// fix quasar bug
mask.value = '##/##/####';
if ($props.multiple && !model.value) {
model.value = [];
}
});
watch(
() => model.value,
(val) => (formattedDate.value = val),
{ immediate: true },
);
const styleAttrs = computed(() => {
return $props.isOutlined
? {
dense: true,
outlined: true,
rounded: true,
}
: {};
});
const manageDate = (dates) => {
if ($props.multiple) {
model.value = dates.map((d) => new Date(d).toISOString());
} else {
formattedDate.value = dates;
}
if ($props.isPopupOpen) isPopupOpen.value = false;
};
</script>
<template>
<div @mouseover="hover = true" @mouseleave="hover = false">
<QInput
ref="vnInputDateRef"
v-model="formattedDate"
class="vn-input-date"
:mask="$props.multiple ? undefined : mask"
placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: isRequired }"
:rules="mixinRules"
:clearable="false"
@click="isPopupOpen = !isPopupOpen"
@keydown="isPopupOpen = false"
hide-bottom-space
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
>
<template #append>
<QIcon
name="close"
size="xs"
v-if="
($attrs.clearable == undefined || $attrs.clearable) &&
hover &&
model &&
!$attrs.disable
"
@click="
vnInputDateRef.focus();
model = null;
isPopupOpen = false;
"
/>
</template>
<QMenu
v-if="$q.screen.gt.xs"
transition-show="scale"
transition-hide="scale"
v-model="isPopupOpen"
anchor="bottom left"
self="top start"
:no-focus="true"
:no-parent-event="true"
>
<VnDate
v-model="popupDate"
@update:model-value="manageDate"
:multiple="multiple"
/>
</QMenu>
<QDialog v-else v-model="isPopupOpen">
<VnDate
v-model="popupDate"
@update:model-value="manageDate"
:multiple="multiple"
/>
</QDialog>
</QInput>
</div>
</template>
<i18n>
es:
Open date: Abrir fecha
</i18n>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import { date } from 'quasar'; import { date } from 'quasar';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
@ -21,7 +21,6 @@ const stateStore = useStateStore();
const validationsStore = useValidator(); const validationsStore = useValidator();
const { models } = validationsStore; const { models } = validationsStore;
const route = useRoute(); const route = useRoute();
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
model: { model: {
@ -273,7 +272,7 @@ onUnmounted(() => {
:data-key :data-key
:url="dataKey + 's'" :url="dataKey + 's'"
:user-filter="filter" :user-filter="filter"
:filter="{ where: { and: [{ originFk: route.params.id }] } }" :user-params="{ originFk: route.params.id }"
:skeleton="false" :skeleton="false"
auto-load auto-load
@on-fetch="setLogTree" @on-fetch="setLogTree"

View File

@ -124,6 +124,7 @@ const {
} = toRefs($props); } = toRefs($props);
const myOptions = ref([]); const myOptions = ref([]);
const myOptionsOriginal = ref([]); const myOptionsOriginal = ref([]);
const myOptionsMap = ref(new Map());
const vnSelectRef = ref(); const vnSelectRef = ref();
const lastVal = ref(); const lastVal = ref();
const noOneText = t('globals.noOne'); const noOneText = t('globals.noOne');
@ -140,7 +141,7 @@ const styleAttrs = computed(() => {
} }
: {}; : {};
}); });
const isLoading = ref(false); const hasFocus = ref(false);
const useURL = computed(() => $props.url); const useURL = computed(() => $props.url);
const value = computed({ const value = computed({
get() { get() {
@ -166,6 +167,10 @@ const computedSortBy = computed(() => {
return $props.sortBy || $props.optionLabel + ' ASC'; return $props.sortBy || $props.optionLabel + ' ASC';
}); });
const valueIsObject = computed(
() => modelValue.value && typeof modelValue.value == 'object',
);
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val); const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
watch(options, (newValue) => { watch(options, (newValue) => {
@ -173,12 +178,22 @@ watch(options, (newValue) => {
}); });
watch(modelValue, async (newValue) => { watch(modelValue, async (newValue) => {
if (newValue?.neq) newValue = newValue.neq;
if (!myOptions?.value?.some((option) => option[optionValue.value] == newValue)) if (!myOptions?.value?.some((option) => option[optionValue.value] == newValue))
await fetchFilter(newValue); await fetchFilter(newValue);
if ($props.noOne) myOptions.value.unshift(noOneOpt.value); if ($props.noOne) myOptions.value.unshift(noOneOpt.value);
}); });
watch(
() => myOptionsOriginal.value,
(newValue) => {
for (const item of newValue) {
myOptionsMap.value.set(item[optionValue.value], item);
}
},
);
onMounted(() => { onMounted(() => {
setOptions(options.value); setOptions(options.value);
if (useURL.value && $props.modelValue && !findKeyInOptions()) if (useURL.value && $props.modelValue && !findKeyInOptions())
@ -187,7 +202,7 @@ onMounted(() => {
}); });
const someIsLoading = computed( const someIsLoading = computed(
() => (isLoading.value || !!arrayData?.isLoading?.value) && !isMenuOpened.value, () => !!arrayData?.isLoading?.value && !isMenuOpened.value,
); );
function findKeyInOptions() { function findKeyInOptions() {
if (!$props.options) return; if (!$props.options) return;
@ -224,6 +239,9 @@ function filter(val, options) {
async function fetchFilter(val) { async function fetchFilter(val) {
if (!$props.url) return; if (!$props.url) return;
if (val && typeof val == 'object') {
val = val.neq;
}
const { fields, include, limit } = $props; const { fields, include, limit } = $props;
const sortBy = computedSortBy.value; const sortBy = computedSortBy.value;
@ -298,13 +316,11 @@ async function onScroll({ to, direction, from, index }) {
if (from === 0 && index === 0) return; if (from === 0 && index === 0) return;
if (!useURL.value && !$props.fetchRef) return; if (!useURL.value && !$props.fetchRef) return;
if (direction === 'decrease') return; if (direction === 'decrease') return;
if (to === lastIndex && arrayData.store.hasMoreData && !isLoading.value) { if (to === lastIndex && arrayData.store.hasMoreData) {
isLoading.value = true;
await arrayData.loadMore(); await arrayData.loadMore();
setOptions(arrayData.store.data); setOptions(arrayData.store.data);
vnSelectRef.value.scrollTo(lastIndex); vnSelectRef.value.scrollTo(lastIndex);
await nextTick(); await nextTick();
isLoading.value = false;
} }
} }
@ -347,22 +363,30 @@ function getCaption(opt) {
if (optionCaption.value === false) return; if (optionCaption.value === false) return;
return opt[optionCaption.value] || opt[optionValue.value]; return opt[optionCaption.value] || opt[optionValue.value];
} }
function getOptionLabel(property) {
if (!myOptionsMap.value.size) return;
let value = modelValue.value;
if (property) {
value = modelValue.value[property];
}
return myOptionsMap.value.get(value)?.[optionLabel.value];
}
</script> </script>
<template> <template>
<QSelect <QSelect
ref="vnSelectRef"
v-model="value" v-model="value"
:options="myOptions" :options="myOptions"
:option-label="optionLabel" :option-label="optionLabel"
:option-value="optionValue" :option-value="optionValue"
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs, hideSelected: hasFocus }"
@filter="filterHandler" @filter="filterHandler"
:emit-value="nullishToTrue($attrs['emit-value'])" :emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])" :map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])" :use-input="hasFocus || !value"
:hide-selected="nullishToTrue($attrs['hide-selected'])" :fill-input="false"
:fill-input="nullishToTrue($attrs['fill-input'])"
ref="vnSelectRef"
lazy-rules lazy-rules
:class="{ required: isRequired }" :class="{ required: isRequired }"
:rules="mixinRules" :rules="mixinRules"
@ -372,10 +396,20 @@ function getCaption(opt) {
:loading="someIsLoading" :loading="someIsLoading"
@virtual-scroll="onScroll" @virtual-scroll="onScroll"
@popup-hide="isMenuOpened = false" @popup-hide="isMenuOpened = false"
@popup-show="isMenuOpened = true" @popup-show="
async () => {
isMenuOpened = true;
hasFocus = true;
await $nextTick();
vnSelectRef?.$el?.querySelector('input')?.focus();
}
"
@keydown="handleKeyDown" @keydown="handleKeyDown"
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'" :data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
:data-url="url" :data-url="url"
@blur="hasFocus = false"
@update:model-value="() => vnSelectRef.blur()"
:is-required="false"
> >
<template #append> <template #append>
<QIcon <QIcon
@ -411,7 +445,12 @@ function getCaption(opt) {
</template> </template>
<template #option="{ opt, itemProps }"> <template #option="{ opt, itemProps }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
<QItemSection v-if="typeof opt !== 'object'"> {{ opt }}</QItemSection> <QItemSection v-if="typeof optionLabel === 'function'">{{
optionLabel(opt)
}}</QItemSection>
<QItemSection v-else-if="typeof opt !== 'object'">
{{ opt }}</QItemSection
>
<QItemSection v-else-if="opt[optionValue] == opt[optionLabel]"> <QItemSection v-else-if="opt[optionValue] == opt[optionLabel]">
<QItemLabel>{{ opt[optionLabel] }}</QItemLabel> <QItemLabel>{{ opt[optionLabel] }}</QItemLabel>
</QItemSection> </QItemSection>
@ -425,6 +464,17 @@ function getCaption(opt) {
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
<template #selected v-if="valueIsObject && nullishToTrue($attrs['emit-value'])">
<span class="nowrap">
<span
class="text-strike"
v-if="modelValue?.neq"
v-text="getOptionLabel('neq')"
:title="getOptionLabel('neq')"
/>
<span v-else>{{ JSON.stringify(modelValue) }}</span>
</span>
</template>
</QSelect> </QSelect>
</template> </template>
@ -432,4 +482,12 @@ function getCaption(opt) {
.q-field--outlined { .q-field--outlined {
max-width: 100%; max-width: 100%;
} }
.q-field__native {
@extend .nowrap;
}
.nowrap {
display: flex;
flex-wrap: nowrap !important;
}
</style> </style>

View File

@ -1,22 +1,20 @@
<script setup> <script setup>
import { ref, useTemplateRef } from 'vue'; import { computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
import VnSelectDialog from './VnSelectDialog.vue'; import VnSelectDialog from './VnSelectDialog.vue';
import CreateNewExpenseForm from '../CreateNewExpenseForm.vue'; import CreateNewExpenseForm from '../CreateNewExpenseForm.vue';
import FetchData from '../FetchData.vue'; import { useArrayData } from 'src/composables/useArrayData';
const model = defineModel({ type: [String, Number, Object] }); const model = defineModel({ type: [String, Number, Object] });
const { t } = useI18n(); const { t } = useI18n();
const expenses = ref([]); const arrayData = useArrayData('expenses', { url: 'Expenses' });
const selectDialogRef = useTemplateRef('selectDialogRef'); const expenses = computed(() => arrayData.store.data);
async function autocompleteExpense(evt) { onMounted(async () => await arrayData.fetch({}));
const val = evt.target.value;
if (!val || isNaN(val)) return; async function updateModel(evt) {
const lookup = expenses.value.find(({ id }) => id == useAccountShortToStandard(val)); await arrayData.fetch({});
if (selectDialogRef.value) model.value = evt.id;
selectDialogRef.value.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
} }
</script> </script>
<template> <template>
@ -30,18 +28,11 @@ async function autocompleteExpense(evt) {
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
:acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]" :acls="[{ model: 'Expense', props: '*', accessType: 'WRITE' }]"
@keydown.tab.prevent="autocompleteExpense"
> >
<template #form> <template #form>
<CreateNewExpenseForm @on-data-saved="$refs.expensesRef.fetch()" /> <CreateNewExpenseForm @on-data-saved="updateModel" />
</template> </template>
</VnSelectDialog> </VnSelectDialog>
<FetchData
ref="expensesRef"
url="Expenses"
auto-load
@on-fetch="(data) => (expenses = data)"
/>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -162,7 +162,6 @@ async function fetch() {
align-items: start; align-items: start;
.label { .label {
color: var(--vn-label-color); color: var(--vn-label-color);
width: 9em;
overflow: hidden; overflow: hidden;
white-space: wrap; white-space: wrap;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -236,10 +236,6 @@ const toModule = computed(() => {
.label { .label {
color: var(--vn-label-color); color: var(--vn-label-color);
font-size: 14px; font-size: 14px;
&:not(:has(a))::after {
content: ':';
}
} }
&.ellipsis > .value { &.ellipsis > .value {
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -186,6 +186,7 @@ async function remove(key) {
function formatValue(value) { function formatValue(value) {
if (typeof value === 'boolean') return value ? t('Yes') : t('No'); if (typeof value === 'boolean') return value ? t('Yes') : t('No');
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value); if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
if (value && typeof value === 'object') return '';
return `"${value}"`; return `"${value}"`;
} }

View File

@ -1,8 +1,9 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import noImage from '/no-user.png'; import noUser from '/no-user.png';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { getDarkSuffix } from 'src/composables/getDarkSuffix';
const $props = defineProps({ const $props = defineProps({
storage: { storage: {
@ -43,7 +44,7 @@ const getUrl = (zoom = false) => {
return `/api/${$props.storage}/${$props.id}/downloadFile?access_token=${token}`; return `/api/${$props.storage}/${$props.id}/downloadFile?access_token=${token}`;
return isEmployee return isEmployee
? `/api/${$props.storage}/${$props.collection}/${curResolution}/${$props.id}/download?access_token=${token}&${timeStamp.value}` ? `/api/${$props.storage}/${$props.collection}/${curResolution}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
: noImage; : noUser;
}; };
const reload = () => { const reload = () => {
timeStamp.value = `timestamp=${Date.now()}`; timeStamp.value = `timestamp=${Date.now()}`;
@ -60,6 +61,7 @@ defineExpose({
v-bind="$attrs" v-bind="$attrs"
@click.stop="show = $props.zoom" @click.stop="show = $props.zoom"
spinner-color="primary" spinner-color="primary"
:error-src="`/no_image${getDarkSuffix()}.png`"
/> />
<QDialog v-if="$props.zoom" v-model="show"> <QDialog v-if="$props.zoom" v-model="show">
<QImg <QImg

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { Dark } from 'quasar';
import { computed } from 'vue'; import { computed } from 'vue';
import { getDarkSuffix } from 'src/composables/getDarkSuffix';
const $props = defineProps({ const $props = defineProps({
logo: { logo: {
@ -12,7 +12,7 @@ const $props = defineProps({
const src = computed({ const src = computed({
get() { get() {
return new URL( return new URL(
`../../assets/${$props.logo}${Dark.isActive ? '_dark' : ''}.svg`, `../../assets/${$props.logo}${getDarkSuffix()}.svg`,
import.meta.url, import.meta.url,
).href; ).href;
}, },

View File

@ -42,12 +42,10 @@ const val = computed(() => $props.value);
<div v-if="label || $slots.label" class="label"> <div v-if="label || $slots.label" class="label">
<slot name="label"> <slot name="label">
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip> <QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
<span style="color: var(--vn-label-color)"> <span style="color: var(--vn-label-color)"> {{ label }}: </span>
{{ label }}
</span>
</slot> </slot>
</div> </div>
<div class="value" v-if="value || $slots.value"> <div class="value">
<slot name="value"> <slot name="value">
<span :title="value" style="text-overflow: ellipsis"> <span :title="value" style="text-overflow: ellipsis">
{{ dash ? dashIfEmpty(value) : value }} {{ dash ? dashIfEmpty(value) : value }}
@ -75,21 +73,13 @@ const val = computed(() => $props.value);
visibility: visible; visibility: visible;
cursor: pointer; cursor: pointer;
} }
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
.copy { .copy {
visibility: hidden; visibility: hidden;
} }
.info { .info {
margin-left: 5px; margin-left: 5px;
} }
} }
:deep(.q-checkbox.disabled) { :deep(.q-checkbox.disabled) {
opacity: 1 !important; opacity: 1 !important;
} }

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { toPercentage } from 'filters/index'; import { toCurrency, toPercentage } from 'filters/index';
import { computed } from 'vue'; import { computed } from 'vue';
@ -8,6 +8,10 @@ const props = defineProps({
type: Number, type: Number,
required: true, required: true,
}, },
format: {
type: String,
default: 'percentage', // 'currency'
},
}); });
const valueClass = computed(() => const valueClass = computed(() =>
@ -21,7 +25,10 @@ const formattedValue = computed(() => props.value);
<template> <template>
<span :class="valueClass"> <span :class="valueClass">
<QIcon :name="iconName" size="sm" class="value-icon" /> <QIcon :name="iconName" size="sm" class="value-icon" />
{{ toPercentage(formattedValue) }} <span v-if="$props.format === 'percentage'">{{
toPercentage(formattedValue)
}}</span>
<span v-if="$props.format === 'currency'">{{ toCurrency(formattedValue) }}</span>
</span> </span>
</template> </template>

View File

@ -9,8 +9,6 @@ export function getColAlign(col) {
case 'number': case 'number':
align = 'right'; align = 'right';
break; break;
case 'time':
case 'date':
case 'checkbox': case 'checkbox':
align = 'center'; align = 'center';
break; break;

View File

@ -0,0 +1,4 @@
import { Dark } from 'quasar';
export function getDarkSuffix() {
return Dark.isActive ? '_dark' : '';
}

View File

@ -313,6 +313,7 @@ export function useArrayData(key, userOptions) {
const { params, limit } = getCurrentFilter(); const { params, limit } = getCurrentFilter();
store.currentFilter = JSON.parse(JSON.stringify(params)); store.currentFilter = JSON.parse(JSON.stringify(params));
delete store.currentFilter.filter.include; delete store.currentFilter.filter.include;
delete store.currentFilter.filter.fields;
store.currentFilter.filter = JSON.stringify(store.currentFilter.filter); store.currentFilter.filter = JSON.stringify(store.currentFilter.filter);
return { params, limit }; return { params, limit };
} }

View File

@ -16,6 +16,8 @@ body.body--light {
--vn-black-text-color: black; --vn-black-text-color: black;
--vn-text-color-contrast: white; --vn-text-color-contrast: white;
--vn-link-color: #1e90ff; --vn-link-color: #1e90ff;
--vn-input-underline-color: #bdbdbd;
--vn-input-icons-color: #797979;
background-color: var(--vn-page-color); background-color: var(--vn-page-color);
@ -29,7 +31,7 @@ body.body--light {
body.body--dark { body.body--dark {
--vn-header-color: #5d5d5d; --vn-header-color: #5d5d5d;
--vn-page-color: #222; --vn-page-color: #222;
--vn-section-color: #3d3d3d; --vn-section-color: #3c3b3b;
--vn-section-hover-color: #747474; --vn-section-hover-color: #747474;
--vn-text-color: white; --vn-text-color: white;
--vn-label-color: #a8a8a8; --vn-label-color: #a8a8a8;
@ -40,6 +42,8 @@ body.body--dark {
--vn-black-text-color: black; --vn-black-text-color: black;
--vn-text-color-contrast: black; --vn-text-color-contrast: black;
--vn-link-color: #66bfff; --vn-link-color: #66bfff;
--vn-input-underline-color: #545353;
--vn-input-icons-color: #888787;
background-color: var(--vn-page-color); background-color: var(--vn-page-color);
@ -155,7 +159,6 @@ select:-webkit-autofill {
cursor: pointer; cursor: pointer;
} }
/* Estilo para el asterisco en campos requeridos */
.q-field.required .q-field__label:after { .q-field.required .q-field__label:after {
content: ' *'; content: ' *';
} }
@ -290,6 +293,18 @@ input::-webkit-inner-spin-button {
.expand { .expand {
max-width: 400px; max-width: 400px;
} }
th {
border-bottom: 1px solid var(--vn-page-color) !important;
}
td {
border-color: var(--vn-page-color);
}
div.q-field__append.q-field__marginal {
color: var(--vn-input-icons-color) !important;
}
.q-field__control:before {
border-color: var(--vn-input-underline-color) !important;
}
} }
.edit-photo-btn { .edit-photo-btn {

View File

@ -352,6 +352,7 @@ globals:
vehicle: Vehicle vehicle: Vehicle
entryPreAccount: Pre-account entryPreAccount: Pre-account
management: Worker management management: Worker management
assignedInvoices: Assigned Invoices
unsavedPopup: unsavedPopup:
title: Unsaved changes will be lost title: Unsaved changes will be lost
subtitle: Are you sure exit without saving? subtitle: Are you sure exit without saving?
@ -512,26 +513,6 @@ entry:
isRaid: Raid isRaid: Raid
invoiceNumber: Invoice invoiceNumber: Invoice
reference: Ref/Alb/Guide 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: ticket:
params: params:
ticketFk: Ticket ID ticketFk: Ticket ID
@ -897,6 +878,7 @@ components:
minPrice: Min. Price minPrice: Min. Price
itemFk: Item id itemFk: Item id
dated: Date dated: Date
date: Date
userPanel: userPanel:
copyToken: Token copied to clipboard copyToken: Token copied to clipboard
settings: Settings settings: Settings

View File

@ -355,6 +355,7 @@ globals:
vehicle: Vehículo vehicle: Vehículo
entryPreAccount: Precontabilizar entryPreAccount: Precontabilizar
management: Gestión de trabajadores management: Gestión de trabajadores
assignedInvoices: Facturas vinculadas
unsavedPopup: unsavedPopup:
title: Los cambios que no haya guardado se perderán title: Los cambios que no haya guardado se perderán
subtitle: ¿Seguro que quiere salir sin guardar? subtitle: ¿Seguro que quiere salir sin guardar?
@ -981,6 +982,7 @@ components:
minPrice: Precio mínimo minPrice: Precio mínimo
itemFk: Id item itemFk: Id item
dated: Fecha dated: Fecha
date: Fecha
userPanel: userPanel:
copyToken: Token copiado al portapapeles copyToken: Token copiado al portapapeles
settings: Configuración settings: Configuración

View File

@ -7,6 +7,8 @@ import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile'; import { tMobile } from 'composables/tMobile';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import axios from 'axios';
import { useArrayData } from 'composables/useArrayData';
const route = useRoute(); const route = useRoute();
@ -19,6 +21,7 @@ const claimResponsibles = ref([]);
const claimRedeliveries = ref([]); const claimRedeliveries = ref([]);
const selected = ref([]); const selected = ref([]);
const saveButtonRef = ref(); const saveButtonRef = ref();
const arrayData = useArrayData('Claim');
const developmentsFilter = computed(() => { const developmentsFilter = computed(() => {
return { return {
@ -105,6 +108,32 @@ const columns = computed(() => [
align: 'left', align: 'left',
}, },
]); ]);
const handleWorker = async (row) => {
const { claimResponsibleFk } = row;
if (!claimResponsibleFk) {
row.workerFk = null;
return;
}
const commercialResponsible = claimResponsibles?.value?.find(
(responsible) => responsible.code === 'com',
);
const claim = arrayData.store.data;
if (claimResponsibleFk === commercialResponsible?.id) {
row.workerFk = claim.workerFk;
return;
}
const { data } = await axios.get(
`ClaimDevelopments/${claim.ticketFk}/getResponsible/${claimResponsibleFk}`,
);
row.workerFk = data?.userFk ?? null;
};
</script> </script>
<template> <template>
<FetchData <FetchData
@ -166,6 +195,20 @@ const columns = computed(() => [
input-debounce="0" input-debounce="0"
hide-selected hide-selected
/> />
<VnSelect
v-else-if="col.name == 'claimResponsible'"
v-model="row[col.model]"
:url="col.url"
:where="col.where"
:sort-by="col.sortBy"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
@update:modelValue="handleWorker(row)"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
/>
<VnSelect <VnSelect
v-else v-else
v-model="row[col.model]" v-model="row[col.model]"

View File

@ -2,14 +2,20 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useQuasar } from 'quasar';
import { toCurrency, toDateHourMin } from 'src/filters'; import { toCurrency, toDateHourMin } from 'src/filters';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import VnRow from 'components/ui/VnRow.vue';
import axios from 'axios';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const quasar = useQuasar();
const tableRef = ref(); const tableRef = ref();
@ -45,26 +51,45 @@ const columns = computed(() => [
align: 'right', align: 'right',
field: 'rating', field: 'rating',
label: t('customer.summary.rating'), label: t('customer.summary.rating'),
name: 'rating', name: 'rating'
create: true,
columnCreate: {
component: 'number',
autofocus: true,
},
}, },
{ {
align: 'right', align: 'right',
field: 'recommendedCredit', field: 'recommendedCredit',
format: ({ recommendedCredit }) => toCurrency(recommendedCredit), format: ({ recommendedCredit }) => toCurrency(recommendedCredit),
label: t('customer.summary.recommendCredit'), label: t('customer.summary.recommendCredit'),
name: 'recommendedCredit', name: 'recommendedCredit'
create: true,
columnCreate: {
component: 'number',
autofocus: true,
},
}, },
]); ]);
const defaultInitialData = {
rating: null,
recommendedCredit: null
};
const createRating = async (data) => {
await axios.post(`Clients/${route.params.id}/setRating`, data);
tableRef.value?.reload();
};
const handleSave = async (data) => {
if (data.rating || data.recommendedCredit) {
await createRating(data);
return;
}
quasar.dialog({
component: VnConfirm,
componentProps: {
title: t('terminationTitle'),
message: t('terminationMessage'),
},
})
.onOk(async () => {
await createRating({ rating: 0, recommendedCredit: 0 });
});
};
</script> </script>
<template> <template>
@ -72,8 +97,7 @@ const columns = computed(() => [
ref="tableRef" ref="tableRef"
data-key="ClientInformas" data-key="ClientInformas"
url="ClientInformas" url="ClientInformas"
:filter="filter" :user-filter="filter"
:order="['created DESC']"
:columns="columns" :columns="columns"
:right-search="false" :right-search="false"
:is-editable="false" :is-editable="false"
@ -82,22 +106,43 @@ const columns = computed(() => [
:disable-option="{ card: true }" :disable-option="{ card: true }"
auto-load auto-load
:create="{ :create="{
urlCreate: `Clients/${route.params.id}/setRating`,
title: 'Create rating', title: 'Create rating',
onDataSaved: () => tableRef.reload(), onDataSaved: ()=> tableRef.reload(),
formInitialData: {}, formInitialData: defaultInitialData,
saveFn: handleSave
}" }"
> >
<template #column-employee="{ row }"> <template #column-employee="{ row }">
<span class="link">{{ row.worker.user.nickname }}</span> <span class="link">{{ row.worker.user.nickname }}</span>
<WorkerDescriptorProxy :id="row.worker.id" /> <WorkerDescriptorProxy :id="row.worker.id" />
</template> </template>
<template #more-create-dialog="{ data }">
<VnRow>
<VnInputNumber
v-model="data.rating"
:label="t('customer.summary.rating')"
:required="!!data.recommendedCredit"
/>
<VnInputNumber
v-model="data.recommendedCredit"
:label="t('customer.summary.recommendCredit')"
:required="!!data.rating"
/>
</VnRow>
</template>
</VnTable> </VnTable>
</template> </template>
<i18n> <i18n>
en:
terminationTitle: Confirm contract termination
terminationMessage: Are you sure you want to terminate the contract? This action will set the rating and recommended credit to 0.
es: es:
Recommended credit: Crédito recomendado Recommended credit: Crédito recomendado
Since: Desde Since: Desde
Employee: Empleado Employee: Empleado
Create rating: Crear calificación
terminationTitle: Confirmar baja de contrato
terminationMessage: ¿Está seguro que desea dar de baja el contrato? Esta acción establecerá la calificación y el crédito recomendado en 0.
</i18n> </i18n>

View File

@ -3,12 +3,13 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { toCurrency, toDateHourMin } from 'src/filters'; import { toCurrency, toDateHourMin } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const arrayData = useArrayData('Customer');
const filter = computed(() => { const filter = computed(() => {
return { return {
include: [ include: [
@ -77,7 +78,10 @@ const columns = computed(() => [
:create="{ :create="{
urlUpdate: `Clients/${route.params.id}`, urlUpdate: `Clients/${route.params.id}`,
title: t('New credit'), title: t('New credit'),
onDataSaved: () => tableRef.reload(), onDataSaved: () => {
arrayData.fetch({ append: false });
tableRef.reload();
},
formInitialData: { credit: tableData.at(0)?.amount }, formInitialData: { credit: tableData.at(0)?.amount },
}" }"
> >

View File

@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router';
import { QBadge, QBtn, QCheckbox } from 'quasar'; import { QBadge, QBtn, QCheckbox } from 'quasar';
import { downloadFile } from 'src/composables/downloadFile'; import { usePrintService } from 'composables/usePrintService';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -15,7 +15,7 @@ import CustomerFileManagementActions from 'src/pages/Customer/components/Custome
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { openReport } = usePrintService();
const ClientDmsRef = ref(null); const ClientDmsRef = ref(null);
const rows = ref([]); const rows = ref([]);
@ -87,7 +87,7 @@ const tableColumnComponents = {
file: { file: {
component: QBtn, component: QBtn,
props: () => ({ flat: true }), props: () => ({ flat: true }),
event: ({ row }) => downloadFile(row.dmsFk), event: ({ row }) => openReport(`dms/${row.dmsFk}/downloadFile`, {}, '_blank'),
}, },
employee: { employee: {
component: QBtn, component: QBtn,

View File

@ -55,7 +55,6 @@ const filterBanks = {
fields: ['id', 'bank', 'accountingTypeFk'], fields: ['id', 'bank', 'accountingTypeFk'],
include: { relation: 'accountingType' }, include: { relation: 'accountingType' },
order: 'id', order: 'id',
limit: 30,
}; };
const filterClientFindOne = { const filterClientFindOne = {
@ -200,7 +199,6 @@ async function getAmountPaid() {
option-label="bank" option-label="bank"
:include="{ relation: 'accountingType' }" :include="{ relation: 'accountingType' }"
sort-by="id" sort-by="id"
:limit="0"
@update:model-value=" @update:model-value="
(value, options) => setPaymentType(data, value, options) (value, options) => setPaymentType(data, value, options)
" "

View File

@ -8,6 +8,6 @@ import filter from './EntryFilter.js';
data-key="Entry" data-key="Entry"
url="Entries" url="Entries"
:descriptor="EntryDescriptor" :descriptor="EntryDescriptor"
:filter="{ ...filter, where: { id: $route.params.id } }" :filter="filter"
/> />
</template> </template>

View File

@ -52,5 +52,22 @@ export default {
fields: ['code', 'description'], fields: ['code', 'description'],
}, },
}, },
{
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
'companyFk',
'warehouseFk',
],
},
},
], ],
}; };

View File

@ -1,5 +1,13 @@
<script setup> <script setup>
import { ref, computed, markRaw, useTemplateRef, onBeforeMount, watch } from 'vue'; import {
ref,
computed,
markRaw,
useTemplateRef,
onBeforeMount,
watch,
onUnmounted,
} from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
@ -19,7 +27,10 @@ import { useQuasar } from 'quasar';
import InvoiceInDescriptorProxy from '../InvoiceIn/Card/InvoiceInDescriptorProxy.vue'; import InvoiceInDescriptorProxy from '../InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import { useStateStore } from 'src/stores/useStateStore'; import { useStateStore } from 'src/stores/useStateStore';
import { downloadFile } from 'src/composables/downloadFile'; import { downloadFile } from 'src/composables/downloadFile';
import { useRoute } from 'vue-router';
import { useAcl } from 'src/composables/useAcl';
const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const { notify } = useNotify(); const { notify } = useNotify();
@ -27,8 +38,6 @@ const user = useState().getUser();
const stateStore = useStateStore(); const stateStore = useStateStore();
const updateDialog = ref(); const updateDialog = ref();
const uploadDialog = ref(); const uploadDialog = ref();
let maxDays;
let defaultDays;
const dataKey = 'entryPreaccountingFilter'; const dataKey = 'entryPreaccountingFilter';
const url = 'Entries/preAccountingFilter'; const url = 'Entries/preAccountingFilter';
const arrayData = useArrayData(dataKey); const arrayData = useArrayData(dataKey);
@ -45,12 +54,16 @@ const defaultDmsDescription = ref();
const dmsTypeId = ref(); const dmsTypeId = ref();
const selectedRows = ref([]); const selectedRows = ref([]);
const totalAmount = ref(); const totalAmount = ref();
const hasDiferentDms = ref(false);
let maxDays;
let defaultDays;
let supplierRef;
let dmsFk;
const totalSelectedAmount = computed(() => { const totalSelectedAmount = computed(() => {
if (!selectedRows.value.length) return 0; if (!selectedRows.value.length) return 0;
return selectedRows.value.reduce((acc, entry) => acc + entry.amount, 0); return selectedRows.value.reduce((acc, entry) => acc + entry.amount, 0);
}); });
let supplierRef;
let dmsFk;
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'id', name: 'id',
@ -63,6 +76,7 @@ const columns = computed(() => [
{ {
name: 'invoiceNumber', name: 'invoiceNumber',
label: t('entry.preAccount.invoiceNumber'), label: t('entry.preAccount.invoiceNumber'),
width: 'max-content',
}, },
{ {
name: 'company', name: 'company',
@ -73,7 +87,7 @@ const columns = computed(() => [
optionLabel: 'code', optionLabel: 'code',
options: companies.value, options: companies.value,
}, },
orderBy: false, class: 'shrink',
}, },
{ {
name: 'warehouse', name: 'warehouse',
@ -102,6 +116,7 @@ const columns = computed(() => [
{ {
name: 'reference', name: 'reference',
label: t('entry.preAccount.reference'), label: t('entry.preAccount.reference'),
width: 'max-content',
}, },
{ {
name: 'shipped', name: 'shipped',
@ -136,6 +151,7 @@ const columns = computed(() => [
class: 'fit', class: 'fit',
event: 'update', event: 'update',
}, },
width: 'max-content',
}, },
{ {
name: 'country', name: 'country',
@ -145,6 +161,7 @@ const columns = computed(() => [
name: 'countryFk', name: 'countryFk',
options: countries.value, options: countries.value,
}, },
class: 'shrink',
}, },
{ {
name: 'description', name: 'description',
@ -165,11 +182,12 @@ const columns = computed(() => [
component: 'number', component: 'number',
name: 'payDem', name: 'payDem',
}, },
class: 'shrink',
}, },
{ {
name: 'fiscalCode', name: 'fiscalCode',
label: t('entry.preAccount.fiscalCode'), label: t('entry.preAccount.fiscalCode'),
format: ({ fiscalCode }) => t(fiscalCode), format: ({ fiscalCode }, dashIfEmpty) => t(dashIfEmpty(fiscalCode)),
columnFilter: { columnFilter: {
component: 'select', component: 'select',
name: 'fiscalCode', name: 'fiscalCode',
@ -208,20 +226,21 @@ const columns = computed(() => [
]); ]);
onBeforeMount(async () => { onBeforeMount(async () => {
const filter = JSON.parse(route.query.entryPreaccountingFilter ?? '{}');
const { data } = await axios.get('EntryConfigs/findOne', { const { data } = await axios.get('EntryConfigs/findOne', {
params: { filter: JSON.stringify({ fields: ['maxDays', 'defaultDays'] }) }, params: { filter: JSON.stringify({ fields: ['maxDays', 'defaultDays'] }) },
}); });
maxDays = data.maxDays; maxDays = data.maxDays;
defaultDays = data.defaultDays; defaultDays = data.defaultDays;
daysAgo.value = arrayData.store.userParams.daysAgo || defaultDays; daysAgo.value = filter.daysAgo || defaultDays;
isBooked.value = arrayData.store.userParams.isBooked || false; isBooked.value = filter.isBooked || false;
stateStore.leftDrawer = false; stateStore.leftDrawer = false;
}); });
watch(selectedRows, (nVal, oVal) => { onUnmounted(() => (stateStore.leftDrawer = true));
const lastRow = nVal.at(-1);
if (lastRow?.isBooked) selectedRows.value.pop(); watch(selectedRows, async (nVal, oVal) => {
if (nVal.length > oVal.length && lastRow.invoiceInFk) if (nVal.length > oVal.length && nVal.at(-1).invoiceInFk)
quasar.dialog({ quasar.dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { title: t('entry.preAccount.hasInvoice') }, componentProps: { title: t('entry.preAccount.hasInvoice') },
@ -232,15 +251,22 @@ function filterByDaysAgo(val) {
if (!val) val = defaultDays; if (!val) val = defaultDays;
else if (val > maxDays) val = maxDays; else if (val > maxDays) val = maxDays;
daysAgo.value = val; daysAgo.value = val;
arrayData.store.userParams.daysAgo = daysAgo.value; table.value.reload({
table.value.reload(); userParams: { ...arrayData.store.userParams, daysAgo: daysAgo.value },
});
} }
async function preAccount() { async function preAccount() {
const entries = selectedRows.value; const entries = selectedRows.value;
const { companyFk, isAgricultural, landed } = entries.at(0); const { companyFk, isAgricultural, landed } = entries.at(0);
supplierRef = null;
dmsFk = undefined;
hasDiferentDms.value = false;
try { try {
dmsFk = entries.find(({ gestDocFk }) => gestDocFk)?.gestDocFk; entries.forEach(({ gestDocFk }) => {
if (!dmsFk && gestDocFk) dmsFk = gestDocFk;
if (dmsFk && gestDocFk && dmsFk != gestDocFk) hasDiferentDms.value = true;
});
if (isAgricultural) { if (isAgricultural) {
const year = new Date(landed).getFullYear(); const year = new Date(landed).getFullYear();
supplierRef = ( supplierRef = (
@ -297,8 +323,6 @@ async function createInvoice() {
} catch (e) { } catch (e) {
throw e; throw e;
} finally { } finally {
supplierRef = null;
dmsFk = undefined;
selectedRows.value.length = 0; selectedRows.value.length = 0;
table.value.reload(); table.value.reload();
} }
@ -359,6 +383,7 @@ async function createInvoice() {
:search-remove-params="false" :search-remove-params="false"
/> />
<VnTable <VnTable
v-if="isBooked !== undefined && daysAgo !== undefined"
v-model:selected="selectedRows" v-model:selected="selectedRows"
:data-key :data-key
:columns :columns
@ -377,10 +402,12 @@ async function createInvoice() {
@on-fetch=" @on-fetch="
(data) => (totalAmount = data?.reduce((acc, entry) => acc + entry.amount, 0)) (data) => (totalAmount = data?.reduce((acc, entry) => acc + entry.amount, 0))
" "
:selection-fn="(rows) => rows.filter((row) => !row.isBooked)"
auto-load auto-load
> >
<template #top-left> <template #top-left>
<QBtn <QBtn
v-if="useAcl().hasAcl('Entry', 'addInvoiceIn', 'WRITE')"
data-cy="preAccount_btn" data-cy="preAccount_btn"
icon="account_balance" icon="account_balance"
color="primary" color="primary"
@ -437,6 +464,11 @@ async function createInvoice() {
:title="t('entry.preAccount.dialog.title')" :title="t('entry.preAccount.dialog.title')"
:message="t('entry.preAccount.dialog.message')" :message="t('entry.preAccount.dialog.message')"
> >
<template #customHTML v-if="hasDiferentDms">
<p class="text-negative">
{{ t('differentGestdoc') }}
</p>
</template>
<template #actions> <template #actions>
<QBtn <QBtn
data-cy="updateFileYes" data-cy="updateFileYes"
@ -471,9 +503,11 @@ en:
IntraCommunity: Intra-community IntraCommunity: Intra-community
NonCommunity: Non-community NonCommunity: Non-community
CanaryIslands: Canary Islands CanaryIslands: Canary Islands
differentGestdoc: The entries have different gestdoc
es: es:
IntraCommunity: Intracomunitaria IntraCommunity: Intracomunitaria
NonCommunity: Extracomunitaria NonCommunity: Extracomunitaria
CanaryIslands: Islas Canarias CanaryIslands: Islas Canarias
National: Nacional National: Nacional
differentGestdoc: Las entradas tienen diferentes gestdoc
</i18n> </i18n>

View File

@ -130,7 +130,7 @@ entry:
supplierFk: Supplier supplierFk: Supplier
country: Country country: Country
description: Entry type description: Entry type
payDem: Payment term payDem: P. term
isBooked: B isBooked: B
isReceived: R isReceived: R
entryType: Entry type entryType: Entry type
@ -138,7 +138,7 @@ entry:
fiscalCode: Account type fiscalCode: Account type
daysAgo: Max 365 days daysAgo: Max 365 days
search: Search search: Search
searchInfo: You can search by supplier name or nickname searchInfo: You can search by supplier name, nickname or tax number
btn: Pre-account btn: Pre-account
hasInvoice: This entry has already an invoice in hasInvoice: This entry has already an invoice in
success: It has been successfully pre-accounted success: It has been successfully pre-accounted

View File

@ -81,7 +81,7 @@ entry:
supplierFk: Proveedor supplierFk: Proveedor
country: País country: País
description: Tipo de Entrada description: Tipo de Entrada
payDem: Plazo de pago payDem: P. pago
isBooked: C isBooked: C
isReceived: R isReceived: R
entryType: Tipo de entrada entryType: Tipo de entrada
@ -89,7 +89,7 @@ entry:
fiscalCode: Tipo de cuenta fiscalCode: Tipo de cuenta
daysAgo: Máximo 365 días daysAgo: Máximo 365 días
search: Buscar search: Buscar
searchInfo: Puedes buscar por nombre o alias de proveedor searchInfo: Puedes buscar por nombre, alias o cif de proveedor
btn: Precontabilizar btn: Precontabilizar
hasInvoice: Esta entrada ya tiene una f. recibida hasInvoice: Esta entrada ya tiene una f. recibida
success: Se ha precontabilizado correctamente success: Se ha precontabilizado correctamente

View File

@ -3,15 +3,11 @@ import { ref, computed, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { toDate } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { getTotal } from 'src/composables/getTotal'; import { getTotal } from 'src/composables/getTotal';
import CrudModel from 'src/components/CrudModel.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import useNotify from 'src/composables/useNotify.js'; 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'; import { toCurrency } from 'filters/index';
import VnTable from 'src/components/VnTable/VnTable.vue';
const route = useRoute(); const route = useRoute();
const { notify } = useNotify(); const { notify } = useNotify();
@ -19,256 +15,187 @@ const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const currency = computed(() => invoiceIn.value?.currency?.code); const currency = computed(() => invoiceIn.value?.currency?.code);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const invoiceInFormRef = ref(); const invoiceInDueDayTableRef = ref();
const invoiceId = +route.params.id; const invoiceId = +route.params.id;
const filter = { where: { invoiceInFk: invoiceId } }; const filter = { where: { invoiceInFk: invoiceId } };
const areRows = ref(false); const areRows = ref(false);
const totalTaxableBase = ref(); const totalTaxableBase = ref();
const noMatch = computed(() => totalAmount.value != totalTaxableBase.value); const totalVat = ref();
const tableRows = computed(
() => invoiceInDueDayTableRef.value?.CrudModelRef?.formData || [],
);
const totalAmount = computed(() => getTotal(tableRows.value, 'amount'));
const noMatch = computed(
() =>
totalAmount.value != totalTaxableBase.value &&
totalAmount.value != totalVat.value,
);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'duedate', name: 'dueDated',
label: t('Date'), label: t('Date'),
field: (row) => toDate(row.dueDated),
sortable: true, sortable: true,
tabIndex: 1, tabIndex: 1,
align: 'left', align: 'left',
component: 'date',
isEditable: true,
create: true,
}, },
{ {
name: 'bank', name: 'bankFk',
label: t('Bank'), label: t('Bank'),
field: (row) => row.bankFk,
model: 'bankFk',
optionLabel: 'bank',
url: 'Accountings',
sortable: true, sortable: true,
tabIndex: 2, tabIndex: 2,
align: 'left', align: 'left',
component: 'select',
attrs: {
url: 'Accountings',
optionLabel: 'bank',
optionValue: 'id',
fields: ['id', 'bank'],
'emit-value': true,
},
format: ({ bank }) => bank?.bank,
isEditable: true,
create: true,
}, },
{ {
name: 'amount', name: 'amount',
label: t('Amount'), label: t('Amount'),
field: (row) => row.amount,
sortable: true, sortable: true,
tabIndex: 3, tabIndex: 3,
align: 'left', align: 'left',
component: 'number',
attrs: {
clearable: true,
'clear-icon': 'close',
},
isEditable: true,
create: true,
}, },
{ {
name: 'foreignvalue', name: 'foreignValue',
label: t('Foreign value'), label: t('Foreign value'),
field: (row) => row.foreignValue,
sortable: true, sortable: true,
tabIndex: 4, tabIndex: 4,
align: 'left', align: 'left',
component: 'number',
isEditable: isNotEuro(currency.value),
create: true,
}, },
]); ]);
const totalAmount = computed(() => getTotal(invoiceInFormRef.value.formData, 'amount'));
const isNotEuro = (code) => code != 'EUR'; const isNotEuro = (code) => code != 'EUR';
async function insert() { async function insert() {
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId }); await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
await invoiceInFormRef.value.reload(); await invoiceInDueDayTableRef.value.reload();
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
} }
async function setTaxableBase() { async function setTaxableBase() {
const ref = invoiceInDueDayTableRef.value;
if (ref) ref.create = null;
const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`); const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`);
totalTaxableBase.value = data.totalTaxableBase; totalTaxableBase.value = data.totalTaxableBase;
totalVat.value = data.totalVat;
} }
onBeforeMount(async () => await setTaxableBase()); onBeforeMount(async () => await setTaxableBase());
</script> </script>
<template> <template>
<CrudModel <div class="invoice-in-due-day">
v-if="invoiceIn" <VnTable
ref="invoiceInFormRef" v-if="invoiceIn"
data-key="InvoiceInDueDays" ref="invoiceInDueDayTableRef"
url="InvoiceInDueDays" data-key="InvoiceInDueDays"
:filter="filter" url="InvoiceInDueDays"
auto-load save-url="InvoiceInDueDays/crud"
:data-required="{ invoiceInFk: invoiceId }" :filter="filter"
v-model:selected="rowsSelected" :user-filter="{
@on-fetch="(data) => (areRows = !!data.length)" include: { relation: 'bank', scope: { fields: ['id', 'bank'] } },
@save-changes="setTaxableBase" }"
> auto-load
<template #body="{ rows }"> :data-required="{ invoiceInFk: invoiceId }"
<QTable v-model:selected="rowsSelected"
v-model:selected="rowsSelected" @on-fetch="(data) => (areRows = !!data.length)"
selection="multiple" @save-changes="setTaxableBase"
:columns :columns="columns"
:rows :is-editable="true"
row-key="$index" :table="{ selection: 'multiple', 'row-key': '$index' }"
:grid="$q.screen.lt.sm" footer
> :right-search="false"
<template #body-cell-duedate="{ row }"> :column-search="false"
<QTd> :disable-option="{ card: true }"
<VnInputDate v-model="row.dueDated" /> class="q-pa-none"
</QTd> >
<template #column-footer-amount>
<QChip
dense
:color="noMatch ? 'negative' : 'transparent'"
class="q-pa-xs"
:title="noMatch ? t('invoiceIn.noMatch', { totalTaxableBase }) : ''"
>
{{ toCurrency(totalAmount) }}
</QChip>
</template>
<template #column-footer-foreignValue>
<template v-if="isNotEuro(currency)">
{{
getTotal(tableRows, 'foreignValue', {
currency: currency,
})
}}
</template> </template>
<template #body-cell-bank="{ row, col }"> </template>
<QTd> </VnTable>
<VnSelect <QPageSticky :offset="[20, 20]" style="z-index: 2">
v-model="row[col.model]" <QBtn
:url="col.url" @click="
:option-label="col.optionLabel" () => {
:option-value="col.optionValue" if (!areRows) return insert();
>
<template #option="scope"> invoiceInDueDayTableRef.create = {
<QItem v-bind="scope.itemProps"> urlCreate: 'InvoiceInDueDays',
<QItemSection> onDataSaved: () => invoiceInDueDayTableRef.reload(),
<QItemLabel>{{ title: t('Create due day'),
`${scope.opt.id}: ${scope.opt.bank}` formInitialData: {
}}</QItemLabel> invoiceInFk: invoiceId,
</QItemSection> dueDated: Date.vnNew(),
</QItem> amount: (totalTaxableBase - totalAmount).toFixed(2),
</template> },
</VnSelect> };
</QTd> invoiceInDueDayTableRef.showForm = true;
</template> }
<template #body-cell-amount="{ row }"> "
<QTd> color="primary"
<VnInputNumber fab
v-model="row.amount" icon="add"
:is-outlined="false" v-shortcut="'+'"
clearable data-cy="invoiceInDueDayAdd"
clear-icon="close" />
/> <QTooltip class="text-no-wrap">
</QTd> {{ t('Create due day') }}
</template> </QTooltip>
<template #body-cell-foreignvalue="{ row }"> </QPageSticky>
<QTd> </div>
<VnInputNumber
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="row.foreignValue"
/>
</QTd>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd />
<QTd />
<QTd />
<QTd>
<QChip
dense
:color="noMatch ? 'negative' : 'transparent'"
class="q-pa-xs"
:title="
noMatch
? t('invoiceIn.noMatch', { totalTaxableBase })
: ''
"
>
{{ toCurrency(totalAmount) }}
</QChip>
</QTd>
<QTd>
<template v-if="isNotEuro(invoiceIn.currency.code)">
{{
getTotal(rows, 'foreignValue', {
currency: invoiceIn.currency.code,
})
}}
</template>
</QTd>
</QTr>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList>
<QItem>
<VnInputDate
class="full-width"
:label="t('Date')"
v-model="props.row.dueDated"
/>
</QItem>
<QItem>
<VnSelect
:label="t('Bank')"
class="full-width"
v-model="props.row['bankFk']"
url="Accountings"
option-label="bank"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
`${scope.opt.id}: ${scope.opt.bank}`
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItem>
<QItem>
<VnInputNumber
:label="t('Amount')"
class="full-width"
v-model="props.row.amount"
/>
</QItem>
<QItem>
<VnInputNumber
:label="t('Foreign value')"
class="full-width"
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="props.row.foreignValue"
clearable
clear-icon="close"
/>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
color="primary"
icon="add"
v-shortcut="'+'"
size="lg"
round
@click="
() => {
if (!areRows) insert();
else
invoiceInFormRef.insert({
amount: (totalTaxableBase - totalAmount).toFixed(2),
invoiceInFk: invoiceId,
});
}
"
/>
</QPageSticky>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.bg {
background-color: var(--vn-light-gray);
}
.q-chip { .q-chip {
color: var(--vn-text-color); color: var(--vn-text-color);
} }
.invoice-in-due-day {
display: flex;
flex-direction: column;
align-items: center;
}
:deep(.full-width) {
max-width: 900px;
}
</style> </style>
<i18n> <i18n>
es: es:
@ -276,4 +203,6 @@ onBeforeMount(async () => await setTaxableBase());
Bank: Caja Bank: Caja
Amount: Importe Amount: Importe
Foreign value: Divisa Foreign value: Divisa
invoiceIn.noMatch: El total {totalTaxableBase} no coincide con el importe
Create due day: Crear Vencimiento
</i18n> </i18n>

View File

@ -3,71 +3,83 @@ import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { getTotal } from 'src/composables/getTotal'; import { getTotal } from 'src/composables/getTotal';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const invoceInIntrastat = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const countries = ref([]); const countries = ref([]);
const intrastats = ref([]); const intrastats = ref([]);
const invoiceInFormRef = ref(); const invoiceInIntrastatRef = ref();
const invoiceInId = computed(() => +route.params.id); const invoiceInId = computed(() => +route.params.id);
const filter = { where: { invoiceInFk: invoiceInId.value } }; const filter = computed(() => ({ where: { invoiceInFk: invoiceInId.value } }));
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'code', name: 'intrastatFk',
label: t('Code'), label: t('Code'),
field: (row) => row.intrastatFk, component: 'select',
options: intrastats.value, columnFilter: false,
model: 'intrastatFk', attrs: {
optionValue: 'id', options: intrastats.value,
optionLabel: (row) => `${row.id}: ${row.description}`, optionValue: 'id',
sortable: true, optionLabel: (row) => `${row.id}: ${row.description}`,
tabIndex: 1, 'data-cy': 'intrastat-code',
align: 'left', sortBy: 'id',
fields: ['id', 'description'],
filterOptions: ['id', 'description']
},
format: (row, dashIfEmpty) => dashIfEmpty(getCode(row)),
create: true,
isEditable: true,
width: 'max-content',
}, },
{ {
name: 'amount', name: 'amount',
label: t('amount'), label: t('amount'),
field: (row) => row.amount, component: 'number',
sortable: true, create: true,
tabIndex: 2, isEditable: true,
align: 'left', columnFilter: false,
}, },
{ {
name: 'net', name: 'net',
label: t('net'), label: t('net'),
field: (row) => row.net, component: 'number',
sortable: true, create: true,
tabIndex: 3, isEditable: true,
align: 'left', columnFilter: false,
}, },
{ {
name: 'stems', name: 'stems',
label: t('stems'), label: t('stems'),
field: (row) => row.stems, component: 'number',
sortable: true, create: true,
tabIndex: 4, isEditable: true,
align: 'left', columnFilter: false,
}, },
{ {
name: 'country', name: 'countryFk',
label: t('country'), label: t('country'),
field: (row) => row.countryFk, component: 'select',
options: countries.value, attrs: {
model: 'countryFk', options: countries.value,
optionValue: 'id', optionLabel: 'code',
optionLabel: 'code', },
sortable: true, create: true,
tabIndex: 5, isEditable: true,
align: 'left', columnFilter: false,
}, },
]); ]);
const tableRows = computed(
() => invoiceInIntrastatRef.value?.CrudModelRef?.formData || [],
);
function getCode(row) {
const code = intrastats.value.find(({ id }) => id === row.intrastatFk);
return code ? `${code.id}: ${code.description}` : null;
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -82,165 +94,51 @@ const columns = computed(() => [
auto-load auto-load
@on-fetch="(data) => (intrastats = data)" @on-fetch="(data) => (intrastats = data)"
/> />
<div class="invoiceIn-intrastat"> <div class="invoice-in-intrastat">
<CrudModel <VnTable
ref="invoiceInFormRef" ref="invoiceInIntrastatRef"
data-key="InvoiceInIntrastats" data-key="InvoiceInIntrastats"
url="InvoiceInIntrastats" url="InvoiceInIntrastats"
save-url="InvoiceInIntrastats/crud"
search-url="InvoiceInIntrastats"
auto-load auto-load
:data-required="{ invoiceInFk: invoiceInId }" :filter
:filter="filter" :user-filter
:insert-on-load="true"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
@on-fetch="(data) => (invoceInIntrastat = data)" :columns
:is-editable="true"
:table="{ selection: 'multiple', 'row-key': '$index' }"
:create="{
urlCreate: 'InvoiceInIntrastats',
title: t('Create Intrastat'),
formInitialData: { invoiceInFk: invoiceInId },
onDataSaved: () => invoiceInIntrastatRef.reload(),
}"
footer
data-cy="invoice-in-intrastat-table"
:right-search="false"
:disable-option="{ card: true }"
> >
<template #body="{ rows }"> <template #column-footer-amount>
<QTable {{ getTotal(tableRows, 'amount', { currency: 'default' }) }}
v-model:selected="rowsSelected"
selection="multiple"
:columns="columns"
:rows="rows"
row-key="$index"
:grid="$q.screen.lt.sm"
>
<template #body-cell="{ row, col }">
<QTd>
<VnInputNumber v-model="row[col.name]" />
</QTd>
</template>
<template #body-cell-code="{ row, col }">
<QTd>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'description']"
data-cy="intrastat-code"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.description}` }}
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-country="{ row, col }">
<QTd>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
/>
</QTd>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd />
<QTd />
<QTd>
{{ getTotal(rows, 'amount', { currency: 'default' }) }}
</QTd>
<QTd>
{{ getTotal(rows, 'net') }}
</QTd>
<QTd>
{{ getTotal(rows, 'stems', { decimalPlaces: 0 }) }}
</QTd>
<QTd />
</QTr>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList>
<QItem>
<VnSelect
:label="t('Code')"
class="full-width"
v-model="props.row['intrastatFk']"
:options="intrastats"
option-value="id"
:option-label="
(row) => `${row.id}:${row.description}`
"
:filter-options="['id', 'description']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{
`${scope.opt.id}: ${scope.opt.description}`
}}
</QItem>
</template>
</VnSelect>
</QItem>
<QItem
v-for="(value, index) of [
'amount',
'net',
'stems',
]"
:key="index"
>
<VnInputNumber
:label="t(value)"
class="full-width"
v-model="props.row[value]"
clearable
clear-icon="close"
/>
</QItem>
<QItem>
<VnSelect
:label="t('country')"
class="full-width"
v-model="props.row['countryFk']"
:options="countries"
option-value="id"
option-label="code"
/>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template> </template>
</CrudModel> <template #column-footer-net>
{{ getTotal(tableRows, 'net') }}
</template>
<template #column-footer-stems>
{{ getTotal(tableRows, 'stems', { decimalPlaces: 0 }) }}
</template>
</VnTable>
</div> </div>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
color="primary"
icon="add"
v-shortcut="'+'"
size="lg"
round
@click="invoiceInFormRef.insert()"
/>
</QPageSticky>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.invoiceIn-intrastat { .invoice-in-intrastat {
> .q-card { display: flex;
.vn-label-value { flex-direction: column;
display: flex; align-items: center;
gap: 1em; }
:deep(.full-width) {
.label { max-width: 900px;
flex: 1;
}
.value {
flex: 0.5;
}
}
}
} }
</style> </style>
<i18n> <i18n>
@ -258,4 +156,5 @@ const columns = computed(() => [
Total amount: Total importe Total amount: Total importe
Total net: Total neto Total net: Total neto
Total stems: Total tallos Total stems: Total tallos
Create Intrastat: Crear Intrastat
</i18n> </i18n>

View File

@ -11,6 +11,8 @@ import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorP
import InvoiceIntoBook from '../InvoiceInToBook.vue'; import InvoiceIntoBook from '../InvoiceInToBook.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue';
import { getTotal } from 'src/composables/getTotal';
const props = defineProps({ id: { type: [Number, String], default: 0 } }); const props = defineProps({ id: { type: [Number, String], default: 0 } });
const { t } = useI18n(); const { t } = useI18n();
@ -161,6 +163,22 @@ const intrastatColumns = ref([
}, },
]); ]);
const vehicleColumns = ref([
{
name: 'numberPlate',
label: 'globals.vehicle',
field: (row) => row.vehicleInvoiceIn?.numberPlate,
format: (value) => value,
align: 'left',
},
{
name: 'amount',
label: 'invoiceIn.list.amount',
field: (row) => toCurrency(row.vehicleInvoiceIn?.amount),
align: 'left',
},
]);
onMounted(async () => { onMounted(async () => {
invoiceInUrl.value = `${await getUrl('')}invoiceIn/${entityId.value}/`; invoiceInUrl.value = `${await getUrl('')}invoiceIn/${entityId.value}/`;
}); });
@ -218,6 +236,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<VnTitle <VnTitle
:url="getLink('basic-data')" :url="getLink('basic-data')"
:text="t('globals.pageTitles.basicData')" :text="t('globals.pageTitles.basicData')"
data-cy="basicDataTitleLink"
/> />
<div class="vn-card-group"> <div class="vn-card-group">
<div class="vn-card-content"> <div class="vn-card-content">
@ -282,7 +301,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
:value="entity.expenseDeductible?.name" :value="entity.expenseDeductible?.name"
/> />
<VnLv <VnLv
:label="t('invoiceIn.card.company')" :label="t('globals.company')"
:value="entity.company?.code" :value="entity.company?.code"
/> />
<VnLv <VnLv
@ -319,8 +338,12 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</div> </div>
</QCard> </QCard>
<!--Vat--> <!--Vat-->
<QCard v-if="entity.invoiceInTax.length" class="vat"> <QCard v-if="entity.invoiceInTax.length" class="col-extend">
<VnTitle :url="getLink('vat')" :text="t('invoiceIn.card.vat')" /> <VnTitle
:url="getLink('vat')"
:text="t('globals.pageTitles.vat')"
data-cy="vatTitleLink"
/>
<QTable <QTable
:columns="vatColumns" :columns="vatColumns"
:rows="entity.invoiceInTax" :rows="entity.invoiceInTax"
@ -363,6 +386,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd> <QTd>{{ toCurrency(entity.totals.totalTaxableBase) }}</QTd>
<QTd></QTd> <QTd></QTd>
<QTd></QTd> <QTd></QTd>
<QTd></QTd>
<QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd> <QTd>{{ toCurrency(getTotalTax(entity.invoiceInTax)) }}</QTd>
<QTd>{{ <QTd>{{
entity.totals.totalTaxableBaseForeignValue && entity.totals.totalTaxableBaseForeignValue &&
@ -371,13 +395,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
currency, currency,
) )
}}</QTd> }}</QTd>
<QTd></QTd>
</QTr> </QTr>
</template> </template>
</QTable> </QTable>
</QCard> </QCard>
<!--Due Day--> <!--Due Day-->
<QCard v-if="entity.invoiceInDueDay.length" class="due-day"> <QCard v-if="entity.invoiceInDueDay.length" class="col-shrink">
<VnTitle :url="getLink('due-day')" :text="t('invoiceIn.card.dueDay')" /> <VnTitle
:url="getLink('due-day')"
:text="t('globals.pageTitles.dueDay')"
data-cy="dueDayTitleLink"
/>
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat> <QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
<template #header="dueDayProps"> <template #header="dueDayProps">
<QTr :props="dueDayProps" class="bg"> <QTr :props="dueDayProps" class="bg">
@ -412,10 +441,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QTable> </QTable>
</QCard> </QCard>
<!--Intrastat--> <!--Intrastat-->
<QCard v-if="entity.invoiceInIntrastat.length"> <QCard v-if="entity.invoiceInIntrastat.length" class="col-extend">
<VnTitle <VnTitle
:url="getLink('intrastat')" :url="getLink('intrastat')"
:text="t('invoiceIn.card.intrastat')" :text="t('globals.pageTitles.intrastat')"
data-cy="intrastatTitleLink"
/> />
<QTable <QTable
:columns="intrastatColumns" :columns="intrastatColumns"
@ -449,6 +479,53 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</template> </template>
</QTable> </QTable>
</QCard> </QCard>
<!-- Vehicle -->
<QCard v-if="entity?.vehicleInvoiceIn?.length" class="col-shrink">
<VnTitle
:url="getLink('vehicle')"
:text="t('globals.vehicle')"
data-cy="vehicleTitleLink"
/>
<QTable :columns="vehicleColumns" :rows="entity.vehicleInvoiceIn" flat>
<template #header="vehicleProps">
<QTr :props="vehicleProps" class="bg">
<QTh
v-for="col in vehicleProps.cols"
:key="col.name"
:props="vehicleProps"
>
{{ t(col.label) }}
</QTh>
</QTr>
</template>
<template #body="vehicleProps">
<QTr :props="vehicleProps">
<QTd>
<span class="link" data-cy="invoiceInSummary_vehicle">
{{ vehicleProps.row.vehicle.numberPlate }}
<VehicleDescriptorProxy
:id="vehicleProps.row.vehicleFk"
/> </span
></QTd>
<QTd align="left">{{
toCurrency(vehicleProps.row.amount)
}}</QTd>
</QTr>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd></QTd>
<QTd>
{{
toCurrency(
getTotal(entity.vehicleInvoiceIn, 'amount'),
)
}}
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template> </template>
</CardSummary> </CardSummary>
</template> </template>
@ -462,15 +539,15 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
@media (min-width: $breakpoint-md) { @media (min-width: $breakpoint-md) {
.summaryBody { .summaryBody {
.vat { .col-extend {
flex: 65%; flex: 65%;
} }
.due-day { .col-shrink {
flex: 30%; flex: 30%;
} }
.vat, .col-extend,
.due-day { .col-shrink {
.q-table th { .q-table th {
padding-right: 0; padding-right: 0;
} }

View File

@ -1,123 +1,177 @@
<script setup> <script setup>
import { ref, computed, nextTick } from 'vue'; import { ref, computed, markRaw, useTemplateRef, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { getTotal } from 'src/composables/getTotal'; import { getTotal } from 'src/composables/getTotal';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import CrudModel from 'src/components/CrudModel.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue';
import { getExchange } from 'src/composables/getExchange'; import { getExchange } from 'src/composables/getExchange';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnSelectExpense from 'src/components/common/VnSelectExpense.vue';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
const { t } = useI18n(); const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const expensesArrayData = useArrayData('expenses', { url: 'Expenses' });
const route = useRoute(); const route = useRoute();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const currency = computed(() => invoiceIn.value?.currency?.code); const currency = computed(() => invoiceIn.value?.currency?.code);
const expenses = ref([]); const expenses = computed(() => expensesArrayData.store.data);
const sageTaxTypes = ref([]); const sageTaxTypes = ref([]);
const sageTransactionTypes = ref([]); const sageTransactionTypes = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const invoiceInFormRef = ref(); const invoiceInVatTableRef = useTemplateRef('invoiceInVatTableRef');
defineProps({ actionIcon: { type: String, default: 'add' } });
defineProps({ function taxRate(invoiceInTax) {
actionIcon: { const sageTaxTypeId = invoiceInTax.taxTypeSageFk;
type: String, const taxRateSelection = sageTaxTypes.value.find(
default: 'add', (transaction) => transaction.id == sageTaxTypeId,
}, );
}); const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0;
return (taxTypeSage / 100) * taxableBase;
}
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'expense', name: 'expenseFk',
label: t('Expense'), label: t('Expense'),
field: (row) => row.expenseFk, component: markRaw(VnSelectExpense),
options: expenses.value, format: (row, dashIfEmpty) => {
model: 'expenseFk', const expense = expenses.value?.find((e) => e.id === row.expenseFk);
optionValue: 'id', return expense ? `${expense.id}: ${expense.name}` : dashIfEmpty(null);
optionLabel: (row) => `${row.id}: ${row.name}`, },
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
width: 'max-content',
cellEvent: {
keydown: (evt, row) => {
if (evt.key !== 'Tab') return;
const val = evt.target.value;
if (!val || isNaN(val)) return;
row.expenseFk = expenses.value.find(
(e) => e.id === useAccountShortToStandard(val),
)?.id;
},
},
}, },
{ {
name: 'taxablebase', name: 'taxableBase',
label: t('Taxable base'), label: t('Taxable base'),
field: (row) => row.taxableBase, component: 'number',
model: 'taxableBase', attrs: {
clearable: true,
'clear-icon': 'close',
},
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
}, },
{ {
name: 'isDeductible', name: 'isDeductible',
label: t('invoiceIn.isDeductible'), label: t('invoiceIn.isDeductible'),
field: (row) => row.isDeductible, component: 'checkbox',
model: 'isDeductible',
align: 'center', align: 'center',
isEditable: true,
create: true,
createAttrs: {
defaultValue: true,
},
}, },
{ {
name: 'sageiva', name: 'taxTypeSageFk',
label: t('Sage iva'), label: t('Sage iva'),
field: (row) => row.taxTypeSageFk, component: 'select',
options: sageTaxTypes.value, attrs: {
model: 'taxTypeSageFk', options: sageTaxTypes.value,
optionValue: 'id', optionValue: 'id',
optionLabel: (row) => `${row.id}: ${row.vat}`, optionLabel: (row) => `${row.id}: ${row.vat}`,
filterOptions: ['id', 'vat'],
'data-cy': 'vat-sageiva',
},
format: ({ taxTypeSageFk }, dashIfEmpty) => {
const taxType = sageTaxTypes.value.find((t) => t.id === taxTypeSageFk);
return taxType ? `${taxType.id}: ${taxType.vat}` : dashIfEmpty(taxTypeSageFk);
},
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
width: 'max-content',
}, },
{ {
name: 'sagetransaction', name: 'transactionTypeSageFk',
label: t('Sage transaction'), label: t('Sage transaction'),
field: (row) => row.transactionTypeSageFk, component: 'select',
options: sageTransactionTypes.value, attrs: {
model: 'transactionTypeSageFk', options: sageTransactionTypes.value,
optionValue: 'id', optionValue: 'id',
optionLabel: (row) => `${row.id}: ${row.transaction}`, optionLabel: (row) => `${row.id}: ${row.transaction}`,
filterOptions: ['id', 'transaction'],
},
format: ({ transactionTypeSageFk }, dashIfEmpty) => {
const transType = sageTransactionTypes.value.find(
(t) => t.id === transactionTypeSageFk,
);
return transType
? `${transType.id}: ${transType.transaction}`
: dashIfEmpty(transactionTypeSageFk);
},
sortable: true, sortable: true,
align: 'left', align: 'left',
isEditable: true,
create: true,
width: 'max-content',
}, },
{ {
name: 'rate', name: 'rate',
label: t('Rate'), label: t('Rate'),
sortable: true, sortable: false,
field: (row) => taxRate(row, row.taxTypeSageFk), format: (row) => taxRate(row).toFixed(2),
align: 'left', align: 'left',
}, },
{ {
name: 'foreignvalue', name: 'foreignValue',
label: t('Foreign value'), label: t('Foreign value'),
component: 'number',
sortable: true, sortable: true,
field: (row) => row.foreignValue,
align: 'left', align: 'left',
create: true,
isEditable: isNotEuro(currency.value),
format: (row, dashIfEmpty) => dashIfEmpty(row.foreignValue),
cellEvent: {
'update:modelValue': async (value, oldValue, row) =>
await handleForeignValueUpdate(value, row),
},
}, },
{ {
name: 'total', name: 'total',
label: 'Total', label: t('Total'),
align: 'left', align: 'left',
format: (row) => (Number(row.taxableBase || 0) + Number(taxRate(row))).toFixed(2),
}, },
]); ]);
const tableRows = computed(
() => invoiceInVatTableRef.value?.CrudModelRef?.formData || [],
);
const taxableBaseTotal = computed(() => { const taxableBaseTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, 'taxableBase'); return getTotal(tableRows.value, 'taxableBase');
}); });
const taxRateTotal = computed(() => { const taxRateTotal = computed(() => {
return getTotal(invoiceInFormRef.value.formData, null, { return tableRows.value.reduce((sum, row) => sum + Number(taxRate(row)), 0);
cb: taxRate,
});
}); });
const combinedTotal = computed(() => { const combinedTotal = computed(() => {
return +taxableBaseTotal.value + +taxRateTotal.value; return +taxableBaseTotal.value + +taxRateTotal.value;
}); });
const filter = { const filter = computed(() => ({
fields: [ fields: [
'id', 'id',
'invoiceInFk', 'invoiceInFk',
@ -131,389 +185,75 @@ const filter = {
where: { where: {
invoiceInFk: route.params.id, invoiceInFk: route.params.id,
}, },
}; }));
onBeforeMount(async () => await expensesArrayData.fetch({}));
const isNotEuro = (code) => code != 'EUR'; const isNotEuro = (code) => code != 'EUR';
function taxRate(invoiceInTax) { async function handleForeignValueUpdate(val, row) {
const sageTaxTypeId = invoiceInTax.taxTypeSageFk; if (!isNotEuro(currency.value)) return;
const taxRateSelection = sageTaxTypes.value.find( row.taxableBase = await getExchange(
(transaction) => transaction.id == sageTaxTypeId, val,
invoiceIn.value?.currencyFk,
invoiceIn.value?.issued,
); );
const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0;
return ((taxTypeSage / 100) * taxableBase).toFixed(2);
}
function autocompleteExpense(evt, row, col, ref) {
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),
);
ref.vnSelectDialogRef.vnSelectRef.toggleOption(lookup);
}
function setCursor(ref) {
nextTick(() => {
const select = ref.vnSelectDialogRef
? ref.vnSelectDialogRef.vnSelectRef
: ref.vnSelectRef;
select.$el.querySelector('input').setSelectionRange(0, 0);
});
} }
</script> </script>
<template> <template>
<FetchData
ref="expensesRef"
url="Expenses"
auto-load
@on-fetch="(data) => (expenses = data)"
/>
<FetchData url="SageTaxTypes" auto-load @on-fetch="(data) => (sageTaxTypes = data)" /> <FetchData url="SageTaxTypes" auto-load @on-fetch="(data) => (sageTaxTypes = data)" />
<FetchData <FetchData
url="sageTransactionTypes" url="sageTransactionTypes"
auto-load auto-load
@on-fetch="(data) => (sageTransactionTypes = data)" @on-fetch="(data) => (sageTransactionTypes = data)"
/> />
<CrudModel <VnTable
ref="invoiceInFormRef"
v-if="invoiceIn" v-if="invoiceIn"
ref="invoiceInVatTableRef"
data-key="InvoiceInTaxes" data-key="InvoiceInTaxes"
url="InvoiceInTaxes" url="InvoiceInTaxes"
save-url="InvoiceInTaxes/crud"
:filter="filter" :filter="filter"
:data-required="{ invoiceInFk: $route.params.id }" :data-required="{ invoiceInFk: $route.params.id }"
:insert-on-load="true"
auto-load auto-load
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:columns="columns"
:is-editable="true"
:table="{ selection: 'multiple', 'row-key': '$index' }"
footer
:right-search="false"
:column-search="false"
:disable-option="{ card: true }"
class="q-pa-none"
:create="{
urlCreate: 'InvoiceInTaxes',
title: t('Add tax'),
formInitialData: { invoiceInFk: $route.params.id, isDeductible: true },
onDataSaved: () => invoiceInVatTableRef.reload(),
}"
:go-to="`/invoice-in/${$route.params.id}/due-day`" :go-to="`/invoice-in/${$route.params.id}/due-day`"
> >
<template #body="{ rows }"> <template #column-footer-taxableBase>
<QTable {{ toCurrency(taxableBaseTotal) }}
v-model:selected="rowsSelected"
selection="multiple"
:columns="columns"
:rows="rows"
row-key="$index"
:grid="$q.screen.lt.sm"
>
<template #body-cell-expense="{ row, col }">
<QTd>
<VnSelectDialog
:ref="`expenseRef-${row.$index}`"
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
:acls="[
{ model: 'Expense', props: '*', accessType: 'WRITE' },
]"
@keydown.tab.prevent="
autocompleteExpense(
$event,
row,
col,
$refs[`expenseRef-${row.$index}`],
)
"
@update:model-value="
setCursor($refs[`expenseRef-${row.$index}`])
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
<template #form>
<CreateNewExpenseForm
@on-data-saved="$refs.expensesRef.fetch()"
/>
</template>
</VnSelectDialog>
</QTd>
</template>
<template #body-cell-isDeductible="{ row }">
<QTd align="center">
<QCheckbox
v-model="row.isDeductible"
data-cy="isDeductible_checkbox"
/>
</QTd>
</template>
<template #body-cell-taxablebase="{ row }">
<QTd shrink>
<VnInputNumber
clear-icon="close"
v-model="row.taxableBase"
clearable
/>
</QTd>
</template>
<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">
<QItemSection>
<QItemLabel>{{ scope.opt.vat }}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<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">
<QItemSection>
<QItemLabel>{{
scope.opt.transaction
}}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-foreignvalue="{ row }">
<QTd shrink>
<VnInputNumber
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="row.foreignValue"
@update:model-value="
async (val) => {
if (!isNotEuro(currency)) return;
row.taxableBase = await getExchange(
val,
row.currencyFk,
invoiceIn.issued,
);
}
"
/>
</QTd>
</template>
<template #bottom-row>
<QTr class="bg">
<QTd />
<QTd />
<QTd>
{{ toCurrency(taxableBaseTotal) }}
</QTd>
<QTd />
<QTd />
<QTd />
<QTd>
{{ toCurrency(taxRateTotal) }}
</QTd>
<QTd />
<QTd>
{{ toCurrency(combinedTotal) }}
</QTd>
</QTr>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard bordered flat class="q-my-xs">
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList>
<QItem>
<VnSelectDialog
:label="t('Expense')"
class="full-width"
v-model="props.row['expenseFk']"
:options="expenses"
option-value="id"
:option-label="(row) => `${row.id}:${row.name}`"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
<template #form>
<CreateNewExpenseForm />
</template>
</VnSelectDialog>
</QItem>
<QItem>
<VnInputNumber
:label="t('Taxable base')"
:class="{
'no-pointer-events': isNotEuro(currency),
}"
class="full-width"
:disable="isNotEuro(currency)"
clear-icon="close"
v-model="props.row.taxableBase"
clearable
/>
</QItem>
<QItem>
<VnSelect
:label="t('Sage iva')"
class="full-width"
v-model="props.row['taxTypeSageFk']"
:options="sageTaxTypes"
option-value="id"
:option-label="(row) => `${row.id}:${row.vat}`"
:filter-options="['id', 'vat']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
scope.opt.vat
}}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItem>
<QItem>
<VnSelect
class="full-width"
v-model="props.row['transactionTypeSageFk']"
:options="sageTransactionTypes"
option-value="id"
:option-label="
(row) => `${row.id}:${row.transaction}`
"
:filter-options="['id', 'transaction']"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
scope.opt.transaction
}}</QItemLabel>
<QItemLabel>
{{ `#${scope.opt.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItem>
<QItem>
{{ toCurrency(taxRate(props.row), currency) }}
</QItem>
<QItem>
<VnInputNumber
:label="t('Foreign value')"
class="full-width"
:class="{
'no-pointer-events': !isNotEuro(currency),
}"
:disable="!isNotEuro(currency)"
v-model="props.row.foreignValue"
/>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template> </template>
</CrudModel> <template #column-footer-rate>
<QPageSticky position="bottom-right" :offset="[25, 25]"> {{ toCurrency(taxRateTotal) }}
<QBtn </template>
color="primary" <template #column-footer-total>
icon="add" {{ toCurrency(combinedTotal) }}
size="lg" </template>
v-shortcut="'+'" </VnTable>
round
@click="invoiceInFormRef.insert()"
>
<QTooltip>{{ t('Add tax') }}</QTooltip>
</QBtn>
</QPageSticky>
</template> </template>
<style lang="scss" scoped>
.bg {
background-color: var(--vn-light-gray);
}
@media (max-width: $breakpoint-xs) {
.q-dialog {
.q-card {
&__section:not(:first-child) {
.q-item {
flex-direction: column;
.q-checkbox {
margin-top: 2rem;
}
}
}
}
}
}
.q-item {
min-height: 0;
}
.default-icon {
cursor: pointer;
border-radius: 50px;
background-color: $primary;
}
</style>
<i18n> <i18n>
es: es:
Expense: Gasto Expense: Gasto
Create a new expense: Crear nuevo gasto Create a new expense: Crear nuevo gasto
Add tax: Crear gasto Add tax: Añadir Gasto/IVA # Changed label slightly
Taxable base: Base imp. Taxable base: Base imp.
Sage tax: Sage iva Sage iva: Sage iva # Kept original label
Sage transaction: Sage transacción Sage transaction: Sage transacción
Rate: Tasa Rate: Cuota # Changed label
Foreign value: Divisa Foreign value: Divisa
Total: Total
invoiceIn.isDeductible: Deducible
</i18n> </i18n>

View File

@ -0,0 +1,107 @@
<script setup>
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import { toCurrency } from 'src/filters';
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useVnConfirm } from 'composables/useVnConfirm';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue';
const tableRef = ref();
const { t } = useI18n();
const route = useRoute();
const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify();
const dataKey = 'InvoiceInVehicleList';
const filter = {
include: [{ relation: 'vehicle', scope: { fields: ['id', 'numberPlate'] } }],
};
const columns = computed(() => [
{
align: 'left',
name: 'vehicleFk',
label: t('globals.vehicle'),
component: 'select',
attrs: {
url: 'vehicles',
fields: ['id', 'numberPlate'],
optionLabel: 'numberPlate',
optionFilterValue: 'numberPlate',
find: {
value: 'vehicleFk',
label: 'vehiclePlateNumber',
},
},
create: true,
format: (row) => row.vehicle?.numberPlate,
cardVisible: true,
},
{
align: 'left',
name: 'amount',
label: t('invoiceIn.list.amount'),
component: 'number',
create: true,
format: (row) => toCurrency(row.amount),
cardVisible: true,
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('invoiceIn.unlinkVehicle'),
icon: 'delete',
action: (row) =>
openConfirmationModal(
t('invoiceIn.unlinkVehicle'),
t('invoiceIn.unlinkVehicleConfirmation'),
() => unassignVehicle(row.id),
),
isPrimary: true,
},
],
},
]);
async function unassignVehicle(id) {
try {
await axios.delete(`VehicleInvoiceIns/${id}`);
notify(t('invoiceIn.unlinkedVehicle'), 'positive');
tableRef.value.reload();
} catch (e) {
throw e;
}
}
</script>
<template>
<VnTable
ref="tableRef"
:data-key="dataKey"
url="VehicleInvoiceIns"
:user-filter="filter"
:filter="{ where: { invoiceInFk: route.params.id } }"
:columns="columns"
:column-search="false"
:right-search="false"
:create="{
urlCreate: 'VehicleInvoiceIns',
title: t('invoiceIn.linkVehicleToInvoiceIn'),
onDataSaved: () => tableRef.reload(),
formInitialData: { invoiceInFk: route.params.id },
}"
auto-load
>
<template #column-vehicleFk="{ row }">
<span class="link" @click.stop>
{{ row.vehicle?.numberPlate }}
<VehicleDescriptorProxy :id="row?.vehicleFk" />
</span>
</template>
</VnTable>
</template>

View File

@ -41,7 +41,7 @@ async function checkToBook(id) {
params: { params: {
where: JSON.stringify({ where: JSON.stringify({
invoiceInFk: id, invoiceInFk: id,
dueDated: { gte: Date.vnNew() }, dueDated: { lte: Date.vnNew() },
}), }),
}, },
}) })

View File

@ -34,14 +34,6 @@ invoiceIn:
originalInvoice: Original invoice originalInvoice: Original invoice
entry: Entry entry: Entry
emailEmpty: The email can't be empty emailEmpty: The email can't be empty
card:
client: Client
company: Company
customerCard: Customer card
ticketList: Ticket List
vat: Vat
dueDay: Due day
intrastat: Intrastat
summary: summary:
currency: Currency currency: Currency
issued: Expedition date issued: Expedition date
@ -69,4 +61,9 @@ invoiceIn:
isBooked: Is booked isBooked: Is booked
account: Ledger account account: Ledger account
correctingFk: Rectificative correctingFk: Rectificative
issued: Issued
noMatch: No match with the vat({totalTaxableBase}) noMatch: No match with the vat({totalTaxableBase})
linkVehicleToInvoiceIn: Link vehicle to invoice
unlinkedVehicle: Unlinked vehicle
unlinkVehicle: Unlink vehicle
unlinkVehicleConfirmation: This vehicle will be unlinked from this invoice! Continue anyway?

View File

@ -33,13 +33,6 @@ invoiceIn:
originalInvoice: Factura origen originalInvoice: Factura origen
entry: Entrada entry: Entrada
emailEmpty: El email no puede estar vacío emailEmpty: El email no puede estar vacío
card:
client: Cliente
company: Empresa
customerCard: Ficha del cliente
ticketList: Listado de tickets
vat: Iva
dueDay: Fecha de vencimiento
summary: summary:
currency: Divisa currency: Divisa
docNumber: Número documento docNumber: Número documento
@ -52,7 +45,7 @@ invoiceIn:
expense: Gasto expense: Gasto
taxableBase: Base imp. taxableBase: Base imp.
rate: Tasa rate: Tasa
sageTransaction: Sage transación sageTransaction: Sage transacción
dueDay: Fecha dueDay: Fecha
bank: Caja bank: Caja
foreignValue: Divisa foreignValue: Divisa
@ -67,4 +60,9 @@ invoiceIn:
isBooked: Contabilizada isBooked: Contabilizada
account: Cuenta contable account: Cuenta contable
correctingFk: Rectificativa correctingFk: Rectificativa
issued: Fecha de emisión
noMatch: No cuadra con el iva({totalTaxableBase}) noMatch: No cuadra con el iva({totalTaxableBase})
linkVehicleToInvoiceIn: Vincular vehículo a factura
unlinkedVehicle: Vehículo desvinculado
unlinkVehicle: Desvincular vehículo
unlinkVehicleConfirmation: Este vehículo se desvinculará de esta factura! ¿Continuar de todas formas?

View File

@ -61,7 +61,7 @@ const onIntrastatCreated = (response, formData) => {
:clear-store-on-unmount="false" :clear-store-on-unmount="false"
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow class="q-py-sm">
<VnSelect <VnSelect
:label="t('item.basicData.type')" :label="t('item.basicData.type')"
v-model="data.typeFk" v-model="data.typeFk"
@ -71,6 +71,7 @@ const onIntrastatCreated = (response, formData) => {
hide-selected hide-selected
map-options map-options
required required
data-cy="itemBasicDataItemType"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -83,23 +84,30 @@ const onIntrastatCreated = (response, formData) => {
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
<VnInput :label="t('item.basicData.reference')" v-model="data.comment" /> <VnInput
:label="t('item.basicData.reference')"
v-model="data.comment"
data-cy="itemBasicDataReference"
/>
<VnInput <VnInput
:label="t('item.basicData.relevancy')" :label="t('item.basicData.relevancy')"
type="number" type="number"
v-model="data.relevancy" v-model="data.relevancy"
data-cy="itemBasicDataRelevancy"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="q-py-sm">
<VnInput <VnInput
:label="t('item.basicData.stems')" :label="t('item.basicData.stems')"
type="number" type="number"
v-model="data.stems" v-model="data.stems"
data-cy="itemBasicDataStems"
/> />
<VnInput <VnInput
:label="t('item.basicData.multiplier')" :label="t('item.basicData.multiplier')"
type="number" type="number"
v-model="data.stemMultiplier" v-model="data.stemMultiplier"
data-cy="itemBasicDataMultiplier"
/> />
<VnSelectDialog <VnSelectDialog
:label="t('item.basicData.generic')" :label="t('item.basicData.generic')"
@ -112,6 +120,7 @@ const onIntrastatCreated = (response, formData) => {
map-options map-options
hide-selected hide-selected
action-icon="filter_alt" action-icon="filter_alt"
data-cy="itemBasicDataGeneric"
> >
<template #form> <template #form>
<FilterItemForm <FilterItemForm
@ -129,7 +138,12 @@ const onIntrastatCreated = (response, formData) => {
</template> </template>
</VnSelectDialog> </VnSelectDialog>
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnCheckbox
v-model="data.isCustomInspectionRequired"
:label="t('item.basicData.isCustomInspectionRequired')"
data-cy="itemBasicDataCustomInspection"
/>
<VnSelectDialog <VnSelectDialog
:label="t('item.basicData.intrastat')" :label="t('item.basicData.intrastat')"
v-model="data.intrastatFk" v-model="data.intrastatFk"
@ -138,6 +152,7 @@ const onIntrastatCreated = (response, formData) => {
option-label="description" option-label="description"
map-options map-options
hide-selected hide-selected
data-cy="itemBasicDataIntrastat"
> >
<template #form> <template #form>
<CreateIntrastatForm <CreateIntrastatForm
@ -165,78 +180,81 @@ const onIntrastatCreated = (response, formData) => {
option-label="name" option-label="name"
hide-selected hide-selected
map-options map-options
data-cy="itemBasicDataExpense"
/> />
</div> </div>
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnCheckbox
v-model="data.hasKgPrice"
:label="t('item.basicData.hasKgPrice')"
data-cy="itemBasicDataHasKgPrice"
/>
<VnInput <VnInput
:label="t('item.basicData.weightByPiece')" :label="t('item.basicData.weightByPiece')"
v-model.number="data.weightByPiece" v-model.number="data.weightByPiece"
:min="0" :min="0"
type="number" type="number"
data-cy="itemBasicDataWeightByPiece"
/> />
<VnInput <VnInput
:label="t('item.basicData.boxUnits')" :label="t('item.basicData.boxUnits')"
v-model.number="data.packingOut" v-model.number="data.packingOut"
:min="0" :min="0"
type="number" type="number"
data-cy="itemBasicDataBoxUnits"
/> />
</VnRow>
<VnRow class="q-py-sm">
<VnCheckbox
v-model="data.isActive"
:label="t('item.basicData.isActive')"
data-cy="itemBasicDataIsActive"
/>
<VnCheckbox
v-model="data.isFragile"
:label="t('item.basicData.isFragile')"
:info="t('item.basicData.isFragileTooltip')"
data-cy="itemBasicDataIsFragile"
/>
<VnCheckbox
v-model="data.isPhotoRequested"
:label="t('item.basicData.isPhotoRequested')"
:info="t('item.basicData.isPhotoRequestedTooltip')"
data-cy="itemBasicDataIsPhotoRequested"
/>
</VnRow>
<VnRow class="q-py-sm">
<VnInput <VnInput
:label="t('item.basicData.recycledPlastic')" :label="t('item.basicData.recycledPlastic')"
v-model.number="data.recycledPlastic" v-model.number="data.recycledPlastic"
:min="0" :min="0"
type="number" type="number"
data-cy="itemBasicDataRecycledPlastic"
/> />
<VnInput <VnInput
:label="t('item.basicData.nonRecycledPlastic')" :label="t('item.basicData.nonRecycledPlastic')"
v-model.number="data.nonRecycledPlastic" v-model.number="data.nonRecycledPlastic"
:min="0" :min="0"
type="number" type="number"
data-cy="itemBasicDataNonRecycledPlastic"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="q-py-sm">
<QCheckbox
v-model="data.isActive"
:label="t('item.basicData.isActive')"
/>
<QCheckbox
v-model="data.hasKgPrice"
:label="t('item.basicData.hasKgPrice')"
/>
<QCheckbox
v-model="data.isCustomInspectionRequired"
:label="t('item.basicData.isCustomInspectionRequired')"
/>
</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"
/>
</VnRow>
<VnRow>
<VnInput <VnInput
:label="t('item.basicData.description')" :label="t('item.basicData.description')"
type="textarea" type="textarea"
v-model="data.description" v-model="data.description"
fill-input fill-input
data-cy="itemBasicDataDescription"
/> />
<VnInput <VnInput
v-show="data.isPhotoRequested"
type="textarea" type="textarea"
:label="t('globals.comment')" :label="t('item.basicData.photoMotivation')"
v-model="data.photoMotivation" v-model="data.photoMotivation"
fill-input fill-input
data-cy="itemBasicDataPhotoMotivation"
/> />
</VnRow> </VnRow>
</template> </template>

View File

@ -102,20 +102,21 @@ const columns = computed(() => [
label: t('itemDiary.in'), label: t('itemDiary.in'),
field: 'invalue', field: 'invalue',
name: 'in', name: 'in',
align: 'left', align: 'right',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('itemDiary.out'), label: t('itemDiary.out'),
field: 'out', field: 'out',
name: 'out', name: 'out',
align: 'left', align: 'right',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('itemDiary.balance'), label: t('itemDiary.balance'),
name: 'balance', name: 'balance',
align: 'left', align: 'right',
class: 'q-px-sm',
}, },
]); ]);
@ -174,7 +175,11 @@ async function updateWarehouse(warehouseFk) {
<template> <template>
<FetchData <FetchData
url="Warehouses" url="Warehouses"
:filter="{ fields: ['id', 'name'], order: 'name ASC' }" :filter="{
fields: ['id', 'name'],
order: 'name ASC',
where: { isDestiny: true },
}"
auto-load auto-load
@on-fetch="(data) => (warehousesOptions = data)" @on-fetch="(data) => (warehousesOptions = data)"
/> />
@ -217,7 +222,8 @@ async function updateWarehouse(warehouseFk) {
<QTable <QTable
:rows="itemBalances" :rows="itemBalances"
:columns="columns" :columns="columns"
class="full-width q-mt-md" class="full-width q-mt-md q-px-md"
style="background-color: var(--vn-section-color)"
:no-data-label="t('globals.noResults')" :no-data-label="t('globals.noResults')"
> >
<template #body-cell-claim="{ row }"> <template #body-cell-claim="{ row }">
@ -294,14 +300,14 @@ async function updateWarehouse(warehouseFk) {
</QTd> </QTd>
</template> </template>
<template #body-cell-in="{ row }"> <template #body-cell-in="{ row }">
<QTd @click.stop> <QTd @click.stop class="text-right">
<span :class="{ 'is-in': row.invalue }"> <span :class="{ 'is-in': row.invalue }">
{{ dashIfEmpty(row.invalue) }} {{ dashIfEmpty(row.invalue) }}
</span> </span>
</QTd> </QTd>
</template> </template>
<template #body-cell-balance="{ row }"> <template #body-cell-balance="{ row }">
<QTd @click.stop> <QTd @click.stop class="text-right">
<QBadge <QBadge
class="balance-negative" class="balance-negative"
:color=" :color="

View File

@ -48,7 +48,7 @@ const columns = computed(() => [
label: t('itemDiary.warehouse'), label: t('itemDiary.warehouse'),
name: 'warehouse', name: 'warehouse',
field: 'warehouse', field: 'warehouse',
align: 'center', align: 'left',
}, },
{ {
label: t('lastEntries.landed'), label: t('lastEntries.landed'),
@ -60,7 +60,7 @@ const columns = computed(() => [
label: t('lastEntries.entry'), label: t('lastEntries.entry'),
name: 'entry', name: 'entry',
field: 'stateName', field: 'stateName',
align: 'center', align: 'right',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -75,14 +75,14 @@ const columns = computed(() => [
label: t('lastEntries.printedStickers'), label: t('lastEntries.printedStickers'),
name: 'printedStickers', name: 'printedStickers',
field: 'printedStickers', field: 'printedStickers',
align: 'center', align: 'right',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('lastEntries.label'), label: t('lastEntries.label'),
name: 'stickers', name: 'stickers',
field: 'stickers', field: 'stickers',
align: 'center', align: 'right',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
style: (row) => highlightedRow(row), style: (row) => highlightedRow(row),
}, },
@ -90,39 +90,39 @@ const columns = computed(() => [
label: 'Packing', label: 'Packing',
name: 'packing', name: 'packing',
field: 'packing', field: 'packing',
align: 'center', align: 'right',
}, },
{ {
label: t('lastEntries.grouping'), label: t('lastEntries.grouping'),
name: 'grouping', name: 'grouping',
field: 'grouping', field: 'grouping',
align: 'center', align: 'right',
}, },
{ {
label: t('itemBasicData.stems'), label: t('itemBasicData.stems'),
name: 'stems', name: 'stems',
field: 'stems', field: 'stems',
align: 'center', align: 'right',
style: (row) => highlightedRow(row), style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.quantity'), label: t('lastEntries.quantity'),
name: 'quantity', name: 'quantity',
field: 'quantity', field: 'quantity',
align: 'center', align: 'right',
style: (row) => highlightedRow(row), style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.cost'), label: t('lastEntries.cost'),
name: 'cost', name: 'cost',
field: 'cost', field: 'right',
align: 'center', align: 'right',
}, },
{ {
label: 'Kg', label: 'Kg',
name: 'weight', name: 'weight',
field: 'weight', field: 'weight',
align: 'center', align: 'right',
style: (row) => highlightedRow(row), style: (row) => highlightedRow(row),
}, },
{ {
@ -136,7 +136,7 @@ const columns = computed(() => [
label: t('lastEntries.supplier'), label: t('lastEntries.supplier'),
name: 'supplier', name: 'supplier',
field: 'supplier', field: 'supplier',
align: 'center', align: 'left',
}, },
]); ]);
@ -269,7 +269,7 @@ function highlightedRow(row) {
</template> </template>
<template #body-cell-entry="{ row }"> <template #body-cell-entry="{ row }">
<QTd @click.stop :style="highlightedRow(row)"> <QTd @click.stop :style="highlightedRow(row)">
<div class="full-width flex justify-center"> <div class="full-width text-right">
<EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense /> <EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense />
<span class="link">{{ row.entryFk }}</span> <span class="link">{{ row.entryFk }}</span>
</div> </div>
@ -282,16 +282,16 @@ function highlightedRow(row) {
</QTd> </QTd>
</template> </template>
<template #body-cell-printedStickers="{ row }"> <template #body-cell-printedStickers="{ row }">
<QTd @click.stop class="text-center" :style="highlightedRow(row)"> <QTd @click.stop class="text-right" :style="highlightedRow(row)">
<span style="color: var(--vn-label-color)"> <span style="color: var(--vn-label-color)">
{{ row.printedStickers }}</span {{ row.printedStickers }}</span
> >
</QTd> </QTd>
</template> </template>
<template #body-cell-packing="{ row }"> <template #body-cell-packing="{ row }">
<QTd @click.stop :style="highlightedRow(row)"> <QTd @click.stop :style="highlightedRow(row)" class="text-right">
<QBadge <QBadge
class="center-content" class="grouping-badge"
:class="getBadgeClass(row.groupingMode, 'packing')" :class="getBadgeClass(row.groupingMode, 'packing')"
rounded rounded
> >
@ -301,9 +301,9 @@ function highlightedRow(row) {
</QTd> </QTd>
</template> </template>
<template #body-cell-grouping="{ row }"> <template #body-cell-grouping="{ row }">
<QTd @click.stop :style="highlightedRow(row)"> <QTd @click.stop :style="highlightedRow(row)" class="text-right">
<QBadge <QBadge
class="center-content" class="grouping-badge"
:class="getBadgeClass(row.groupingMode, 'grouping')" :class="getBadgeClass(row.groupingMode, 'grouping')"
rounded rounded
> >
@ -313,7 +313,7 @@ function highlightedRow(row) {
</QTd> </QTd>
</template> </template>
<template #body-cell-cost="{ row }"> <template #body-cell-cost="{ row }">
<QTd @click.stop class="text-center" :style="highlightedRow(row)"> <QTd @click.stop class="text-right" :style="highlightedRow(row)">
<span> <span>
{{ toCurrency(row.cost, 'EUR', 3) }} {{ toCurrency(row.cost, 'EUR', 3) }}
<QTooltip> <QTooltip>
@ -357,10 +357,7 @@ function highlightedRow(row) {
.q-badge--rounded { .q-badge--rounded {
border-radius: 50%; border-radius: 50%;
} }
.center-content { .grouping-badge {
display: flex;
max-width: max-content;
margin: auto;
padding: 0 11px; padding: 0 11px;
height: 28px; height: 28px;
} }

View File

@ -58,7 +58,7 @@ const columns = computed(() => [
{ {
label: t('shelvings.item'), label: t('shelvings.item'),
name: 'itemFk', name: 'itemFk',
align: 'left', align: 'right',
columnFilter: false, columnFilter: false,
}, },
{ {
@ -102,19 +102,20 @@ const columns = computed(() => [
name: 'label', name: 'label',
align: 'left', align: 'left',
columnFilter: { inWhere: true }, columnFilter: { inWhere: true },
component: 'number',
format: (row) => (row.stock / row.packing).toFixed(2), format: (row) => (row.stock / row.packing).toFixed(2),
}, },
{ {
label: t('shelvings.packing'), label: t('shelvings.packing'),
name: 'packing', name: 'packing',
attrs: { inWhere: true }, attrs: { inWhere: true },
align: 'left', component: 'number',
}, },
{ {
label: t('globals.visible'), label: t('globals.visible'),
name: 'stock', name: 'stock',
attrs: { inWhere: true }, attrs: { inWhere: true },
align: 'left', component: 'number',
}, },
]); ]);
@ -138,21 +139,12 @@ watchEffect(selectedRows);
<template> <template>
<template v-if="stateStore.isHeaderMounted()"> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#st-data"> <Teleport to="#st-data">
<div class="q-pa-md q-mr-lg q-ma-xs" style="border: 2px solid #222"> <QCardSection class="column items-center" horizontal>
<QCardSection horizontal> <div>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="details-label">{{ t('shelvings.totalLabels') }} </span>
{{ t('shelvings.total') }} <span>: {{ totalLabels }}</span>
</span> </div>
</QCardSection> </QCardSection>
<QCardSection class="column items-center" horizontal>
<div>
<span class="details-label"
>{{ t('shelvings.totalLabels') }}
</span>
<span>: {{ totalLabels }}</span>
</div></QCardSection
>
</div>
</Teleport> </Teleport>
<Teleport to="#st-actions"> <Teleport to="#st-actions">
<QBtn <QBtn

View File

@ -2,6 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { dashIfEmpty } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
@ -48,7 +49,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
<ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" /> <ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" />
</template> </template>
<template #body="{ entity: { item, tags, visible, available, botanical } }"> <template #body="{ entity: { item, tags, visible, available, botanical } }">
<QCard class="vn-one photo"> <QCard class="vn-one photo" v-if="$route.name != 'ItemSummary'">
<ItemDescriptorImage <ItemDescriptorImage
:entity-id="entityId" :entity-id="entityId"
:visible="visible" :visible="visible"
@ -56,84 +57,108 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
:show-edit-button="false" :show-edit-button="false"
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-two">
<VnTitle <VnTitle
:url="getUrl(entityId, 'basic-data')" :url="getUrl(entityId, 'basic-data')"
:text="t('globals.summary.basicData')" :text="t('globals.summary.basicData')"
/> />
<VnLv :label="t('globals.name')" :value="item.name" /> <div class="vn-card-group">
<VnLv :label="t('item.summary.completeName')" :value="item.longName" /> <div class="vn-card-content">
<VnLv :label="t('item.summary.family')" :value="item.itemType.name" /> <VnLv :label="t('globals.name')" :value="item.name" />
<VnLv :label="t('globals.size')" :value="item.size" /> <VnLv
<VnLv :label="t('globals.origin')" :value="item.origin.name" /> :label="t('item.summary.completeName')"
<VnLv :label="t('item.summary.stems')" :value="item.stems" /> :value="item.longName"
<VnLv />
:label="t('item.summary.multiplier')" <VnLv
:value="item.stemMultiplier" :label="t('item.summary.family')"
/> :value="item.itemType.name"
/>
<VnLv :label="t('globals.size')" :value="item.size" />
<VnLv :label="t('globals.origin')" :value="item.origin.name" />
<VnLv :label="t('item.summary.stems')" :value="item.stems" />
<VnLv
:label="t('item.summary.multiplier')"
:value="item.stemMultiplier"
/>
<VnLv :label="t('item.summary.buyer')"> <VnLv :label="t('item.summary.buyer')">
<template #value> <template #value>
<VnUserLink <VnUserLink
:name="item.itemType.worker.user.name" :name="item.itemType.worker.user.name"
:worker-id="item.itemType.worker.id" :worker-id="item.itemType.worker.id"
/>
</template>
</VnLv>
<VnLv :info="t('Este artículo necesita una foto')">
<template #value>
<QCheckbox
:label="t('item.summary.doPhoto')"
v-model="item.isPhotoRequested"
:disable="true"
size="xs"
/>
</template>
</VnLv>
<VnLv :label="t('globals.description')">
<template #value>
<span
style="
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
"
v-text="dashIfEmpty(item.description)"
/>
</template>
</VnLv>
</div>
<div class="vn-card-content">
<VnLv
:label="t('item.summary.intrastatCode')"
:value="item.intrastat.id"
/> />
</template> <VnLv
</VnLv> :label="t('globals.intrastat')"
<VnLv :info="t('Este artículo necesita una foto')"> :value="item.intrastat.description"
<template #value>
<QCheckbox
:label="t('item.summary.doPhoto')"
v-model="item.isPhotoRequested"
:disable="true"
/> />
</template> <VnLv :label="t('item.summary.ref')" :value="item.comment" />
</VnLv> <VnLv
</QCard> :label="t('item.summary.relevance')"
<QCard class="vn-one"> :value="item.relevancy"
<VnTitle />
:url="getUrl(entityId, 'basic-data')" <VnLv
:text="t('item.summary.otherData')" :label="t('item.summary.weight')"
/> :value="item.weightByPiece"
<VnLv />
:label="t('item.summary.intrastatCode')" <VnLv :label="t('item.summary.units')" :value="item.packingOut" />
:value="item.intrastat.id" <VnLv
/> :label="t('item.summary.expense')"
<VnLv :value="item.expense.name"
:label="t('globals.intrastat')" />
:value="item.intrastat.description" <VnLv
/> :label="t('item.summary.generic')"
<VnLv :label="t('item.summary.ref')" :value="item.comment" /> :value="item.genericFk"
<VnLv :label="t('item.summary.relevance')" :value="item.relevancy" /> />
<VnLv :label="t('item.summary.weight')" :value="item.weightByPiece" /> <VnLv
<VnLv :label="t('item.summary.units')" :value="item.packingOut" /> :label="t('item.summary.recycledPlastic')"
<VnLv :label="t('item.summary.expense')" :value="item.expense.name" /> :value="item.recycledPlastic"
<VnLv :label="t('item.summary.generic')" :value="item.genericFk" /> />
<VnLv <VnLv
:label="t('item.summary.recycledPlastic')" :label="t('item.summary.nonRecycledPlastic')"
:value="item.recycledPlastic" :value="item.nonRecycledPlastic"
/> />
<VnLv </div>
:label="t('item.summary.nonRecycledPlastic')" </div>
:value="item.nonRecycledPlastic"
/>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle :url="getUrl(entityId, 'tags')" :text="t('globals.tags')" /> <VnTitle :url="getUrl(entityId, 'tags')" :text="t('globals.tags')" />
<VnLv <VnLv
v-for="(tag, index) in tags" v-for="(tag, index) in tags"
:key="index" :key="index"
:label="`${tag.priority} ${tag.tag.name}:`" :label="`${tag.priority} ${tag.tag.name}`"
:value="tag.value" :value="tag.value"
/> />
</QCard> </QCard>
<QCard class="vn-one" v-if="item.description">
<VnTitle
:url="getUrl(entityId, 'basic-data')"
:text="t('globals.description')"
/>
<p v-text="item.description" />
</QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" /> <VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" />
<VnLv <VnLv

View File

@ -17,7 +17,7 @@ const itemTagsRef = ref();
const tagOptions = ref([]); const tagOptions = ref([]);
const valueOptionsMap = ref(new Map()); const valueOptionsMap = ref(new Map());
const getSelectedTagValues = async (tag) => { const getSelectedTagValues = async (tag) => {
if (!tag.tagFk && tag.tag.isFree) return; if (!tag.tagFk && tag.tag?.isFree) return;
const filter = { const filter = {
fields: ['value'], fields: ['value'],
order: 'value ASC', order: 'value ASC',
@ -25,6 +25,7 @@ const getSelectedTagValues = async (tag) => {
}; };
const params = { filter: JSON.stringify(filter) }; const params = { filter: JSON.stringify(filter) };
if (!tag.tagFk) return;
const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, { const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, {
params, params,
}); });
@ -82,7 +83,6 @@ const insertTag = (rows) => {
value: undefined, value: undefined,
name: undefined, name: undefined,
}, },
}" }"
:data-default="{ :data-default="{
tag: { tag: {
@ -113,7 +113,7 @@ const insertTag = (rows) => {
<VnRow <VnRow
v-for="(row, index) in rows" v-for="(row, index) in rows"
:key="index" :key="index"
class="items-center" class="items-center q-py-sm"
> >
<VnSelect <VnSelect
:label="t('itemTags.tag')" :label="t('itemTags.tag')"
@ -139,9 +139,7 @@ const insertTag = (rows) => {
emit-value emit-value
use-input use-input
class="col" class="col"
:is-clearable="false"
:required="false" :required="false"
:rules="validate('itemTag.tagFk')"
:use-like="false" :use-like="false"
sort-by="value" sort-by="value"
/> />
@ -152,7 +150,6 @@ const insertTag = (rows) => {
v-model="row.value" v-model="row.value"
:label="t('itemTags.value')" :label="t('itemTags.value')"
:is-clearable="false" :is-clearable="false"
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
:data-cy="`tag${row?.tag?.name}Value`" :data-cy="`tag${row?.tag?.name}Value`"
/> />
<VnInput <VnInput
@ -161,7 +158,6 @@ const insertTag = (rows) => {
v-model="row.priority" v-model="row.priority"
:required="true" :required="true"
:rules="validate('itemTag.priority')" :rules="validate('itemTag.priority')"
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
/> />
<div class="row justify-center" style="flex: 0"> <div class="row justify-center" style="flex: 0">
<QIcon <QIcon
@ -188,11 +184,8 @@ const insertTag = (rows) => {
v-shortcut="'+'" v-shortcut="'+'"
fab fab
data-cy="createNewTag" data-cy="createNewTag"
> :title="t('globals.add')"
<QTooltip> />
{{ t('itemTags.addTag') }}
</QTooltip>
</QBtn>
</QPageSticky> </QPageSticky>
</template> </template>
</CrudModel> </CrudModel>

View File

@ -64,27 +64,29 @@ const submitTaxes = async (data) => {
auto-load auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QCard class="q-px-lg q-py-md"> <div style="display: flex; justify-content: center">
<VnRow <QCard class="q-px-lg q-py-md">
v-for="(row, index) in rows" <VnRow
:key="index" v-for="(row, index) in rows"
class="row q-gutter-md q-mb-md" :key="index"
> class="row q-gutter-md q-mb-md"
<VnInput >
:label="t('tax.country')" <VnInput
v-model="row.country.name" :label="t('tax.country')"
disable v-model="row.country.name"
/> disable
<VnSelect />
:label="t('tax.class')" <VnSelect
v-model="row.taxClassFk" :label="t('tax.class')"
:options="taxesOptions" v-model="row.taxClassFk"
option-label="description" :options="taxesOptions"
option-value="id" option-label="description"
hide-selected option-value="id"
/> hide-selected
</VnRow> />
</QCard> </VnRow>
</QCard>
</div>
</template> </template>
</CrudModel> </CrudModel>
</template> </template>

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { ref, computed, onBeforeMount } from 'vue'; import { ref, computed, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
@ -18,16 +17,13 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import axios from 'axios'; import axios from 'axios';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
const entityId = computed(() => route.params.id);
const { openCloneDialog } = cloneItem(); const { openCloneDialog } = cloneItem();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const route = useRoute();
const dataKey = 'ItemList'; const dataKey = 'ItemList';
const validPriorities = ref([]); const validPriorities = ref([]);
const defaultTag = ref(); const defaultItem = ref(null);
const defaultPriority = ref();
const itemFilter = { const itemFilter = {
include: [ include: [
@ -59,15 +55,14 @@ const itemFilter = {
}; };
const columns = computed(() => [ const columns = computed(() => [
{ {
label: '',
name: 'image', name: 'image',
align: 'left', align: 'left',
columnFilter: false, columnFilter: false,
}, },
{ {
align: 'right',
label: t('item.list.id'), label: t('item.list.id'),
name: 'id', name: 'id',
align: 'left',
isId: true, isId: true,
chip: { chip: {
condition: () => true, condition: () => true,
@ -75,36 +70,36 @@ const columns = computed(() => [
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'right',
label: t('entry.summary.grouping'), label: t('entry.summary.grouping'),
name: 'grouping', name: 'grouping',
align: 'left',
columnFilter: { columnFilter: {
component: 'number', component: 'number',
inWhere: true, inWhere: true,
}, },
}, },
{ {
align: 'right',
label: t('entry.summary.packing'), label: t('entry.summary.packing'),
name: 'packing', name: 'packing',
align: 'left',
columnFilter: { columnFilter: {
component: 'number', component: 'number',
inWhere: true, inWhere: true,
}, },
}, },
{ {
align: 'left',
label: t('globals.description'), label: t('globals.description'),
name: 'description', name: 'description',
align: 'left',
columnFilter: { columnFilter: {
name: 'search', name: 'search',
}, },
columnClass: 'expand', columnClass: 'expand',
}, },
{ {
align: 'right',
label: t('item.list.stems'), label: t('item.list.stems'),
name: 'stems', name: 'stems',
align: 'left',
columnFilter: { columnFilter: {
component: 'number', component: 'number',
inWhere: true, inWhere: true,
@ -112,19 +107,20 @@ const columns = computed(() => [
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'right',
label: t('globals.size'), label: t('globals.size'),
name: 'size', name: 'size',
align: 'left',
columnFilter: { columnFilter: {
component: 'number', component: 'number',
inWhere: true, inWhere: true,
}, },
cardVisible: true, cardVisible: true,
columnClass: 'expand',
}, },
{ {
align: 'left',
label: t('item.list.typeName'), label: t('item.list.typeName'),
name: 'typeFk', name: 'typeFk',
align: 'left',
component: 'select', component: 'select',
attrs: { attrs: {
url: 'ItemTypes', url: 'ItemTypes',
@ -173,26 +169,17 @@ const columns = computed(() => [
}, },
{ {
label: t('globals.intrastat'), label: t('globals.intrastat'),
name: 'intrastat', name: 'intrastatFk',
align: 'left', align: 'left',
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Intrastats', url: 'Intrastats',
optionValue: 'description', fields: ['id', 'description'],
optionLabel: 'description', optionLabel: 'description',
}, optionValue: 'id',
columnFilter: {
name: 'intrastat',
attrs: {
url: 'Intrastats',
optionValue: 'description',
optionLabel: 'description',
},
},
columnField: {
component: null,
}, },
cardVisible: true, cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.intrastat),
}, },
{ {
label: t('item.list.origin'), label: t('item.list.origin'),
@ -238,21 +225,15 @@ const columns = computed(() => [
}, },
}, },
{ {
label: t('item.list.weight'), label: t('item.list.weightByPiece'),
toolTip: t('item.list.weightByPiece'), toolTip: t('item.list.weightByPiece'),
name: 'weightByPiece', name: 'weightByPiece',
component: 'input', component: 'input',
columnField: {
component: null,
},
columnFilter: {
inWhere: true,
},
}, },
{ {
align: 'right',
label: t('item.list.stemMultiplier'), label: t('item.list.stemMultiplier'),
name: 'stemMultiplier', name: 'stemMultiplier',
align: 'left',
component: 'input', component: 'input',
columnField: { columnField: {
component: null, component: null,
@ -301,7 +282,6 @@ const columns = computed(() => [
actions: [ actions: [
{ {
title: t('globals.clone'), title: t('globals.clone'),
icon: 'vn:clone', icon: 'vn:clone',
action: openCloneDialog, action: openCloneDialog,
isPrimary: true, isPrimary: true,
@ -317,15 +297,10 @@ const columns = computed(() => [
]); ]);
onBeforeMount(async () => { onBeforeMount(async () => {
const { data } = await axios.get('ItemConfigs'); const { data } = await axios.get('ItemConfigs/findOne');
defaultTag.value = data[0].defaultTag; defaultItem.value = data;
defaultPriority.value = data[0].defaultPriority;
data.forEach((priority) => {
validPriorities.value = priority.validPriorities;
});
}); });
</script> </script>
<template> <template>
<VnSection <VnSection
:data-key="dataKey" :data-key="dataKey"
@ -338,27 +313,29 @@ onBeforeMount(async () => {
}" }"
> >
<template #advanced-menu> <template #advanced-menu>
<ItemListFilter data-key="ItemList" /> <ItemListFilter :data-key="dataKey" />
</template> </template>
<template #body> <template #body>
<VnTable <VnTable
v-if="defaultTag" v-if="defaultItem"
ref="tableRef"
:data-key="dataKey" :data-key="dataKey"
:columns="columns" ref="tableRef"
:right-search="false" search-url="ItemList"
redirect="Item" url="Items/filter"
:filter="itemFilter"
:create="{ :create="{
urlCreate: 'Items/new', urlCreate: 'Items/new',
title: t('item.list.newItem'), title: t('item.list.newItem'),
onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`), onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`),
formInitialData: { formInitialData: {
editorFk: entityId, tag: defaultItem?.defaultTag,
tag: defaultTag, priority: defaultItem?.defaultPriority,
priority: defaultPriority,
}, },
}" }"
:is-editable="false" :columns="columns"
redirect="Item"
:right-search="false"
auto-load
> >
<template #column-image="{ row }"> <template #column-image="{ row }">
<VnImg <VnImg
@ -374,10 +351,18 @@ onBeforeMount(async () => {
<ItemDescriptorProxy :id="row.id" /> <ItemDescriptorProxy :id="row.id" />
</span> </span>
</template> </template>
<template #column-description="{ row }">
<span class="row column full-width justify-between items-start">
{{ row?.name }}
<span class="subName">
{{ row?.subName?.toUpperCase() }} &nbsp;
</span>
</span>
<FetchedTags :item="row" :columns="3" />
</template>
<template #column-typeName="{ row }"> <template #column-typeName="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
{{ row.typeName }} {{ row.typeName }}
{{ row.typeFk }}
<ItemTypeDescriptorProxy :id="row.typeFk" /> <ItemTypeDescriptorProxy :id="row.typeFk" />
</span> </span>
</template> </template>
@ -387,20 +372,11 @@ onBeforeMount(async () => {
<WorkerDescriptorProxy :id="row.buyerFk" /> <WorkerDescriptorProxy :id="row.buyerFk" />
</span> </span>
</template> </template>
<template #column-description="{ row }">
<div class="row column full-width justify-between items-start">
{{ row?.name }}
<div v-if="row?.subName" class="subName">
{{ row?.subName.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row" :columns="3" />
</template>
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnInput <VnInput
v-model="data.provisionalName" v-model="data.provisionalName"
:label="t('globals.description')" :label="t('Provisional name')"
:is-required="true" :required="true"
/> />
<VnSelect <VnSelect
url="Tags" url="Tags"
@ -410,7 +386,7 @@ onBeforeMount(async () => {
option-label="name" option-label="name"
option-value="id" option-value="id"
:is-required="true" :is-required="true"
:sort-by="['name ASC']" :order="['name ASC']"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -427,7 +403,7 @@ onBeforeMount(async () => {
:options="validPriorities" :options="validPriorities"
v-model="data.priority" v-model="data.priority"
:label="t('item.create.priority')" :label="t('item.create.priority')"
:is-required="true" :required="true"
/> />
<VnSelect <VnSelect
url="ItemTypes" url="ItemTypes"
@ -436,7 +412,7 @@ onBeforeMount(async () => {
:fields="['id', 'code', 'name']" :fields="['id', 'code', 'name']"
option-label="name" option-label="name"
option-value="id" option-value="id"
:is-required="true" :required="true"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -456,7 +432,7 @@ onBeforeMount(async () => {
:fields="['id', 'description']" :fields="['id', 'description']"
option-label="description" option-label="description"
option-value="id" option-value="id"
:is-required="true" :required="true"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -476,7 +452,7 @@ onBeforeMount(async () => {
:fields="['id', 'code', 'name']" :fields="['id', 'code', 'name']"
option-label="code" option-label="code"
option-value="id" option-value="id"
:is-required="true" :required="true"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -503,7 +479,5 @@ onBeforeMount(async () => {
</style> </style>
<i18n> <i18n>
es: es:
New item: Nuevo artículo Provisional name: Nombre provisional
Create Item: Crear artículo
You can search by id: Puedes buscar por id
</i18n> </i18n>

View File

@ -8,7 +8,6 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue'; import VnCheckbox from 'src/components/common/VnCheckbox.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import axios from 'axios'; import axios from 'axios';
@ -85,26 +84,6 @@ const removeTag = (index, params, search) => {
applyTags(params, search); applyTags(params, search);
}; };
const applyFieldFilters = (params) => {
fieldFiltersValues.value.forEach((fieldFilter) => {
if (
fieldFilter.selectedField &&
(fieldFilter.value !== null ||
fieldFilter.value !== '' ||
fieldFilter.value !== undefined)
) {
params[fieldFilter.name] = fieldFilter.value;
}
});
arrayData.applyFilter({ params });
};
const removeFieldFilter = (index, params, search) => {
delete params[fieldFiltersValues.value[index].name];
(fieldFiltersValues.value || []).splice(index, 1);
applyFieldFilters(params, search);
};
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
if (arrayData.store?.userParams?.categoryFk) if (arrayData.store?.userParams?.categoryFk)
@ -125,7 +104,6 @@ onMounted(async () => {
}); });
}); });
// Fill fieldFiltersValues with existent userParams
if (arrayData.store?.userParams) { if (arrayData.store?.userParams) {
fieldFiltersValues.value = Object.entries(arrayData.store?.userParams) fieldFiltersValues.value = Object.entries(arrayData.store?.userParams)
.filter(([key, value]) => value && _moreFields.includes(key)) .filter(([key, value]) => value && _moreFields.includes(key))
@ -249,6 +227,16 @@ onMounted(async () => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.isFloramondo')"
v-model="params.isFloramondo"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<!-- Tags filter --> <!-- Tags filter -->
<QItemLabel header> <QItemLabel header>
{{ t('params.tags') }} {{ t('params.tags') }}
@ -315,74 +303,6 @@ onMounted(async () => {
@click="removeTag(index, params, searchFn)" @click="removeTag(index, params, searchFn)"
/> />
</QItem> </QItem>
<!-- Filter fields -->
<QItemLabel header
>{{ t('More fields') }}
<QIcon
name="add_circle"
class="fill-icon-on-hover q-ml-md"
size="sm"
color="primary"
@click="fieldFiltersValues.push({})"
/></QItemLabel>
<QItem v-for="(fieldFilter, index) in fieldFiltersValues" :key="index">
<QItemSection class="col">
<VnSelect
class="full-width"
:label="t('params.tag')"
:model-value="fieldFilter.selectedField"
:options="moreFields"
option-label="label"
option-value="label"
dense
filled
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="
($event) => {
fieldFilter.name = $event.name;
fieldFilter.value = null;
fieldFilter.selectedField = $event;
}
"
/>
</QItemSection>
<QItemSection class="col">
<VnCheckbox
v-if="fieldFilter.selectedField?.type === 'boolean'"
v-model="fieldFilter.value"
:label="t('params.value')"
@update:model-value="applyFieldFilters(params, searchFn)"
/>
<VnInput
v-else
v-model="fieldFilter.value"
:label="t('params.value')"
:disable="!fieldFilter.selectedField"
filled
@keydown.enter="applyFieldFilters(params, searchFn)"
/>
</QItemSection>
<QItemSection side
><QIcon
name="delete"
class="fill-icon-on-hover q-ml-xs"
size="sm"
color="primary"
@click="removeFieldFilter(index, params, searchFn)"
/></QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.isFloramondo')"
v-model="params.isFloramondo"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>
@ -410,6 +330,17 @@ en:
Green: Green Green: Green
Handmade: Handmade Handmade: Handmade
Plant: Plant Plant: Plant
packing: Packing
grouping: Grouping
stems: Stems
size: Size
intrastatFk: Intrastat
ori:
id: Origin
workerFk: Buyer
weightByPiece: Weight/stem
stemMultiplier: Stem multiplier
landed: Landed date
es: es:
More fields: Más campos More fields: Más campos
params: params:
@ -433,4 +364,15 @@ es:
Green: Verde Green: Verde
Handmade: Hecho a mano Handmade: Hecho a mano
Plant: Planta Plant: Planta
packing: Packing
grouping: Grouping
stems: Tallos
size: Altura
intrastatFk: Intrastat
ori:
id: Origen
workerFk: Comprador
weightByPiece: Peso/tallo
stemMultiplier: Multiplicador de tallos
landed: Fecha de entrega
</i18n> </i18n>

View File

@ -1,30 +1,27 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { toDate, dashIfEmpty } from 'src/filters';
import axios from 'axios'; import axios from 'axios';
import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
import { toDate } from 'src/filters';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemRequestFilter from './ItemRequestFilter.vue'; import ItemRequestFilter from './ItemRequestFilter.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const stateStore = useStateStore(); const stateStore = useStateStore();
const denyFormRef = ref(null); const denyFormRef = ref(null);
const denyRequestId = ref(null); const denyRequestId = ref(null);
const denyRequestIndex = ref(null);
const itemRequestsOptions = ref([]);
const userParams = { const userParams = {
state: 'pending',
daysOnward: 7, daysOnward: 7,
}; };
const tableRef = ref(); const tableRef = ref();
@ -34,9 +31,13 @@ onMounted(async () => {
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'id',
visible: false,
},
{
align: 'right',
label: t('globals.ticketId'), label: t('globals.ticketId'),
name: 'ticketFk', name: 'ticketFk',
align: 'left',
isId: true, isId: true,
chip: { chip: {
condition: () => true, condition: () => true,
@ -44,15 +45,16 @@ const columns = computed(() => [
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'center',
label: t('globals.shipped'), label: t('globals.shipped'),
name: 'shipped', name: 'shipped',
align: 'left',
component: 'date', component: 'date',
columnField: { columnField: {
component: null, component: null,
}, },
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)), format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)),
columnClass: 'shrink', columnClass: 'shrink',
isEditable: false,
}, },
{ {
label: t('globals.description'), label: t('globals.description'),
@ -78,6 +80,7 @@ const columns = computed(() => [
component: null, component: null,
}, },
columnClass: 'shrink', columnClass: 'shrink',
isEditable: false,
}, },
{ {
align: 'left', align: 'left',
@ -91,6 +94,7 @@ const columns = computed(() => [
component: null, component: null,
}, },
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName), format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
isEditable: false,
}, },
{ {
label: t('item.buyRequest.requested'), label: t('item.buyRequest.requested'),
@ -121,21 +125,52 @@ const columns = computed(() => [
component: null, component: null,
}, },
columnClass: 'shrink', columnClass: 'shrink',
isEditable: false,
}, },
{ {
label: t('globals.item'), label: t('globals.item'),
name: 'item', name: 'itemFk',
align: 'left', align: 'left',
component: 'input', component: 'input',
columnClass: 'expand', columnClass: 'expand',
isEditable: ({ isOk }) => isOk === null,
}, },
{ {
label: t('item.buyRequest.achieved'), label: t('item.buyRequest.achieved'),
name: 'achieved', name: 'saleQuantity',
align: 'left', align: 'left',
component: 'input', component: 'input',
columnClass: 'shrink', columnClass: 'shrink',
isEditable: ({ itemFk, isOk }) => {
if (itemFk && isOk === null) return true;
},
beforeDestroy: (row) => {
if (!row.saleQuantity) {
return tableRef.value.reload();
}
try {
axios
.post(`TicketRequests/${row.id}/confirm`, {
id: row.id,
itemFk: parseInt(row.itemFk),
quantity: parseInt(row.saleQuantity),
})
.then(() => {
axios
.get(`Items/findOne`, { where: { id: row.itemFk } })
.then((response) => {
row.itemDescription = response.data.name;
row.state = 1;
});
notify(t('globals.dataSaved'), 'positive');
return tableRef.value.reload();
});
} catch (error) {
notify(error.response.data.error.message, 'negative');
return tableRef.value.reload();
}
},
}, },
{ {
label: t('item.buyRequest.concept'), label: t('item.buyRequest.concept'),
@ -144,11 +179,12 @@ const columns = computed(() => [
sortable: true, sortable: true,
component: 'input', component: 'input',
columnClass: 'expand', columnClass: 'expand',
isEditable: false,
}, },
{ {
label: t('globals.state'), label: t('globals.state'),
name: 'state', name: 'state',
format: (row) => getState(row.isOk), format: ({ isOk }) => getState(isOk),
align: 'left', align: 'left',
}, },
{ {
@ -163,7 +199,23 @@ const columns = computed(() => [
{ {
align: 'right', align: 'right',
label: '', label: '',
name: 'denyOptions', name: 'tableActions',
actions: [
{
title: (row) => row.response,
icon: 'insert_drive_file',
isPrimary: true,
show: (row) => row?.response?.length,
},
{
title: t('Discard'),
icon: 'thumb_down',
fill: true,
isPrimary: true,
show: ({ isOk }) => isOk === null,
action: (row) => showDenyRequestForm(row.id),
},
],
}, },
]); ]);
@ -181,54 +233,17 @@ const getBadgeColor = (date) => {
if (difference > 0) return 'alert'; if (difference > 0) return 'alert';
}; };
const changeQuantity = async (request) => {
if (request.saleFk) {
const params = {
quantity: request.saleQuantity,
};
await axios.patch(`Sales/${request.saleFk}`, params);
}
await confirmRequest(request);
notify(t('globals.dataSaved'), 'positive');
};
const confirmRequest = async (request) => {
if (!request.itemFk || !request.saleQuantity) return;
const params = {
itemFk: request.itemFk,
quantity: request.saleQuantity,
attenderFk: request.attenderFk,
};
const { data } = await axios.post(`TicketRequests/${request.id}/confirm`, params);
request.itemDescription = data.concept;
request.isOk = true;
};
const getState = (isOk) => { const getState = (isOk) => {
if (isOk === null) return t('Pending'); if (isOk === null) return t('Pending');
else if (isOk) return t('Accepted'); else if (isOk) return t('Accepted');
else return t('Denied'); else return t('Denied');
}; };
const showDenyRequestForm = (requestId, rowIndex) => { const showDenyRequestForm = (requestId) => {
denyRequestId.value = requestId; denyRequestId.value = requestId;
denyRequestIndex.value = rowIndex;
denyFormRef.value.show(); denyFormRef.value.show();
}; };
const onDenyAccept = (_, responseData) => {
itemRequestsOptions.value[denyRequestIndex.value].isOk = responseData.isOk;
itemRequestsOptions.value[denyRequestIndex.value].attenderFk =
responseData.attenderFk;
itemRequestsOptions.value[denyRequestIndex.value].response = responseData.response;
denyRequestId.value = null;
denyRequestIndex.value = null;
tableRef.value.reload();
};
</script> </script>
<template> <template>
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
@ -240,12 +255,13 @@ const onDenyAccept = (_, responseData) => {
data-key="itemRequest" data-key="itemRequest"
url="ticketRequests/filter" url="ticketRequests/filter"
order="shipped ASC, isOk ASC" order="shipped ASC, isOk ASC"
:columns="columns"
:user-params="userParams" :user-params="userParams"
:right-search="false" :is-editable="true"
auto-load :columns="columns"
:disable-option="{ card: true }" :disable-option="{ card: true }"
chip-locale="item.params" :right-search="false"
:default-remove="false"
auto-load
> >
<template #column-ticketFk="{ row }"> <template #column-ticketFk="{ row }">
<span class="link"> <span class="link">
@ -254,16 +270,14 @@ const onDenyAccept = (_, responseData) => {
</span> </span>
</template> </template>
<template #column-shipped="{ row }"> <template #column-shipped="{ row }">
<QTd> <QBadge
<QBadge :color="getBadgeColor(row.shipped)"
:color="getBadgeColor(row.shipped)" text-color="black"
text-color="black" class="q-pa-xs"
class="q-pa-sm" style="font-size: 14px"
style="font-size: 14px" >
> {{ toDate(row.shipped) }}
{{ toDate(row.shipped) }} </QBadge>
</QBadge>
</QTd>
</template> </template>
<template #column-attenderName="{ row }"> <template #column-attenderName="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
@ -284,74 +298,34 @@ const onDenyAccept = (_, responseData) => {
<DepartmentDescriptorProxy :id="row.departmentFk" /> <DepartmentDescriptorProxy :id="row.departmentFk" />
</span> </span>
</template> </template>
<template #column-item="{ row }">
<span>
<VnInput v-model.number="row.itemFk" dense />
</span>
</template>
<template #column-achieved="{ row }">
<span>
<VnInput
ref="achievedRef"
type="number"
v-model.number="row.saleQuantity"
:disable="!row.itemFk || row.isOk != null"
@blur="changeQuantity(row)"
@keyup.enter="$refs.achievedRef.vnInputRef.blur()"
dense
/>
</span>
</template>
<template #column-concept="{ row }"> <template #column-concept="{ row }">
<span @click.stop disabled="row.isOk != null"> <span :class="{ link: row.itemDescription }" @click.stop>
{{ row.itemDescription }} {{ dashIfEmpty(row.itemDescription) }}
<ItemDescriptorProxy v-if="row.itemFk" :id="row.itemFk" />
</span> </span>
</template> </template>
<template #moreFilterPanel="{ params }">
<VnInputNumber
:label="t('params.scopeDays')"
v-model.number="params.daysOnward"
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
@remove="handleScopeDays()"
class="q-px-xs q-pr-lg"
filled
dense
lazy-rules
is-outlined
/>
</template>
<template #column-denyOptions="{ row, rowIndex }">
<QIcon
v-if="row.response?.length"
name="insert_drive_file"
color="primary"
size="sm"
>
<QTooltip>
{{ row.response }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.isOk == null"
name="thumb_down"
color="primary"
size="sm"
class="fill-icon"
@click="showDenyRequestForm(row.id, rowIndex)"
>
<QTooltip>
{{ t('Discard') }}
</QTooltip>
</QIcon>
</template>
</VnTable> </VnTable>
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale"> <QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
<ItemRequestDenyForm :request-id="denyRequestId" @on-data-saved="onDenyAccept" /> <FormModelPopup
:url-create="`TicketRequests/${denyRequestId}/deny`"
:title="t('Specify the reasons to deny this request')"
:form-initial-data="{ id: denyRequestId }"
@on-data-saved="tableRef.reload()"
>
<template #form-inputs="{ data }">
<VnInput
ref="textAreaRef"
type="textarea"
v-model="data.observation"
fill-input
:required="true"
auto-grow
data-cy="discardTextArea"
/>
</template>
</FormModelPopup>
</QDialog> </QDialog>
</template> </template>
<i18n> <i18n>
es: es:
Discard: Descartar Discard: Descartar

View File

@ -1,59 +0,0 @@
<script setup>
import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
defineProps({
requestId: {
type: Number,
default: null,
required: true,
},
});
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const textAreaRef = ref(null);
const bankEntityFormData = reactive({});
const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
};
onMounted(async () => {
await nextTick();
textAreaRef.value.focus();
});
</script>
<template>
<FormModelPopup
:url-create="`TicketRequests/${$props.requestId}/deny`"
:title="t('Specify the reasons to deny this request')"
:form-initial-data="bankEntityFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<VnRow>
<div class="col">
<VnInput
ref="textAreaRef"
type="textarea"
v-model="data.observation"
fill-input
:required="true"
autogrow
/>
</div>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
Specify the reasons to deny this request: Especifica las razones para descartar la petición
</i18n>

View File

@ -189,7 +189,7 @@ onMounted(async () => {
<QCheckbox <QCheckbox
:label="t('params.mine')" :label="t('params.mine')"
v-model="params.mine" v-model="params.mine"
:toggle-indeterminate="false" :toggle-indeterminate="null"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -212,6 +212,13 @@ en:
state: State state: State
daysOnward: Days onward daysOnward: Days onward
myTeam: My team myTeam: My team
shipped: Shipped
description: Description
departmentFk: Department
quantity: Quantity
price: Price
item: Item
concept: Concept
dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time
denied: Denied denied: Denied
accepted: Accepted accepted: Accepted
@ -230,6 +237,13 @@ es:
state: Estado state: Estado
daysOnward: Días en adelante daysOnward: Días en adelante
myTeam: Mi equipo myTeam: Mi equipo
shipped: Enviado
description: Descripción
departmentFk: Departamento
quantity: Cantidad
price: Precio
item: Artículo
concept: Concepto
dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez
denied: Denegada denied: Denegada
accepted: Aceptada accepted: Aceptada

View File

@ -42,11 +42,11 @@ const itemPackingTypesOptions = ref([]);
/> />
<FormModel :url-update="`ItemTypes/${route.params.id}`" model="ItemType" auto-load> <FormModel :url-update="`ItemTypes/${route.params.id}`" model="ItemType" auto-load>
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow class="q-py-sm">
<VnInput v-model="data.code" :label="t('itemType.shared.code')" /> <VnInput v-model="data.code" :label="t('itemType.shared.code')" />
<VnInput v-model="data.name" :label="t('itemType.shared.name')" /> <VnInput v-model="data.name" :label="t('itemType.shared.name')" />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnSelect <VnSelect
url="Workers/search" url="Workers/search"
v-model="data.workerFk" v-model="data.workerFk"
@ -58,11 +58,7 @@ const itemPackingTypesOptions = ref([]);
hide-selected hide-selected
> >
<template #prepend> <template #prepend>
<VnAvatar <VnAvatar :worker-id="data.workerFk" color="primary" />
:worker-id="data.workerFk"
color="primary"
:title="title"
/>
</template> </template>
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -85,7 +81,7 @@ const itemPackingTypesOptions = ref([]);
hide-selected hide-selected
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnSelect <VnSelect
v-model="data.temperatureFk" v-model="data.temperatureFk"
:label="t('itemType.shared.temperature')" :label="t('itemType.shared.temperature')"
@ -96,7 +92,7 @@ const itemPackingTypesOptions = ref([]);
/> />
<VnInput v-model="data.life" :label="t('itemType.summary.life')" /> <VnInput v-model="data.life" :label="t('itemType.summary.life')" />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnSelect <VnSelect
v-model="data.itemPackingTypeFk" v-model="data.itemPackingTypeFk"
:label="t('itemType.shared.itemPackingType')" :label="t('itemType.shared.itemPackingType')"
@ -107,7 +103,7 @@ const itemPackingTypesOptions = ref([]);
/> />
<VnInput v-model="data.maxRefs" :label="t('itemType.shared.maxRefs')" /> <VnInput v-model="data.maxRefs" :label="t('itemType.shared.maxRefs')" />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<QCheckbox <QCheckbox
v-model="data.isFragile" v-model="data.isFragile"
:label="t('itemType.shared.fragile')" :label="t('itemType.shared.fragile')"

View File

@ -8,7 +8,8 @@ import filter from './ItemTypeFilter.js';
<VnCard <VnCard
data-key="ItemType" data-key="ItemType"
url="ItemTypes" url="ItemTypes"
:filter="filter" :filter
:id-in-where="true"
:descriptor="ItemTypeDescriptor" :descriptor="ItemTypeDescriptor"
/> />
</template> </template>

View File

@ -30,7 +30,6 @@ const entityId = computed(() => {
:filter="filter" :filter="filter"
title="code" title="code"
data-key="ItemType" data-key="ItemType"
:to-module="{ name: 'ItemTypeList' }"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="$t('itemType.shared.code')" :value="entity.code" /> <VnLv :label="$t('itemType.shared.code')" :value="entity.code" />

View File

@ -17,3 +17,10 @@ itemType:
isUnconventionalSize: Is unconventional size isUnconventionalSize: Is unconventional size
search: Search item type search: Search item type
searchInfo: Search item type by id, name or code searchInfo: Search item type by id, name or code
params:
id: Id
code: Code
name: Name
categoryFk: Category
workerFk: Comprador
temperatureFk: Temperature

View File

@ -17,3 +17,10 @@ itemType:
isUnconventionalSize: Es de tamaño poco convencional isUnconventionalSize: Es de tamaño poco convencional
search: Buscar familia search: Buscar familia
searchInfo: Buscar familia por id, nombre o código searchInfo: Buscar familia por id, nombre o código
params:
id: Id
code: Código
name: Nombre
categoryFk: Reino
workerFk: Comprador
temperatureFk: Temperatura

View File

@ -25,6 +25,10 @@ const exprBuilder = (param, value) => {
return { return {
code: { like: `%${value}%` }, code: { like: `%${value}%` },
}; };
case 'temperatureFk':
return {
temperatureFk: value,
};
case 'search': case 'search':
if (value) { if (value) {
if (!isNaN(value)) { if (!isNaN(value)) {
@ -51,16 +55,19 @@ const exprBuilder = (param, value) => {
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'right',
name: 'id', name: 'id',
label: t('id'), label: 'Id',
isId: true, isId: true,
columnFilter: {
inWhere: true,
},
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
name: 'code', name: 'code',
label: t('code'), label: t('itemType.shared.code'),
isTitle: true, isTitle: true,
cardVisible: true, cardVisible: true,
}, },
@ -71,8 +78,7 @@ const columns = computed(() => [
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'left', label: t('itemType.shared.worker'),
label: t('worker'),
name: 'workerFk', name: 'workerFk',
component: 'select', component: 'select',
attrs: { attrs: {
@ -80,7 +86,6 @@ const columns = computed(() => [
optionLabel: 'nickname', optionLabel: 'nickname',
optionValue: 'id', optionValue: 'id',
}, },
format: (row) => row.worker?.user?.name,
cardVisible: true, cardVisible: true,
columnField: { component: null }, columnField: { component: null },
columnFilter: { columnFilter: {
@ -95,6 +100,7 @@ const columns = computed(() => [
}, },
inWhere: true, inWhere: true,
}, },
format: (row) => row.worker?.user?.name,
}, },
{ {
align: 'left', align: 'left',
@ -104,19 +110,24 @@ const columns = computed(() => [
attrs: { attrs: {
options: itemCategoriesOptions.value, options: itemCategoriesOptions.value,
}, },
columnFilter: {
inWhere: true,
},
cardVisible: false, cardVisible: false,
visible: false, format: (row, dashIfEmpty) => dashIfEmpty(row.category?.name),
}, },
{ {
align: 'left', align: 'left',
name: 'Temperature', name: 'temperatureFk',
label: t('Temperature'), label: t('Temperature'),
component: 'select', component: 'select',
attrs: { attrs: {
options: temperatureOptions.value, options: temperatureOptions.value,
}, },
columnFilter: {
inWhere: true,
},
cardVisible: false, cardVisible: false,
visible: false,
}, },
]); ]);
</script> </script>
@ -141,20 +152,28 @@ const columns = computed(() => [
:array-data-props="{ :array-data-props="{
url: 'ItemTypes', url: 'ItemTypes',
order: 'name ASC', order: 'name ASC',
exprBuilder, exprBuilder: exprBuilder,
userFilter: { userFilter: {
include: { include: [
relation: 'worker', {
scope: { relation: 'worker',
fields: ['id'], scope: {
include: { fields: ['id'],
relation: 'user', include: {
scope: { relation: 'user',
fields: ['id', 'name'], scope: {
fields: ['id', 'name'],
},
}, },
}, },
}, },
}, {
relation: 'category',
scope: {
fields: ['id', 'name'],
},
},
],
}, },
}" }"
> >
@ -169,7 +188,7 @@ const columns = computed(() => [
formInitialData: {}, formInitialData: {},
}" }"
:columns="columns" :columns="columns"
auto-load :right-search="false"
redirect="item/item-type" redirect="item/item-type"
> >
<template #column-workerFk="{ row }"> <template #column-workerFk="{ row }">
@ -208,15 +227,17 @@ const columns = computed(() => [
<i18n> <i18n>
es: es:
id: Id params:
code: Código id: Id
worker: Trabajador code: Código
ItemCategory: Reino worker: Trabajador
Temperature: Temperatura ItemCategory: Reino
Create ItemTypes: Crear familia Temperature: Temperatura
Create ItemTypes: Crear familia
en: en:
code: Code params:
worker: Worker code: Code
ItemCategory: ItemCategory worker: Worker
Temperature: Temperature ItemCategory: ItemCategory
Temperature: Temperature
</i18n> </i18n>

View File

@ -74,12 +74,13 @@ const closeForm = () => {
class="editOption" class="editOption"
:label="t('Field to edit')" :label="t('Field to edit')"
:options="fieldsOptions" :options="fieldsOptions"
hide-selected
option-label="label" option-label="label"
option-value="name"
v-model="selectedField" v-model="selectedField"
data-cy="EditFixedPriceSelectOption" data-cy="EditFixedPriceSelectOption"
@update:model-value="newValue = null" @update:model-value="newValue = null"
:class="{ 'is-select': selectedField?.component === 'select' }" :class="{ 'is-select': selectedField?.component === 'select' }"
:emit-value="false"
> >
<template #option="{ opt, itemProps }"> <template #option="{ opt, itemProps }">
<QItem v-bind="itemProps" class="q-pa-xs row items-center"> <QItem v-bind="itemProps" class="q-pa-xs row items-center">

View File

@ -8,10 +8,11 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
import axios from 'axios'; import axios from 'axios';
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults'; import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { useState } from 'src/composables/useState'; import useNotify from 'src/composables/useNotify.js';
const MATCH = 'match'; const MATCH = 'match';
const { notifyResults } = displayResults(); const { notifyResults } = displayResults();
const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const $props = defineProps({ const $props = defineProps({
@ -42,7 +43,7 @@ const ticketConfig = ref({});
const proposalTableRef = ref(null); const proposalTableRef = ref(null);
const sale = computed(() => $props.sales[0]); const sale = computed(() => $props.sales[0]);
const saleFk = computed(() => sale.value.saleFk); const saleFk = computed(() => sale.value?.saleFk);
const filter = computed(() => ({ const filter = computed(() => ({
where: $props.filter, where: $props.filter,
@ -55,10 +56,6 @@ const defaultColumnAttrs = {
sortable: false, sortable: false,
}; };
const emit = defineEmits(['onDialogClosed', 'itemReplaced']); const emit = defineEmits(['onDialogClosed', 'itemReplaced']);
const conditionalValuePrice = (price) =>
price > 1 + ticketConfig.value.lackAlertPrice / 100 ? 'match' : 'not-match';
const columns = computed(() => [ const columns = computed(() => [
{ {
...defaultColumnAttrs, ...defaultColumnAttrs,
@ -97,7 +94,15 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
sortable: true, sortable: true,
label: t('item.list.color'), label: t('item.list.producer'),
name: 'subName',
field: 'subName',
columnClass: 'expand',
},
{
align: 'left',
sortable: true,
label: t('proposal.tag5'),
name: 'tag5', name: 'tag5',
field: 'value5', field: 'value5',
columnClass: 'expand', columnClass: 'expand',
@ -105,7 +110,7 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
sortable: true, sortable: true,
label: t('item.list.stems'), label: t('proposal.tag6'),
name: 'tag6', name: 'tag6',
field: 'value6', field: 'value6',
columnClass: 'expand', columnClass: 'expand',
@ -113,12 +118,27 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
sortable: true, sortable: true,
label: t('item.list.producer'), label: t('proposal.tag7'),
name: 'tag7', name: 'tag7',
field: 'value7', field: 'value7',
columnClass: 'expand', columnClass: 'expand',
}, },
{
align: 'left',
sortable: true,
label: t('proposal.tag8'),
name: 'tag8',
field: 'value8',
columnClass: 'expand',
},
{
align: 'left',
sortable: true,
label: t('proposal.advanceable'),
name: 'advanceable',
field: 'advanceable',
columnClass: 'expand',
},
{ {
...defaultColumnAttrs, ...defaultColumnAttrs,
label: t('proposal.price2'), label: t('proposal.price2'),
@ -156,7 +176,6 @@ const columns = computed(() => [
{ {
title: t('Replace'), title: t('Replace'),
icon: 'change_circle', icon: 'change_circle',
show: (row) => isSelectionAvailable(row),
action: change, action: change,
isPrimary: true, isPrimary: true,
}, },
@ -164,19 +183,26 @@ const columns = computed(() => [
}, },
]); ]);
function extractMatchValues(obj) { const priceStatusClass = (proposalPrice) => {
return Object.keys(obj) const originalPrice = sale.value?.price;
.filter((key) => key.startsWith(MATCH)) const { lackAlertPrice: lackAlert } = ticketConfig.value;
.map((key) => parseInt(key.replace(MATCH, ''), 10)); if (!originalPrice || !ticketConfig.value || typeof lackAlert !== 'number') {
} return 'price-ok';
const gradientStyle = (value) => { }
const percentage = ((proposalPrice - originalPrice) / originalPrice) * 100;
return percentage > lackAlert ? 'price-alert' : 'price-ok';
};
const gradientStyleClass = (row) => {
let color = 'white'; let color = 'white';
const perc = parseFloat(value); const value = parseFloat(row);
switch (true) { switch (true) {
case perc >= 0 && perc < 33: case value >= 0 && value < 33:
color = 'primary'; color = 'primary';
break; break;
case perc >= 33 && perc < 66: case value >= 33 && value < 66:
color = 'warning'; color = 'warning';
break; break;
@ -186,59 +212,89 @@ const gradientStyle = (value) => {
} }
return color; return color;
}; };
const extractMatchValues = (obj) => {
return Object.keys(obj)
.filter((key) => key.startsWith(MATCH))
.map((key) => parseInt(key.replace(MATCH, ''), 10));
};
const statusConditionalValue = (row) => { const statusConditionalValue = (row) => {
const matches = extractMatchValues(row); const matches = extractMatchValues(row);
const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0); const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0);
return 100 * (value / matches.length); return 100 * (value / matches.length);
}; };
const isSelectionAvailable = (itemProposal) => { const canReplace = (itemProposal) => {
const { price2 } = itemProposal; if (!canReplaceByPrice(itemProposal)) {
const salePrice = sale.value.price; return false;
const byPrice = (100 * price2) / salePrice > ticketConfig.value.lackAlertPrice;
if (byPrice) {
return byPrice;
} }
const byQuantity = return canReplaceByQuantity(itemProposal);
(100 * itemProposal.available) / Math.abs($props.itemLack.lack) < };
ticketConfig.value.lackAlertPrice; const differenceByPrice = ({ price2: proposalPrice }) => {
return byQuantity; const { price: salePrice } = sale.value;
const percentage = ((proposalPrice - salePrice) / salePrice) * 100;
return percentage;
};
const canReplaceByPrice = (itemProposal) =>
differenceByPrice(itemProposal) < ticketConfig.value.lackAlertPrice;
const differenceByQuantity = ({ available }) => {
const { quantity: saleQuantity } = sale.value;
const percentage = ((saleQuantity - available) / available) * 100;
return percentage;
}; };
async function change({ itemFk: substitutionFk }) { const canReplaceByQuantity = (itemProposal) =>
try { differenceByQuantity(itemProposal) < ticketConfig.value.lackAlertPrice;
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'); async function change(itemSubstitution) {
emit('itemReplaced', { if (!canReplaceByPrice(itemSubstitution)) {
type: 'refresh', notify(t('notAvailableByPrice'), 'warning');
quantity: quantity.value, return;
itemProposal: proposalSelected.value[0],
});
proposalSelected.value = [];
} catch (error) {
console.error(error);
} }
if (!canReplaceByQuantity(itemSubstitution)) {
notify(t('notAvailableByQuantity'), 'warning');
return;
}
const { itemFk: substitutionFk } = itemSubstitution;
let body;
const promises = $props.sales.map(({ saleFk, quantity, ticketFk }) => {
body = {
saleFk,
substitutionFk,
quantity,
ticketFk,
};
return axios.post('Sales/replaceItem', body);
});
const results = await Promise.allSettled(promises);
notifyResults(results, 'ticketFk');
emit('itemReplaced', {
...body,
type: 'refresh',
itemProposal: proposalSelected.value[0],
});
proposalSelected.value = [];
} }
async function handleTicketConfig(data) { async function handleTicketConfig(data) {
ticketConfig.value = data[0]; ticketConfig.value = data[0];
} }
function filterRows(data) {
const filteredRows = data.sort((a, b) => canReplace(b) - canReplace(a));
proposalTableRef.value.CrudModelRef.formData = filteredRows;
}
</script> </script>
<template> <template>
<FetchData <FetchData
url="TicketConfigs" url="TicketConfigs"
:filter="{ fields: ['lackAlertPrice'] }" :filter="{ fields: ['lackAlertPrice'] }"
@on-fetch="handleTicketConfig" @on-fetch="handleTicketConfig"
></FetchData> auto-load
/>
<QInnerLoading <QInnerLoading
:showing="isLoading" :showing="isLoading"
:label="t && t('globals.pleaseWait')" :label="t && t('globals.pleaseWait')"
@ -255,13 +311,23 @@ async function handleTicketConfig(data) {
:user-filter="filter" :user-filter="filter"
:columns="columns" :columns="columns"
class="full-width q-mt-md" class="full-width q-mt-md"
@on-fetch="filterRows"
row-key="id" row-key="id"
:row-click="change" :row-click="change"
:is-editable="false" :is-editable="false"
:right-search="false" :right-search="false"
:without-header="true"
:disable-option="{ card: true, table: true }" :disable-option="{ card: true, table: true }"
> >
<template #top-right>
<QBtn
:disable="false"
flat
class="q-mr-sm"
color="primary"
icon="refresh"
@click="proposalTableRef.reload()"
/>
</template>
<template #column-longName="{ row }"> <template #column-longName="{ row }">
<QTd <QTd
class="flex" class="flex"
@ -269,15 +335,17 @@ async function handleTicketConfig(data) {
> >
<div <div
class="middle full-width" class="middle full-width"
:class="[`proposal-${gradientStyle(statusConditionalValue(row))}`]" :class="[
`proposal-${gradientStyleClass(statusConditionalValue(row))}`,
]"
> >
<QTooltip> {{ statusConditionalValue(row) }}% </QTooltip> <QTooltip> {{ statusConditionalValue(row) }}% </QTooltip>
</div> </div>
<div style="flex: 2 0 100%; align-content: center"> <div style="flex: 2 0 100%; align-content: center">
<div> <span class="link" @click.stop>
<span class="link">{{ row.longName }}</span> {{ row.longName }}
<ItemDescriptorProxy :id="row.id" /> <ItemDescriptorProxy :id="row.id" />
</div> </span>
</div> </div>
</QTd> </QTd>
</template> </template>
@ -290,6 +358,9 @@ async function handleTicketConfig(data) {
<template #column-tag7="{ row }"> <template #column-tag7="{ row }">
<span :class="{ match: !row.match7 }">{{ row.value7 }}</span> <span :class="{ match: !row.match7 }">{{ row.value7 }}</span>
</template> </template>
<template #column-tag8="{ row }">
<span :class="{ match: !row.match8 }">{{ row.value8 }}</span>
</template>
<template #column-counter="{ row }"> <template #column-counter="{ row }">
<span <span
:class="{ :class="{
@ -304,8 +375,20 @@ async function handleTicketConfig(data) {
</template> </template>
<template #column-price2="{ row }"> <template #column-price2="{ row }">
<div class="flex column items-center content-center"> <div class="flex column items-center content-center">
<VnStockValueDisplay :value="(sales[0].price - row.price2) / 100" /> <QTooltip :offset="[0, 5]" anchor="top middle" self="bottom middle">
<span :class="[conditionalValuePrice(row.price2)]">{{ <div>{{ $t('proposal.price2') }}: {{ toCurrency(row.price2) }}</div>
<div>
{{ $t('proposal.itemOldPrice') }}:{{
toCurrency(sales[0]?.price)
}}
</div>
<div>{{ $t('%€') }}: {{ differenceByPrice(row) }}%</div>
</QTooltip>
<VnStockValueDisplay
:format="'currency'"
:value="row.price2 - sales[0]?.price"
/>
<span :class="[priceStatusClass(row.price2)]">{{
toCurrency(row.price2) toCurrency(row.price2)
}}</span> }}</span>
</div> </div>
@ -319,12 +402,26 @@ async function handleTicketConfig(data) {
margin-right: 2px; margin-right: 2px;
flex: 2 0 5px; flex: 2 0 5px;
} }
.price-alert {
color: $negative;
&.q-tooltip {
background-color: $negative;
color: white;
}
}
.price-ok {
color: inherit;
&.q-tooltip {
background-color: $positive;
color: white;
}
}
.match { .match {
color: $negative; color: $negative;
} }
.not-match {
color: inherit;
}
.proposal-warning { .proposal-warning {
background-color: $warning; background-color: $warning;
} }
@ -344,3 +441,14 @@ async function handleTicketConfig(data) {
font-size: smaller; font-size: smaller;
} }
</style> </style>
<i18n>
en:
notAvailable: Not available for replacement
notAvailableByPrice: Not available for replacement by price
notAvailableByQuantity: Not available for replacement by quantity
es:
notAvailable: No disponible para reemplazo
notAvailableByPrice: No disponible para reemplazo por precio
notAvailableByQuantity: No disponible para reemplazo por cantidad
Replace: Remplazar
</i18n>

View File

@ -23,33 +23,32 @@ const $props = defineProps({
default: () => [], default: () => [],
}, },
}); });
const { dialogRef } = useDialogPluginComponent();
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
const emit = defineEmits([ const emit = defineEmits([
'onDialogClosed', 'onDialogClosed',
'onDialogOk',
'itemReplaced', 'itemReplaced',
...useDialogPluginComponent.emits, ...useDialogPluginComponent.emits,
]); ]);
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() }); defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
const itemReplaced = (data) => {
onDialogOK(data);
dialogRef.value.hide();
};
</script> </script>
<template> <template>
<QDialog ref="dialogRef" transition-show="scale" transition-hide="scale"> <QDialog ref="dialogRef" transition-show="scale" transition-hide="scale">
<QCard class="dialog-width"> <QCard class="dialog-width">
<QCardSection class="row items-center q-pb-none"> <QCardSection class="row items-center q-pb-none">
<span class="text-h6 text-grey">{{ $t('itemProposal') }}</span> <span class="text-h6 text-grey" v-text="$t('itemProposal')" />
<QSpace /> <QSpace />
<QBtn icon="close" flat round dense v-close-popup /> <QBtn icon="close" flat round dense v-close-popup />
</QCardSection> </QCardSection>
<QCardSection> <QCardSection>
<ItemProposal <ItemProposal v-bind="$props" @item-replaced="itemReplaced"
v-bind="$props" /></QCardSection>
@item-replaced="
(data) => {
emit('itemReplaced', data);
dialogRef.hide();
}
"
></ItemProposal
></QCardSection>
</QCard> </QCard>
</QDialog> </QDialog>
</template> </template>

View File

@ -99,9 +99,6 @@ item:
concept: Concept concept: Concept
denyOptions: Deny denyOptions: Deny
scopeDays: Scope days scopeDays: Scope days
searchbar:
label: Search item
info: You can search by id
descriptor: descriptor:
item: Item item: Item
buyer: Buyer buyer: Buyer
@ -158,6 +155,7 @@ item:
isPhotoRequestedTooltip: This item does need a photo isPhotoRequestedTooltip: This item does need a photo
isCustomInspectionRequired: Needs physical inspection (PIF) isCustomInspectionRequired: Needs physical inspection (PIF)
description: Description description: Description
photoMotivation: Comment for the photographer
fixedPrice: fixedPrice:
itemFk: Item ID itemFk: Item ID
groupingPrice: Grouping price groupingPrice: Grouping price
@ -218,7 +216,7 @@ item:
genus: Genus genus: Genus
specie: Specie specie: Specie
search: 'Search item' search: 'Search item'
searchInfo: 'You can search by id' searchInfo: 'You can search by id or barcode'
regularizeStock: Regularize stock regularizeStock: Regularize stock
itemProposal: Items proposal itemProposal: Items proposal
proposal: proposal:
@ -231,6 +229,11 @@ proposal:
value6: value6 value6: value6
value7: value7 value7: value7
value8: value8 value8: value8
tag5: Tag5
tag6: Tag6
tag7: Tag7
tag8: Tag8
advanceable: Advanceable
available: Available available: Available
minQuantity: minQuantity minQuantity: minQuantity
price2: Price price2: Price

View File

@ -73,13 +73,6 @@ itemTags:
addTag: Añadir etiqueta addTag: Añadir etiqueta
tag: Etiqueta tag: Etiqueta
value: Valor value: Valor
itemType:
shared:
code: Código
name: Nombre
worker: Trabajador
category: Reino
temperature: Temperatura
searchbar: searchbar:
label: Buscar artículo label: Buscar artículo
info: Buscar por id de artículo info: Buscar por id de artículo
@ -155,15 +148,16 @@ item:
weightByPiece: Peso (gramos)/tallo weightByPiece: Peso (gramos)/tallo
boxUnits: Unidades/caja boxUnits: Unidades/caja
recycledPlastic: Plastico reciclado recycledPlastic: Plastico reciclado
nonRecycledPlastic: Plático no reciclado nonRecycledPlastic: Plástico no reciclado
isActive: Activo isActive: Activo
hasKgPrice: Precio en kg hasKgPrice: Precio en kg
isFragile: Frágil isFragile: Frágil
isFragileTooltip: Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...) isFragileTooltip: Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
isPhotoRequested: Hacer foto isPhotoRequested: Hacer foto
isPhotoRequestedTooltip: Este artículo necesita una foto isPhotoRequestedTooltip: Este artículo necesita una foto
isCustomInspectionRequired: Necesita inspección física (PIF) isCustomInspectionRequired: Necesita insp. física (PIF)
description: Descripción description: Descripción
photoMotivation: Comentario para el fotógrafo
fixedPrice: fixedPrice:
itemFk: ID Artículo itemFk: ID Artículo
groupingPrice: Precio grouping groupingPrice: Precio grouping
@ -212,6 +206,8 @@ item:
minSalesQuantity: Cantidad mínima de venta minSalesQuantity: Cantidad mínima de venta
genus: Genus genus: Genus
specie: Specie specie: Specie
search: 'Buscar artículo'
searchInfo: 'Puedes buscar por id de artículo o código de barras'
regularizeStock: Regularizar stock regularizeStock: Regularizar stock
buyRequest: buyRequest:
ticketId: 'ID Ticket' ticketId: 'ID Ticket'
@ -237,11 +233,16 @@ proposal:
value6: value6 value6: value6
value7: value7 value7: value7
value8: value8 value8: value8
tag5: Tag5
tag6: Tag6
tag7: Tag7
tag8: Tag8
available: Disponible available: Disponible
minQuantity: Min. cantidad minQuantity: Min. cantidad
price2: Precio price2: Precio
located: Ubicado located: Ubicado
counter: Contador counter: Contador
advanceable: Adelantable
difference: Diferencial difference: Diferencial
groupingPrice: Precio Grouping groupingPrice: Precio Grouping
itemOldPrice: Precio itemOld itemOldPrice: Precio itemOld

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
@ -34,34 +34,42 @@ const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const arrayData = useArrayData(props.dataKey); const arrayData = useArrayData(props.dataKey);
const categoryList = ref(null); const categoryList = ref(null);
const typeList = ref([]); const typeList = ref([]);
const searchByTag = ref(null); const searchByTag = ref(null);
const vnFilterPanelRef = ref(); const vnFilterPanelRef = ref();
const orderByList = ref([ const orderByListStatic = [
{ id: 'relevancy DESC, name', name: t('params.relevancy'), priority: 999 }, { id: 'relevancy DESC, name', name: t('params.relevancy'), priority: 999 },
{ id: 'showOrder, price', name: t('params.colorAndPrice'), priority: 999 }, { id: 'showOrder, price', name: t('params.colorAndPrice'), priority: 999 },
{ id: 'name', name: t('params.name'), priority: 999 }, { id: 'name', name: t('params.name'), priority: 999 },
{ id: 'price', name: t('params.price'), priority: 999 }, { id: 'price', name: t('params.price'), priority: 999 },
]); ];
const orderWayList = ref([ const orderWayList = [
{ id: 'ASC', name: t('params.ASC') }, { id: 'ASC', name: t('params.ASC') },
{ id: 'DESC', name: t('params.DESC') }, { id: 'DESC', name: t('params.DESC') },
]); ];
const orderBySelected = ref('relevancy DESC, name'); const orderBySelected = ref('relevancy DESC, name');
const orderWaySelected = ref('ASC'); const orderWaySelected = ref('ASC');
const orderByList = computed(() =>
orderByListStatic.concat(
props.tags.map((tag) => ({
...tag,
field: tag.id,
isTag: true,
})),
),
);
const resetCategory = (params, search) => { function resetCategory(params, search) {
typeList.value = null; typeList.value = null;
params.categoryFk = null; params.categoryFk = null;
params.typeFk = null; params.typeFk = null;
arrayData.store.userFilter = null; arrayData.store.userFilter = null;
search(); search();
}; }
const selectCategory = async (params, category, search) => { async function selectCategory(params, category, search) {
if (vnFilterPanelRef.value.params.categoryFk === category?.id) { if (vnFilterPanelRef.value.params.categoryFk === category?.id) {
resetCategory(params, search); resetCategory(params, search);
return; return;
@ -69,16 +77,16 @@ const selectCategory = async (params, category, search) => {
params.typeFk = null; params.typeFk = null;
params.categoryFk = category.id; params.categoryFk = category.id;
await loadTypes(category?.id); await loadTypes(category?.id);
}; }
const loadTypes = async (id) => { async function loadTypes(id) {
const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, { const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
params: { itemCategoryId: id }, params: { itemCategoryId: id },
}); });
typeList.value = data; typeList.value = data;
}; }
const applyTags = (tagInfo, params, search) => { function applyTags(tagInfo, params, search) {
if (!tagInfo || !tagInfo.values.length) { if (!tagInfo || !tagInfo.values.length) {
params.tagGroups = null; params.tagGroups = null;
search(); search();
@ -88,7 +96,7 @@ const applyTags = (tagInfo, params, search) => {
if (!params.tagGroups) params.tagGroups = []; if (!params.tagGroups) params.tagGroups = [];
params.tagGroups.push(tagInfo); params.tagGroups.push(tagInfo);
search(); search();
}; }
async function onSearchByTag(value) { async function onSearchByTag(value) {
if (!value.target.value) return; if (!value.target.value) return;
@ -102,16 +110,16 @@ async function onSearchByTag(value) {
searchByTag.value = null; searchByTag.value = null;
} }
const removeTagGroupParam = (params, search, valIndex) => { function removeTagGroupParam(params, search, valIndex) {
if (!valIndex && valIndex !== 0) { if (!valIndex && valIndex !== 0) {
params.tagGroups = null; params.tagGroups = null;
} else { } else {
params.tagGroups.splice(valIndex, 1); params.tagGroups.splice(valIndex, 1);
} }
search(); search();
}; }
const setCategoryList = (data) => { function setCategoryList(data) {
categoryList.value = (data || []) categoryList.value = (data || [])
.filter((category) => category.display) .filter((category) => category.display)
.map((category) => ({ .map((category) => ({
@ -121,17 +129,22 @@ const setCategoryList = (data) => {
vnFilterPanelRef.value.params.categoryFk && vnFilterPanelRef.value.params.categoryFk &&
loadTypes(vnFilterPanelRef.value.params.categoryFk); loadTypes(vnFilterPanelRef.value.params.categoryFk);
}; }
const getCategoryClass = (category, params) => { function getCategoryClass(category, params) {
if (category.id === params?.categoryFk) { if (category.id === params?.categoryFk) {
return 'active'; return 'active';
} }
}; }
function addOrder(value, field, params) { function addOrder(value, field, params) {
let { orderBy } = params; let { orderBy } = params;
orderBy = JSON.parse(orderBy); orderBy = JSON.parse(orderBy);
if (field == 'field') {
orderBy.isTag = !orderByListStatic.some((tag) => tag.id === value);
}
orderBy[field] = value; orderBy[field] = value;
params.orderBy = JSON.stringify(orderBy); params.orderBy = JSON.stringify(orderBy);
vnFilterPanelRef.value.search(); vnFilterPanelRef.value.search();

View File

@ -68,6 +68,19 @@ onMounted(async () => {
<template #menu="{ entity }"> <template #menu="{ entity }">
<RouteDescriptorMenu :route="entity" /> <RouteDescriptorMenu :route="entity" />
</template> </template>
<template #actions="{ entity }">
<QCardActions class="flex justify-center" style="padding-inline: 0">
<QBtn
size="md"
icon="vn:delivery"
color="primary"
:href="`https://grafana.verdnatura.es/d/edkvyi479dbeob/pronostico-de-entregas?orgId=1&var-vRouteFk=${entity.id}`"
target="_blank"
>
<QTooltip>{{ $t('route.deliveryForecast') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</EntityDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -4,6 +4,7 @@ export default {
'id', 'id',
'workerFk', 'workerFk',
'agencyModeFk', 'agencyModeFk',
'dated',
'created', 'created',
'm3', 'm3',
'warehouseFk', 'warehouseFk',

View File

@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { QIcon } from 'quasar'; import { QIcon } from 'quasar';
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters'; import { dashIfEmpty, toCurrency, toDate, toDateHourMinSec, toHour } from 'src/filters';
import { openBuscaman } from 'src/utils/buscaman'; import { openBuscaman } from 'src/utils/buscaman';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
@ -80,6 +80,20 @@ const ticketColumns = ref([
sortable: false, sortable: false,
align: 'left', align: 'left',
}, },
{
name: 'delivered',
label: t('route.delivered'),
field: (row) => dashIfEmpty(toDateHourMinSec(row?.delivered)),
sortable: false,
align: 'center',
},
{
name: 'estimated',
label: t('route.estimated'),
field: (row) => dashIfEmpty(toDateHourMinSec(row?.estimated)),
sortable: false,
align: 'center',
},
{ {
name: 'packages', name: 'packages',
label: t('route.summary.packages'), label: t('route.summary.packages'),
@ -89,7 +103,7 @@ const ticketColumns = ref([
}, },
{ {
name: 'volume', name: 'volume',
label: t('route.summary.m3'), label: 'm³',
field: (row) => row?.volume, field: (row) => row?.volume,
sortable: false, sortable: false,
align: 'center', align: 'center',
@ -267,61 +281,3 @@ const ticketColumns = ref([
</CardSummary> </CardSummary>
</div> </div>
</template> </template>
<i18n>
en:
route:
summary:
date: Date
agency: Agency
vehicle: Vehicle
driver: Driver
cost: Cost
started: Started time
finished: Finished time
kmStart: Km start
kmEnd: Km end
volume: Volume
packages: Packages
description: Description
tickets: Tickets
order: Order
street: Street
city: City
pc: PC
client: Client
state: State
m3:
packaging: Packaging
ticket: Ticket
closed: Closed
open: Open
yes: Yes
no: No
es:
route:
summary:
date: Fecha
agency: Agencia
vehicle: Vehículo
driver: Conductor
cost: Costo
started: Hora inicio
finished: Hora fin
kmStart: Km inicio
kmEnd: Km fin
volume: Volumen
packages: Bultos
description: Descripción
tickets: Tickets
order: Orden
street: Dirección fiscal
city: Población
pc: CP
client: Cliente
state: Estado
packaging: Encajado
closed: Cerrada
open: Abierta
yes:
no: No
</i18n>

View File

@ -2,7 +2,7 @@
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty, toDateHourMinSec } from 'src/filters';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import axios from 'axios'; import axios from 'axios';
@ -24,49 +24,63 @@ const selectedRows = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'order', name: 'order',
label: t('Order'), label: t('route.ticket.order'),
field: (row) => dashIfEmpty(row?.priority), field: (row) => dashIfEmpty(row?.priority),
sortable: false, sortable: false,
align: 'center', align: 'center',
}, },
{ {
name: 'client', name: 'client',
label: t('Client'), label: t('route.ticket.client'),
field: (row) => row?.nickname, field: (row) => row?.nickname,
sortable: false, sortable: false,
align: 'left', align: 'left',
}, },
{ {
name: 'street', name: 'street',
label: t('Street'), label: t('route.ticket.street'),
field: (row) => row?.street, field: (row) => row?.street,
sortable: false, sortable: false,
align: 'left', align: 'left',
}, },
{ {
name: 'pc', name: 'pc',
label: t('PC'), label: t('route.ticket.PC'),
field: (row) => row?.postalCode, field: (row) => row?.postalCode,
sortable: false, sortable: false,
align: 'center', align: 'center',
}, },
{ {
name: 'city', name: 'city',
label: t('City'), label: t('route.ticket.city'),
field: (row) => row?.city, field: (row) => row?.city,
sortable: false, sortable: false,
align: 'left', align: 'left',
}, },
{ {
name: 'warehouse', name: 'warehouse',
label: t('Warehouse'), label: t('route.ticket.warehouse'),
field: (row) => row?.warehouseName, field: (row) => row?.warehouseName,
sortable: false, sortable: false,
align: 'left', align: 'left',
}, },
{
name: 'delivered',
label: t('route.delivered'),
field: (row) => dashIfEmpty(toDateHourMinSec(row?.delivered)),
sortable: false,
align: 'left',
},
{
name: 'estimated',
label: t('route.estimated'),
field: (row) => dashIfEmpty(toDateHourMinSec(row?.estimated)),
sortable: false,
align: 'left',
},
{ {
name: 'packages', name: 'packages',
label: t('Packages'), label: t('route.ticket.packages'),
field: (row) => row?.packages, field: (row) => row?.packages,
sortable: false, sortable: false,
align: 'center', align: 'center',
@ -80,14 +94,14 @@ const columns = computed(() => [
}, },
{ {
name: 'packaging', name: 'packaging',
label: t('Packaging'), label: t('route.ticket.packaging'),
field: (row) => row?.ipt, field: (row) => row?.ipt,
sortable: false, sortable: false,
align: 'center', align: 'center',
}, },
{ {
name: 'ticket', name: 'ticket',
label: t('Ticket'), label: t('route.ticket.ticket'),
field: (row) => row?.id, field: (row) => row?.id,
sortable: false, sortable: false,
align: 'center', align: 'center',
@ -188,8 +202,8 @@ const confirmRemove = (ticket) => {
.dialog({ .dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
title: t('Confirm removal from route'), title: t('route.ticket.confirmRemoval'),
message: t('Are you sure you want to remove this ticket from the route?'), message: t('route.ticket.confirmRemovalConfirmation'),
promise: () => removeTicket(ticket), promise: () => removeTicket(ticket),
}, },
}) })
@ -219,7 +233,7 @@ const openSmsDialog = async () => {
quasar.dialog({ quasar.dialog({
component: SendSmsDialog, component: SendSmsDialog,
componentProps: { componentProps: {
title: t('Send SMS to the selected tickets'), title: t('route.ticket.sendSmsTickets'),
url: 'Routes/sendSms', url: 'Routes/sendSms',
destinationFk: clientsId.toString(), destinationFk: clientsId.toString(),
destination: clientsPhone.toString(), destination: clientsPhone.toString(),
@ -240,18 +254,25 @@ const openSmsDialog = async () => {
<QDialog v-model="confirmationDialog"> <QDialog v-model="confirmationDialog">
<QCard style="min-width: 350px"> <QCard style="min-width: 350px">
<QCardSection> <QCardSection>
<p class="text-h6 q-ma-none">{{ t('Select the starting date') }}</p> <p class="text-h6 q-ma-none">
{{ t('route.ticket.selectStartingDate') }}
</p>
</QCardSection> </QCardSection>
<QCardSection class="q-pt-none"> <QCardSection class="q-pt-none">
<VnInputDate <VnInputDate
:label="t('Stating date')" :label="t('route.ticket.startingDate')"
v-model="startingDate" v-model="startingDate"
autofocus autofocus
/> />
</QCardSection> </QCardSection>
<QCardActions align="right"> <QCardActions align="right">
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" /> <QBtn
flat
:label="t('globals.cancel')"
v-close-popup
class="text-primary"
/>
<QBtn color="primary" v-close-popup @click="cloneRoutes"> <QBtn color="primary" v-close-popup @click="cloneRoutes">
{{ t('globals.clone') }} {{ t('globals.clone') }}
</QBtn> </QBtn>
@ -262,7 +283,7 @@ const openSmsDialog = async () => {
<QToolbar class="justify-end"> <QToolbar class="justify-end">
<div id="st-actions" class="q-pa-sm"> <div id="st-actions" class="q-pa-sm">
<QBtn icon="vn:wand" color="primary" class="q-mr-sm" @click="sortRoutes"> <QBtn icon="vn:wand" color="primary" class="q-mr-sm" @click="sortRoutes">
<QTooltip>{{ t('Sort routes') }}</QTooltip> <QTooltip>{{ t('route.ticket.sortRoutes') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
icon="vn:buscaman" icon="vn:buscaman"
@ -271,7 +292,7 @@ const openSmsDialog = async () => {
:disable="!selectedRows?.length" :disable="!selectedRows?.length"
@click="goToBuscaman()" @click="goToBuscaman()"
> >
<QTooltip>{{ t('Open buscaman') }}</QTooltip> <QTooltip>{{ t('route.ticket.openBuscaman') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
icon="filter_alt" icon="filter_alt"
@ -280,7 +301,7 @@ const openSmsDialog = async () => {
:disable="!selectedRows?.length" :disable="!selectedRows?.length"
@click="deletePriorities" @click="deletePriorities"
> >
<QTooltip>{{ t('Delete priority') }}</QTooltip> <QTooltip>{{ t('route.ticket.deletePriority') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
icon="format_list_numbered" icon="format_list_numbered"
@ -288,11 +309,7 @@ const openSmsDialog = async () => {
class="q-mr-sm" class="q-mr-sm"
@click="setOrderedPriority" @click="setOrderedPriority"
> >
<QTooltip <QTooltip>{{ t('route.ticket.renumberAllTickets') }} </QTooltip>
>{{
t('Renumber all tickets in the order you see on the screen')
}}
</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
icon="sms" icon="sms"
@ -301,7 +318,7 @@ const openSmsDialog = async () => {
:disable="!selectedRows?.length" :disable="!selectedRows?.length"
@click="openSmsDialog" @click="openSmsDialog"
> >
<QTooltip>{{ t('Send SMS to all clients') }}</QTooltip> <QTooltip>{{ t('route.ticket.sendSmsClients') }}</QTooltip>
</QBtn> </QBtn>
</div> </div>
</QToolbar> </QToolbar>
@ -339,7 +356,11 @@ const openSmsDialog = async () => {
@click="setHighestPriority(row, rows)" @click="setHighestPriority(row, rows)"
> >
<QTooltip> <QTooltip>
{{ t('Assign highest priority') }} {{
t(
'route.ticket.assignHighestPriority',
)
}}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<VnInput <VnInput
@ -354,7 +375,9 @@ const openSmsDialog = async () => {
<QTd> <QTd>
<span class="link" @click="goToBuscaman(row)"> <span class="link" @click="goToBuscaman(row)">
{{ value }} {{ value }}
<QTooltip>{{ t('Open buscaman') }}</QTooltip> <QTooltip>{{
t('route.ticket.openBuscaman')
}}</QTooltip>
</span> </span>
</QTd> </QTd>
</template> </template>
@ -411,7 +434,7 @@ const openSmsDialog = async () => {
@click="openTicketsDialog" @click="openTicketsDialog"
> >
<QTooltip> <QTooltip>
{{ t('Add ticket') }} {{ t('route.ticket.addTicket') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
</QPageSticky> </QPageSticky>
@ -432,24 +455,3 @@ const openSmsDialog = async () => {
gap: 12px; gap: 12px;
} }
</style> </style>
<i18n>
es:
Order: Orden
Street: Dirección fiscal
City: Población
PC: CP
Client: Cliente
Warehouse: Almacén
Packages: Bultos
Packaging: Encajado
Confirm removal from route: Quitar de la ruta
Are you sure you want to remove this ticket from the route?: ¿Seguro que quieres quitar este ticket de la ruta?
Sort routes: Ordenar rutas
Open buscaman: Abrir buscaman
Delete priority: Borrar orden
Renumber all tickets in the order you see on the screen: Renumerar todos los tickets con el orden que ves por pantalla
Assign highest priority: Asignar máxima prioridad
Send SMS to all clients: Mandar sms a todos los clientes de las rutas
Send SMS to the selected tickets: Enviar SMS a los tickets seleccionados
Add ticket: Añadir ticket
</i18n>

View File

@ -0,0 +1,140 @@
<script setup>
import VnTable from 'src/components/VnTable/VnTable.vue';
import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import { toDate, toCurrency } from 'src/filters/index';
import { useRoute } from 'vue-router';
import { ref, computed } from 'vue';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useI18n } from 'vue-i18n';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const tableRef = ref();
const { t } = useI18n();
const route = useRoute();
const { notify } = useNotify();
const dataKey = 'VehicleInvoiceIn';
const { openConfirmationModal } = useVnConfirm();
const columns = computed(() => [
{
align: 'left',
name: 'issued',
label: t('invoiceIn.list.issued'),
columnFilter: {
component: 'date',
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.issued)),
cardVisible: true,
},
{
align: 'left',
name: 'supplierFk',
label: t('invoiceIn.list.supplier'),
columnFilter: {
component: 'select',
attrs: {
url: 'Suppliers',
fields: ['id', 'name'],
},
},
format: ({ supplierName }) => supplierName,
columnClass: 'expand',
cardVisible: true,
},
{
align: 'left',
name: 'supplierRef',
label: t('invoiceIn.supplierRef'),
cardVisible: true,
},
{
align: 'left',
name: 'amount',
label: t('invoiceIn.list.amount'),
format: ({ amount }) => toCurrency(amount),
columnFilter: false,
cardVisible: true,
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('vehicle.ticket.unassignInvoice'),
icon: 'delete',
action: (row) =>
openConfirmationModal(
t('vehicle.ticket.unassignInvoice'),
t('vehicle.ticket.unassignInvoiceConfirmation'),
() => unassignInvoice(row.id),
),
isPrimary: true,
},
],
},
]);
async function unassignInvoice(id) {
try {
await axios.delete(`VehicleInvoiceIns/${id}`);
notify(t('vehicle.ticket.unassignedInvoice'), 'positive');
tableRef.value.reload();
} catch (e) {
throw e;
}
}
</script>
<template>
<VnTable
ref="tableRef"
:data-key="dataKey"
:url="`vehicles/${route.params.id}/getInvoices`"
:columns="columns"
search-url="vehicleInvoiceIns"
:order="['issued DESC', 'supplierRef ASC']"
:create="{
urlCreate: 'VehicleInvoiceIns',
title: t('vehicle.ticket.assignInvoice'),
formInitialData: {
vehicleFk: parseInt(route.params.id, 10),
},
onDataSaved: ({ id }) => tableRef.reload(),
}"
auto-load
>
<template #column-supplierFk="{ row }">
<span class="link" @click.stop>
{{ row.supplierName }}
<SupplierDescriptorProxy :id="row.supplierId" />
</span>
</template>
<template #column-supplierRef="{ row }">
<span class="link" @click.stop>
{{ row.supplierRef }}
<InvoiceInDescriptorProxy :id="row.invoiceInFk" />
</span>
</template>
<template #more-create-dialog="{ data }">
<VnSelect
url="invoiceIns"
:label="t('invoiceIn.supplierRef')"
:fields="['id', 'supplierRef', 'supplierFk']"
:filter-options="['id', 'supplierRef']"
v-model="data.invoiceInFk"
option-label="supplierRef"
:required="true"
>
</VnSelect>
<VnInputNumber
:label="t('invoiceIn.list.amount')"
v-model="data.amount"
required
/>
</template>
</VnTable>
</template>

View File

@ -1,16 +1,20 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
import { downloadFile } from 'src/composables/downloadFile';
import FetchData from 'src/components/FetchData.vue';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceInDescriptorProxy from 'src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import VehicleFilter from '../VehicleFilter.js'; 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 props = defineProps({ id: { type: [Number, String], default: null } });
const invoices = ref([]);
const route = useRoute(); const route = useRoute();
const entityId = computed(() => props.id || +route.params.id); const entityId = computed(() => props.id || +route.params.id);
const baseLink = `#/${route.meta.moduleName.toLowerCase()}/vehicle/${entityId.value}`; const baseLink = `#/${route.meta.moduleName.toLowerCase()}/vehicle/${entityId.value}`;
@ -23,6 +27,11 @@ const links = {
}; };
</script> </script>
<template> <template>
<FetchData
:url="`Vehicles/${entityId}/getInvoices`"
auto-load
@on-fetch="(data) => (invoices = data)"
/>
<CardSummary <CardSummary
data-key="Vehicle" data-key="Vehicle"
:url="`Vehicles/${entityId}`" :url="`Vehicles/${entityId}`"
@ -132,6 +141,45 @@ const links = {
</QList> </QList>
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard class="vn-max">
<VnTitle
:url="links['invoice-in']"
:text="$t('globals.pageTitles.assignedInvoices')"
/>
<QTable :rows="invoices" style="text-align: center">
<template #body-cell="{ value }">
<QTd>{{ value }}</QTd>
</template>
<template #header="props">
<QTr class="tr-header" :props="props">
<QTh auto-width>{{ $t('invoiceIn.list.issued') }}</QTh>
<QTh auto-width>{{ $t('invoiceIn.list.supplier') }}</QTh>
<QTh auto-width>{{ $t('invoiceIn.supplierRef') }}</QTh>
<QTh auto-width>{{ $t('invoiceIn.list.amount') }}</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<QTd>{{ toDate(props.row.issued) }}</QTd>
<QTd>
<span class="link">
{{ props.row.supplierName }}
<SupplierDescriptorProxy :id="props.row.supplierId" />
</span>
</QTd>
<QTd>
<span class="link">
{{ props.row.supplierRef }}
<InvoiceInDescriptorProxy
:id="props.row.supplierId"
/>
</span>
</QTd>
<QTd>{{ toCurrency(props.row.amount) }}</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template> </template>
</CardSummary> </CardSummary>
</template> </template>

View File

@ -108,6 +108,13 @@ const columns = computed(() => [
options: countries.value, options: countries.value,
}, },
}, },
{
name: 'isActive',
label: t('globals.active'),
visible: false,
cardVisible: false,
component: 'checkbox',
},
{ {
align: 'right', align: 'right',
name: 'tableActions', name: 'tableActions',

View File

@ -14,11 +14,16 @@ vehicle:
amountCooler: Amount cooler amountCooler: Amount cooler
remove: Vehicle removed remove: Vehicle removed
search: Search Vehicle search: Search Vehicle
searchInfo: Search by id or number plate searchInfo: Search by id
deleteTitle: This item will be deleted deleteTitle: This item will be deleted
deleteSubtitle: Are you sure you want to continue? deleteSubtitle: Are you sure you want to continue?
params: params:
vehicleTypeFk: Type vehicleTypeFk: Type
vehicleStateFk: State vehicleStateFk: State
ticket:
assignInvoice: Assign invoice
unassignedInvoice: Unassigned invoice
unassignInvoice: Unassign invoice
unassignInvoiceConfirmation: This invoice will be unassigned from this vehicle! Continue anyway?
errors: errors:
documentIdEmpty: The document identifier can't be empty documentIdEmpty: The document identifier can't be empty

View File

@ -14,11 +14,16 @@ vehicle:
nLeasing: Nº leasing nLeasing: Nº leasing
remove: Vehículo eliminado remove: Vehículo eliminado
search: Buscar Vehículo search: Buscar Vehículo
searchInfo: Buscar por id o matrícula searchInfo: Buscar por id
deleteTitle: Este elemento será eliminado deleteTitle: Este elemento será eliminado
deleteSubtitle: ¿Seguro que quieres continuar? deleteSubtitle: ¿Seguro que quieres continuar?
params: params:
vehicleTypeFk: Tipo vehicleTypeFk: Tipo
vehicleStateFk: Estado vehicleStateFk: Estado
ticket:
assignInvoice: Vincular factura
unassignedInvoice: Factura desvinculada
unassignInvoice: Desvincular factura
unassignInvoiceConfirmation: Esta factura se desvinculará de este vehículo! ¿Continuar de todas formas?
errors: errors:
documentIdEmpty: El número de documento no puede estar vacío documentIdEmpty: El número de documento no puede estar vacío

View File

@ -1,6 +1,33 @@
route: route:
filter: filter:
Served: Served Served: Served
summary:
date: Date
agency: Agency
vehicle: Vehicle
driver: Driver
cost: Cost
started: Started time
finished: Finished time
kmStart: Km start
kmEnd: Km end
volume: Volume
packages: Packages
description: Description
tickets: Tickets
order: Order
street: Street
city: City
pc: PC
client: Client
state: State
m3:
packaging: Packaging
ticket: Ticket
closed: Closed
open: Open
yes: Yes
no: No
extendedList: extendedList:
selectStartingDate: Select the starting date selectStartingDate: Select the starting date
startingDate: Starting date startingDate: Starting date
@ -51,6 +78,8 @@ route:
agencyModeName: Agency route agencyModeName: Agency route
isOwn: Own isOwn: Own
isAnyVolumeAllowed: Any volume allowed isAnyVolumeAllowed: Any volume allowed
isActive: Active
issued: Issued
created: Created created: Created
addressFromFk: Sender addressFromFk: Sender
addressToFk: Destination addressToFk: Destination
@ -75,3 +104,45 @@ route:
searchInfo: You can search by route reference searchInfo: You can search by route reference
dated: Dated dated: Dated
preview: Preview preview: Preview
delivered: Delivered
estimated: Estimated
cmr:
search: Search Cmr
searchInfo: You can search Cmr by Id
params:
results: results
cmrFk: CMR id
hasCmrDms: Attached in gestdoc
true: Yes
false: No
ticketFk: Ticketd id
routeFk: Route id
countryFk: Country
clientFk: Client id
warehouseFk: Warehouse
shipped: Preparation date
viewCmr: View CMR
downloadCmrs: Download CMRs
search: General search
ticket:
order: Order
street: Street
city: City
PC: PC
client: Client
warehouse: Warehouse
packages: Packages
packaging: Packaging
ticket: Ticket
confirmRemoval: Confirm removal from route
confirmRemovalConfirmation: Are you sure you want to remove this ticket from the route?
selectStartingDate: Select the starting date
startingDate: Starting date
sortRoutes: Sort routes
openBuscaman: Open buscaman
deletePriority: Delete priority
renumberAllTickets: Renumber all tickets in the order you see on the screen
assignHighest: Assign highest priority
sendSmsTickets: Send SMS to the selected tickets
sendSmsClients: Send SMS to all clients
addTicket: Add ticket

View File

@ -1,6 +1,31 @@
route: route:
filter: filter:
Served: Servida Served: Servida
summary:
date: Fecha
agency: Agencia
vehicle: Vehículo
driver: Conductor
cost: Costo
started: Hora inicio
finished: Hora fin
kmStart: Km inicio
kmEnd: Km fin
volume: Volumen
packages: Bultos
description: Descripción
tickets: Tickets
order: Orden
street: Dirección fiscal
city: Población
pc: CP
client: Cliente
state: Estado
packaging: Encajado
closed: Cerrada
open: Abierta
yes:
no: No
extendedList: extendedList:
selectStartingDate: Seleccione la fecha de inicio selectStartingDate: Seleccione la fecha de inicio
statingDate: Fecha de inicio statingDate: Fecha de inicio
@ -52,6 +77,8 @@ route:
agencyAgreement: Agencia Acuerdo agencyAgreement: Agencia Acuerdo
isOwn: Propio isOwn: Propio
isAnyVolumeAllowed: Cualquier volumen isAnyVolumeAllowed: Cualquier volumen
isActive: Activo
issued: F. emisión
created: Creado created: Creado
addressFromFk: Remitente addressFromFk: Remitente
addressToFk: Destinatario addressToFk: Destinatario
@ -76,17 +103,42 @@ route:
searchInfo: Puedes buscar por referencia de la ruta searchInfo: Puedes buscar por referencia de la ruta
dated: Fecha dated: Fecha
preview: Vista previa preview: Vista previa
delivered: Entregado
estimated: Pronóstico
cmr: cmr:
list: list:
results: resultados results: resultados
cmrFk: Id CMR cmrFk: Id CMR
hasCmrDms: Gestdoc hasCmrDms: Gestdoc
'true': true:
'false': 'No' false: No
ticketFk: Id ticket ticketFk: Id ticket
routeFk: Id ruta routeFk: Id ruta
country: País country: País
clientFk: Id cliente clientFk: Id cliente
shipped: Fecha preparación warehouseFk: Almacén
shipped: F. preparación
viewCmr: Ver CMR viewCmr: Ver CMR
downloadCmrs: Descargar CMRs downloadCmrs: Descargar CMRs
ticket:
order: Orden
street: Dirección fiscal
city: Población
PC: CP
client: Cliente
warehouse: Almacén
packages: Bultos
packaging: Encajado
ticket: Ticket
confirmRemoval: Quitar de la ruta
confirmRemovalConfirmation: ¿Seguro que quieres quitar este ticket de la ruta?
selectStartingDate: Seleccionar fecha de inicio
startingDate: F. Inicio
sortRoutes: Ordenar rutas
openBuscaman: Abrir buscaman
deletePriority: Borrar orden
renumberAllTickets: Renumerar todos los tickets con el orden que ves por pantalla
assignHighest: Asignar máxima prioridad
sendSmsTickets: Enviar SMS a los tickets seleccionados
sendSmsClients: Mandar sms a todos los clientes de las rutas
addTicket: Añadir ticket

View File

@ -26,7 +26,8 @@ const { notify } = useNotify();
const totalRows = ref({}); const totalRows = ref({});
const arrayData = useArrayData('SupplierConsumption', { const arrayData = useArrayData('SupplierConsumption', {
url: 'Suppliers/consumption', url: 'Suppliers/consumption',
order: ['itemTypeFk', 'itemName', 'itemSize'], order: ['shipped DESC', 'itemTypeFk', 'itemName', 'itemSize'],
limit: 0,
userFilter: { where: { supplierFk: route.params.id } }, userFilter: { where: { supplierFk: route.params.id } },
}); });
const headerColumns = computed(() => [ const headerColumns = computed(() => [

View File

@ -187,11 +187,11 @@ const getRowUpdateInputEvents = (sale) => {
}; };
const resetChanges = async () => { const resetChanges = async () => {
const _selectedRows = selectedRows.value;
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
tableRef.value.CrudModelRef.hasChanges = false; tableRef.value.CrudModelRef.hasChanges = false;
await tableRef.value.reload(); await tableRef.value.reload();
tableRef.value.selected = _selectedRows;
selectedRows.value = [];
}; };
const changeQuantity = async (sale) => { const changeQuantity = async (sale) => {
if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity) if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity)

View File

@ -1,11 +1,13 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import split from './components/split'; import split from './components/split';
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults'; import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
const { notifyResults } = displayResults(); const { notifyResults } = displayResults();
const emit = defineEmits(['ticketTransferred']); const emit = defineEmits(['ticketTransferred']);
const { t } = useI18n();
const $props = defineProps({ const $props = defineProps({
ticket: { ticket: {
@ -27,7 +29,7 @@ const splitSelectedRows = async () => {
<template> <template>
<VnInputDate <VnInputDate
class="q-mr-sm" class="q-mr-sm"
:label="$t('New date')" :label="t('New date')"
v-model="splitDate" v-model="splitDate"
clearable clearable
autofocus autofocus
@ -41,6 +43,9 @@ const splitSelectedRows = async () => {
</style> </style>
<i18n> <i18n>
es: es:
New date: Nueva fecha
Split: Separar
Transfer lines: Transferir líneas
Sales to transfer: Líneas a transferir Sales to transfer: Líneas a transferir
Destination ticket: Ticket destinatario Destination ticket: Ticket destinatario
</i18n> </i18n>

View File

@ -1,11 +1,11 @@
import axios from 'axios'; import axios from 'axios';
export default async function (data, date) { export default async function (data, landed) {
const reducedData = data.reduce((acc, item) => { const reducedData = data.reduce((acc, item) => {
const existing = acc.find(({ ticketFk }) => ticketFk === item.id); const existing = acc.find(({ ticketFk }) => ticketFk === item.id);
if (existing) { if (existing) {
existing.sales.push(item.saleFk); existing.sales.push(item.saleFk);
} else { } else {
acc.push({ ticketFk: item.ticketFk, sales: [item.saleFk], date }); acc.push({ ticketFk: item.ticketFk, sales: [item.saleFk], landed });
} }
return acc; return acc;
}, []); }, []);

View File

@ -17,7 +17,6 @@ import ItemProposalProxy from 'src/pages/Item/components/ItemProposalProxy.vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const editableStates = ref([]);
const stateStore = useStateStore(); const stateStore = useStateStore();
const tableRef = ref(); const tableRef = ref();
const changeItemDialogRef = ref(null); const changeItemDialogRef = ref(null);
@ -70,14 +69,11 @@ const showItemProposal = () => {
}) })
.onOk(itemProposalEvt); .onOk(itemProposalEvt);
}; };
const isButtonDisabled = computed(() => selectedRows.value.length !== 1);
</script> </script>
<template> <template>
<FetchData
url="States/editableStates"
@on-fetch="(data) => (editableStates = data)"
auto-load
/>
<FetchData <FetchData
:url="`Items/${entityId}/getCard`" :url="`Items/${entityId}/getCard`"
:fields="['longName']" :fields="['longName']"
@ -99,11 +95,7 @@ const showItemProposal = () => {
> >
<template #top-right> <template #top-right>
<QBtnGroup push class="q-mr-lg" style="column-gap: 1px"> <QBtnGroup push class="q-mr-lg" style="column-gap: 1px">
<QBtn <QBtn data-cy="transferLines" color="primary" :disable="isButtonDisabled">
data-cy="transferLines"
color="primary"
:disable="!(selectedRows.length === 1)"
>
<template #default> <template #default>
<QIcon name="vn:splitline" /> <QIcon name="vn:splitline" />
<QIcon name="vn:ticket" /> <QIcon name="vn:ticket" />
@ -124,7 +116,7 @@ const showItemProposal = () => {
<QBtn <QBtn
color="primary" color="primary"
@click="showItemProposal" @click="showItemProposal"
:disable="!(selectedRows.length === 1)" :disable="isButtonDisabled"
data-cy="itemProposal" data-cy="itemProposal"
> >
<QIcon name="import_export" class="rotate-90" /> <QIcon name="import_export" class="rotate-90" />
@ -135,7 +127,7 @@ const showItemProposal = () => {
<VnPopupProxy <VnPopupProxy
data-cy="changeItem" data-cy="changeItem"
icon="sync" icon="sync"
:disable="!(selectedRows.length === 1)" :disable="isButtonDisabled"
:tooltip="t('negative.detail.modal.changeItem.title')" :tooltip="t('negative.detail.modal.changeItem.title')"
> >
<template #extraIcon> <QIcon name="vn:item" /> </template> <template #extraIcon> <QIcon name="vn:item" /> </template>
@ -149,7 +141,7 @@ const showItemProposal = () => {
<VnPopupProxy <VnPopupProxy
data-cy="changeState" data-cy="changeState"
icon="sync" icon="sync"
:disable="!(selectedRows.length === 1)" :disable="isButtonDisabled"
:tooltip="t('negative.detail.modal.changeState.title')" :tooltip="t('negative.detail.modal.changeState.title')"
> >
<template #extraIcon> <QIcon name="vn:eye" /> </template> <template #extraIcon> <QIcon name="vn:eye" /> </template>
@ -163,7 +155,7 @@ const showItemProposal = () => {
<VnPopupProxy <VnPopupProxy
data-cy="changeQuantity" data-cy="changeQuantity"
icon="sync" icon="sync"
:disable="!(selectedRows.length === 1)" :disable="isButtonDisabled"
:tooltip="t('negative.detail.modal.changeQuantity.title')" :tooltip="t('negative.detail.modal.changeQuantity.title')"
@click="showChangeQuantityDialog = true" @click="showChangeQuantityDialog = true"
> >

View File

@ -7,6 +7,8 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDateTime from 'src/components/common/VnInputDateTime.vue'; import VnInputDateTime from 'src/components/common/VnInputDateTime.vue';
import VnInputDates from 'src/components/common/VnInputDates.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -73,8 +75,8 @@ const setUserParams = (params) => {
<VnFilterPanel <VnFilterPanel
:data-key="props.dataKey" :data-key="props.dataKey"
:search-button="true" :search-button="true"
:hidden-tags="['excludedDates']"
@set-user-params="setUserParams" @set-user-params="setUserParams"
:unremovable-params="['warehouseFk']"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
@ -92,7 +94,7 @@ const setUserParams = (params) => {
dense dense
filled filled
@update:model-value=" @update:model-value="
(value) => { () => {
setUserParams(params); setUserParams(params);
} }
" "
@ -127,8 +129,19 @@ const setUserParams = (params) => {
dense dense
filled filled
/> />
</QItemSection> </QItem </QItemSection>
><QItem> </QItem>
<QItem>
<QItemSection>
<VnInputDates
v-model="params.excludedDates"
filled
:label="t('negative.excludedDates')"
>
</VnInputDates>
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="categoriesOptions"> <QItemSection v-if="categoriesOptions">
<VnSelect <VnSelect
:label="t('negative.categoryFk')" :label="t('negative.categoryFk')"

View File

@ -7,6 +7,7 @@ import { onBeforeMount } from 'vue';
import { dashIfEmpty, toDate, toHour } from 'src/filters'; import { dashIfEmpty, toDate, toHour } from 'src/filters';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import TicketLackFilter from './TicketLackFilter.vue'; import TicketLackFilter from './TicketLackFilter.vue';
@ -45,10 +46,10 @@ const columns = computed(() => [
}, },
{ {
columnClass: 'shrink', columnClass: 'shrink',
name: 'timed', name: 'minTimed',
align: 'center', align: 'center',
label: t('negative.timed'), label: t('negative.timed'),
format: ({ timed }) => toHour(timed), format: ({ minTimed }) => toHour(minTimed),
sortable: true, sortable: true,
cardVisible: true, cardVisible: true,
columnFilter: { columnFilter: {
@ -64,9 +65,25 @@ const columns = computed(() => [
columnFilter: { columnFilter: {
component: 'input', component: 'input',
type: 'number', type: 'number',
inWhere: false, columnClass: 'shrink',
}, },
}, },
{
name: 'nextEntryFk',
align: 'center',
label: t('negative.nextEntryFk'),
format: ({ nextEntryFk }) => nextEntryFk,
sortable: false,
columnFilter: false,
},
{
name: 'nextEntryLanded',
align: 'center',
label: t('negative.nextEntryLanded'),
format: ({ nextEntryLanded }) => toDate(nextEntryLanded),
sortable: false,
columnFilter: false,
},
{ {
name: 'longName', name: 'longName',
align: 'left', align: 'left',
@ -195,6 +212,12 @@ const setUserParams = (params) => {
<span @click.stop>{{ row.itemFk }}</span> <span @click.stop>{{ row.itemFk }}</span>
</div> </div>
</template> </template>
<template #column-nextEntryFk="{ row }">
<span class="link" @click.stop>
{{ row.nextEntryFk }}
<EntryDescriptorProxy :id="row.nextEntryFk" />
</span>
</template>
<template #column-longName="{ row }"> <template #column-longName="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
{{ row.longName }} {{ row.longName }}

View File

@ -35,6 +35,7 @@ const filterLack = ref({
order: 'ts.alertLevelCode ASC', order: 'ts.alertLevelCode ASC',
}); });
const editableStates = ref([]);
const selectedRows = ref([]); const selectedRows = ref([]);
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
@ -135,9 +136,12 @@ const saveChange = async (field, { row }) => {
try { try {
switch (field) { switch (field) {
case 'alertLevelCode': case 'alertLevelCode':
const { id: code } = editableStates.value.find(
({ name }) => name === row.code,
);
await axios.post(`Tickets/state`, { await axios.post(`Tickets/state`, {
ticketFk: row.ticketFk, ticketFk: row.ticketFk,
code: row[field], code,
}); });
break; break;
@ -160,6 +164,11 @@ function onBuysFetched(data) {
</script> </script>
<template> <template>
<FetchData
url="States/editableStates"
@on-fetch="(data) => (editableStates = data)"
auto-load
/>
<FetchData <FetchData
ref="fetchItemLack" ref="fetchItemLack"
:url="`Tickets/itemLack`" :url="`Tickets/itemLack`"
@ -309,12 +318,12 @@ function onBuysFetched(data) {
</template> </template>
<template #column-alertLevelCode="props"> <template #column-alertLevelCode="props">
<VnSelect <VnSelect
url="States/editableStates" :options="editableStates"
auto-load auto-load
hide-selected hide-selected
option-value="id" option-value="name"
option-label="name" option-label="name"
v-model="props.row.alertLevelCode" v-model="props.row.code"
v-on="getInputEvents(props)" v-on="getInputEvents(props)"
/> />
</template> </template>

View File

@ -19,18 +19,18 @@ const $props = defineProps({
const updateItem = async () => { const updateItem = async () => {
try { try {
showChangeItemDialog.value = true; showChangeItemDialog.value = true;
const rowsToUpdate = $props.selectedRows.map(({ saleFk, quantity }) => const rowsToUpdate = $props.selectedRows.map(({ saleFk, ticketFk, quantity }) =>
axios.post(`Sales/replaceItem`, { axios.post(`Sales/replaceItem`, {
saleFk, saleFk,
ticketFk,
substitutionFk: newItem.value, substitutionFk: newItem.value,
quantity, quantity,
}), }),
); );
const result = await Promise.allSettled(rowsToUpdate); const result = await Promise.allSettled(rowsToUpdate);
notifyResults(result, 'saleFk'); notifyResults(result, 'ticketFk');
emit('update-item', newItem.value); emit('update-item', newItem.value);
} catch (err) { } catch (err) {
console.error('Error updating item:', err);
return err; return err;
} }
}; };
@ -41,6 +41,7 @@ const updateItem = async () => {
<QCardSection class="row items-center justify-center column items-stretch"> <QCardSection class="row items-center justify-center column items-stretch">
<span>{{ $t('negative.detail.modal.changeItem.title') }}</span> <span>{{ $t('negative.detail.modal.changeItem.title') }}</span>
<VnSelect <VnSelect
data-cy="New item_select"
url="Items/WithName" url="Items/WithName"
:fields="['id', 'name']" :fields="['id', 'name']"
:sort-by="['id DESC']" :sort-by="['id DESC']"

View File

@ -19,9 +19,9 @@ const $props = defineProps({
const updateState = async () => { const updateState = async () => {
try { try {
showChangeStateDialog.value = true; showChangeStateDialog.value = true;
const rowsToUpdate = $props.selectedRows.map(({ id }) => const rowsToUpdate = $props.selectedRows.map(({ ticketFk }) =>
axios.post(`Tickets/state`, { axios.post(`Tickets/state`, {
ticketFk: id, ticketFk,
code: newState.value, code: newState.value,
}), }),
); );
@ -49,8 +49,9 @@ const updateState = async () => {
v-model="newState" v-model="newState"
:options="editableStates" :options="editableStates"
option-label="name" option-label="name"
option-value="code" option-value="id"
autofocus autofocus
data-cy="New state_select"
/> />
</QCardSection> </QCardSection>
<QCardActions align="right"> <QCardActions align="right">

View File

@ -14,8 +14,6 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnRow from 'src/components/ui/VnRow.vue'; import VnRow from 'src/components/ui/VnRow.vue';
import TicketFilter from './TicketFilter.vue'; import TicketFilter from './TicketFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'src/components/FetchData.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
@ -25,6 +23,7 @@ import TicketProblems from 'src/components/TicketProblems.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
import { getAddresses } from 'src/pages/Customer/composables/getAddresses'; import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies'; import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
import TicketNewPayment from './components/TicketNewPayment.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -73,11 +72,6 @@ const initializeFromQuery = () => {
const selectedRows = ref([]); const selectedRows = ref([]);
const hasSelectedRows = computed(() => selectedRows.value.length > 0); const hasSelectedRows = computed(() => selectedRows.value.length > 0);
const showForm = ref(false);
const dialogData = ref();
const companiesOptions = ref([]);
const accountingOptions = ref([]);
const amountToReturn = ref();
const dataKey = 'TicketList'; const dataKey = 'TicketList';
const formInitialData = ref({}); const formInitialData = ref({});
@ -372,7 +366,11 @@ function openBalanceDialog(ticket) {
); );
if (!isSameClient) { if (!isSameClient) {
throw new Error('You cannot make a payment on account from multiple clients'); notify(
t('You cannot make a payment on account from multiple clients'),
'negative',
);
return;
} }
for (let ticketData of checkedTickets) { for (let ticketData of checkedTickets) {
@ -381,101 +379,48 @@ function openBalanceDialog(ticket) {
description.value.push(ticketData.id); description.value.push(ticketData.id);
} }
const balanceCreateDialog = ref({ const dialogData = ref({
amountPaid: amountPaid.value, amountPaid: amountPaid.value,
clientFk: clientFk.value, clientFk: clientFk.value,
description: `Albaran: ${description.value.join(', ')}`, description: `Albaran: ${description.value.join(', ')}`,
}); });
dialogData.value = balanceCreateDialog; quasar.dialog({
showForm.value = true; component: TicketNewPayment,
} componentProps: {
clientId: clientFk.value,
async function onSubmit() { formData: dialogData.value,
const { data: email } = await axios.get('Clients', {
params: {
filter: JSON.stringify({ where: { id: dialogData.value.value.clientFk } }),
}, },
}); });
const { data } = await axios.post(
`Clients/${dialogData.value.value.clientFk}/createReceipt`,
{
payed: dialogData.value.payed,
companyFk: dialogData.value.companyFk,
bankFk: dialogData.value.bankFk,
amountPaid: dialogData.value.value.amountPaid,
description: dialogData.value.value.description,
clientFk: dialogData.value.value.clientFk,
email: email[0].email,
},
);
if (data) notify('globals.dataSaved', 'positive');
showForm.value = false;
} }
const setAmountToReturn = (newAmountGiven) => { function exprBuilder(param, value) {
const amountPaid = dialogData.value.value.amountPaid; switch (param) {
case 'stateFk':
amountToReturn.value = newAmountGiven - amountPaid; return { 'ts.stateFk': value };
}; case 'provinceFk':
return { 'a.provinceFk': value };
function setReference(data) { case 'hour':
let newDescription = ''; return { 'z.hour': value };
case 'shipped':
switch (data) { return {
case 1: 't.shipped': {
newDescription = `${t( between: this.dateRange(value),
'ticketList.creditCard', },
)}, ${dialogData.value.value.description.replace( };
/^(Credit Card, |Cash, |Transfers, )/, case 'departmentFk':
'', return { 'c.departmentFk': value };
)}`; case 'id':
break; case 'refFk':
case 2: case 'zoneFk':
newDescription = `${t( case 'nickname':
'ticketList.cash', case 'agencyModeFk':
)}, ${dialogData.value.value.description.replace( case 'warehouseFk':
/^(Credit Card, |Cash, |Transfers, )/, return { [`t.${param}`]: value };
'',
)}`;
break;
case 3:
newDescription = `${newDescription.replace(
/^(Credit Card, |Cash, |Transfers, )/,
'',
)}`;
break;
case 4:
newDescription = `${t(
'ticketList.transfers',
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
'',
)}`;
break;
case 3317:
newDescription = '';
break;
default:
break;
} }
dialogData.value.value.description = newDescription;
} }
</script> </script>
<template> <template>
<FetchData
url="Companies"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<FetchData
url="Accountings"
@on-fetch="(data) => (accountingOptions = data)"
auto-load
/>
<VnSection <VnSection
:data-key="dataKey" :data-key="dataKey"
:columns="columns" :columns="columns"
@ -657,42 +602,36 @@ function setReference(data) {
</VnSelect> </VnSelect>
</VnRow> </VnRow>
<VnRow> <VnRow>
<div class="col"> <VnInputDate
<VnInputDate placeholder="dd-mm-aaa"
placeholder="dd-mm-aaa" :label="t('globals.landed')"
:label="t('globals.landed')" v-model="data.landed"
v-model="data.landed" @update:model-value="() => fetchAvailableAgencies(data)"
@update:model-value="() => fetchAvailableAgencies(data)" />
/>
</div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<div class="col"> <VnSelect
<VnSelect url="Warehouses"
url="Warehouses" :sort-by="['name']"
:sort-by="['name']" :label="t('globals.warehouse')"
:label="t('globals.warehouse')" v-model="data.warehouseId"
v-model="data.warehouseId" hide-selected
hide-selected required
required :where="{
:where="{ isForTicket: true,
isForTicket: true, }"
}" @update:model-value="() => fetchAvailableAgencies(data)"
@update:model-value="() => fetchAvailableAgencies(data)" />
/>
</div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<div class="col"> <VnSelect
<VnSelect :label="t('globals.agency')"
:label="t('globals.agency')" v-model="data.agencyModeId"
v-model="data.agencyModeId" :options="agenciesOptions"
:options="agenciesOptions" option-value="agencyModeFk"
option-value="agencyModeFk" option-label="agencyMode"
option-label="agencyMode" hide-selected
hide-selected />
/>
</div>
</VnRow> </VnRow>
</template> </template>
</VnTable> </VnTable>
@ -722,99 +661,6 @@ function setReference(data) {
{{ t('ticketList.accountPayment') }} {{ t('ticketList.accountPayment') }}
</QTooltip> </QTooltip>
</QPageSticky> </QPageSticky>
<QDialog ref="dialogRef" v-model="showForm">
<QCard class="q-pa-md q-mb-md">
<QForm @submit="onSubmit()" class="q-pa-sm">
{{ t('ticketList.addPayment') }}
<VnRow>
<VnInputDate
:label="t('ticketList.date')"
v-model="dialogData.payed"
/>
<VnSelect
:label="t('ticketList.company')"
v-model="dialogData.companyFk"
:options="companiesOptions"
option-label="code"
hide-selected
>
</VnSelect>
</VnRow>
<VnRow>
<VnSelect
:label="t('ticketList.bank')"
v-model="dialogData.bankFk"
:options="accountingOptions"
option-label="bank"
hide-selected
@update:model-value="setReference"
/>
<VnInput
:label="t('ticketList.amount')"
v-model="dialogData.value.amountPaid"
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<span>
{{ t('ticketList.cash') }}
</span>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<VnInput
:label="t('ticketList.deliveredAmount')"
v-model="dialogData.value.amountGiven"
@update:model-value="setAmountToReturn"
type="number"
/>
<VnInput
:label="t('ticketList.amountToReturn')"
:model-value="amountToReturn"
type="number"
readonly
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 3 || dialogData.bankFk === 3117">
<VnInput
:label="t('ticketList.compensation')"
v-model="dialogData.value.compensation"
type="text"
/>
</VnRow>
<VnRow>
<VnInput
:label="t('ticketList.reference')"
v-model="dialogData.value.description"
type="text"
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<QCheckbox
:label="t('ticketList.viewReceipt')"
v-model="dialogData.value.viewReceipt"
:toggle-indeterminate="false"
/>
<QCheckbox
:label="t('ticketList.sendEmail')"
v-model="dialogData.value.senEmail"
:toggle-indeterminate="false"
/>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
color="primary"
@click="onSubmit()"
/>
<QBtn
flat
:label="t('globals.close')"
color="primary"
v-close-popup
/>
</div>
</QForm>
</QCard>
</QDialog>
<QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2"> <QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2">
<QBtn <QBtn
@click="sendDocuware(selectedRows)" @click="sendDocuware(selectedRows)"
@ -842,4 +688,5 @@ es:
Zone: Zona Zone: Zona
New ticket: Nuevo ticket New ticket: Nuevo ticket
Component lack: Faltan componentes Component lack: Faltan componentes
You cannot make a payment on account from multiple clients: No puedes hacer un pago a cuenta de varios clientes
</i18n> </i18n>

View File

@ -0,0 +1,324 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useDialogPluginComponent } from 'quasar';
import { usePrintService } from 'src/composables/usePrintService';
import useNotify from 'src/composables/useNotify.js';
import FormModelPopup from 'src/components/FormModelPopup.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
import { useState } from 'src/composables/useState';
const { t } = useI18n();
const { notify } = useNotify();
const { sendEmail, openReport } = usePrintService();
const { dialogRef } = useDialogPluginComponent();
const $props = defineProps({
formData: {
type: Object,
required: true,
},
clientId: {
type: Number,
required: true,
},
promise: {
type: Function,
default: null,
},
});
const closeButton = ref(null);
const viewReceipt = ref();
const shouldSendEmail = ref(false);
const maxAmount = ref();
const accountingType = ref({});
const isCash = ref(false);
const formModelRef = ref(false);
const amountToReturn = ref();
const filterBanks = {
fields: ['id', 'bank', 'accountingTypeFk'],
include: { relation: 'accountingType' },
order: 'id',
};
const state = useState();
const user = state.getUser();
const originalDescription = ref('');
const initialData = ref({
...$props.formData,
companyFk: user.value.companyFk,
payed: Date.vnNew(),
originalDescription: '',
});
function setPaymentType(data, accounting) {
if (!accounting) return;
data.bankFk = accounting.id;
accountingType.value = accounting.accountingType;
data.description = [];
isCash.value = accountingType.value.code == 'cash';
viewReceipt.value = isCash.value;
switch (accountingType.value.code) {
case 'compensation':
data.description.push($props.formData.description);
break;
default:
if (
accountingType.value.receiptDescription != null &&
accountingType.value.receiptDescription != ''
) {
data.description.push(accountingType.value.receiptDescription);
}
const originalDescription =
data.originalDescription || $props.formData.description;
if (originalDescription) {
data.description.push(originalDescription);
}
}
data.description = data.description.join(', ');
data.payed = Date.vnNew();
if (accountingType.value.daysInFuture) {
data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture);
}
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
}
const calculateFromAmount = (event) => {
initialData.value.amountToReturn = Number(
(parseFloat(initialData.value.deliveredAmount) + parseFloat(event) * -1).toFixed(
2,
),
);
};
const calculateFromDeliveredAmount = (event) => {
amountToReturn.value = Number((event - initialData.value.amountPaid).toFixed(2));
};
function onBeforeSave(data) {
const exceededAmount = data.amountPaid > maxAmount.value;
if (isCash.value && exceededAmount)
return notify(t('Amount exceeded', { maxAmount: maxAmount.value }), 'negative');
if (isCash.value && shouldSendEmail.value && !data.email)
return notify(t('There is no assigned email for this client'), 'negative');
return data;
}
async function onDataSaved({ email, id }) {
try {
if (shouldSendEmail.value && isCash.value)
await sendEmail(`Receipts/${id}/receipt-email`, {
recipient: email,
});
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank');
} finally {
if ($props.promise) $props.promise();
if (closeButton.value) closeButton.value.click();
}
}
async function getSupplierClientReferences(data) {
if (!data) return (initialData.value.description = '');
const params = { bankAccount: data.compensationAccount };
const { data: reference } = await axios(`Clients/getClientOrSupplierReference`, {
params,
});
if (reference.supplierId) {
data.description = t('Supplier Compensation Reference', {
supplierId: reference.supplierId,
supplierName: reference.supplierName,
});
return;
}
data.description = t('Client Compensation Reference', {
clientId: reference.clientId,
clientName: reference.clientName,
});
}
async function getAmountPaid() {
const filter = {
where: {
clientFk: $props.clientId,
companyFk: initialData.value.companyFk,
},
};
const { data } = await getClientRisk(filter);
initialData.value.amountPaid = (data?.length && data[0].amount) || undefined;
}
async function onSubmit(formData) {
const clientFk = $props.clientId;
const {
data: [{ email }],
} = await axios.get('Clients', {
params: {
filter: JSON.stringify({ where: { id: clientFk } }),
},
});
const { data } = await axios.post(`Clients/${clientFk}/createReceipt`, {
payed: formData.payed,
companyFk: formData.companyFk,
bankFk: formData.bankFk,
amountPaid: formData.amountPaid,
description: formData.description,
clientFk,
email,
});
if (data) notify('globals.dataSaved', 'positive');
await onDataSaved(data);
}
</script>
<template>
<QDialog ref="dialogRef" persistent>
<FormModelPopup
ref="formModelRef"
:form-initial-data="initialData"
:save-fn="onSubmit"
:prevent-submit="true"
:mapper="onBeforeSave"
>
<template #form-inputs="{ data, validate }">
<h5 class="q-mt-none">{{ t('New payment') }}</h5>
<VnRow>
<VnSelect
autofocus
:label="t('Bank')"
v-model="data.bankFk"
url="Accountings"
:filter="filterBanks"
option-label="bank"
:include="{ relation: 'accountingType' }"
sort-by="id"
@update:model-value="
(value, options) => setPaymentType(data, value, options)
"
:emit-value="false"
data-cy="paymentBank"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.id }}:&ensp;{{ scope.opt.bank }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInputNumber
:label="t('Amount')"
:required="true"
@update:model-value="calculateFromAmount($event)"
clearable
v-model.number="data.amountPaid"
data-cy="paymentAmount"
:positive="false"
/>
</VnRow>
<VnRow>
<VnInputDate
:label="t('Date')"
v-model="data.payed"
:required="true"
/>
<VnSelect
url="Companies"
:label="t('Company')"
:required="true"
:rules="validate('entry.companyFk')"
hide-selected
option-label="code"
v-model="data.companyFk"
@update:model-value="getAmountPaid()"
/>
</VnRow>
<div v-if="accountingType.code == 'compensation'">
<div class="text-h6">
{{ t('Compensation') }}
</div>
<VnRow>
<VnAccountNumber
:label="t('Compensation account')"
clearable
v-model="data.compensationAccount"
@blur="getSupplierClientReferences(data)"
/>
</VnRow>
</div>
<VnInput
:label="t('Reference')"
:required="true"
clearable
v-model="data.description"
/>
<div v-if="accountingType.code == 'cash'">
<div class="text-h6">{{ t('Cash') }}</div>
<VnRow>
<VnInputNumber
:label="t('Delivered amount')"
@update:model-value="calculateFromDeliveredAmount($event)"
clearable
v-model="data.deliveredAmount"
/>
<VnInputNumber
:label="t('Amount to return')"
disable
v-model="amountToReturn"
/>
</VnRow>
<VnRow>
<QCheckbox v-model="viewReceipt" :label="t('View recipt')" />
<QCheckbox v-model="shouldSendEmail" :label="t('Send email')" />
</VnRow>
</div>
</template>
</FormModelPopup>
</QDialog>
</template>
<i18n>
en:
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
es:
New payment: Añadir pago
Date: Fecha
Company: Empresa
Bank: Caja
Amount: Importe
Reference: Referencia
Cash: Efectivo
Delivered amount: Cantidad entregada
Amount to return: Cantidad a devolver
View recipt: Ver recibido
Send email: Enviar correo
Compensation: Compensación
Compensation account: Cuenta para compensar
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
There is no assigned email for this client: No hay correo asignado para este cliente
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {maxAmount}
</i18n>

View File

@ -206,7 +206,6 @@ ticketList:
toLines: Go to lines toLines: Go to lines
addressNickname: Address nickname addressNickname: Address nickname
ref: Reference ref: Reference
hour: Hour
rounding: Rounding rounding: Rounding
noVerifiedData: No verified data noVerifiedData: No verified data
warehouse: Warehouse warehouse: Warehouse
@ -215,6 +214,8 @@ ticketList:
clientFrozen: Client frozen clientFrozen: Client frozen
componentLack: Component lack componentLack: Component lack
negative: negative:
nextEntryFk: Next entry
nextEntryLanded: Next entry landed
hour: Hour hour: Hour
id: Id Article id: Id Article
longName: Article longName: Article
@ -225,6 +226,7 @@ negative:
value: Negative value: Negative
itemFk: Article itemFk: Article
producer: Producer producer: Producer
excludedDates: Excluded dates
warehouse: Warehouse warehouse: Warehouse
warehouseFk: Warehouse warehouseFk: Warehouse
category: Category category: Category

View File

@ -215,6 +215,8 @@ ticketList:
addressNickname: Alias consignatario addressNickname: Alias consignatario
ref: Referencia ref: Referencia
negative: negative:
nextEntryLanded: F. Entrada
nextEntryFk: Entrada
hour: Hora hour: Hora
id: Id Articulo id: Id Articulo
longName: Artículo longName: Artículo
@ -225,7 +227,8 @@ negative:
origen: Origen origen: Origen
value: Negativo value: Negativo
warehouseFk: Almacen warehouseFk: Almacen
producer: Producer producer: Productor
excludedDates: Fechas excluidas
category: Categoría category: Categoría
categoryFk: Familia categoryFk: Familia
typeFk: Familia typeFk: Familia

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