Compare commits

...

131 Commits

Author SHA1 Message Date
Javier Segarra c890dfeb7d Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6434-improve-signInLog
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-24 12:45:22 +01:00
Javier Segarra cbcc2ba17c refs #6434 perf: remove default value param 2023-11-24 12:44:36 +01:00
Jorge Penadés 74dd64126a Merge pull request '#5835 getTotals fixed' (!1854) from 5835-fixGetTotals into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1854
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-24 09:56:21 +00:00
Jorge Penadés b3584d9db5 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5835-fixGetTotals
gitea/salix/pipeline/head This commit looks good Details
2023-11-24 10:12:45 +01:00
Javi Gallego 4a62588464 Merge pull request '6028_route_getRouteByWorker' (!1857) from 6028_route_getRouteByWorker into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1857
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2023-11-23 14:01:53 +00:00
Jorge Penadés 5f09907c98 Merge branch '5835-fixGetTotals' of https://gitea.verdnatura.es/verdnatura/salix into 5835-fixGetTotals
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-23 13:21:09 +01:00
Jorge Penadés 025e4f3f87 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5835-fixGetTotals 2023-11-23 13:21:00 +01:00
Jorge Penadés f4a1d09ef8 Merge branch 'dev' into 5835-fixGetTotals
gitea/salix/pipeline/head This commit looks good Details
2023-11-23 12:04:05 +00:00
Sergio De la torre 756e55f5c2 Merge pull request 'refs #5890 fix: getTickets' (!1852) from 5890_collection_getTickets into dev
gitea/salix/pipeline/head Build queued... Details
Reviewed-on: #1852
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2023-11-23 11:23:15 +00:00
Alex Moreno 4735307f5d refs #5890: fix(Collection/getTickets): accepts iss.quantity nulls
gitea/salix/pipeline/head This commit looks good Details
2023-11-23 11:49:00 +01:00
Jorge Penadés 6b2c1e09de refs #5835 refactor
gitea/salix/pipeline/head This commit looks good Details
2023-11-23 11:31:00 +01:00
Javi Gallego 5b145a57e6 error: refs 6028 test front quitado
gitea/salix/pipeline/head This commit looks good Details
2023-11-23 11:29:53 +01:00
Jorge Penadés 048e3089cd Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5835-fixGetTotals
gitea/salix/pipeline/head This commit looks good Details
2023-11-23 11:26:41 +01:00
Javi Gallego f44424dbcf test: refs #6028 test
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-23 09:44:47 +01:00
Javi Gallego 5b289ec083 Merge branch 'dev' into 6028_route_getRouteByWorker 2023-11-23 09:21:44 +01:00
Alex Moreno 1277bec801 Merge branch 'dev' into 5890_collection_getTickets
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-23 07:56:02 +00:00
Alex Moreno 217aa1ac77 refs #6491 deploy(2350) init version
gitea/salix/pipeline/head This commit looks good Details
2023-11-23 08:35:08 +01:00
Alex Moreno f4fabb9fff refs #6491 refactor(order_filter): optimized
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-23 08:26:14 +01:00
Carlos Andrés 800d12e0f2 Merge branch 'dev' into 5890_collection_getTickets
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-22 18:00:34 +00:00
Javi Gallego e856de0cfd feat: refs #6028 test
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-22 15:54:38 +01:00
Guillermo Bonet 31c1add02b Merge pull request 'refactor: refs #4502 Deleted zoneEstimatedDelivery' (!1844) from 4502-zoneEstimatedDelivery into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1844
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-22 12:52:36 +00:00
Javi Gallego e146c0ea46 Merge branch 'dev' into 6028_route_getRouteByWorker
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-22 12:42:20 +01:00
Guillermo Bonet d52c82fb19 Merge branch 'dev' into 4502-zoneEstimatedDelivery
gitea/salix/pipeline/head This commit looks good Details
2023-11-22 11:36:07 +00:00
Jorge Penadés a27bb53089 refs #5835 getTotals fixed
gitea/salix/pipeline/head This commit looks good Details
2023-11-22 08:57:45 +01:00
Sergio De la torre 707173d618 refs #5890 fix: getTickets
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-22 07:04:33 +01:00
Alex Moreno f9382a1c22 Merge pull request 'refs #6454 feat: dump db' (!1849) from 6454-dbDump into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1849
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-21 12:26:53 +00:00
Alex Moreno 95a55f612d Merge branch 'dev' into 6454-dbDump
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 10:58:21 +00:00
Pablo Natek bddb787075 Merge pull request 'remove(barcode): refs #5228 remove barcode from report' (!1847) from 5228-fixPalletReport into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1847
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2023-11-21 10:44:49 +00:00
Pablo Natek 6fc818deab Merge branch 'dev' into 5228-fixPalletReport
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 10:33:20 +00:00
Guillermo Bonet b3d1c72adc Merge branch 'dev' into 4502-zoneEstimatedDelivery
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 09:13:41 +00:00
Alex Moreno e909c2bc26 Merge branch 'dev' into 6454-dbDump
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 09:13:17 +00:00
Alex Moreno 02e8b90926 refs #6454 test: fix e2e by dump
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 10:12:07 +01:00
Jorge Penadés 4c287f6a23 Merge pull request 'ref #5835 due day new and fixtures' (!1833) from 5835-newInvoiceInDueDay into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1833
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-21 09:08:59 +00:00
Jorge Penadés dd11cf5327 ref #5835 fix test
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 09:03:25 +01:00
Jorge Penadés 34e5409533 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5835-newInvoiceInDueDay
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 08:19:11 +01:00
Pablo Natek c62390fa4a Merge branch 'dev' into 5228-fixPalletReport
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 06:55:51 +00:00
Pablo Natek fc653746f0 remove(barcode): refs #5228 remove barcode from report
gitea/salix/pipeline/head This commit looks good Details
2023-11-21 07:54:25 +01:00
Alex Moreno ee1a0c15ee refs #6454 feat: dump db
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-20 15:09:55 +01:00
Guillermo Bonet 5fdf18825c Merge branch 'dev' into 4502-zoneEstimatedDelivery
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-20 13:14:51 +00:00
Alex Moreno e25ca914a0 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix into dev
gitea/salix/pipeline/head This commit looks good Details
2023-11-20 10:13:02 +01:00
Guillermo Bonet b2b12adb3d Merge branch '4502-zoneEstimatedDelivery' of https://gitea.verdnatura.es/verdnatura/salix into 4502-zoneEstimatedDelivery
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-20 09:47:58 +01:00
Guillermo Bonet 61b2d4c5fd refactor: refs #4502 zoneEstimatedDelivery 2023-11-20 09:47:57 +01:00
Guillermo Bonet badb4d69a2 Merge branch 'dev' into 4502-zoneEstimatedDelivery
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-20 06:15:40 +00:00
Pablo Natek f27fdaa023 Merge pull request 'fix(filter): refs #6461 now is mockDate in local' (!1845) from 6461-fixFilterNegativeBases into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1845
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2023-11-17 11:23:24 +00:00
Pablo Natek e2a2454d4e Merge branch 'dev' into 6461-fixFilterNegativeBases
gitea/salix/pipeline/head This commit looks good Details
2023-11-17 10:31:35 +00:00
Pablo Natek dc100f7959 fix(filter): refs #6461 now is mockDate in local
gitea/salix/pipeline/head This commit looks good Details
2023-11-17 11:29:57 +01:00
Juan Ferrer bb6f3b4cfd fix: refs #6432 account sync fixes
gitea/salix/pipeline/head This commit looks good Details
2023-11-17 08:56:25 +01:00
Alex Moreno 21701c40ae Merge pull request '#6335 -ticketAdvance_splitOrCreate' (!1817) from 6335-ticketAdvance_splitOrCreate into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1817
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-17 06:55:58 +00:00
Guillermo Bonet 8da36569f7 refactor: refs #4502 zoneEstimatedDelivery
gitea/salix/pipeline/head Build queued... Details
2023-11-17 07:44:43 +01:00
Guillermo Bonet 500eb22dc6 Merge branch 'dev' into 4502-zoneEstimatedDelivery
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-17 06:43:58 +00:00
Guillermo Bonet 3d1c69be56 refactor: refs #4502 zoneEstimatedDelivery
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-17 07:43:32 +01:00
Guillermo Bonet 2f611391b9 Revert "refactor: refs #4502 Deleted zoneEstimatedDelivery"
gitea/salix/pipeline/head This commit looks good Details
This reverts commit bfd6e1f398.
2023-11-17 07:17:29 +01:00
Juan Ferrer c2e295f777 fix: refs #6432 account sync fixes 2023-11-17 00:08:29 +01:00
Juan Ferrer 14eb14ad3f fix: refs #6432 bookworm upgrade
gitea/salix/pipeline/head This commit looks good Details
2023-11-17 00:00:36 +01:00
Juan Ferrer 6930c2d52e fix: #6432 account sync fixes
gitea/salix/pipeline/head This commit looks good Details
2023-11-16 23:13:16 +01:00
Juan Ferrer ea5a881d1f fix: refs #6432 account sync fixes
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-16 23:10:30 +01:00
Juan Ferrer 1937560118 Merge branch 'master' of https://gitea.verdnatura.es/verdnatura/salix
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-16 23:07:30 +01:00
Juan Ferrer 4f5242e3ae fix: refs #6432 account sync fixes 2023-11-16 23:07:26 +01:00
Alex Moreno aea3f7b2b9 refs #6434 fix: use trim
gitea/salix/pipeline/head This commit looks good Details
2023-11-16 15:10:47 +01:00
Alex Moreno b8d256e4e4 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6335-ticketAdvance_splitOrCreate
gitea/salix/pipeline/head This commit looks good Details
2023-11-16 09:58:51 +01:00
Alex Moreno f677382f96 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix into dev
gitea/salix/pipeline/head This commit looks good Details
2023-11-16 09:29:53 +01:00
Guillermo Bonet bfd6e1f398 refactor: refs #4502 Deleted zoneEstimatedDelivery
gitea/salix/pipeline/head This commit looks good Details
2023-11-16 08:29:00 +01:00
Javi Gallego 87bd6c4282 Merge pull request 'fix: refs: #6207 impide facturar si no tienes incoterm' (!1829) from 6207-incoterm-solo-en-serie-world into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1829
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2023-11-15 09:26:28 +00:00
Javi Gallego a4328e607e Merge branch 'dev' into 6207-incoterm-solo-en-serie-world
gitea/salix/pipeline/head This commit looks good Details
2023-11-15 10:11:05 +01:00
Alex Moreno 2a67048594 Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix into dev
gitea/salix/pipeline/head This commit looks good Details
2023-11-14 15:54:47 +01:00
Alex Moreno e25969360c Merge pull request 'ref #5914 renameTable' (!1831) from 5914-warmfix-renameTable into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1831
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
2023-11-14 14:04:34 +00:00
Alex Moreno aed673cea3 Merge branch 'dev' into 5914-warmfix-renameTable
gitea/salix/pipeline/head This commit looks good Details
2023-11-14 13:24:41 +00:00
Sergio De la torre 2bec1a2bed refs #6014 fix: response type. fix: accessType EXECUTE
gitea/salix/pipeline/head This commit looks good Details
2023-11-14 14:02:40 +01:00
Alex Moreno 2cd9c9c688 Merge branch 'dev' into 5914-warmfix-renameTable
gitea/salix/pipeline/head This commit looks good Details
2023-11-14 09:36:47 +00:00
Alex Moreno bc6389fd66 Merge branch 'dev' into 6335-ticketAdvance_splitOrCreate
gitea/salix/pipeline/head This commit looks good Details
2023-11-14 09:00:12 +00:00
Alex Moreno aec1c9d447 fix: correct align
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-14 09:54:07 +01:00
Alex Moreno 12f23fc894 Merge pull request '6014-executeRoutineMethod' (!1809) from 6014-executeRoutineMethod into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1809
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-14 08:45:04 +00:00
Alex Moreno cce61ae8cc refs #6014 refactor(execute): schema required
gitea/salix/pipeline/head This commit looks good Details
2023-11-14 09:17:46 +01:00
Alex Moreno 47d85c4c61 refs #6335 fix: remove unnecessary translations
gitea/salix/pipeline/head This commit looks good Details
2023-11-13 12:54:02 +01:00
Alex Moreno 59d2da24eb refs #6014 refactor(execute): split code
gitea/salix/pipeline/head This commit looks good Details
2023-11-13 09:36:20 +01:00
Alex Moreno 0fed5c1ab4 Merge branch 'dev' into 6335-ticketAdvance_splitOrCreate
gitea/salix/pipeline/head This commit looks good Details
2023-11-10 09:59:24 +00:00
Alex Moreno 69255fe2fc refs #6014 feat(execute): use user_hasRoutinePriv. feat(execute): split in executeProc & executeFunc
gitea/salix/pipeline/head This commit looks good Details
2023-11-10 10:58:58 +01:00
Alex Moreno f1b5e45491 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6014-executeRoutineMethod 2023-11-10 10:53:11 +01:00
Alex Moreno ad07c4ec97 Merge pull request '#4797 Seccion Worker/Notification' (!1494) from 4797-worker-notification-selector into dev
gitea/salix/pipeline/head This commit looks good Details
Reviewed-on: #1494
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
2023-11-10 09:47:40 +00:00
Alex Moreno 2eacf7391e refs #6335 fix(db_ticket_canAdvance): not use unnecessary ifnulls
gitea/salix/pipeline/head This commit looks good Details
2023-11-10 08:46:49 +01:00
Alex Moreno e9666a5930 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6335-ticketAdvance_splitOrCreate 2023-11-10 08:11:52 +01:00
Jorge Penadés 40b9a83606 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5835-newInvoiceInDueDay
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 15:59:12 +01:00
Jorge Penadés 8d94a94e14 ref #5914 fix test back
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 15:56:50 +01:00
Jorge Penadés 513562955b ref #5835 test back
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 15:53:41 +01:00
Jorge Penadés 52f1843580 ref #5835 due day new and fixtures
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 15:00:49 +01:00
Jorge Penadés 0baa9c5a98 ref #5914 fix summary
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-09 14:34:53 +01:00
Jorge Penadés b07858fddb ref #5914 replace name
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-09 14:33:46 +01:00
Sergio De la torre 4aac9f93cc Merge branch 'dev' into 6014-executeRoutineMethod
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 13:03:25 +00:00
Jorge Penadés 3e45334149 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5914-warmfix-renameTable
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-09 12:04:47 +01:00
Jorge Penadés 01d3c23cf0 ref #5914 renameTable
gitea/salix/pipeline/head There was a failure building this commit Details
2023-11-09 11:57:52 +01:00
Alex Moreno a0c4573f4d refs #4797 fix: correct sql folder
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 09:34:37 +01:00
Alex Moreno 52f419d4cf Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector 2023-11-09 09:33:22 +01:00
Alex Moreno dbe6844419 refs #6415 feat: init version 23.48
gitea/salix/pipeline/head This commit looks good Details
2023-11-09 09:31:32 +01:00
Alex Moreno 204323ac69 test(notification_getList): fix
gitea/salix/pipeline/head This commit looks good Details
2023-11-08 14:57:07 +01:00
Alex Moreno 0de5bc5109 refs #4797 refactor: getList & notificationSubscription method 2023-11-08 14:56:47 +01:00
Javi Gallego 9d54618620 fix: refs: #6207 impide facturar si no tienes incoterm
gitea/salix/pipeline/head This commit looks good Details
2023-11-08 10:01:02 +01:00
Alex Moreno 8c5e40c27f refs #4797 fix: correct sql folder
gitea/salix/pipeline/head This commit looks good Details
2023-11-03 15:16:03 +01:00
Alex Moreno d06810b1b9 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-11-03 14:58:55 +01:00
Alex Moreno a523e32373 refs #6335 style(ticketAdvance): fix with split-progress
gitea/salix/pipeline/head This commit looks good Details
2023-11-03 07:57:53 +01:00
Alex Moreno 740d2fb976 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6335-ticketAdvance_splitOrCreate 2023-11-02 08:48:03 +01:00
Alex Moreno d7542bcbb4 refs #6335 fix(ticketAdvance): procedure
gitea/salix/pipeline/head There was a failure building this commit Details
2023-10-31 15:11:03 +01:00
Alex Moreno 37dce99447 refs #6335 test(ticketAdvance): add test to keep price
gitea/salix/pipeline/head There was a failure building this commit Details
2023-10-30 15:00:05 +01:00
Alex Moreno a80715583c refs #6335 feat(ticketAdvance): change the date if you can
gitea/salix/pipeline/head There was a failure building this commit Details
2023-10-30 14:16:25 +01:00
Alex Moreno bd8cc897c6 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6335-ticketAdvance_splitOrCreate 2023-10-30 07:21:31 +01:00
Alex Moreno ae160890a8 refs #6335 feat(ticketAdvance): use componentUpdate
gitea/salix/pipeline/head There was a failure building this commit Details
2023-10-27 15:06:20 +02:00
Alex Moreno 6b94ea7d11 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6335-ticketAdvance_splitOrCreate 2023-10-26 09:48:35 +02:00
Alex Moreno 26bdbabff5 refs #6335 feat(ticketAdvanced): show all tickets
gitea/salix/pipeline/head There was a failure building this commit Details
2023-10-25 15:48:19 +02:00
Alex Moreno 483526c970 refs #6015 feat(executeRoutine): check db restrictions. test: add executeRoutine tests
gitea/salix/pipeline/head This commit looks good Details
2023-10-23 15:03:05 +02:00
Alex Moreno c04ceeced2 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6014-executeRoutineMethod 2023-10-20 09:52:00 +02:00
Alex Moreno 4d3d38ebe3 refs #6014 feat: executeRoutine
gitea/salix/pipeline/head This commit looks good Details
2023-10-18 15:22:04 +02:00
Sergio De la torre 639d5963a3 refs #6028 get_routes
gitea/salix/pipeline/head There was a failure building this commit Details
2023-07-17 13:50:07 +02:00
Alex Moreno 6f06bac0c1 Merge branch 'dev' into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-23 05:55:08 +00:00
Alexandre Riera 8db7a5b16a refs #4797 moved sql
gitea/salix/pipeline/head This commit looks good Details
2023-05-18 14:53:09 +02:00
Alexandre Riera 58c25786a6 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-18 14:52:33 +02:00
Alexandre Riera ccdc43d4a3 Merge branch 'dev' into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-16 07:22:13 +00:00
Alexandre Riera 2c254cbc08 refs #4797 added fields
gitea/salix/pipeline/head There was a failure building this commit Details
2023-05-15 14:34:32 +02:00
Alexandre Riera f0cd01fe7d Merge branch '4797-worker-notification-selector' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector
gitea/salix/pipeline/head There was a failure building this commit Details
2023-05-15 14:27:57 +02:00
Alexandre Riera d4e2049bc7 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector 2023-05-15 14:27:49 +02:00
Alex Moreno 931514a8e8 Merge branch 'dev' into 4797-worker-notification-selector
gitea/salix/pipeline/head There was a failure building this commit Details
2023-05-15 09:41:10 +00:00
Alexandre Riera 33ef9bdf61 refs #4797 refactor
gitea/salix/pipeline/head This commit looks good Details
2023-05-11 07:18:58 +02:00
Alexandre Riera 9cb756544e refs #4797 added id to notification in activeNotifications
gitea/salix/pipeline/head This commit looks good Details
2023-05-10 14:13:38 +02:00
Alexandre Riera f9e7963e74 refs #4797 minor fixes
gitea/salix/pipeline/head This commit looks good Details
2023-05-10 14:10:00 +02:00
Alexandre Riera 5171e63188 refs #4797 minor fixes
gitea/salix/pipeline/head This commit looks good Details
2023-05-09 14:20:20 +02:00
Alexandre Riera e7d16dc8ba refs #4797 fix test, add getList
gitea/salix/pipeline/head This commit looks good Details
2023-05-09 11:34:32 +02:00
Alexandre Riera b37bde2ba7 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-09 08:14:42 +02:00
Alex Moreno a60497dfc0 Merge branch 'dev' into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-03 05:09:33 +00:00
Alexandre Riera 04ad2cb3df Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-02 14:30:45 +02:00
Alexandre Riera 4c0ec6faf5 changedModel sms
gitea/salix/pipeline/head This commit looks good Details
2023-05-02 14:26:47 +02:00
Alexandre Riera 13c37e88ef Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4797-worker-notification-selector
gitea/salix/pipeline/head This commit looks good Details
2023-05-02 11:38:07 +02:00
Alexandre Riera 70c46efe27 refs #4797 fix back test
gitea/salix/pipeline/head This commit looks good Details
2023-05-02 09:51:04 +02:00
Alexandre Riera c2de5c5ff4 refs #4797 added redirection section
gitea/salix/pipeline/head There was a failure building this commit Details
2023-05-02 07:28:59 +02:00
122 changed files with 5640 additions and 3397 deletions

View File

@ -10,5 +10,9 @@
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
},
"cSpell.words": [
"salix",
"fdescribe"
]
}

View File

@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2350.01] - 2023-12-14
### Added
### Changed
### Fixed
## [2348.01] - 2023-11-30
### Added
- (Ticket -> Adelantar) Permite mover lineas sin generar negativos
- (Ticket -> Adelantar) Permite modificar la fecha de los tickets
- (Trabajadores -> Notificaciones) Nueva sección (lilium)
### Changed
### Fixed
- (Ticket -> RocketChat) Arreglada detección de cambios
## [2346.01] - 2023-11-16
### Added

View File

@ -1,4 +1,4 @@
FROM debian:bullseye-slim
FROM debian:bookworm-slim
ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive
@ -25,7 +25,13 @@ RUN apt-get update \
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Extra dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
samba-common-bin samba-dsdb-modules\
&& rm -rf /var/lib/apt/lists/* \
&& npm -g install pm2

View File

@ -49,7 +49,7 @@ module.exports = Self => {
ish.packing,
ish.grouping,
s.isAdded,
s.originalQuantity,
s.originalQuantity,
s.quantity saleQuantity,
iss.quantity reservedQuantity,
SUM(iss.quantity) OVER (PARTITION BY s.id ORDER BY ish.id) accumulatedQuantity,
@ -75,7 +75,7 @@ module.exports = Self => {
LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk
LEFT JOIN origin o ON o.id = i.originFk
WHERE tc.collectionFk = ?
GROUP BY ish.id, p.code, p2.code
GROUP BY s.id, ish.id, p.code, p2.code
ORDER BY pickingOrder;`, [id], myOptions);
if (print)
@ -105,7 +105,7 @@ module.exports = Self => {
LEFT JOIN vn.buy c ON c.itemFk = s.itemFk
LEFT JOIN vn.entry e ON e.id = c.entryFk
LEFT JOIN vn.travel tr ON tr.id = e.travelFk
WHERE s.ticketFk = ?
WHERE s.ticketFk = ?
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
[ticketId], myOptions);
ticket.sales = [];

View File

@ -1,133 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('newCollection', {
description: 'Make a new collection of tickets',
accessType: 'WRITE',
accepts: [{
arg: 'collectionFk',
type: 'Number',
required: false,
description: 'The collection id'
}, {
arg: 'sectorFk',
type: 'Number',
required: true,
description: 'The sector of worker'
}, {
arg: 'vWagons',
type: 'Number',
required: true,
description: 'The number of wagons'
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/newCollection`,
verb: 'POST'
}
});
Self.newCollection = async(ctx, collectionFk, sectorFk, vWagons) => {
let query = '';
const userId = ctx.req.accessToken.userId;
if (!collectionFk) {
query = `CALL vn.collectionTrain_newBeta(?,?,?)`;
const [result] = await Self.rawSql(query, [sectorFk, vWagons, userId], {userId});
if (result.length == 0)
throw new Error(`No collections for today`);
collectionFk = result[0].vCollectionFk;
}
query = `CALL vn.collectionTicket_get(?)`;
const [tickets] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionSale_get(?)`;
const [sales] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionPlacement_get(?)`;
const [placements] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionSticker_print(?,?)`;
await Self.rawSql(query, [collectionFk, sectorFk], {userId});
return makeCollection(tickets, sales, placements, collectionFk);
};
/**
* Returns a collection json
* @param {*} tickets - Request tickets
* @param {*} sales - Request sales
* @param {*} placements - Request placements
* @param {*} collectionFk - Request placements
* @return {Object} Collection JSON
*/
async function makeCollection(tickets, sales, placements, collectionFk) {
let collection = [];
for (let i = 0; i < tickets.length; i++) {
let ticket = {};
ticket['ticketFk'] = tickets[i]['ticketFk'];
ticket['level'] = tickets[i]['level'];
ticket['agencyName'] = tickets[i]['agencyName'];
ticket['warehouseFk'] = tickets[i]['warehouseFk'];
ticket['salesPersonFk'] = tickets[i]['salesPersonFk'];
let ticketSales = [];
for (let x = 0; x < sales.length; x++) {
if (sales[x]['ticketFk'] == ticket['ticketFk']) {
let sale = {};
sale['collectionFk'] = collectionFk;
sale['ticketFk'] = sales[x]['ticketFk'];
sale['saleFk'] = sales[x]['saleFk'];
sale['itemFk'] = sales[x]['itemFk'];
sale['quantity'] = sales[x]['quantity'];
if (sales[x]['quantityPicked'] != null)
sale['quantityPicked'] = sales[x]['quantityPicked'];
else
sale['quantityPicked'] = 0;
sale['longName'] = sales[x]['longName'];
sale['size'] = sales[x]['size'];
sale['color'] = sales[x]['color'];
sale['discount'] = sales[x]['discount'];
sale['price'] = sales[x]['price'];
sale['stems'] = sales[x]['stems'];
sale['category'] = sales[x]['category'];
sale['origin'] = sales[x]['origin'];
sale['clientFk'] = sales[x]['clientFk'];
sale['productor'] = sales[x]['productor'];
sale['reserved'] = sales[x]['reserved'];
sale['isPreviousPrepared'] = sales[x]['isPreviousPrepared'];
sale['isPrepared'] = sales[x]['isPrepared'];
sale['isControlled'] = sales[x]['isControlled'];
let salePlacements = [];
for (let z = 0; z < placements.length; z++) {
if (placements[z]['saleFk'] == sale['saleFk']) {
let placement = {};
placement['saleFk'] = placements[z]['saleFk'];
placement['itemFk'] = placements[z]['itemFk'];
placement['placement'] = placements[z]['placement'];
placement['shelving'] = placements[z]['shelving'];
placement['created'] = placements[z]['created'];
placement['visible'] = placements[z]['visible'];
placement['order'] = placements[z]['order'];
placement['grouping'] = placements[z]['grouping'];
salePlacements.push(placement);
}
}
sale['placements'] = salePlacements;
ticketSales.push(sale);
}
}
ticket['sales'] = ticketSales;
collection.push(ticket);
}
return collection;
}
};

View File

@ -1,12 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('newCollection()', () => {
it('should return a new collection', async() => {
pending('#3400 analizar que hacer con rutas de back collection');
let ctx = {req: {accessToken: {userId: 1106}}};
let response = await models.Collection.newCollection(ctx, 1, 1, 1);
expect(response.length).toBeGreaterThan(0);
expect(response[0].ticketFk).toEqual(2);
});
});

View File

@ -0,0 +1,54 @@
module.exports = Self => {
Self.remoteMethod('getList', {
description: 'Get list of the available and active notification subscriptions',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
description: 'User to modify',
http: {source: 'path'}
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getList`,
verb: 'GET'
}
});
Self.getList = async(id, options) => {
const activeNotificationsMap = new Map();
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const availableNotificationsMap = await Self.getAvailable(id, myOptions);
const activeNotifications = await Self.app.models.NotificationSubscription.find({
fields: ['id', 'notificationFk'],
include: {relation: 'notification'},
where: {userFk: id}
}, myOptions);
for (active of activeNotifications) {
activeNotificationsMap.set(active.notificationFk, {
id: active.id,
notificationFk: active.notificationFk,
name: active.notification().name,
description: active.notification().description,
active: true
});
availableNotificationsMap.delete(active.notificationFk);
}
return {
active: [...activeNotificationsMap.entries()],
available: [...availableNotificationsMap.entries()]
};
};
};

View File

@ -0,0 +1,13 @@
const models = require('vn-loopback/server/server').models;
describe('NotificationSubscription getList()', () => {
it('should return a list of available and active notifications of a user', async() => {
const userId = 9;
const {active, available} = await models.NotificationSubscription.getList(userId);
const notifications = await models.Notification.find({});
const totalAvailable = notifications.length - active.length;
expect(active.length).toEqual(2);
expect(available.length).toEqual(totalAvailable);
});
});

View File

@ -1,6 +1,5 @@
module.exports = Self => {
require('../methods/collection/getCollection')(Self);
require('../methods/collection/newCollection')(Self);
require('../methods/collection/getSectors')(Self);
require('../methods/collection/setSaleQuantity')(Self);
require('../methods/collection/previousLabel')(Self);

View File

@ -1,62 +1,74 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/notification/getList')(Self);
Self.observe('before save', async function(ctx) {
await checkModifyPermission(ctx);
});
Self.observe('before delete', async function(ctx) {
await checkModifyPermission(ctx);
});
async function checkModifyPermission(ctx) {
const models = Self.app.models;
const instance = ctx.instance;
const userId = ctx.options.accessToken.userId;
const user = await ctx.instance.userFk;
const modifiedUser = await getUserToModify(null, user, models);
if (userId != modifiedUser.id && userId != modifiedUser.bossFk)
throw new UserError('You dont have permission to modify this user');
});
let notificationFk;
let workerId;
Self.remoteMethod('deleteNotification', {
description: 'Deletes a notification subscription',
accepts: [
{
arg: 'ctx',
type: 'object',
http: {source: 'context'}
},
{
arg: 'notificationId',
type: 'number',
required: true
},
],
returns: {
type: 'object',
root: true
},
http: {
verb: 'POST',
path: '/deleteNotification'
if (instance) {
notificationFk = instance.notificationFk;
workerId = instance.userFk;
} else {
const notificationSubscription = await models.NotificationSubscription.findById(ctx.where.id);
notificationFk = notificationSubscription.notificationFk;
workerId = notificationSubscription.userFk;
}
});
Self.deleteNotification = async function(ctx, notificationId) {
const models = Self.app.models;
const user = ctx.req.accessToken.userId;
const modifiedUser = await getUserToModify(notificationId, null, models);
const worker = await models.Worker.findById(workerId, {fields: ['id', 'bossFk']});
const available = await Self.getAvailable(workerId);
const hasAcl = available.has(notificationFk);
if (user != modifiedUser.id && user != modifiedUser.bossFk)
throw new UserError('You dont have permission to modify this user');
await models.NotificationSubscription.destroyById(notificationId);
};
async function getUserToModify(notificationId, userFk, models) {
let userToModify = userFk;
if (notificationId) {
const subscription = await models.NotificationSubscription.findById(notificationId);
userToModify = subscription.userFk;
}
return await models.Worker.findOne({
fields: ['id', 'bossFk'],
where: {
id: userToModify
}
});
if (!hasAcl || (userId != worker.id && userId != worker.bossFk))
throw new UserError('The notification subscription of this worker cant be modified');
}
Self.getAvailable = async function(userId, options) {
const availableNotificationsMap = new Map();
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const roles = await models.RoleMapping.find({
fields: ['roleId'],
where: {principalId: userId}
}, myOptions);
const availableNotifications = await models.NotificationAcl.find({
fields: ['notificationFk', 'roleFk'],
include: {relation: 'notification'},
where: {
roleFk: {
inq: roles.map(role => role.roleId),
},
}
}, myOptions);
for (available of availableNotifications) {
availableNotificationsMap.set(available.notificationFk, {
id: null,
notificationFk: available.notificationFk,
name: available.notification().name,
description: available.notification().description,
active: false
});
}
return availableNotificationsMap;
};
};

View File

@ -1,74 +1,126 @@
const models = require('vn-loopback/server/server').models;
describe('loopback model NotificationSubscription', () => {
it('Should fail to delete a notification if the user is not editing itself or a subordinate', async() => {
it('should fail to add a notification subscription if the worker doesnt have ACLs', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const user = 9;
const options = {transaction: tx, accessToken: {userId: 9}};
await models.NotificationSubscription.create({notificationFk: 1, userFk: 62}, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual('The notification subscription of this worker cant be modified');
});
it('should fail to add a notification subscription if the user isnt editing itself or subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx, accessToken: {userId: 1}};
await models.NotificationSubscription.create({notificationFk: 1, userFk: 9}, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual('The notification subscription of this worker cant be modified');
});
it('should fail to delete a notification subscription if the user isnt editing itself or subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx, accessToken: {userId: 9}};
const notificationSubscriptionId = 2;
const ctx = {req: {accessToken: {userId: user}}};
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
await models.NotificationSubscription.destroyAll({id: notificationSubscriptionId}, options);
let error;
try {
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
} catch (e) {
error = e;
}
expect(error.message).toContain('You dont have permission to modify this user');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
error = e;
}
expect(error.message).toEqual('The notification subscription of this worker cant be modified');
});
it('Should delete a notification if the user is editing itself', async() => {
it('should add a notification subscription if the user is editing itself', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const user = 9;
const options = {transaction: tx, accessToken: {userId: 9}};
await models.NotificationSubscription.create({notificationFk: 2, userFk: 9}, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toBeUndefined();
});
it('should delete a notification subscription if the user is editing itself', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx, accessToken: {userId: 9}};
const notificationSubscriptionId = 6;
await models.NotificationSubscription.destroyAll({id: notificationSubscriptionId}, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toBeUndefined();
});
it('should add a notification subscription if the user is editing a subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx, accessToken: {userId: 9}};
await models.NotificationSubscription.create({notificationFk: 1, userFk: 5}, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toBeUndefined();
});
it('should delete a notification subscription if the user is editing a subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
let error;
try {
const options = {transaction: tx, accessToken: {userId: 19}};
const notificationSubscriptionId = 4;
const ctx = {req: {accessToken: {userId: user}}};
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
await models.NotificationSubscription.destroyAll({id: notificationSubscriptionId}, options);
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
const deletedNotification = await models.NotificationSubscription.findById(notificationSubscriptionId);
expect(deletedNotification).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
error = e;
}
});
it('Should delete a notification if the user is editing a subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
try {
const options = {transaction: tx};
const user = 9;
const notificationSubscriptionId = 5;
const ctx = {req: {accessToken: {userId: user}}};
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
const deletedNotification = await models.NotificationSubscription.findById(notificationSubscriptionId);
expect(deletedNotification).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(error).toBeUndefined();
});
});

View File

@ -139,7 +139,7 @@ module.exports = function(Self) {
}
};
Self.validateLogin = async function(user, password, ctx = null) {
Self.validateLogin = async function(user, password, ctx) {
const loginInfo = Object.assign({password}, Self.userUses(user));
const token = await Self.login(loginInfo, 'user');

View File

@ -0,0 +1,2 @@
ALTER TABLE account.sambaConfig
ADD userDn varchar(255) NOT NULL COMMENT 'Base DN for users without domain DN part';

View File

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Application', 'executeProc', '*', 'ALLOW', 'ROLE', 'employee'),
('Application', 'executeFunc', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('NotificationSubscription', 'getList', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,152 @@
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT)
BEGIN
/**
* Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar.
*
* @param vDateFuture Fecha de los tickets que se quieren adelantar.
* @param vDateToAdvance Fecha a cuando se quiere adelantar.
* @param vWarehouseFk Almacén
*/
DECLARE vDateInventory DATE;
SELECT inventoried INTO vDateInventory FROM config;
CREATE OR REPLACE TEMPORARY TABLE tmp.stock
(itemFk INT PRIMARY KEY,
amount INT)
ENGINE = MEMORY;
INSERT INTO tmp.stock(itemFk, amount)
SELECT itemFk, SUM(quantity) amount FROM
(
SELECT itemFk, quantity
FROM itemTicketOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM itemEntryIn
WHERE landed >= vDateInventory
AND landed <= vDateToAdvance
AND isVirtualStock = FALSE
AND warehouseInFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM itemEntryOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseOutFk = vWarehouseFk
) t
GROUP BY itemFk HAVING amount != 0;
CREATE OR REPLACE TEMPORARY TABLE tmp.filter
(INDEX (id))
SELECT
origin.ticketFk futureId,
dest.ticketFk id,
dest.state,
origin.futureState,
origin.futureIpt,
dest.ipt,
origin.workerFk,
origin.futureLiters,
origin.futureLines,
dest.shipped,
origin.shipped futureShipped,
dest.totalWithVat,
origin.totalWithVat futureTotalWithVat,
dest.agency,
dest.agencyModeFk,
origin.futureAgency,
origin.agencyModeFk futureAgencyModeFk,
dest.lines,
dest.liters,
origin.futureLines - origin.hasStock AS notMovableLines,
(origin.futureLines = origin.hasStock) AS isFullMovable,
dest.zoneFk,
origin.futureZoneFk,
origin.futureZoneName,
origin.classColor futureClassColor,
dest.classColor,
origin.clientFk futureClientFk,
origin.addressFk futureAddressFk,
origin.warehouseFk futureWarehouseFk,
origin.companyFk futureCompanyFk,
IFNULL(dest.nickname, origin.nickname) nickname,
dest.landed
FROM (
SELECT
s.ticketFk,
c.salesPersonFk workerFk,
t.shipped,
t.totalWithVat,
st.name futureState,
am.name futureAgency,
count(s.id) futureLines,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt,
CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters,
SUM((s.quantity <= IFNULL(st.amount,0))) hasStock,
z.id futureZoneFk,
z.name futureZoneName,
st.classColor,
t.clientFk,
t.nickname,
t.addressFk,
t.warehouseFk,
t.companyFk,
t.agencyModeFk
FROM ticket t
JOIN client c ON c.id = t.clientFk
JOIN sale s ON s.ticketFk = t.id
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state st ON st.id = ts.stateFk
JOIN agencyMode am ON t.agencyModeFk = am.id
JOIN zone z ON t.zoneFk = z.id
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
LEFT JOIN tmp.stock st ON st.itemFk = i.id
WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture)
AND t.warehouseFk = vWarehouseFk
GROUP BY t.id
) origin
LEFT JOIN (
SELECT
t.id ticketFk,
st.name state,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt,
t.shipped,
t.totalWithVat,
am.name agency,
CAST(SUM(litros) AS DECIMAL(10,0)) liters,
CAST(COUNT(*) AS DECIMAL(10,0)) `lines`,
st.classColor,
t.clientFk,
t.nickname,
t.addressFk,
t.zoneFk,
t.warehouseFk,
t.companyFk,
t.landed,
t.agencyModeFk
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state st ON st.id = ts.stateFk
JOIN agencyMode am ON t.agencyModeFk = am.id
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
WHERE t.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance)
AND t.warehouseFk = vWarehouseFk
AND st.order <= 5
GROUP BY t.id
) dest ON dest.addressFk = origin.addressFk
WHERE origin.hasStock;
DROP TEMPORARY TABLE tmp.stock;
END$$
DELIMITER ;

View File

View File

@ -0,0 +1,26 @@
DELETE FROM `salix`.`ACL`
WHERE
model = 'Route'
AND property = '*'
AND accessType = 'READ';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Route', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getRoutesByWorker', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'canViewAllRoute', 'READ', 'ALLOW', 'ROLE', 'deliveryBoss'),
('Route', 'cmr', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'downloadCmrsZip', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'downloadZip', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getByWorker', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getDeliveryPoint', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getExternalCmrs', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getSuggestedTickets', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getTickets', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'guessPriority', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Route', 'insertTicket', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getDeliveryPoint', 'READ', 'ALLOW', 'ROLE', 'deliveryBoss'),
('Route', 'summary', 'READ', 'ALLOW', 'ROLE', 'employee');

File diff suppressed because one or more lines are too long

View File

@ -59,10 +59,6 @@ INSERT IGNORE INTO `vn`.`greugeConfig`(`id`, `freightPickUpPrice`)
VALUES
('1', '11');
INSERT INTO `vn`.`packagingConfig`(`upperGap`)
VALUES
('10');
UPDATE `account`.`role` SET id = 100 WHERE id = 0;
INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPrefix`, `userHost`, `tplUser`)
@ -73,7 +69,7 @@ CALL `account`.`role_sync`;
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`)
SELECT id, name, CONCAT(name, 'Nick'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2'
FROM `account`.`role` WHERE id <> 20
FROM `account`.`role`
ORDER BY id;
INSERT INTO `account`.`account`(`id`)
@ -87,9 +83,15 @@ INSERT INTO `vn`.`educationLevel` (`id`, `name`)
(1, 'ESTUDIOS PRIMARIOS COMPLETOS'),
(2, 'ENSEÑANZAS DE BACHILLERATO');
INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `bossFk`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, NULL
FROM `account`.`user`
WHERE `id` = 9;
INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `bossFk`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, 9
FROM `account`.`user`;
FROM `account`.`user`
WHERE `id` <> 9;
UPDATE `vn`.`worker` SET bossFk = NULL WHERE id = 20;
UPDATE `vn`.`worker` SET bossFk = 20 WHERE id = 1 OR id = 9;
@ -367,7 +369,7 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city
(1105, 'Max Eisenhardt', '251628698', 'MAGNETO', 'Rogue', 'UNKNOWN WHEREABOUTS', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist','normal'),
(1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist','normal'),
(1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist','normal'),
(1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','normal'),
@ -405,7 +407,7 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1),
(6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1),
(7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
(9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1),
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1),
@ -437,7 +439,7 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0),
(126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0),
(127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
(129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0),
(130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0);
@ -549,15 +551,6 @@ INSERT INTO `vn`.`supplierActivity`(`code`, `name`)
('flowerPlants', 'Wholesale of flowers and plants'),
('vegetablesFruits', 'Fruit and vegetable trade');
INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `provinceFk`, `postalCode`, `city`, `phone`, `mobile`)
VALUES
(1, 1, 'Ace Chemicals', 'The Midtown', 1, '46000', 'Gotham', '111111111', '222222222'),
(2, 1, 'Arkham Asylum', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(3, 2, 'Wayne Tower', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(4, 2, 'Bank of Gotham', 'Founders Island', 1, '46000', 'Gotham', '111111111', '222222222'),
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`)
VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
@ -568,6 +561,15 @@ INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`
(791, 'Bros SL', 'Bros nick', 5115000791, 1, '37718083S', 0, util.VN_CURDATE(), 1, 'supplier address 7', 'ASGARD', 3, 46600, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
(1381, 'Ornamentales', 'Ornamentales', 7185001381, 1, '07972486L', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `provinceFk`, `postalCode`, `city`, `phone`, `mobile`)
VALUES
(1, 1, 'Ace Chemicals', 'The Midtown', 1, '46000', 'Gotham', '111111111', '222222222'),
(2, 1, 'Arkham Asylum', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(3, 2, 'Wayne Tower', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(4, 2, 'Bank of Gotham', 'Founders Island', 1, '46000', 'Gotham', '111111111', '222222222'),
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES
(1, 1, 123121212, 654789123, 'supplier1@email.es', 'observation1', 'the boss'),
@ -966,6 +968,10 @@ INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPack
('cc', 1640038.00, 56.00, 220.00, 128.00, 1, util.VN_CURDATE(), 15, 90.00),
('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, util.VN_CURDATE(), 16, 0.00);
INSERT INTO `vn`.`packagingConfig`(`upperGap`, `defaultSmallPackageFk`, `defaultBigPackageFk`)
VALUES
('10', 1, 'pallet 100');
INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`)
VALUES
(1, 'En reparto', 'ON DELIVERY'),
@ -2788,6 +2794,11 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`)
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES
(1, 9),
(1, 1),
(2, 1),
(3, 9),
(4, 1),
(5, 9),
(6, 9);
INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`)
@ -2800,6 +2811,8 @@ INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`)
VALUES
(1, 1109),
(1, 1110),
(2, 1110),
(4, 1110),
(2, 1109),
(1, 9),
(1, 3),
@ -2880,7 +2893,9 @@ INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES
(1, 1);
(1, 1),
(2, 20),
(7, 1);
INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk`, `businessTypeFk`)
VALUES

File diff suppressed because it is too large Load Diff

View File

@ -45,12 +45,12 @@ TABLES=(
alertLevel
bookingPlanner
businessType
cplusInvoiceType472
siiTypeInvoiceIn
siiTypeInvoiceOut
cplusRectificationType
cplusSubjectOp
cplusTaxBreak
cplusTrascendency472
siiTrascendencyInvoiceIn
claimResponsible
claimReason
claimRedelivery
@ -68,6 +68,8 @@ TABLES=(
volumeConfig
workCenter
companyI18n
workerTimeControlError
silexACL
)
dump_tables ${TABLES[@]}

View File

@ -145,6 +145,7 @@ export default {
adController: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adController"]',
adUser: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adUser"]',
adPassword: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adPassword"]',
userDn: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.userDn"]',
verifyCert: 'vn-account-samba vn-check[ng-model="$ctrl.config.verifyCert"]',
save: 'vn-account-samba vn-submit'
},
@ -722,7 +723,7 @@ export default {
isFullMovable: 'vn-check[ng-model="filter.isFullMovable"]',
warehouseFk: 'vn-autocomplete[label="Warehouse"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets with negatives"]',
acceptButton: '.vn-confirm.shown button[response="accept"]',
firstCheck: 'tbody > tr:nth-child(2) > td > vn-check',
tableId: 'vn-textfield[name="id"]',

View File

@ -22,7 +22,7 @@ describe('Travel basic data path', () => {
await page.waitForState('travel.card.basicData');
});
it('should set a wrong delivery date then receive an error on submit', async() => {
it('should throw error if try move a travel with entries', async() => {
const lastMonth = Date.vnNew();
lastMonth.setMonth(lastMonth.getMonth() - 1);
@ -30,6 +30,23 @@ describe('Travel basic data path', () => {
await page.waitToClick(selectors.travelBasicData.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Cannot past travels with entries');
});
it('should set a wrong delivery date then receive an error on submit', async() => {
await page.loginAndModule('buyer', 'travel');
await page.write(selectors.travelIndex.generalSearchFilter, '4');
await page.keyboard.press('Enter');
await page.accessToSection('travel.card.basicData');
await page.waitForState('travel.card.basicData');
const lastMonth = Date.vnNew();
lastMonth.setMonth(lastMonth.getMonth() - 2);
await page.pickDate(selectors.travelBasicData.deliveryDate, lastMonth);
await page.waitToClick(selectors.travelBasicData.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Landing cannot be lesser than shipment');
});
@ -39,7 +56,7 @@ describe('Travel basic data path', () => {
await page.waitToClick(selectors.travelBasicData.undoChanges);
const result = await page.waitToGetProperty(selectors.travelBasicData.reference, 'value');
expect(result).toEqual('third travel');
expect(result).toEqual('fourth travel');
});
it('should now edit the whole form then save', async() => {

View File

@ -23,18 +23,6 @@ describe('Account Accounts path', () => {
expect(message.text).toContain('Roles synchronized!');
});
it('should sync user', async() => {
await page.waitToClick(selectors.accountAccounts.syncUser);
await page.write(selectors.accountAccounts.syncUserName, 'sysadmin');
await page.write(selectors.accountAccounts.syncUserPassword, 'nightmare');
await page.waitToClick(selectors.accountAccounts.buttonAccept);
const message = await page.waitForSnackbar();
expect(message.text).toContain('User synchronized!');
});
it('should relogin', async() => {
await page.loginAndModule('sysadmin', 'account');
await page.accessToSection('account.accounts');

View File

@ -20,8 +20,9 @@ describe('Account Samba path', () => {
await page.waitToClick(selectors.accountSamba.checkEnable);
await page.write(selectors.accountSamba.adDomain, '1234');
await page.write(selectors.accountSamba.adController, '1234');
await page.write(selectors.accountSamba.adUser, 'nightmare');
await page.write(selectors.accountSamba.adPassword, 'sysadmin');
await page.write(selectors.accountSamba.adUser, 'sysadmin');
await page.write(selectors.accountSamba.adPassword, 'nightmare');
await page.write(selectors.accountSamba.userDn, 'testDn');
await page.waitToClick(selectors.accountSamba.verifyCert);
await page.waitToClick(selectors.accountSamba.save);

View File

@ -0,0 +1,28 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.execute = async(ctx, type, query, params, options) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
params = params ?? [];
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
const chain = query.split(' ')[1];
const [canExecute] = await models.ProcsPriv.rawSql(
'SELECT account.user_hasRoutinePriv(?,?,?)',
[type, chain, userId],
myOptions);
if (!Object.values(canExecute)[0]) throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
const argString = params.map(() => '?').join(',');
const response = await models.ProcsPriv.rawSql(query + `(${argString})`, params, myOptions);
if (!Array.isArray(response)) return;
return response[0];
};
};

View File

@ -0,0 +1,41 @@
module.exports = Self => {
Self.remoteMethodCtx('executeFunc', {
description: 'Return result of function',
accessType: 'EXECUTE',
accepts: [
{
arg: 'routine',
type: 'string',
description: 'The routine name',
required: true,
http: {source: 'path'}
},
{
arg: 'schema',
type: 'string',
description: 'The routine schema',
required: true,
},
{
arg: 'params',
type: ['any'],
description: 'The params array',
},
],
returns: {
type: 'any',
root: true
},
http: {
path: `/:routine/execute-func`,
verb: 'POST'
}
});
Self.executeFunc = async(ctx, routine, schema, params, options) => {
const query = `SELECT ${schema}.${routine}`;
const response = await Self.execute(ctx, 'FUNCTION', query, params, options);
return Object.values(response)[0];
};
};

View File

@ -0,0 +1,39 @@
module.exports = Self => {
Self.remoteMethodCtx('executeProc', {
description: 'Return result of procedure',
accessType: 'EXECUTE',
accepts: [
{
arg: 'routine',
type: 'string',
description: 'The routine name',
required: true,
http: {source: 'path'}
},
{
arg: 'schema',
type: 'string',
description: 'The routine schema',
required: true,
},
{
arg: 'params',
type: ['any'],
description: 'The params array',
},
],
returns: {
type: 'any',
root: true
},
http: {
path: `/:routine/execute-proc`,
verb: 'POST'
}
});
Self.executeProc = async(ctx, routine, schema, params, options) => {
const query = `CALL ${schema}.${routine}`;
return Self.execute(ctx, 'PROCEDURE', query, params, options);
};
};

View File

@ -0,0 +1,161 @@
const models = require('vn-loopback/server/server').models;
describe('Application execute()/executeProc()/executeFunc()', () => {
const userWithoutPrivileges = 1;
const userWithPrivileges = 9;
const userWithInheritedPrivileges = 120;
let tx;
function getCtx(userId) {
return {
req: {
accessToken: {userId},
headers: {origin: 'http://localhost'}
}
};
}
beforeEach(async() => {
tx = await models.Application.beginTransaction({});
const options = {transaction: tx};
await models.Application.rawSql(`
CREATE OR REPLACE PROCEDURE vn.myProcedure(vMyParam INT)
BEGIN
SELECT vMyParam myParam, t.*
FROM ticket t
LIMIT 2;
END
`, null, options);
await models.Application.rawSql(`
CREATE OR REPLACE FUNCTION bs.myFunction(vMyParam INT) RETURNS int(11)
BEGIN
RETURN vMyParam;
END
`, null, options);
await models.Application.rawSql(`
GRANT EXECUTE ON PROCEDURE vn.myProcedure TO developer;
GRANT EXECUTE ON FUNCTION bs.myFunction TO developer;
`, null, options);
});
it('should throw error when execute procedure and not have privileges', async() => {
const ctx = getCtx(userWithoutPrivileges);
let error;
try {
const options = {transaction: tx};
await models.Application.execute(
ctx,
'PROCEDURE',
'CALL vn.myProcedure',
[1],
options
);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`You don't have enough privileges`);
});
it('should execute procedure and get data', async() => {
const ctx = getCtx(userWithPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.execute(
ctx,
'PROCEDURE',
'CALL vn.myProcedure',
[1],
options
);
expect(response.length).toEqual(2);
expect(response[0].myParam).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
describe('Application executeProc()', () => {
it('should execute procedure and get data (executeProc)', async() => {
const ctx = getCtx(userWithPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.executeProc(
ctx,
'myProcedure',
'vn',
[1],
options
);
expect(response.length).toEqual(2);
expect(response[0].myParam).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});
describe('Application executeFunc()', () => {
it('should execute function and get data', async() => {
const ctx = getCtx(userWithPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.executeFunc(
ctx,
'myFunction',
'bs',
[1],
options
);
expect(response).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should execute function and get data with user with inherited privileges', async() => {
const ctx = getCtx(userWithInheritedPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.executeFunc(
ctx,
'myFunction',
'bs',
[1],
options
);
expect(response).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});
});

View File

@ -2,4 +2,7 @@
module.exports = function(Self) {
require('../methods/application/status')(Self);
require('../methods/application/post')(Self);
require('../methods/application/execute')(Self);
require('../methods/application/executeProc')(Self);
require('../methods/application/executeFunc')(Self);
};

View File

@ -0,0 +1,44 @@
{
"name": "ProcsPriv",
"base": "VnModel",
"options": {
"mysql": {
"table": "mysql.procs_priv"
}
},
"properties": {
"name": {
"id": 1,
"type": "string",
"mysql": {
"columnName": "Routine_name"
}
},
"schema": {
"id": 3,
"type": "string",
"mysql": {
"columnName": "Db"
}
},
"role": {
"type": "string",
"mysql": {
"columnName": "user"
}
},
"type": {
"id": 2,
"type": "string",
"mysql": {
"columnName": "Routine_type"
}
},
"host": {
"type": "string",
"mysql": {
"columnName": "Host"
}
}
}
}

View File

@ -197,5 +197,7 @@
"Booking completed": "Booking complete",
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets",
"Try again": "Try again"
"Try again": "Try again",
"keepPrice": "keepPrice",
"Cannot past travels with entries": "Cannot past travels with entries"
}

View File

@ -224,7 +224,7 @@
"date in the future": "Fecha en el futuro",
"reference duplicated": "Referencia duplicada",
"This ticket is already a refund": "Este ticket ya es un abono",
"isWithoutNegatives": "isWithoutNegatives",
"isWithoutNegatives": "Sin negativos",
"routeFk": "routeFk",
"Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador",
"No hay un contrato en vigor": "No hay un contrato en vigor",
@ -323,8 +323,10 @@
"The response is not a PDF": "La respuesta no es un PDF",
"Booking completed": "Reserva completada",
"The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación",
"Incoterms data for consignee is missing": "Faltan los datos de los Incoterms para el consignatario",
"The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada",
"User disabled": "Usuario desactivado",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima",
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima"
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima",
"Cannot past travels with entries": "No se pueden pasar envíos con entradas"
}

View File

@ -49,5 +49,13 @@
},
"Container": {
"dataSource": "vn"
},
"ProcsPriv": {
"dataSource": "vn",
"options": {
"mysql": {
"table": "mysql.procs_priv"
}
}
}
}
}

View File

@ -1,3 +1,4 @@
const ForbiddenError = require('vn-loopback/util/forbiddenError');
module.exports = Self => {
Self.remoteMethod('sync', {
@ -32,9 +33,13 @@ module.exports = Self => {
const models = Self.app.models;
const user = await models.VnUser.findOne({
fields: ['id'],
fields: ['id', 'password'],
where: {name: userName}
}, myOptions);
if (user && password && !await user.hasPassword(password))
throw new ForbiddenError('Wrong password');
const isSync = !await models.UserSync.exists(userName, myOptions);
if (!force && isSync && user) return;
@ -42,4 +47,3 @@ module.exports = Self => {
await models.UserSync.destroyById(userName, myOptions);
};
};

View File

@ -5,7 +5,7 @@ const crypto = require('crypto');
const nthash = require('smbhash').nthash;
module.exports = Self => {
const shouldSync = process.env.NODE_ENV === 'production';
const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() {
return await Self.findOne({
@ -140,6 +140,7 @@ module.exports = Self => {
try {
if (shouldSync)
await client.del(dn);
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' removed from LDAP`);
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;

View File

@ -27,8 +27,7 @@ module.exports = Self => {
const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows
FROM mysql.user
WHERE User = ?
AND Host = ?`,
WHERE User = ? AND Host = ?`,
[mysqlUser, this.userHost]
);
let userExists = row.nRows > 0;
@ -38,8 +37,7 @@ module.exports = Self => {
const [row] = await Self.rawSql(
`SELECT Priv AS priv
FROM mysql.global_priv
WHERE User = ?
AND Host = ?`,
WHERE User = ? AND Host = ?`,
[mysqlUser, this.userHost]
);
const priv = row && JSON.parse(row.priv);
@ -88,10 +86,18 @@ module.exports = Self => {
else
throw err;
}
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, this.userHost]);
if (role) {
const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows
FROM mysql.user
WHERE User = ? AND Host = ''`,
[role]
);
const roleExists = row.nRows > 0;
if (roleExists) {
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, this.userHost]);
await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
[role, mysqlUser, this.userHost]);
} else {

View File

@ -1,6 +1,6 @@
const ldap = require('../util/ldapjs-extra');
const ssh = require('node-ssh');
const execFile = require('child_process').execFile;
/**
* Summary of userAccountControl flags:
@ -11,6 +11,8 @@ const UserAccountControlFlags = {
};
module.exports = Self => {
const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() {
return await Self.findOne({
fields: [
@ -19,6 +21,7 @@ module.exports = Self => {
'adController',
'adUser',
'adPassword',
'userDn',
'verifyCert'
]
});
@ -26,88 +29,123 @@ module.exports = Self => {
Object.assign(Self.prototype, {
async init() {
let sshClient = new ssh.NodeSSH();
await sshClient.connect({
host: this.adController,
username: this.adUser,
password: this.adPassword
});
const baseDn = this.adDomain
.split('.')
.map(part => `dc=${part}`)
.join(',');
const bindDn = `cn=${this.adUser},cn=Users,${baseDn}`;
let adUser = `cn=${this.adUser},${this.usersDn()}`;
let adClient = ldap.createClient({
const adClient = ldap.createClient({
url: `ldaps://${this.adController}:636`,
tlsOptions: {rejectUnauthorized: this.verifyCert}
});
await adClient.bind(adUser, this.adPassword);
await adClient.bind(bindDn, this.adPassword);
Object.assign(this, {
sshClient,
adClient
adClient,
fullUsersDn: `${this.userDn},${baseDn}`,
bindDn
});
},
async deinit() {
await this.sshClient.dispose();
await this.adClient.unbind();
},
usersDn() {
let dnBase = this.adDomain
.split('.')
.map(part => `dc=${part}`)
.join(',');
return `cn=Users,${dnBase}`;
async sambaTool(command, args = []) {
let authArgs = [
'--URL', `ldaps://${this.adController}`,
'--simple-bind-dn', this.bindDn,
'--password', this.adPassword
];
if (!this.verifyCert)
authArgs.push('--option', 'tls verify peer = no_check');
const allArgs = [command].concat(
args, authArgs
);
if (!shouldSync) return;
return await new Promise((resolve, reject) => {
execFile('samba-tool', allArgs, (err, stdout, stderr) => {
if (err)
reject(err);
else
resolve({stdout, stderr});
});
});
},
async syncUser(userName, info, password) {
let {sshClient} = this;
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
async getAdUser(userName) {
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
scope: 'sub',
attributes: ['userAccountControl'],
attributes: [
'dn',
'userAccountControl',
'uidNumber',
'accountExpires',
'mail'
],
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
});
let isEnabled = sambaUser
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);
if (process.env.NODE_ENV === 'test')
return;
if (sambaUser) {
for (const intProp of ['uidNumber', 'userAccountControl']) {
if (sambaUser[intProp] != null)
sambaUser[intProp] = parseInt(sambaUser[intProp]);
}
}
return sambaUser;
},
async syncUser(userName, info, password) {
let sambaUser = await this.getAdUser(userName);
let entry;
if (info.hasAccount) {
if (!sambaUser) {
await sshClient.exec('samba-tool user create', [
userName,
'--uid-number', `${info.uidNumber}`,
'--mail-address', info.corporateMail,
await this.sambaTool('user', [
'create', userName,
'--userou', this.userDn,
'--random-password'
]);
await sshClient.exec('samba-tool user setexpiry', [
userName,
'--noexpiry'
]);
await sshClient.exec('mkhomedir_helper', [
userName,
'0027'
]);
}
if (!isEnabled) {
await sshClient.exec('samba-tool user enable', [
userName
]);
sambaUser = await this.getAdUser(userName);
}
if (password) {
await sshClient.exec('samba-tool user setpassword', [
userName,
await this.sambaTool('user', [
'setpassword', userName,
'--newpassword', password
]);
}
} else if (isEnabled) {
await sshClient.exec('samba-tool user disable', [
userName
]);
entry = {
userAccountControl: sambaUser.userAccountControl
& ~UserAccountControlFlags.ACCOUNTDISABLE,
uidNumber: info.uidNumber,
accountExpires: 0,
mail: info.corporateMail
};
} else if (sambaUser) {
entry = {
userAccountControl: sambaUser.userAccountControl
| UserAccountControlFlags.ACCOUNTDISABLE
};
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' disabled on Samba`);
}
if (sambaUser && entry) {
const changes = [];
for (const prop in entry) {
if (sambaUser[prop] == entry[prop]) continue;
changes.push(new ldap.Change({
operation: 'replace',
modification: {
[prop]: entry[prop]
}
}));
}
if (changes.length && shouldSync)
await this.adClient.modify(sambaUser.dn, changes);
}
},
/**
@ -117,14 +155,15 @@ module.exports = Self => {
*/
async getUsers(usersToSync) {
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
let filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
const filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}`
+ `:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
let opts = {
const opts = {
scope: 'sub',
attributes: ['sAMAccountName'],
filter: `(&(objectClass=user)(${filter}))`
};
await this.adClient.searchForeach(this.usersDn(), opts,
await this.adClient.searchForeach(this.fullUsersDn, opts,
o => usersToSync.add(o.sAMAccountName));
}
});

View File

@ -28,6 +28,10 @@
"adPassword": {
"type": "string"
},
"userDn": {
"type": "string",
"required": true
},
"verifyCert": {
"type": "boolean"
}

View File

@ -12,40 +12,40 @@
<vn-card class="vn-pa-lg" vn-focus>
<vn-vertical>
<vn-textfield
label="Homedir base"
label="Homedir base"
ng-model="$ctrl.config.homedir"
rule="AccountConfig">
</vn-textfield>
<vn-textfield
label="Shell"
label="Shell"
ng-model="$ctrl.config.shell"
rule="AccountConfig">
</vn-textfield>
<vn-input-number
label="User and role base id"
label="User and role base id"
ng-model="$ctrl.config.idBase"
rule="AccountConfig">
</vn-input-number>
<vn-horizontal>
<vn-input-number
label="Min"
label="Min"
ng-model="$ctrl.config.min"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Max"
label="Max"
ng-model="$ctrl.config.max"
rule="AccountConfig">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Warn"
label="Warn"
ng-model="$ctrl.config.warn"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Inact"
label="Inact"
ng-model="$ctrl.config.inact"
rule="AccountConfig">
</vn-input-number>
@ -61,10 +61,6 @@
label="Synchronize all"
ng-click="$ctrl.onSynchronizeAll()">
</vn-button>
<vn-button
label="Synchronize user"
ng-click="syncUser.show()">
</vn-button>
<vn-button
label="Synchronize roles"
ng-click="$ctrl.onSynchronizeRoles()">
@ -77,25 +73,3 @@
</vn-button>
</vn-button-bar>
</form>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onUserSync()"
on-close="$ctrl.onSyncClose()">
<tpl-body>
<vn-textfield
label="Username"
ng-model="$ctrl.syncUser"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
info="If password is not specified, just user attributes are synchronized">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,6 +1,5 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import UserError from 'core/lib/user-error';
export default class Controller extends Section {
onSynchronizeAll() {
@ -8,27 +7,10 @@ export default class Controller extends Section {
this.$http.patch(`Accounts/syncAll`);
}
onUserSync() {
if (!this.syncUser)
throw new UserError('Please enter the username');
let params = {
password: this.syncPassword,
force: true
};
return this.$http.patch(`Accounts/${this.syncUser}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSynchronizeRoles() {
this.$http.patch(`RoleInherits/sync`)
.then(() => this.vnApp.showSuccess(this.$t('Roles synchronized!')));
}
onSyncClose() {
this.syncUser = '';
this.syncPassword = '';
}
}
ngModule.component('vnAccountAccounts', {

View File

@ -3,7 +3,6 @@ Homedir base: Directorio base para carpetas de usuario
Shell: Intérprete de línea de comandos
User and role base id: Id base usuarios y roles
Synchronize all: Sincronizar todo
Synchronize user: Sincronizar usuario
Synchronize roles: Sincronizar roles
If password is not specified, just user attributes are synchronized: >-
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
@ -12,5 +11,4 @@ Users synchronized!: ¡Usuarios sincronizados!
Username: Nombre de usuario
Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario
User synchronized!: ¡Usuario sincronizado!
Roles synchronized!: ¡Roles sincronizados!

View File

@ -67,6 +67,15 @@
translate>
Deactivate user
</vn-item>
<vn-item
ng-if="$ctrl.user.active"
ng-click="syncUser.show()"
name="synchronizeUser"
vn-acl="it"
vn-acl-action="remove"
translate>
Synchronize
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
@ -153,6 +162,32 @@
<button response="accept" translate>Change password</button>
</tpl-buttons>
</vn-dialog>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onSync()"
on-close="$ctrl.onSyncClose()">
<tpl-title ng-translate>
Do you want to synchronize user?
</tpl-title>
<tpl-body>
<vn-check
label="Synchronize password"
ng-model="$ctrl.shouldSyncPassword"
info="If password is not specified, just user attributes are synchronized"
vn-focus>
</vn-check>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
ng-if="$ctrl.shouldSyncPassword">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>
<vn-popup vn-id="summary">
<vn-user-summary user="$ctrl.user"></vn-user-summary>
</vn-popup>

View File

@ -120,6 +120,20 @@ class Controller extends Descriptor {
this.vnApp.showSuccess(this.$t(message));
});
}
onSync() {
const params = {force: true};
if (this.shouldSyncPassword)
params.password = this.syncPassword;
return this.$http.patch(`Accounts/${this.user.name}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSyncClose() {
this.shouldSyncPassword = false;
this.syncPassword = undefined;
}
}
ngModule.component('vnUserDescriptor', {

View File

@ -22,6 +22,10 @@ Old password: Contraseña antigua
New password: Nueva contraseña
Repeat password: Repetir contraseña
Password changed succesfully!: ¡Contraseña modificada correctamente!
Synchronize: Sincronizar
Do you want to synchronize user?: ¿Quieres sincronizar el usuario?
Synchronize password: Sincronizar contraseña
User synchronized!: ¡Usuario sincronizado!
Role changed succesfully!: ¡Rol modificado correctamente!
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,

View File

@ -12,7 +12,7 @@
<vn-card class="vn-pa-lg" vn-focus>
<vn-vertical>
<vn-check
label="Enable synchronization"
label="Enable synchronization"
ng-model="watcher.hasData">
</vn-check>
</vn-vertical>
@ -20,28 +20,33 @@
ng-if="watcher.hasData"
class="vn-mt-md">
<vn-textfield
label="AD domain"
label="AD domain"
ng-model="$ctrl.config.adDomain"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="Domain controller"
label="Domain controller"
ng-model="$ctrl.config.adController"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="AD user"
label="AD user"
ng-model="$ctrl.config.adUser"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="AD password"
label="AD password"
ng-model="$ctrl.config.adPassword"
type="password"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="User DN (without domain part)"
ng-model="$ctrl.config.userDn"
rule="SambaConfig">
</vn-textfield>
<vn-check
label="Verify certificate"
label="Verify certificate"
ng-model="$ctrl.config.verifyCert">
</vn-check>
</vn-vertical>
@ -63,4 +68,4 @@
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>
</form>

View File

@ -3,6 +3,7 @@ Domain controller: Controlador de dominio
AD domain: Dominio AD
AD user: Usuario AD
AD password: Contraseña AD
User DN (without domain part): DN usuarios (sin la parte del dominio)
Verify certificate: Verificar certificado
Test connection: Probar conexión
Samba connection established!: ¡Conexión con Samba establecida!

View File

@ -82,12 +82,15 @@ module.exports = Self => {
if (args.zoneFk) {
let stmts = [];
stmts.push(new ParameterizedSQL('CALL vn.zone_getPostalCode(?)', [args.zoneFk]));
stmts.push(`SELECT name FROM tmp.zoneNodes`);
stmts.push(`
SELECT zn.geoFk, zn.name
FROM tmp.zoneNodes zn
JOIN zone z ON z.id = zn.zoneFk`);
stmts.push(`DROP TEMPORARY TABLE tmp.zoneNodes`);
const sql = ParameterizedSQL.join(stmts, ';');
const [results] = await conn.executeStmt(sql);
const results = await conn.executeStmt(sql);
for (let result of results)
for (let result of results[1])
postalCode.push(result.name);
}

View File

@ -158,7 +158,7 @@
},
"user": {
"type": "belongsTo",
"model": "Account",
"model": "VnUser",
"foreignKey": "id"
},
"payMethod": {

View File

@ -0,0 +1,37 @@
module.exports = Self => {
Self.remoteMethodCtx('new', {
description: 'Creates a new invoiceIn due day',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceIn id',
},
http: {
path: '/new',
verb: 'POST'
}
});
Self.new = async(ctx, id, options) => {
let tx;
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
await Self.rawSql(`CALL vn.invoiceInDueDay_calculate(?)`, [id], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,45 @@
const LoopBackContext = require('loopback-context');
const models = require('vn-loopback/server/server').models;
describe('invoiceInDueDay new()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should correctly create a new due day', async() => {
const userId = 9;
const invoiceInFk = 6;
const ctx = {
req: {
accessToken: {userId: userId},
}
};
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceInDueDay.destroyAll({
invoiceInFk
}, options);
await models.InvoiceInDueDay.new(ctx, invoiceInFk, options);
const result = await models.InvoiceInDueDay.find({where: {invoiceInFk}}, options);
expect(result).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -29,15 +29,18 @@ module.exports = Self => {
SELECT iit.*,
SUM(iidd.amount) totalDueDay
FROM vn.invoiceIn ii
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase,
CAST(SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) AS DECIMAL(10,2)) totalVat
LEFT JOIN (
SELECT SUM(iit.taxableBase) totalTaxableBase,
CAST(
SUM(IFNULL(iit.taxableBase * (1 + (ti.PorcentajeIva / 100)), iit.taxableBase))
AS DECIMAL(10, 2)
) totalVat
FROM vn.invoiceInTax iit
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE iit.invoiceInFk = ?) iit ON TRUE
WHERE iit.invoiceInFk = ?
) iit ON TRUE
LEFT JOIN vn.invoiceInDueDay iidd ON iidd.invoiceInFk = ii.id
WHERE
ii.id = ?`, [id, id]);
WHERE ii.id = ?`, [id, id]);
return result;
};
};

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/invoice-in-due-day/new')(Self);
};

View File

@ -7,7 +7,7 @@ export default class Controller extends Section {
super($element, $);
this.vnReport = vnReport;
const now = new Date();
const now = Date.vnNew();
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
this.params = {

View File

@ -162,61 +162,69 @@ module.exports = Self => {
const stmts = [];
let stmt;
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.filter');
stmts.push(`SET @_optimizer_search_depth = @@optimizer_search_depth`);
stmts.push(`SET SESSION optimizer_search_depth = 0`);
stmt = new ParameterizedSQL(
`CREATE TEMPORARY TABLE tmp.filter
stmt = new ParameterizedSQL(`
CREATE OR REPLACE TEMPORARY TABLE tmp.filter
(PRIMARY KEY (id))
ENGINE = MEMORY
SELECT
t.id,
t.shipped,
CAST(DATE(t.shipped) AS CHAR) AS shippedDate,
t.nickname,
t.refFk,
t.routeFk,
t.warehouseFk,
t.clientFk,
t.totalWithoutVat,
t.totalWithVat,
io.id AS invoiceOutId,
a.provinceFk,
p.name AS province,
w.name AS warehouse,
am.name AS agencyMode,
am.id AS agencyModeFk,
st.name AS state,
wk.lastName AS salesPerson,
ts.stateFk AS stateFk,
ts.alertLevel AS alertLevel,
ts.code AS alertLevelCode,
u.name AS userName,
c.salesPersonFk,
c.credit,
z.hour AS zoneLanding,
z.name AS zoneName,
z.id AS zoneFk,
st.classColor,
TIME_FORMAT(t.shipped, '%H:%i') AS preparationHour,
TIME_FORMAT(z.hour, '%H:%i') AS theoreticalhour,
TIME_FORMAT(zed.etc, '%H:%i') AS practicalHour
FROM ticket t
LEFT JOIN invoiceOut io ON t.refFk = io.ref
LEFT JOIN zone z ON z.id = t.zoneFk
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.id
LEFT JOIN zoneEstimatedDelivery zed ON zed.zoneFk = t.zoneFk`);
SELECT t.id,
t.shipped,
CAST(DATE(t.shipped) AS CHAR) shippedDate,
t.nickname,
t.refFk,
t.routeFk,
t.warehouseFk,
t.clientFk,
t.totalWithoutVat,
t.totalWithVat,
io.id invoiceOutId,
a.provinceFk,
p.name province,
w.name warehouse,
am.name agencyMode,
am.id agencyModeFk,
st.name state,
wk.lastName salesPerson,
ts.stateFk stateFk,
ts.alertLevel alertLevel,
ts.code alertLevelCode,
u.name userName,
c.salesPersonFk,
c.credit,
z.hour zoneLanding,
z.name zoneName,
z.id zoneFk,
st.classColor,
TIME_FORMAT(t.shipped, '%H:%i') preparationHour,
TIME_FORMAT(z.hour, '%H:%i') theoreticalhour,
TIME_FORMAT(zed.etc, '%H:%i') practicalHour
FROM ticket t
LEFT JOIN invoiceOut io ON t.refFk = io.ref
LEFT JOIN zone z ON z.id = t.zoneFk
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.id
LEFT JOIN (
SELECT zoneFk,
CAST(
IFNULL(zoneClosureHour, zoneHour) +
SUM(IF(hasToRecalcPrice, volume, 0)) * 60 /
GREATEST(IFNULL(m3, 0), IFNULL(minSpeed, 0))
AS time
) etc
FROM zoneEstimatedDelivery
GROUP BY zoneFk
) zed ON zed.zoneFk = t.zoneFk
`);
if (args.orderFk) {
stmt.merge({

View File

@ -139,46 +139,65 @@ module.exports = Self => {
filter = mergeFilters(filter, {where});
const stmts = [];
let stmt;
stmts.push(`SET @_optimizer_search_depth = @@optimizer_search_depth`);
stmts.push(`SET SESSION optimizer_search_depth = 0`);
stmt = new ParameterizedSQL(
`CREATE OR REPLACE TEMPORARY TABLE tmp.filter
(INDEX (id))
ENGINE = MEMORY
SELECT
o.id,
o.total,
o.date_send landed,
o.date_make created,
o.customer_id clientFk,
o.agency_id agencyModeFk,
o.address_id addressFk,
o.company_id companyFk,
o.source_app sourceApp,
o.confirmed isConfirmed,
c.name clientName,
c.salesPersonFk,
u.nickname workerNickname,
u.name name,
co.code companyCode,
zed.zoneFk,
zed.hourTheoretical,
zed.hourEffective,
am.name AS agencyName
FROM hedera.order o
LEFT JOIN address a ON a.id = o.address_id
LEFT JOIN agencyMode am ON am.id = o.agency_id
LEFT JOIN client c ON c.id = o.customer_id
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.id
LEFT JOIN company co ON co.id = o.company_id
LEFT JOIN orderTicket ot ON ot.orderFk = o.id
LEFT JOIN ticket t ON t.id = ot.ticketFk
LEFT JOIN zoneEstimatedDelivery zed ON zed.zoneFk = t.zoneFk`);
stmt = new ParameterizedSQL(`
CREATE OR REPLACE TEMPORARY TABLE tmp.filter
(INDEX (id))
ENGINE = MEMORY
SELECT o.id,
o.total,
o.date_send landed,
o.date_make created,
o.customer_id clientFk,
o.agency_id agencyModeFk,
o.address_id addressFk,
o.company_id companyFk,
o.source_app sourceApp,
o.confirmed isConfirmed,
c.name clientName,
c.salesPersonFk,
u.nickname workerNickname,
u.name name,
co.code companyCode,
zed.zoneFk,
zed.hourTheoretical,
zed.hourEffective,
am.name agencyName
FROM hedera.order o
LEFT JOIN address a ON a.id = o.address_id
LEFT JOIN agencyMode am ON am.id = o.agency_id
LEFT JOIN client c ON c.id = o.customer_id
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.id
LEFT JOIN company co ON co.id = o.company_id
LEFT JOIN orderTicket ot ON ot.orderFk = o.id
LEFT JOIN ticket t ON t.id = ot.ticketFk
LEFT JOIN (
SELECT zoneFk,
CAST(
util.VN_CURDATE() +
INTERVAL HOUR(IFNULL(zoneClosureHour, zoneHour)) * 60 +
MINUTE(IFNULL(zoneClosureHour, zoneHour)) MINUTE
AS time
) hourTheoretical,
CAST(
IFNULL(zoneClosureHour, zoneHour) +
SUM(IF(hasToRecalcPrice, volume, 0)) * 60 /
GREATEST(IFNULL(m3, 0), IFNULL(minSpeed, 0))
AS time
) hourEffective
FROM zoneEstimatedDelivery
GROUP BY zoneFk
) zed ON zed.zoneFk = t.zoneFk
`);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY id`);
stmt.merge(conn.makePagination(filter));
stmts.push(stmt);
stmts.push(`SET SESSION optimizer_search_depth = @_optimizer_search_depth`);
stmt = new ParameterizedSQL(`SELECT * FROM tmp.filter`);
stmt.merge(conn.makeOrderBy(filter.order));

View File

@ -105,7 +105,7 @@ module.exports = Self => {
}
});
filter = mergeFilters(ctx.args.filter, {where});
filter = mergeFilters(filter, {where});
let stmts = [];
let stmt;
@ -129,9 +129,11 @@ module.exports = Self => {
r.description,
am.name agencyName,
u.name AS workerUserName,
v.numberPlate AS vehiclePlateNumber
v.numberPlate AS vehiclePlateNumber,
Date_format(r.time, '%H:%i') hour
FROM route r
LEFT JOIN agencyMode am ON am.id = r.agencyModeFk
LEFT JOIN agency a ON a.id = am.agencyFk
LEFT JOIN vehicle v ON v.id = r.vehicleFk
LEFT JOIN worker w ON w.id = r.workerFk
LEFT JOIN account.user u ON u.id = w.id`

View File

@ -0,0 +1,65 @@
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('getByWorker', {
description: 'Return the routes by worker',
accessType: 'READ',
returns: {
type: ['object'],
root: true
},
http: {
path: `/getByWorker`,
verb: 'GET'
}
});
Self.getByWorker = async ctx => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const canViewAll = await models.ACL.checkAccessAcl(ctx, 'Route', 'canViewAllRoute', 'READ');
let filterGrant = {};
if (canViewAll) {
const userConfig = await models.UserConfig.getUserConfig(ctx, myOptions);
filterGrant = {
where: {'a.warehouseFk': userConfig.warehouseFk}
};
} else {
filterGrant = {
where: {'r.workerFk': userId}
};
}
const currentDate = Date.vnNew();
currentDate.setHours(0, 0, 0, 0);
const nextDay = Date.vnNew();
nextDay.setDate(currentDate.getDate() + 1);
const filter = {
where: {
and: [
{
or: [
{'r.created': currentDate},
{'r.created': nextDay}
]
}
]
},
order: [
'r.created ASC',
'r.time ASC',
'am.name ASC'
]
};
const result = await Self.filter(ctx, mergeFilters(filter, filterGrant));
return result;
};
};

View File

@ -1,7 +1,7 @@
module.exports = Self => {
Self.remoteMethod('getDeliveryPoint', {
description: 'get the deliveryPoint address',
accessType: 'WRITE',
accessType: 'READ',
accepts: {
arg: 'vehicleId',
type: 'number',

View File

@ -1,7 +1,7 @@
module.exports = Self => {
Self.remoteMethodCtx('guessPriority', {
description: 'Changes automatically the priority of the tickets in a route',
accessType: 'READ',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
@ -15,7 +15,7 @@ module.exports = Self => {
},
http: {
path: `/:id/guessPriority`,
verb: 'GET'
verb: 'PATCH'
}
});

View File

@ -3,7 +3,7 @@ const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('insertTicket', {
description: 'Check if the ticket can be insert into the route and insert it',
accessType: 'READ',
accessType: 'WRITE',
accepts: [{
arg: 'routeId',
type: 'number',

View File

@ -0,0 +1,36 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('route getByWorker()', () => {
const userId = 56;
const activeCtx = {
accessToken: {userId: userId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should return routes assigned to the worker', async() => {
const result = await app.models.Route.getByWorker(ctx);
expect(result.every(route => route.workerFk === userId)).toBe(true);
});
it('should return all routes if user has canViewAllRoute permission', async() => {
// Simular que el usuario tiene permiso para ver todas las rutas
spyOn(app.models.ACL, 'checkAccessAcl').and.returnValue(Promise.resolve(true));
const result = await app.models.Route.getByWorker(ctx);
expect(result.some(route => route.workerFk != userId)).toBe(true);
});
});

View File

@ -17,6 +17,7 @@ module.exports = Self => {
require('../methods/route/cmr')(Self);
require('../methods/route/getExternalCmrs')(Self);
require('../methods/route/downloadCmrsZip')(Self);
require('../methods/route/getByWorker')(Self);
Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000'
@ -31,5 +32,5 @@ module.exports = Self => {
const routeMaxKm = 1000;
if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
err();
};
}
};

View File

@ -120,7 +120,7 @@ class Controller extends Section {
guessPriority() {
let query = `Routes/${this.$params.id}/guessPriority/`;
this.$http.get(query).then(() => {
this.$http.patch(query).then(() => {
this.vnApp.showSuccess(this.$t('Order changed'));
this.$.model.refresh();
});

View File

@ -209,22 +209,6 @@ describe('Route', () => {
});
});
describe('guessPriority()', () => {
it('should perform a GET query then call both refresh and showSuccess methods', () => {
jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$params = {id: 99};
const url = `Routes/${controller.$params.id}/guessPriority/`;
$httpBackend.expectGET(url).respond('ok');
controller.guessPriority();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Order changed');
expect(controller.$.model.refresh).toHaveBeenCalledWith();
});
});
describe('onDrop()', () => {
it('should call the insert method when dragging a ticket number', () => {
jest.spyOn(controller, 'insert');

View File

@ -75,8 +75,7 @@ module.exports = Self => {
{
arg: 'option',
type: 'number',
description: 'Action id',
required: true
description: 'Action id'
},
{
arg: 'isWithoutNegatives',
@ -88,7 +87,18 @@ module.exports = Self => {
arg: 'withWarningAccept',
type: 'boolean',
description: 'Has pressed in confirm message',
}],
},
{
arg: 'newTicket',
type: 'number',
description: 'Ticket id you want to move the lines to, if applicable',
},
{
arg: 'keepPrice',
type: 'boolean',
description: 'If prices should be maintained',
}
],
returns: {
type: ['object'],
root: true
@ -141,12 +151,18 @@ module.exports = Self => {
const salesNewTicketLength = salesNewTicket.length;
if (salesNewTicketLength && sales.length != salesNewTicketLength) {
const newTicket = await models.Ticket.transferSales(ctx, args.id, null, salesNewTicket, myOptions);
const newTicket = await models.Ticket.transferSales(
ctx,
args.id,
args.newTicket,
salesNewTicket,
myOptions
);
args.id = newTicket.id;
}
}
const originalTicket = await models.Ticket.findOne({
const ticketToChange = await models.Ticket.findOne({
where: {id: args.id},
fields: [
'id',
@ -179,33 +195,38 @@ module.exports = Self => {
},
]
}, myOptions);
const originalTicket = JSON.parse(JSON.stringify(ticketToChange));
const ticketChanges = {
clientFk: args.clientFk,
nickname: args.nickname,
agencyModeFk: args.agencyModeFk,
addressFk: args.addressFk,
zoneFk: args.zoneFk,
warehouseFk: args.warehouseFk,
companyFk: args.companyFk,
shipped: args.shipped,
landed: args.landed,
isDeleted: args.isDeleted
};
args.routeFk = null;
let response;
if (args.keepPrice) {
ticketChanges.routeFk = null;
response = await ticketToChange.updateAttributes(ticketChanges, myOptions);
} else {
const hasToBeUnrouted = true;
response = await Self.rawSql(
'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[args.id].concat(Object.values(ticketChanges), [hasToBeUnrouted, args.option]),
myOptions
);
}
if (args.isWithoutNegatives === false) delete args.isWithoutNegatives;
const updatedTicket = Object.assign({}, args);
delete updatedTicket.ctx;
delete updatedTicket.option;
// Force to unroute ticket
const hasToBeUnrouted = true;
const query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
const res = await Self.rawSql(query, [
args.id,
args.clientFk,
args.nickname,
args.agencyModeFk,
args.addressFk,
args.zoneFk,
args.warehouseFk,
args.companyFk,
args.shipped,
args.landed,
args.isDeleted,
hasToBeUnrouted,
args.option
], myOptions);
if (originalTicket.addressFk != updatedTicket.addressFk && args.id) {
if (ticketToChange.addressFk != updatedTicket.addressFk && args.id) {
await models.TicketObservation.destroyAll({
ticketFk: args.id
}, myOptions);
@ -232,10 +253,15 @@ module.exports = Self => {
const hasChanges = Object.keys(changes.old).length > 0 || Object.keys(changes.new).length > 0;
if (hasChanges) {
delete changes.old.client;
delete changes.old.address;
delete changes.new.client;
delete changes.new.address;
const oldProperties = await loggable.translateValues(Self, changes.old);
const newProperties = await loggable.translateValues(Self, changes.new);
const salesPersonId = originalTicket.client().salesPersonFk;
const salesPersonId = ticketToChange.client().salesPersonFk;
if (salesPersonId) {
const url = await Self.app.models.Url.getUrl();
@ -256,10 +282,10 @@ module.exports = Self => {
}
}
res.id = args.id;
response.id = args.id;
if (tx) await tx.commit();
return res;
return response;
} catch (e) {
if (tx) await tx.rollback();
throw e;

View File

@ -49,7 +49,6 @@ module.exports = function(Self) {
myOptions.transaction = tx;
}
let serial;
try {
const ticketToInvoice = await Self.rawSql(`
SELECT id
@ -60,7 +59,7 @@ module.exports = function(Self) {
where: {
id: {inq: ticketsIds}
},
fields: ['id', 'clientFk']
fields: ['id', 'clientFk', 'addressFk']
}, myOptions);
await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions);
@ -72,13 +71,19 @@ module.exports = function(Self) {
throw new UserError(`This client can't be invoiced`);
const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`;
const [result] = await Self.rawSql(query, [
const [{serial}] = await Self.rawSql(query, [
clientId,
companyFk,
invoiceType,
], myOptions);
serial = result.serial;
const invoiceOutSerial = await models.InvoiceOutSerial.findById(serial);
if (invoiceOutSerial?.taxAreaFk == 'WORLD') {
const address = await models.Address.findById(firstTicket.addressFk);
if (!address || !address.customsAgentFk || !address.incotermsFk)
throw new UserError('Incoterms data for consignee is missing');
}
await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions);
const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions);

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