Compare commits

...

145 commits

Author SHA1 Message Date
da05700dec Add pdf link to open jo list #517 2020-10-04 23:59:58 +08:00
a70777cd84 Merge branch '515-cmb-create-new-pdf-file' into '488-cmb-live'
Resolve "CMB -  create new pdf file"

See merge request jankstudio/resq!595
2020-10-04 14:42:54 +00:00
694fccfbad Replace pdf filler images with the real ones #515 2020-10-04 22:41:28 +08:00
Korina Cordero
43aabbd5ee Add placeholder images to pdf file. #515 2020-10-02 10:19:06 +00:00
Korina Cordero
8b63b963c0 Modify pdf form. #515 2020-10-02 07:36:46 +00:00
Korina Cordero
c86de77c21 Merge branch '488-cmb-live' of gitlab.com:jankstudio/resq into 515-cmb-create-new-pdf-file 2020-10-02 04:33:37 +00:00
75c03863bb Merge branch '512-cmb-warranty-expiration-computation' into '488-cmb-live'
Recommit warranty computation changes. #512

See merge request jankstudio/resq!599
2020-10-02 03:34:04 +00:00
Korina Cordero
b212fa99bc Recommit warranty computation changes. #512 2020-10-02 03:15:07 +00:00
f9f3ac6099 Merge branch '512-cmb-warranty-expiration-computation' into '488-cmb-live'
Add WarrantyHandler directory. #512

See merge request jankstudio/resq!598
2020-10-02 02:23:02 +00:00
Korina Cordero
0660e67ec2 Add WarrantyHandler directory. #512 2020-10-02 02:22:00 +00:00
e53bb8f69b Revert "Merge branch '512-cmb-warranty-expiration-computation' into '488-cmb-live'"
This reverts merge request !594
2020-10-01 15:09:43 +00:00
6106b01964 Merge branch '514-cmb-refactor-realtime-map' into '488-cmb-live'
Initialize rider availability in dashboard map #514

See merge request jankstudio/resq!597
2020-10-01 14:33:11 +00:00
3d96299c23 Initialize rider availability in dashboard map #514 2020-10-01 22:31:48 +08:00
adadb611b8 Merge branch '514-cmb-refactor-realtime-map' into '488-cmb-live'
Change mqtt session to persistent #514

See merge request jankstudio/resq!596
2020-10-01 14:11:54 +00:00
00091ccc57 Change mqtt session to persistent #514 2020-10-01 21:56:27 +08:00
Korina Cordero
d08ade3952 Redo the pdf form. #515 2020-10-01 10:57:48 +00:00
d2d371ccf7 Merge branch '512-cmb-warranty-expiration-computation' into '488-cmb-live'
Resolve "CMB - warranty expiration computation"

See merge request jankstudio/resq!594
2020-10-01 08:19:16 +00:00
Korina Cordero
d3bcc1176c Fix warranty computation. #512 2020-10-01 06:32:05 +00:00
792fc73d90 Merge branch '509-cmb-jo-form-for-rider-assignment' into '488-cmb-live'
Resolve "CMB - JO form for rider assignment"

See merge request jankstudio/resq!593
2020-10-01 01:26:33 +00:00
Korina Cordero
4e2757ac56 Disable the advance order checkbox. #509 2020-09-30 08:16:47 +00:00
Korina Cordero
931a212005 Add assign advance order screens. #509 2020-09-30 08:13:23 +00:00
Korina Cordero
c4bf772f70 Add assign advance order to acl, menu, and route. #509 2020-09-29 10:57:07 +00:00
Korina Cordero
55ac665938 Add edit advance JO. #509 2020-09-29 07:55:35 +00:00
Korina Cordero
a030ec6679 Add routes and templates for advance order. #509 2020-09-29 04:27:11 +00:00
Korina Cordero
18a2be4aa9 Merge branch '508-cmb-remove-tier-2-notes-from-form' into '488-cmb-live'
Resolve "CMB - remove tier 2 notes from form"

See merge request jankstudio/resq!590
2020-09-28 04:05:54 +00:00
Korina Cordero
59c71d2638 Removed Tier 1 and Tier 2 Notes from walkin and onestep forms. #508 2020-09-28 04:01:14 +00:00
Korina Cordero
7dc1eb2a5c Merge branch '506-cmb-remove-fax-number' into '488-cmb-live'
Resolve "CMB - remove fax number"

See merge request jankstudio/resq!589
2020-09-28 03:39:25 +00:00
Korina Cordero
2b42eb08a4 Remove fax number from onestep and walkin forms. #506 2020-09-28 03:32:12 +00:00
Korina Cordero
dc2252f3e1 Merge branch '505-cmb-label-changes-for-carfix-details-and-landline' into '488-cmb-live'
Change labels for landline ad CarFix Details. #505

See merge request jankstudio/resq!588
2020-09-28 02:42:46 +00:00
Korina Cordero
b7291e94b1 Change labels for landline ad CarFix Details. #505 2020-09-25 03:16:33 +00:00
1986423230 Merge branch '501-cmb-jo-in-view-open-should-disappear-only-when-completed' into '488-cmb-live'
JO stays in View Open list until JO has status of completed. #501

See merge request jankstudio/resq!587
2020-09-18 07:41:10 +00:00
Korina Cordero
7e637bdfc0 JO stays in View Open list until JO has status of completed. #501 2020-09-18 07:16:15 +00:00
e0f41c57c1 Merge branch '498-cmb-rearrange-columns' into '488-cmb-live'
Add JO ID and status to header to JO form. #498

See merge request jankstudio/resq!586
2020-09-18 06:41:59 +00:00
9606e9aa1c Merge branch '500-cmb-add-debit-card-to-mode-of-payment' into '488-cmb-live'
Add debit card to the mode of payment. #500

See merge request jankstudio/resq!585
2020-09-18 06:41:52 +00:00
075db2312d Merge branch '499-cmb-customer-info-disappears-if-jo-is-advance-order' into '488-cmb-live'
Remove obj.isAdvanceOrder from the New Customer toggle. #499

See merge request jankstudio/resq!584
2020-09-18 06:41:39 +00:00
Korina Cordero
a863a5e34b Add JO ID and status to header to JO form. #498 2020-09-18 06:40:15 +00:00
Korina Cordero
86519d79c5 Add debit card to the mode of payment. #500 2020-09-18 06:29:37 +00:00
Korina Cordero
566c5added Remove obj.isAdvanceOrder from the New Customer toggle. #499 2020-09-18 06:24:18 +00:00
012c486602 Merge branch '497-cmb-display-cancellation-reason-in-jo' into '488-cmb-live'
Add cancellation reason to JO form. #497

See merge request jankstudio/resq!581
2020-09-17 09:25:22 +00:00
5ef05b3770 Merge branch '498-cmb-rearrange-columns' into '488-cmb-live'
Fix edit icon for open and behind schedule. #498

See merge request jankstudio/resq!583
2020-09-17 08:19:06 +00:00
Korina Cordero
25c399eeb2 Revert back to btn-reassign-hub. #498 2020-09-17 08:14:53 +00:00
Korina Cordero
d286ace684 Fix edit icon for open and behind schedule. #498 2020-09-17 08:02:27 +00:00
094ccbe163 Merge branch '498-cmb-rearrange-columns' into '488-cmb-live'
Rearranged the columns for the View pages. #498

See merge request jankstudio/resq!582
2020-09-17 07:38:51 +00:00
Korina Cordero
6dd3068e70 Rearranged the columns for the View pages. #498 2020-09-17 07:37:22 +00:00
Korina Cordero
6f5246d3e0 Add cancellation reason to JO form. #497 2020-09-17 07:04:03 +00:00
8068a662f6 Check active jo before removing it for rider #488 2020-09-16 22:22:26 +08:00
331d483606 Send the mqtt event for cancel directly #488 2020-09-16 21:31:07 +08:00
7152b0f610 Add message to cancel mqtt event #488 2020-09-16 21:15:08 +08:00
1e23764452 Initialize jo_data as null in RAPI call #488 2020-09-16 20:23:36 +08:00
2c4167a093 Set job_order field to null if no ongoing in RAPI call #488 2020-09-16 20:18:07 +08:00
97d8b9e147 Merge branch '495-cmb-rider-cannot-start-second-jo-after-completion-of-first-jo' into '488-cmb-live'
Resolve "CMB - rider cannot start second JO after completion of first JO"

See merge request jankstudio/resq!580
2020-09-16 08:38:20 +00:00
928d5412e5 Merge branch '496-cmb-clear-rider-s-active-jo-when-jo-is-cancelled' into '488-cmb-live'
Clear rider's active job order when JO is cancelled via rider app and admin panel. #496

See merge request jankstudio/resq!579
2020-09-16 08:38:05 +00:00
Korina Cordero
8bcd9b9546 Merge branch '496-cmb-clear-rider-s-active-jo-when-jo-is-cancelled' of gitlab.com:jankstudio/resq into 495-cmb-rider-cannot-start-second-jo-after-completion-of-first-jo 2020-09-16 08:08:12 +00:00
Korina Cordero
2b07a89544 Make explicit setting of active JO to null. #496 2020-09-16 08:07:10 +00:00
Korina Cordero
9ad9de61c7 Set rider's active job order to null after JO completion. #495 2020-09-16 08:05:05 +00:00
Korina Cordero
dbb05b16d9 Clear rider's active job order when JO is cancelled via rider app and admin panel. #496 2020-09-16 07:50:45 +00:00
5661bb4daf Merge branch '490-cmb-add-labels-to-jo-pictures-and-should-be-able-to-click-on-pictures' into '488-cmb-live'
Fix labels for Odometer Reading and Proof of Payment. #490

See merge request jankstudio/resq!578
2020-09-16 06:15:06 +00:00
Korina Cordero
32f2b21dbf Fix labels for Odometer Reading and Proof of Payment. #490 2020-09-16 06:13:43 +00:00
59ecc19ef7 Merge branch '489-cmb-rider-realtime-not-displaying' into '488-cmb-live'
Use new mqtt library and enable reconnect #489

See merge request jankstudio/resq!577
2020-09-15 15:28:02 +00:00
0880a75fa7 Use new mqtt library and enable reconnect #489 2020-09-15 23:25:44 +08:00
40b28e6e4d Merge branch '492-cmb-sort-in-descending-order-view-all-and-view-open' into '488-cmb-live'
Change default sorting to descending order for View All, View Open and View Behind Schedule. #492

See merge request jankstudio/resq!575
2020-09-15 07:54:53 +00:00
Korina Cordero
9019c4b450 Change default sorting to descending order for View All, View Open and View Behind Schedule. #492 2020-09-15 07:36:00 +00:00
b822829b38 Merge branch '490-cmb-add-labels-to-jo-pictures-and-should-be-able-to-click-on-pictures' into '488-cmb-live'
Add labels to JO pictures. Make pictures clickable. #490

See merge request jankstudio/resq!574
2020-09-15 07:07:57 +00:00
Korina Cordero
d69330e744 Add labels to JO pictures. Make pictures clickable. #490 2020-09-15 06:48:30 +00:00
1c1096da17 Merge branch '487-cmb-update-vehicle-current-battery-for-migrated-data' into '472-cmb-release'
Resolve "CMB - update vehicle current battery for migrated data"

See merge request jankstudio/resq!573
2020-09-15 01:24:24 +00:00
Korina Cordero
77bc17d41e Set the last name for migrated customers to LEGACY. #487 2020-09-11 07:50:17 +00:00
Korina Cordero
51cdc6ff15 Add command to set current battery of customer vehicle from migrated data. #487 2020-09-11 06:32:57 +00:00
Korina Cordero
4de3277030 Merge branch '484-cmb-cmb-changes' into '472-cmb-release'
Resolve "CMB - CMB changes"

See merge request jankstudio/resq!569
2020-09-08 03:32:40 +00:00
Korina Cordero
c46fa82b68 Make responsible party field mandatory. Fix issues found for walkin JO. #484 2020-09-08 03:31:22 +00:00
Korina Cordero
dacd9efe01 Merge branch '472-cmb-release' of gitlab.com:jankstudio/resq into 484-cmb-cmb-changes 2020-09-08 02:24:38 +00:00
Korina Cordero
818277159c Remove test hubs and riders. #472 2020-09-07 04:51:35 +00:00
Korina Cordero
5b70085162 Set rider limit to 50. #472 2020-09-07 04:10:02 +00:00
Korina Cordero
92a7380b4f Fix display issue of cancel reason. #472 2020-09-07 03:27:38 +00:00
Korina Cordero
9052f8102c Get name for cancel reason. #472 2020-09-07 03:00:22 +00:00
Korina Cordero
0fb08002e8 Merge branch '484-cmb-cmb-changes' into '472-cmb-release'
Resolve "CMB - CMB changes"

See merge request jankstudio/resq!568
2020-09-07 02:44:09 +00:00
Korina Cordero
399cf23f29 Merge branch '460-cmb-data-migration-for-carfix-data' into '472-cmb-release'
Resolve "CMB - Data migration for Carfix data"

See merge request jankstudio/resq!565
2020-09-07 02:42:34 +00:00
Korina Cordero
ad77e20bd3 Merge branch '460-cmb-data-migration-for-carfix-data' of gitlab.com:jankstudio/resq into 484-cmb-cmb-changes 2020-09-07 02:13:06 +00:00
Korina Cordero
4703cd5080 Remove rider plate number from form. Get plate number from rider. #484 2020-09-07 02:06:20 +00:00
Korina Cordero
ce8e8974bd Add rider plate number to JO. #484 2020-09-04 09:48:50 +00:00
Korina Cordero
171fa14e40 Add job order number to notifications. Add cancel reason to notifications if JO is cancelled. #484 2020-09-04 08:41:20 +00:00
Korina Cordero
5185043934 Merge branch '483-cmb-agent-s-name-in-open-and-view-all' into '472-cmb-release'
Add agent name to the view all and view open JO pages. #483

See merge request jankstudio/resq!567
2020-09-04 08:07:48 +00:00
Korina Cordero
8eebf6ee1c Add invalid mobile numbers to invalid file. #460 2020-09-04 08:07:31 +00:00
Korina Cordero
089bced14c Add agent name to the view all and view open JO pages. #483 2020-09-04 03:14:37 +00:00
Korina Cordero
8c32792c38 Create command to add vehicle manufacturers, vehicles and their compatible batteries. #460 2020-09-02 11:34:50 +00:00
Korina Cordero
49ee85898e Create command to add vehicle manufacturers, vehicles and their compatible batteries. #460 2020-09-02 11:33:17 +00:00
Korina Cordero
b8885edaaa Modify commands for importing battery information. #460 2020-09-02 09:55:45 +00:00
Korina Cordero
9b7fa2048a Add command to create battery manufacturers and models and import battery sizes. #460 2020-09-02 08:36:39 +00:00
Korina Cordero
d0fcbe8cf3 Removed old import carfix data command. #460 2020-09-02 02:46:58 +00:00
Korina Cordero
885d1911a2 Merge branch '472-cmb-release' of gitlab.com:jankstudio/resq into 460-cmb-data-migration-for-carfix-data 2020-09-01 10:01:36 +00:00
Korina Cordero
4e9c503eb2 Add command to migrate the imported CarFix data into CMBLegacyJobOrder. #460 2020-09-01 09:59:49 +00:00
Korina Cordero
ecbd83e3a3 Merge branch '478-cmb-move-adding-removing-rider-to-and-from-cache' into '472-cmb-release'
Set display marker to false when rider is unavailable. #478

See merge request jankstudio/resq!560
2020-08-26 08:56:52 +00:00
Korina Cordero
b31f254c82 Set display marker to false when rider is unavailable. #478 2020-08-26 08:55:53 +00:00
Korina Cordero
f5fa74f945 Merge branch '478-cmb-move-adding-removing-rider-to-and-from-cache' into '472-cmb-release'
Add checking if rider is available. #478

See merge request jankstudio/resq!559
2020-08-26 07:52:30 +00:00
Korina Cordero
ce79354f5d Add checking if rider is available. #478 2020-08-26 07:51:32 +00:00
Korina Cordero
b150c5ed29 Merge branch '478-cmb-move-adding-removing-rider-to-and-from-cache' into '472-cmb-release'
Revert to old behavior. #478

See merge request jankstudio/resq!558
2020-08-26 07:46:50 +00:00
Korina Cordero
663e28c403 Revert to old behavior. #478 2020-08-26 07:45:55 +00:00
Korina Cordero
24a75378c3 Merge branch '478-cmb-move-adding-removing-rider-to-and-from-cache' into '472-cmb-release'
Add checking if rider is available. #478

See merge request jankstudio/resq!557
2020-08-26 07:37:23 +00:00
Korina Cordero
aa6ec44e27 Add checking if rider is available. #478 2020-08-26 07:35:30 +00:00
Korina Cordero
3f0cca1fc9 Merge branch '478-cmb-move-adding-removing-rider-to-and-from-cache' into '472-cmb-release'
Fix error in goOnline. #478

See merge request jankstudio/resq!556
2020-08-26 04:54:36 +00:00
Korina Cordero
3157ce7edc Fix error in goOnline. #478 2020-08-26 04:53:53 +00:00
Korina Cordero
ab11e8aee5 Merge branch '478-cmb-move-adding-removing-rider-to-and-from-cache' into '472-cmb-release'
Move adding/removal of rider to and from rider cache when going online/offline. #478

See merge request jankstudio/resq!555
2020-08-26 04:42:18 +00:00
Korina Cordero
309605da24 Move adding/removal of rider to and from rider cache when going online/offline. #478 2020-08-26 04:06:50 +00:00
Korina Cordero
832d96c171 Add saving of customer and customer vehicle from legacy job order data. #460 2020-08-25 10:06:24 +00:00
Korina Cordero
deacd5876c Merge branch '474-cmb-notifications-are-always-new' into '472-cmb-release'
Resolve "CMB - notifications are always new"

See merge request jankstudio/resq!554
2020-08-25 07:31:42 +00:00
Korina Cordero
08b4b25f4e Return count of unread notifications. #474 2020-08-25 07:21:24 +00:00
Korina Cordero
f5f771c4ed Merge branch '472-cmb-release' of gitlab.com:jankstudio/resq into 474-cmb-notifications-are-always-new 2020-08-25 02:06:21 +00:00
Korina Cordero
2d24a7b84a Move the delete commands out of the foreign key check block. #472 2020-08-25 01:49:12 +00:00
Korina Cordero
1f6ee02390 Update sql script to delete all customer and customer vehicles. #472 2020-08-25 01:08:33 +00:00
Korina Cordero
0aa3780c77 Set unread and fresh flags for notifications. #474 2020-08-24 11:26:05 +00:00
e4ad0169d7 Merge branch '473-cmb-reassign-issue' into '472-cmb-release'
Fix for error in JO creation. #473

See merge request jankstudio/resq!553
2020-08-24 04:09:00 +00:00
Korina Cordero
9fe4098ac4 Fix for error in JO creation. #473 2020-08-24 03:47:43 +00:00
28ded5faa8 Merge branch '473-cmb-reassign-issue' into '472-cmb-release'
Fix reassign issue. #473

See merge request jankstudio/resq!552
2020-08-24 03:09:22 +00:00
Korina Cordero
ed22eebdc2 Fix reassign issue. #473 2020-08-24 03:05:22 +00:00
198406a5ec Add notification sending for JOs cancelled by rider #472 2020-08-23 01:33:26 +08:00
c2e10a12e6 Make sure date_create has default for Notification entity #472 2020-08-23 01:29:59 +08:00
678dfcc65d Fix generate url inside rider api handler #472 2020-08-23 01:26:19 +08:00
01f5717c1c Add notification sending when rider rejects JO #472 2020-08-23 01:09:45 +08:00
0bd968d753 Fix clear sql to clear rider active JO #472 2020-08-23 00:46:09 +08:00
c8c3ffef42 Fix notification image #472 2020-08-23 00:35:56 +08:00
885665f9d3 Add notification entity and service #472 2020-08-23 00:09:19 +08:00
4e6bc0d95f Add sql for clearing jo data for production #472 2020-08-22 11:07:01 +08:00
b6d2726d41 Fix caching for rider marker name loading #472 2020-08-21 17:42:29 +08:00
78554ec9c6 Fix getting rider name for rider markers #472 2020-08-21 17:04:30 +08:00
Korina Cordero
108d49ca5f Create migration command for cmb legacy job order. #460 2020-08-20 09:51:00 +00:00
Korina Cordero
4bfbfbf995 Create command to import Carfix data. #460 2020-08-20 06:11:34 +00:00
Korina Cordero
4796a9be14 Fix a matching issue in the previous commands. Add saving of customer and customer vehicle for CarFix command. #460 2020-08-19 08:32:50 +00:00
Korina Cordero
eed91a413a Add command to import CarFix data. #460 2020-08-18 10:36:19 +00:00
Korina Cordero
ae2a22b159 Merge branch '457-cmb-add-telephone-number-to-search' of gitlab.com:jankstudio/resq into 460-cmb-data-migration-for-carfix-data 2020-08-18 03:52:51 +00:00
root
a2245fef2b Merge branch '457-cmb-add-telephone-number-to-search' of gitlab.com:jankstudio/resq into 457-cmb-add-telephone-number-to-search 2020-08-17 16:33:43 +08:00
root
a05f53fd88 Pass as option the rider availability url. #457 2020-08-17 16:32:43 +08:00
Korina Cordero
763d141860 Fix SSL problem. #457 2020-08-15 08:02:17 +00:00
Korina Cordero
a864b6b3eb Merge branch '457-cmb-add-telephone-number-to-search' of gitlab.com:jankstudio/resq into 457-cmb-add-telephone-number-to-search 2020-08-15 07:51:08 +00:00
Korina Cordero
4866d01eb3 Add ssl checking. #457 2020-08-15 07:31:17 +00:00
Korina Cordero
2f60c28068 Merge branch 'revert-4874791f' into '457-cmb-add-telephone-number-to-search'
Revert "Merge branch '467-cmb-release-2' into '457-cmb-add-telephone-number-to-search'"

See merge request jankstudio/resq!550
2020-08-14 11:22:41 +00:00
Korina Cordero
526296c8a3 Revert "Merge branch '467-cmb-release-2' into '457-cmb-add-telephone-number-to-search'"
This reverts commit 4874791fed
2020-08-14 11:21:53 +00:00
Korina Cordero
4874791fed Merge branch '467-cmb-release-2' into '457-cmb-add-telephone-number-to-search'
# Conflicts:
#   config/routes/rider.yaml
#   public/assets/js/dashboard_map.js
#   public/assets/js/map_mqtt.js
#   src/Controller/RiderController.php
2020-08-14 10:10:45 +00:00
Korina Cordero
166c4942f1 Add rider name to rider labels. #457 2020-08-14 09:35:57 +00:00
Korina Cordero
b1d397c127 Merge branch '458-cmb-service-type-transaction-names' of gitlab.com:jankstudio/resq into 457-cmb-add-telephone-number-to-search 2020-08-14 09:26:37 +00:00
Korina Cordero
b284cb7b64 Merge branch '424-cmb-release' of gitlab.com:jankstudio/resq into 460-cmb-data-migration-for-carfix-data 2020-08-13 06:12:22 +00:00
Korina Cordero
66e46c4bad Merge branch '457-cmb-add-telephone-number-to-search' into '424-cmb-release'
Remove rider name from labels in Dashboard. #457

See merge request jankstudio/resq!539
2020-08-06 08:39:04 +00:00
Korina Cordero
b20c61a830 Remove rider name from labels in Dashboard. #457 2020-08-06 08:38:02 +00:00
Korina Cordero
b9706ede89 Merge branch '457-cmb-add-telephone-number-to-search' into '424-cmb-release'
Remove log messages. #457

See merge request jankstudio/resq!538
2020-08-05 10:32:25 +00:00
Korina Cordero
90918c49ef Merge branch '458-cmb-service-type-transaction-names' into '424-cmb-release'
Resolve "CMB - service type transaction names"

See merge request jankstudio/resq!533
2020-08-05 09:47:01 +00:00
Korina Cordero
8006545242 Merge branch '457-cmb-add-telephone-number-to-search' into '424-cmb-release'
Resolve "CMB - add telephone number to search"

See merge request jankstudio/resq!535
2020-08-05 09:46:31 +00:00
Korina Cordero
74039a8011 Set service types to battery sales, warranty replacement, jumpstart, and warranty claim. #458 2020-08-05 06:48:17 +00:00
92 changed files with 13437 additions and 1466 deletions

View file

@ -74,3 +74,6 @@ MAPTILER_API_KEY=map_tiler_api_key
# API version
API_VERSION=insert_api_version_here
# SSL
SSL_ENABLE=true_or_false

14
composer.lock generated
View file

@ -1,7 +1,7 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f03b92d48946e8b2ee19466f931c826f",
@ -3214,20 +3214,20 @@
},
{
"name": "symfony/filesystem",
"version": "v4.4.4",
"version": "v4.4.10",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd"
"reference": "b27f491309db5757816db672b256ea2e03677d30"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd",
"reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b27f491309db5757816db672b256ea2e03677d30",
"reference": "b27f491309db5757816db672b256ea2e03677d30",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"php": ">=7.1.3",
"symfony/polyfill-ctype": "~1.8"
},
"type": "library",
@ -3260,7 +3260,7 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2020-01-21T08:20:44+00:00"
"time": "2020-05-30T18:50:54+00:00"
},
{
"name": "symfony/finder",

View file

@ -270,6 +270,16 @@ access_keys:
label: Autoassign Test
- id: jo_hub.list
label: Hub View
- id: jo_behind_schedule.list
label: View Behind Schedule
- id: jo_advance_order.form
label: New Advance Order
- id: jo_advance_order.edit
label: New Advance Order Edit
- id: jo_assign_advance_order.list
label: Assign Advance Order
- id: jo_assign_advance_order.form
label: Edit Assign Advance Order
- id: support
label: Customer Support Access

View file

@ -106,6 +106,14 @@ main_menu:
acl: jo_onestep.form
label: One-step Process
parent: joborder
- id: jo_advance_order_form
acl: jo_advance_order.form
label: New Advance Order
parent: joborder
- id: jo_assign_advance_order
acl: jo_assign_advance_order.list
label: Assign Advance Order
parent: joborder
- id: jo_walkin_form
acl: jo_walkin.form
label: Walk-in
@ -118,6 +126,10 @@ main_menu:
acl: jo_all.list
label: View All
parent: joborder
- id: jo_behind_schedule
acl: jo_behind_schedule.list
label: View Behind Schedule
parent: joborder
- id: support
acl: support.menu

View file

@ -5,6 +5,7 @@ parameters:
latitude: 14.6091
longitude: 121.0223
image_upload_directory: '%kernel.project_dir%/public/uploads'
jo_extra_upload_directory: '%kernel.project_dir%/public/uploads/jo_extra'
job_order_refresh_interval: 300000
api_acl_file: 'api_acl.yaml'
api_access_key: 'api_access_keys'
@ -12,6 +13,7 @@ parameters:
app_access_key: 'access_keys'
cvu_brand_id: "%env(CVU_BRAND_ID)%"
country_code: "%env(COUNTRY_CODE)%"
api_version: "%env(API_VERSION)%"
services:
# default configuration for services in *this* file
@ -73,6 +75,7 @@ services:
$pass: "%env(RT_PASS)%"
$usage_type: "%env(RT_USAGE_TYPE)%"
$shortcode: "%env(RT_SHORTCODE)%"
$dr_url: "https://resqaws.jankstudio.com/sms/delivery_receipt"
App\Service\MQTTClient:
arguments:
@ -167,7 +170,7 @@ services:
# job order generator
App\Service\JobOrderHandler\CMBJobOrderHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
$country_code: "%env(COUNTRY_CODE)%"
#job order generator interface
App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler"
@ -187,13 +190,19 @@ services:
App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\CMBRiderAssignmentHandler"
# rider API service
App\Service\RiderAPIHandler\CMBRiderAPIHandler:
App\Service\RiderAPIHandler\CMBRiderAPIHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
$upload_dir: "%jo_extra_upload_directory%"
# rider API interface
App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\CMBRiderAPIHandler"
# warranty handler service
App\Service\WarrantyHandler\CMBWarrantyHandler: ~
# warranty handler interface
App\Service\WarrantyHandlerInterface: "@App\\Service\\WarrantyHandler\\CMBWarrantyHandler"
# map manager
#App\Service\GISManager\Bing: ~
App\Service\GISManager\OpenStreet: ~
@ -218,6 +227,12 @@ services:
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\NotificationManager:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$redis_mqtt_key: "mqtt_events"
$em: "@doctrine.orm.entity_manager"
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
@ -229,6 +244,14 @@ services:
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
$status_key: "%env(STATUS_RIDER_KEY)%"
# inventory manager
App\Service\InventoryManager:
arguments:
$api_url: "%env(INVENTORY_API_URL)%"
$api_ocp_key: "%env(INVENTORY_API_OCP)%"
$api_auth_prefix: "%env(INVENTORY_API_AUTH_TOKEN_PREFIX)%"
$api_auth_token: "%env(INVENTORY_API_AUTH_TOKEN)%"
# API logging
App\EventSubscriber\LogSubscriber:
arguments:

View file

@ -37,6 +37,10 @@ main_menu:
acl: rider.list
label: Riders
parent: logistics
- id: service_charge_list
acl: service_charge.list
label: Service Charges
parent: logistics
- id: battery
acl: battery.menu
@ -98,21 +102,21 @@ main_menu:
acl: joborder.menu
label: Job Order
icon: flaticon-calendar-3
- id: jo_in
acl: jo_in.list
label: Incoming
- id: jo_onestep_form
acl: jo_onestep.form
label: One-step Process
parent: joborder
- id: jo_proc
acl: jo_proc.list
label: Dispatch
- id: jo_advance_order_form
acl: jo_advance_order.form
label: New Advance Order
parent: joborder
- id: jo_assign
acl: jo_assign.list
label: Rider Assignment
- id: jo_assign_advance_order
acl: jo_assign_advance_order.list
label: Assign Advance Order
parent: joborder
- id: jo_fulfill
acl: jo_fulfill.list
label: Fulfillment
- id: jo_walkin_form
acl: jo_walkin.form
label: Walk-in
parent: joborder
- id: jo_open
acl: jo_open.list
@ -122,9 +126,9 @@ main_menu:
acl: jo_all.list
label: View All
parent: joborder
- id: jo_hub_view
acl: jo_hub.list
label: Hub View
- id: jo_behind_schedule
acl: jo_behind_schedule.list
label: View Behind Schedule
parent: joborder
- id: support
@ -185,12 +189,3 @@ main_menu:
acl: review.list
label: Reviews
parent: partner
- id: analytics
acl: analytics.menu
label: Analytics
icon: flaticon-graphic
- id: analytics_forecast_form
acl: analytics.forecast
label: Forecasting
parent: analytics

View file

@ -46,6 +46,10 @@ security:
provider: api_key_user_provider
user_checker: Catalyst\AuthBundle\Service\UserChecker
cmb_rider_api:
pattern: ^\/cmbrapi\/
security: false
main:
provider: user_provider
form_login:

View file

@ -8,3 +8,4 @@ twig:
mqtt_host: "%env(MQTT_WS_HOST)%"
mqtt_port: "%env(MQTT_WS_PORT)%"
dashboard_enable: "%env(DASHBOARD_ENABLE)%"
ssl_enable: "%env(SSL_ENABLE)%"

View file

@ -74,6 +74,7 @@ services:
$pass: "%env(RT_PASS)%"
$usage_type: "%env(RT_USAGE_TYPE)%"
$shortcode: "%env(RT_SHORTCODE)%"
$dr_url: "https://resqaws.jankstudio.com/sms/delivery_receipt"
App\Service\MQTTClient:
arguments:

View file

@ -0,0 +1,156 @@
# rider app api
cmb_rapi_register:
path: /cmbrapi/register
controller: App\Controller\CMBRAPIController::register
methods: [POST]
cmb_rapi_login:
path: /cmbrapi/login
controller: App\Controller\CMBRAPIController::login
methods: [POST]
cmb_rapi_logout:
path: /cmbrapi/logout
controller: App\Controller\CMBRAPIController::logout
methods: [POST]
cmb_rapi_jo_get:
path: /cmbrapi/joborder
controller: App\Controller\CMBRAPIController::getJobOrder
methods: [GET]
cmb_rapi_jo_accept:
path: /cmbrapi/joaccept
controller: App\Controller\CMBRAPIController::acceptJobOrder
methods: [POST]
cmb_rapi_jo_cancel:
path: /cmbrapi/jocancel
controller: App\Controller\CMBRAPIController::cancelJobOrder
methods: [POST]
cmb_rapi_arrive:
path: /cmbrapi/arrive
controller: App\Controller\CMBRAPIController::arrive
methods: [POST]
cmb_rapi_performed:
path: /cmbrapi/joperform
controller: App\Controller\CMBRAPIController::performJobOrder
methods: [POST]
cmb_rapi_payment:
path: /cmbrapi/jopayment
controller: App\Controller\CMBRAPIController::payment
methods: [POST]
cmb_rapi_hub_arrive:
path: /cmbrapi/hub_arrive
controller: App\Controller\CMBRAPIController::hubArrive
methods: [POST]
cmb_rapi_promos:
path: /cmbrapi/promos
controller: App\Controller\CMBRAPIController::getPromos
methods: [GET]
cmb_rapi_batteries:
path: /cmbrapi/batteries
controller: App\Controller\CMBRAPIController::getBatteries
methods: [GET]
cmb_rapi_change_service:
path: /cmbrapi/service
controller: App\Controller\CMBRAPIController::changeService
methods: [POST]
cmb_rapi_available:
path: /cmbrapi/available
controller: App\Controller\CMBRAPIController::available
methods: [POST]
cmb_rapi_jo_history:
path: /cmbrapi/johistory/{period}
controller: App\Controller\CMBRAPIController::getJobOrderHistory
methods: [GET]
cmb_rapi_assigned_jo_get:
path: /cmbrapi/assignedjos
controller: App\Controller\CMBRAPIController::getAssignedJobOrders
methods: [GET]
cmb_rapi_jo_in_transit:
path: /cmbrapi/jotransit
controller: App\Controller\CMBRAPIController::setJobOrderInTransit
methods: [POST]
cmb_rapi_invoice_generate:
path: /cmbrapi/generateinvoice
controller: App\Controller\CMBRAPIController::generateInvoice
methods: [GET]
cmb_rapi_online:
path: /cmbrapi/online
controller: App\Controller\CMBRAPIController::goOnline
methods: [POST]
cmb_rapi_offline:
path: /cmbrapi/offline
controller: App\Controller\CMBRAPIController::goOffline
methods: [POST]
cmb_rapi_jo_start:
path: /cmbrapi/jostart
controller: App\Controller\CMBRAPIController::startJobOrder
methods: [POST]
cmb_rapi_jo_complete:
path: /cmbrapi/jocomplete
controller: App\Controller\CMBRAPIController::completeJobOrder
methods: [POST]
cmb_rapi_jo_set_active:
path: /cmbrapi/joactive
controller: App\Controller\CMBRAPIController::setActiveJobOrder
methods: [POST]
cmb_rapi_jo_reject:
path: /cmbrapi/joreject
controller: App\Controller\CMBRAPIController::rejectJobOrder
methods: [POST]
cmb_rapi_jo_odometer:
path: /cmbrapi/odometer
controller: App\Controller\CMBRAPIController::setOdometer
methods: [POST]
cmb_rapi_jo_finish_photos_upload:
path: /cmbrapi/uploadfinishphotos
controller: App\Controller\CMBRAPIController::uploadFinishPhotos
methods: [POST]
cmb_rapi_status:
path: /cmbrapi/status
controller: App\Controller\CMBRAPIController::getStatus
methods: [GET]
cmb_rapi_jo_ongoing:
path: /cmbrapi/joongoing
controller: App\Controller\CMBRAPIController::getOngoingJobOrder
methods: [GET]
cmb_rapi_payment_methods:
path: /cmbrapi/paymentmethods
controller: App\Controller\CMBRAPIController::getPaymentMethods
methods: [GET]
cmb_rapi_cancel_reasons:
path: /cmbrapi/cancelreasons
controller: App\Controller\CMBRAPIController::getCancelReasons
methods: [GET]
cmb_rapi_jo_verify:
path: /cmbrapi/joverify
controller: App\Controller\CMBRAPIController::verifyJobOrder
methods: [GET]

View file

@ -253,3 +253,57 @@ jo_hub_view_form:
controller: App\Controller\JobOrderController::hubViewForm
methods: [GET]
jo_behind_schedule:
path: /job-order/behind-schedule
controller: App\Controller\JobOrderController::listBehindSchedule
methods: [GET]
jo_behind_schedule_rows:
path: /job-order/behind-schedule-rows
controller: App\Controller\JobOrderController::getRows
methods: [POST]
defaults:
tier: "behind_schedule"
jo_advance_order_form:
path: /job-order/advance-order
controller: App\Controller\JobOrderController::advanceOrderForm
methods: [GET]
jo_advance_order_submit:
path: /job-order/advance-order
controller: App\Controller\JobOrderController::advanceOrderSubmit
methods: [POST]
jo_advance_order_edit_form:
path: /job-order/advance-order/{id}/edit
controller: App\Controller\JobOrderController::advanceOrderEditForm
methods: [GET]
jo_advance_order_edit_submit:
path: /job-order/advance-order/{id}/edit
controller: App\Controller\JobOrderController::advanceOrderEditSubmit
methods: [POST]
jo_assign_advance_order:
path: /job-order/assign-advance-order
controller: App\Controller\JobOrderController::listAssignAdvanceOrder
methods: [GET]
jo_assign_advance_order_rows:
path: /job-order/assign-advance-joborder-rows
controller: App\Controller\JobOrderController::getRows
methods: [POST]
defaults:
tier: "assign_advance_order"
jo_assign_advance_order_form:
path: /job-order/assign-advance-order/{id}
controller: App\Controller\JobOrderController::assignAdvanceOrderForm
methods: [GET]
jo_assign_advance_order_submit:
path: /job-order/assign-advance-order/{id}
controller: App\Controller\JobOrderController::assignAdvanceOrderSubmit
methods: [POST]

View file

@ -0,0 +1,9 @@
notification_ajax_list:
path: /ajax/notifications
controller: App\Controller\NotificationController::ajaxList
methods: [GET]
notification_ajax_update:
path: /ajax/notifications
controller: App\Controller\NotificationController::ajaxUpdate
methods: [POST]

View file

@ -56,3 +56,14 @@ rider_priority_down_jo:
path: /riders/{id}/priority_down/{jo_id}
controller: App\Controller\RiderController::priorityDownJO
methods: [GET]
rider_ajax_avialable:
path: /riders/{id}/available
controller: App\Controller\RiderController::ajaxAvailable
methods: [GET]
rider_ajax_rider_name:
path: /riders/{id}/name
controller: App\Controller\RiderController::ajaxRiderName
methods: [GET]

View file

@ -5,6 +5,7 @@ parameters:
latitude: 14.6091
longitude: 121.0223
image_upload_directory: '%kernel.project_dir%/public/uploads'
jo_extra_upload_directory: '%kernel.project_dir%/public/uploads/jo_extra'
job_order_refresh_interval: 300000
api_acl_file: 'api_acl.yaml'
api_access_key: 'api_access_keys'
@ -161,39 +162,46 @@ services:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
# invoice generator
App\Service\InvoiceGenerator\ResqInvoiceGenerator: ~
App\Service\InvoiceGenerator\CMBInvoiceGenerator: ~
# invoice generator interface
App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator"
App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\CMBInvoiceGenerator"
# job order generator
App\Service\JobOrderHandler\ResqJobOrderHandler:
App\Service\JobOrderHandler\CMBJobOrderHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
#job order generator interface
App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\ResqJobOrderHandler"
App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler"
# customer generator
App\Service\CustomerHandler\ResqCustomerHandler:
App\Service\CustomerHandler\CMBCustomerHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
# customer generator interface
App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\ResqCustomerHandler"
App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\CMBCustomerHandler"
# rider assignment
App\Service\RiderAssignmentHandler\ResqRiderAssignmentHandler: ~
App\Service\RiderAssignmentHandler\CMBRiderAssignmentHandler: ~
# rider assignment interface
App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\ResqRiderAssignmentHandler"
App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\CMBRiderAssignmentHandler"
# rider API service
App\Service\RiderAPIHandler\ResqRiderAPIHandler:
App\Service\RiderAPIHandler\CMBRiderAPIHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
$country_code: "%env(COUNTRY_CODE)%"
$upload_dir: "%jo_extra_upload_directory%"
App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\ResqRiderAPIHandler"
App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\CMBRiderAPIHandler"
# warranty handler service
App\Service\WarrantyHandler\CMBWarrantyHandler: ~
# warranty handler interface
App\Service\WarrantyHandlerInterface: "@App\\Service\\WarrantyHandler\\CMBWarrantyHandler"
# map manager
#App\Service\GISManager\Bing: ~
@ -219,6 +227,12 @@ services:
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\NotificationManager:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$redis_mqtt_key: "mqtt_events"
$em: "@doctrine.orm.entity_manager"
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"

View file

@ -155,6 +155,29 @@ span.has-danger,
color: #fff !important;
}
.m-table__row--is_in_progress td {
background-color: #FFA500 !important;
color: #fff !important;
}
.m-table__row--is_in_progress td > span,
.m-table__row--is_in_progress td > span a,
.m-table__row--is_in_progress td > span a i {
color: #fff !important;
}
.m-table__row--is_assigned td {
background-color: #0000ff !important;
color: #fff !important;
}
.m-table__row--is_assigned td > span,
.m-table__row--is_assigned td > span a,
.m-table__row--is_assigned td > span a i {
color: #fff !important;
}
.m-datatable.m-datatable--default > .m-datatable__table {
min-height: 0 !important;
}
@ -358,4 +381,4 @@ span.has-danger,
.map-info .m-badge {
border-radius: 0;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -3,6 +3,8 @@ class DashboardMap {
this.options = options;
this.rider_markers = rider_markers;
this.cust_markers = cust_markers;
this.rider_availability = {};
this.rider_names = {};
// layer groups
this.layer_groups = {
@ -146,6 +148,49 @@ class DashboardMap {
}
}
putMarkerWithLabel(id, lat, lng, markers, icon, layer_group, popup_url, name) {
var my = this;
// existing marker
if (markers.hasOwnProperty(id)) {
markers[id].setLatLng(L.latLng(lat, lng));
return;
}
// new marker
// add label
markers[id] = L.marker(
[lat, lng],
{ icon: icon }
);
var marker_label = id + ' - ' + name;
markers[id].bindTooltip(marker_label,
{
permanent: true,
direction: 'right'
}
);
markers[id].addTo(layer_group);
if (my.options.enable_popup) {
markers[id].bindPopup('Loading...');
// bind ajax for popup
markers[id].on('click', function(e) {
var popup = e.target.getPopup();
var url = popup_url.replace('[id]', id);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
}
}
putCustomerMarker(id, lat, lng) {
this.putMarker(
id,
@ -170,6 +215,8 @@ class DashboardMap {
this.layer_groups.customer.removeLayer(markers[id]);
this.layer_groups.mobile_customer.removeLayer(markers[id]);
delete markers[id];
}
putMobileCustomerMarker(id, lat, lng) {
@ -185,27 +232,37 @@ class DashboardMap {
}
putRiderAvailableMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_available,
this.layer_groups.rider_available,
this.options.rider_popup_url
);
var my = this;
my.getRiderName(id, function(name) {
my.putMarkerWithLabel(
id,
lat,
lng,
my.rider_markers,
my.options.icons.rider_available,
my.layer_groups.rider_available,
my.options.rider_popup_url,
name
);
});
}
putRiderActiveJOMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_active_jo,
this.layer_groups.rider_active_jo,
this.options.rider_popup_url
);
var my = this;
my.getRiderName(id, function(name) {
my.putMarkerWithLabel(
id,
lat,
lng,
my.rider_markers,
my.options.icons.rider_active_jo,
my.layer_groups.rider_active_jo,
my.options.rider_popup_url,
name
);
});
}
removeRiderMarker(id) {
@ -219,6 +276,8 @@ class DashboardMap {
this.layer_groups.rider_active_jo.removeLayer(markers[id]);
this.layer_groups.rider_available.removeLayer(markers[id]);
delete markers[id];
}
loadLocations(location_url) {
@ -252,14 +311,45 @@ class DashboardMap {
$.each(riders, function(id, data) {
var lat = data.latitude;
var lng = data.longitude;
var name = '';
if (data.has_jo)
if (data.has_jo) {
my.rider_availability[id] = false;
my.putRiderActiveJOMarker(id, lat, lng);
else
} else {
my.rider_availability[id] = true;
my.putRiderAvailableMarker(id, lat, lng);
}
});
// console.log(rider_markers);
});
}
getRiderName(id, callback) {
var name = '';
var rider_url = this.options.rider_name_url.replace('[id]', id);
var my = this;
console.log('getting rider name for rider ' + id);
// check if we have it in cache
if (this.rider_names.hasOwnProperty(id)) {
name = this.rider_names[id];
callback(name);
} else {
// ajax call to get it
$.ajax({
method: "GET",
url: rider_url
}).done(function(response) {
name = response.rider_name;
// set name in cache
my.rider_names[id] = name;
callback(name);
});
}
}
}

View file

@ -1,7 +1,9 @@
class MapEventHandler {
constructor(options, dashmap) {
constructor(options, dashmap, ssl) {
this.options = options;
this.dashmap = dashmap;
this.ssl = ssl;
this.subscribed = false;
}
connect(user_id, host, port) {
@ -9,24 +11,42 @@ class MapEventHandler {
var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
console.log(client_id);
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
let protocol = 'ws';
if (this.ssl)
protocol = 'wss';
this.mqtt = new Paho.Client(protocol + '://' + host + ':' + port + '/mqtt', client_id);
var options = {
// useSSL: true,
timeout: 3,
useSSL: this.ssl,
timeout: 10,
keepAliveInterval: 10,
invocationContext: this,
onSuccess: this.onConnect.bind(this),
reconnect: true,
cleanSession: false
};
this.mqtt.onMessageArrived = this.onMessage.bind(this);
this.mqtt.onConnectionLost = this.onConnectionLost;
console.log('connecting to mqtt server...');
this.mqtt.connect(options);
}
onConnectionLost(err) {
console.log('mqtt connection lost');
console.log(err);
}
onConnect(icontext) {
console.log('mqtt connected!');
var my = icontext.invocationContext;
if (my.subscribed) {
console.log('already subscribed, not initializing subscriptions');
return;
}
if (my.options.track_rider) {
// subscribe to rider locations
console.log('subscribing to ' + my.options.channels.rider_location);
@ -35,6 +55,10 @@ class MapEventHandler {
// subscribe to rider status
console.log('subscribing to ' + my.options.channels.rider_status);
my.mqtt.subscribe(my.options.channels.rider_status);
// subscribe to rider availability
console.log('subscribing to ' + my.options.channels.rider_availability);
my.mqtt.subscribe(my.options.channels.rider_availability);
}
if (my.options.track_jo) {
@ -51,11 +75,12 @@ class MapEventHandler {
my.mqtt.subscribe(my.options.channels.jo_origin);
}
my.subscribed = true;
}
onMessage(msg) {
// console.log(msg);
console.log('received message');
// console.log('received message');
var channel = msg.destinationName;
var chan_split = channel.split('/');
@ -73,13 +98,55 @@ class MapEventHandler {
}
handleRider(chan_split, payload) {
console.log("rider message");
//console.log("rider message");
var rider_id = chan_split[1];
//console.log('url ' + this.dashmap.options.rider_availability_url);
switch (chan_split[2]) {
case "location":
console.log("got location for rider " + chan_split[1] + " - " + payload);
var pl_split = payload.split(':');
console.log(pl_split);
case "availability":
console.log("got availability for rider " + chan_split[1] + " - " + payload);
var obj = JSON.parse(payload);
var status = obj.status;
console.log("status " + status);
switch (status) {
case 'rider_offline':
this.dashmap.rider_availability[chan_split[1]] = false;
this.dashmap.removeRiderMarker(chan_split[1]);
break;
case 'rider_online':
this.dashmap.rider_availability[chan_split[1]] = true;
var lat = parseFloat(obj.latitude);
var lng = parseFloat(obj.longitude);
// cheeck if rider is available / unavailable
// TODO: make url not hardcoded
var dashmap = this.dashmap;
var url = dashmap.options.rider_availability_url;
var rider_availability_url = url.replace('[id]', chan_split[1]);
//console.log(rider_availability_url);
$.get(rider_availability_url).done(function(data) {
console.log('rider availability - ' + data);
switch (data) {
case 'available':
console.log('putting available marker ' + chan_split[1] + ' ' + lat + ':' + lng);
dashmap.switchRiderStatus(chan_split[1], 'available');
dashmap.putRiderAvailableMarker(chan_split[1], lat, lng);
break;
case 'unavailable':
console.log('putting active jo marker ' + chan_split[1] + ' ' + lat + ':' + lng);
dashmap.switchRiderStatus(chan_split[1], 'jo');
dashmap.putRiderActiveJOMarker(chan_split[1], lat, lng);
break;
}
});
break;
}
break;
case "location":
// console.log("got location for rider " + chan_split[1] + " - " + payload
var pl_split = payload.split(':');
// console.log(pl_split);
// check for correct format
if (pl_split.length != 2)
break;
@ -87,8 +154,22 @@ class MapEventHandler {
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
// TODO: check if available or not
this.dashmap.putRiderAvailableMarker(chan_split[1], lat, lng);
var display_marker = true;
if (this.dashmap.rider_availability.hasOwnProperty(chan_split[1])) {
if (!this.dashmap.rider_availability[chan_split[1]]) {
console.log('NOT displaying marker for inactive rider');
display_marker = false;
}
} else {
console.log('rider not in availability check - ' + chan_split[1]);
display_marker = false;
}
// TODO: cache rider availability (available vs active jo) status and check before displaying icon
// NOTE: we really should fix our terms since available can mean many things
if (display_marker) {
this.dashmap.putRiderAvailableMarker(chan_split[1], lat, lng);
}
break;
case "status":
console.log("got status for rider " + chan_split[1] + " - " + payload);

View file

@ -0,0 +1,111 @@
class NotificationHandler {
constructor(options) {
this.options = options;
}
clearAll() {
// clear notification count
document.getElementById('notif-count').innerHTML = '';
// remove notifications
document.getElementById('notif-body').innerHTML = '';
}
loadAll() {
console.log('loading notifications');
// ajax load
var self = this;
var notif_update_url = this.options['notif_ajax_update_url'];
var xhr = new XMLHttpRequest();
xhr.open('GET', this.options['notif_ajax_url']);
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
var notifs = data.notifications;
// update notification unread count
var count_html = data.unread_count;
document.getElementById('notif-count').innerHTML = count_html;
// do we have any notifications?
if (notifs.length <= 0)
return;
// add actual notifications
var notif_body = document.getElementById('notif-body');
var notif_index = 0;
notifs.forEach(function(notif) {
var notif_date = moment(notif.date).fromNow();
var notif_html = '<div class="m-list-timeline__item">';
notif_html += '<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>';
notif_html += '<span class="m-list-timeline__text">';
notif_html += '<a href="">' + notif.text + '</a>'
notif_html += '</span>';
notif_html += '<span class="m-list-timeline__time">';
notif_html += notif_date;
notif_html += '</span>';
notif_html += '</div>';
notif_body.insertAdjacentHTML('beforeend', notif_html);
document.getElementsByClassName('m-list-timeline__item')[notif_index].addEventListener('click', function(e) {
e.preventDefault();
$.ajax({
method: "POST",
url: notif_update_url,
data: {id: notif.id}
}).done(function(response) {
window.location.href = notif.link;
});
});
notif_index++;
});
}
};
xhr.send();
}
listen(user_id, host, port, use_ssl = false) {
var d = new Date();
var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
useSSL: use_ssl,
timeout: 3,
invocationContext: this,
onSuccess: this.onConnect.bind(this)
}
this.mqtt.onMessageArrived = this.onMessage.bind(this);
this.mqtt.connect(options);
}
onConnect(icontext) {
console.log('notification mqtt connected');
var my = icontext.invocationContext;
// subscribe to general notifications
my.mqtt.subscribe('user/0/notification');
}
onMessage(msg) {
console.log('notification event received');
// we don't care about messasge, we update
this.clearAll();
this.loadAll();
}
getIcon(type_id) {
if (type_id in this.options['icons']) {
return this.options['icons'][type_id];
}
return this.options['default_icon'];
}
}

179
resq_settings/cmb/menu.yaml Normal file
View file

@ -0,0 +1,179 @@
main_menu:
- id: home
acl: dashboard.menu
label: Dashboard
icon: flaticon-line-graph
- id: user
acl: user.menu
label: User
icon: flaticon-users
- id: user_list
acl: user.list
label: Users
parent: user
- id: role_list
acl: role.list
label: Roles
parent: user
- id: apiuser
acl: apiuser.menu
label: API User
icon: flaticon-users
- id: api_user_list
acl: apiuser.list
label: API Users
parent: apiuser
- id: api_role_list
acl: apirole.list
label: API Roles
parent: apiuser
- id: logistics
acl: logistics.menu
label: Logistics
icon: fa fa-truck
- id: rider_list
acl: rider.list
label: Riders
parent: logistics
- id: service_charge_list
acl: service_charge.list
label: Service Charges
parent: logistics
- id: battery
acl: battery.menu
label: Battery
icon: fa fa-battery-3
- id: battery_list
acl: battery.list
label: Batteries
parent: battery
- id: bmfg_list
acl: bmfg.list
label: Manufacturers
parent: battery
- id: bmodel_list
acl: bmodel.list
label: Models
parent: battery
- id: bsize_list
acl: bsize.list
label: Sizes
parent: battery
- id: promo_list
acl: promo.list
label: Promos
parent: battery
- id: vehicle
acl: vehicle.menu
label: Vehicle
icon: fa fa-car
- id: vehicle_list
acl: vehicle.list
label: Vehicles
parent: vehicle
- id: vmfg_list
acl: vmfg.list
label: Manufacturers
parent: vehicle
- id: location
acl: location.menu
label: Location
icon: fa fa-home
- id: outlet_list
acl: outlet.menu
label: Outlet
parent: location
- id: hub_list
acl: hub.menu
label: Hub
parent: location
- id: geofence_list
acl: geofence.menu
label: Geofence
parent: location
- id: joborder
acl: joborder.menu
label: Job Order
icon: flaticon-calendar-3
- id: jo_onestep_form
acl: jo_onestep.form
label: One-step Process
parent: joborder
- id: jo_walkin_form
acl: jo_walkin.form
label: Walk-in
parent: joborder
- id: jo_open
acl: jo_open.list
label: Open
parent: joborder
- id: jo_all
acl: jo_all.list
label: View All
parent: joborder
- id: support
acl: support.menu
label: Customer Support
icon: flaticon-support
- id: customer_list
acl: customer.list
label: Customers
parent: support
- id: ticket_list
acl: ticket.list
label: Tickets
parent: support
- id: general_search
acl: general.search
label: Search
parent: support
- id: warranty_search
acl: warranty.search
label: Customer Battery Search
parent: support
- id: privacy_policy_list
acl: privacy_policy.list
label: Privacy Policy
parent: support
- id: warranty_list
acl: warranty.list
label: Warranty
parent: support
- id: warranty_upload
acl: warranty.upload
label: Warranty Upload
parent: support
- id: static_content_list
acl: static_content.list
label: Static Content
parent: support
- id: service
acl: service.menu
label: Other Services
icon: flaticon-squares
- id: service_list
acl: service.list
label: Services
parent: service
- id: partner
acl: partner.menu
label: Partners
icon: flaticon-network
- id: partner_list
acl: partner.list
label: Partners
parent: partner
- id: review_list
acl: review.list
label: Reviews
parent: partner

View file

@ -0,0 +1,27 @@
# text
title_login: Res-Q for CMB | Login
block_title: Res-Q for CMB
control_panel_sign_in: Sign-in to Control Panel
alt_image_logo_login: Res-Q for CMB
alt_image_dashboard: Res-Q for CMB
copyright: Res-Q for CMB
battery_size_tradein_brand: Trade-in Motolite
battery_size_tradein_premium: Trade-in Premium
battery_size_tradein_other: Trade-in Other
add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Res-Q for CMB Job Order
country_code_prefix: '+60'
delivery_instructions_label: 'CarFix Details'
# images
image_logo_login: /assets/images/black-text-logo-01.png
icon_login: /assets/images/battery-assist-bm-logo-32x32.png
icon_base_32x32: /assets/images/black-text-logo-01-32x32.png
icon_base_16x16: /assets/images/black-text-logo-01-16x16.png
image_dashboard: /assets/images/century_logo.png
image_jo_pdf: /public/assets/images/black-text-logo-01-115x115.png
# default point for maps
default_lat: 3.084216
default_long: 101.6129996
default_region: my

View file

@ -0,0 +1,246 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
map_default:
latitude: 14.6091
longitude: 121.0223
image_upload_directory: '%kernel.project_dir%/public/uploads'
jo_extra_upload_directory: '%kernel.project_dir%/public/uploads/jo_extra'
job_order_refresh_interval: 300000
api_acl_file: 'api_acl.yaml'
api_access_key: 'api_access_keys'
app_acl_file: 'acl.yaml'
app_access_key: 'access_keys'
cvu_brand_id: "%env(CVU_BRAND_ID)%"
country_code: "%env(COUNTRY_CODE)%"
api_version: "%env(API_VERSION)%"
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Menu,Access}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Menu\Generator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
Catalyst\AuthBundle\Service\ACLGenerator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
$acl_file: "%app_acl_file%"
Catalyst\AuthBundle\Service\ACLVoter:
arguments:
$user_class: "App\\Entity\\User"
tags: ['security.voter']
Catalyst\AuthBundle\Service\UserChecker:
App\Service\FileUploader:
arguments:
$target_dir: '%image_upload_directory%'
App\Service\MapTools:
arguments:
$em: "@doctrine.orm.entity_manager"
$gmaps_api_key: "%env(GMAPS_API_KEY)%"
$cust_dist_limit: "%env(CUST_DISTANCE_LIMIT)%"
App\Service\RisingTideGateway:
arguments:
$em: "@doctrine.orm.entity_manager"
$user: "%env(RT_USER)%"
$pass: "%env(RT_PASS)%"
$usage_type: "%env(RT_USAGE_TYPE)%"
$shortcode: "%env(RT_SHORTCODE)%"
$dr_url: "https://resqaws.jankstudio.com/sms/delivery_receipt"
App\Service\MQTTClient:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
$key: "mqtt_events"
App\Service\APNSClient:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
App\Service\RedisClientProvider:
arguments:
$scheme: "%env(REDIS_CLIENT_SCHEME)%"
$host: "%env(REDIS_CLIENT_HOST)%"
$port: "%env(REDIS_CLIENT_PORT)%"
$password: "%env(REDIS_CLIENT_PASSWORD)%"
App\Service\GeofenceTracker:
arguments:
$geofence_flag: "%env(GEOFENCE_ENABLE)%"
App\Service\WarrantyHandler:
arguments:
$em: "@doctrine.orm.entity_manager"
App\Command\SetCustomerPrivacyPolicyCommand:
arguments:
$policy_promo: "%env(POLICY_PROMO)%"
$policy_third_party: "%env(POLICY_THIRD_PARTY)%"
$policy_mobile: "%env(POLICY_MOBILE)%"
App\Command\CreateCustomerFromWarrantyCommand:
arguments:
$cvu_mfg_id: "%env(CVU_MFG_ID)%"
$cvu_brand_id: "%env(CVU_BRAND_ID)%"
# rider tracker service
App\Service\RiderTracker:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
Catalyst\APIBundle\Security\APIKeyUserProvider:
arguments:
$em: "@doctrine.orm.entity_manager"
Catalyst\APIBundle\Security\APIKeyAuthenticator:
arguments:
$em: "@doctrine.orm.entity_manager"
Catalyst\APIBundle\Command\UserCreateCommand:
arguments:
$em: "@doctrine.orm.entity_manager"
tags: ['console.command']
Catalyst\APIBundle\Command\TestCommand:
tags: ['console.command']
Catalyst\APIBundle\Command\TestAPICommand:
tags: ['console.command']
Catalyst\APIBundle\Access\Voter:
arguments:
$acl_gen: "@Catalyst\\APIBundle\\Access\\Generator"
$user_class: "Catalyst\\APIBundle\\Entity\\User"
tags: ['security.voter']
Catalyst\APIBundle\Access\Generator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
$acl_file: "%api_acl_file%"
Catalyst\MenuBundle\Menu\Generator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
Catalyst\MenuBundle\Listener\MenuAnnotationListener:
arguments:
$menu_name: "main_menu"
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
# invoice generator
App\Service\InvoiceGenerator\CMBInvoiceGenerator: ~
# invoice generator interface
App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\CMBInvoiceGenerator"
# job order generator
App\Service\JobOrderHandler\CMBJobOrderHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
#job order generator interface
App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler"
# customer generator
App\Service\CustomerHandler\CMBCustomerHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
# customer generator interface
App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\CMBCustomerHandler"
# rider assignment
App\Service\RiderAssignmentHandler\CMBRiderAssignmentHandler: ~
# rider assignment interface
App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\CMBRiderAssignmentHandler"
# rider API service
App\Service\RiderAPIHandler\CMBRiderAPIHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
$upload_dir: "%jo_extra_upload_directory%"
App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\CMBRiderAPIHandler"
# map manager
#App\Service\GISManager\Bing: ~
App\Service\GISManager\OpenStreet: ~
#App\Service\GISManager\Google: ~
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing"
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
App\EventListener\JobOrderActiveCacheListener:
arguments:
$jo_cache: "@App\\Service\\JobOrderCache"
$mqtt: "@App\\Service\\MQTTClient"
tags:
- name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postRemove'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%"
App\Service\RiderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
$status_key: "%env(STATUS_RIDER_KEY)%"
# inventory manager
App\Service\InventoryManager:
arguments:
$api_url: "%env(INVENTORY_API_URL)%"
$api_ocp_key: "%env(INVENTORY_API_OCP)%"
$api_auth_prefix: "%env(INVENTORY_API_AUTH_TOKEN_PREFIX)%"
$api_auth_token: "%env(INVENTORY_API_AUTH_TOKEN)%"
# API logging
App\EventSubscriber\LogSubscriber:
arguments:
$api_log_flag: "%env(API_LOGGING)%"

View file

@ -0,0 +1,186 @@
main_menu:
- id: home
acl: dashboard.menu
label: Dashboard
icon: flaticon-line-graph
- id: user
acl: user.menu
label: User
icon: flaticon-users
- id: user_list
acl: user.list
label: Users
parent: user
- id: role_list
acl: role.list
label: Roles
parent: user
- id: apiuser
acl: apiuser.menu
label: API User
icon: flaticon-users
- id: api_user_list
acl: apiuser.list
label: API Users
parent: apiuser
- id: api_role_list
acl: apirole.list
label: API Roles
parent: apiuser
- id: logistics
acl: logistics.menu
label: Logistics
icon: fa fa-truck
- id: rider_list
acl: rider.list
label: Riders
parent: logistics
- id: battery
acl: battery.menu
label: Battery
icon: fa fa-battery-3
- id: battery_list
acl: battery.list
label: Batteries
parent: battery
- id: bmfg_list
acl: bmfg.list
label: Manufacturers
parent: battery
- id: bmodel_list
acl: bmodel.list
label: Models
parent: battery
- id: bsize_list
acl: bsize.list
label: Sizes
parent: battery
- id: promo_list
acl: promo.list
label: Promos
parent: battery
- id: vehicle
acl: vehicle.menu
label: Vehicle
icon: fa fa-car
- id: vehicle_list
acl: vehicle.list
label: Vehicles
parent: vehicle
- id: vmfg_list
acl: vmfg.list
label: Manufacturers
parent: vehicle
- id: location
acl: location.menu
label: Location
icon: fa fa-home
- id: outlet_list
acl: outlet.menu
label: Outlet
parent: location
- id: hub_list
acl: hub.menu
label: Hub
parent: location
- id: geofence_list
acl: geofence.menu
label: Geofence
parent: location
- id: joborder
acl: joborder.menu
label: Job Order
icon: flaticon-calendar-3
- id: jo_in
acl: jo_in.list
label: Incoming
parent: joborder
- id: jo_proc
acl: jo_proc.list
label: Dispatch
parent: joborder
- id: jo_assign
acl: jo_assign.list
label: Rider Assignment
parent: joborder
- id: jo_fulfill
acl: jo_fulfill.list
label: Fulfillment
parent: joborder
- id: jo_open
acl: jo_open.list
label: Open
parent: joborder
- id: jo_all
acl: jo_all.list
label: View All
parent: joborder
- id: jo_hub.view
label: Hub View
parent: joborder
- id: support
acl: support.menu
label: Customer Support
icon: flaticon-support
- id: customer_list
acl: customer.list
label: Customers
parent: support
- id: ticket_list
acl: ticket.list
label: Tickets
parent: support
- id: general_search
acl: general.search
label: Search
parent: support
- id: warranty_search
acl: warranty.search
label: Customer Battery Search
parent: support
- id: privacy_policy_list
acl: privacy_policy.list
label: Privacy Policy
parent: support
- id: warranty_list
acl: warranty.list
label: Warranty
parent: support
- id: warranty_upload
acl: warranty.upload
label: Warranty Upload
parent: support
- id: static_content_list
acl: static_content.list
label: Static Content
parent: support
- id: service
acl: service.menu
label: Other Services
icon: flaticon-squares
- id: service_list
acl: service.list
label: Services
parent: service
- id: partner
acl: partner.menu
label: Partners
icon: flaticon-network
- id: partner_list
acl: partner.list
label: Partners
parent: partner
- id: review_list
acl: review.list
label: Reviews
parent: partner

View file

@ -0,0 +1,27 @@
# text
title_login: Motolite Res-Q | Login
block_title: Motolite Res-Q
control_panel_sign_in: Sign-in to Control Panel
alt_image_logo_login: Res-Q
alt_image_dashboard: Motolite
copyright: Motolite Res-Q
battery_size_tradein_brand: Trade-in Motolite
battery_size_tradein_premium: Trade-in Premium
battery_size_tradein_other: Trade-in Other
add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Motolite Res-Q Job Order
country_code_prefix: '+63'
delivery_instructions_label: Delivery Instructions
# images
image_logo_login: /assets/images/logo-resq.png
icon_login: /assets/demo/default/media/img/logo/favicon.ico
icon_base_32x32: /assets/images/favicon/favicon-32x32.png
icon_base_16x16: /assets/images/favicon/favicon-16x16.png
image_dashboard: /assets/images/logo-motolite.png
image_jo_pdf: /public/assets/images/logo-resq.png
# default point for maps
default_lat: 14.6091
default_long: 121.0223
default_region: ph

View file

@ -0,0 +1,244 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
map_default:
latitude: 14.6091
longitude: 121.0223
image_upload_directory: '%kernel.project_dir%/public/uploads'
job_order_refresh_interval: 300000
api_acl_file: 'api_acl.yaml'
api_access_key: 'api_access_keys'
app_acl_file: 'acl.yaml'
app_access_key: 'access_keys'
cvu_brand_id: "%env(CVU_BRAND_ID)%"
country_code: "%env(COUNTRY_CODE)%"
api_version: "%env(API_VERSION)%"
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Menu,Access}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Menu\Generator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
Catalyst\AuthBundle\Service\ACLGenerator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
$acl_file: "%app_acl_file%"
Catalyst\AuthBundle\Service\ACLVoter:
arguments:
$user_class: "App\\Entity\\User"
tags: ['security.voter']
Catalyst\AuthBundle\Service\UserChecker:
App\Service\FileUploader:
arguments:
$target_dir: '%image_upload_directory%'
App\Service\MapTools:
arguments:
$em: "@doctrine.orm.entity_manager"
$gmaps_api_key: "%env(GMAPS_API_KEY)%"
$cust_dist_limit: "%env(CUST_DISTANCE_LIMIT)%"
App\Service\RisingTideGateway:
arguments:
$em: "@doctrine.orm.entity_manager"
$user: "%env(RT_USER)%"
$pass: "%env(RT_PASS)%"
$usage_type: "%env(RT_USAGE_TYPE)%"
$shortcode: "%env(RT_SHORTCODE)%"
$dr_url: "https://resqaws.jankstudio.com/sms/delivery_receipt"
App\Service\MQTTClient:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
$key: "mqtt_events"
App\Service\APNSClient:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
App\Service\RedisClientProvider:
arguments:
$scheme: "%env(REDIS_CLIENT_SCHEME)%"
$host: "%env(REDIS_CLIENT_HOST)%"
$port: "%env(REDIS_CLIENT_PORT)%"
$password: "%env(REDIS_CLIENT_PASSWORD)%"
App\Service\GeofenceTracker:
arguments:
$geofence_flag: "%env(GEOFENCE_ENABLE)%"
App\Service\WarrantyHandler:
arguments:
$em: "@doctrine.orm.entity_manager"
App\Command\SetCustomerPrivacyPolicyCommand:
arguments:
$policy_promo: "%env(POLICY_PROMO)%"
$policy_third_party: "%env(POLICY_THIRD_PARTY)%"
$policy_mobile: "%env(POLICY_MOBILE)%"
App\Command\CreateCustomerFromWarrantyCommand:
arguments:
$cvu_mfg_id: "%env(CVU_MFG_ID)%"
$cvu_brand_id: "%env(CVU_BRAND_ID)%"
# rider tracker service
App\Service\RiderTracker:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
Catalyst\APIBundle\Security\APIKeyUserProvider:
arguments:
$em: "@doctrine.orm.entity_manager"
Catalyst\APIBundle\Security\APIKeyAuthenticator:
arguments:
$em: "@doctrine.orm.entity_manager"
Catalyst\APIBundle\Command\UserCreateCommand:
arguments:
$em: "@doctrine.orm.entity_manager"
tags: ['console.command']
Catalyst\APIBundle\Command\TestCommand:
tags: ['console.command']
Catalyst\APIBundle\Command\TestAPICommand:
tags: ['console.command']
Catalyst\APIBundle\Access\Voter:
arguments:
$acl_gen: "@Catalyst\\APIBundle\\Access\\Generator"
$user_class: "Catalyst\\APIBundle\\Entity\\User"
tags: ['security.voter']
Catalyst\APIBundle\Access\Generator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
$acl_file: "%api_acl_file%"
Catalyst\MenuBundle\Menu\Generator:
arguments:
$router: "@router.default"
$cache_dir: "%kernel.cache_dir%"
$config_dir: "%kernel.root_dir%/../config"
Catalyst\MenuBundle\Listener\MenuAnnotationListener:
arguments:
$menu_name: "main_menu"
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
# invoice generator
App\Service\InvoiceGenerator\ResqInvoiceGenerator: ~
# invoice generator interface
App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator"
# job order generator
App\Service\JobOrderHandler\ResqJobOrderHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
#job order generator interface
App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\ResqJobOrderHandler"
# customer generator
App\Service\CustomerHandler\ResqCustomerHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
# customer generator interface
App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\ResqCustomerHandler"
# rider assignment
App\Service\RiderAssignmentHandler\ResqRiderAssignmentHandler: ~
# rider assignment interface
App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\ResqRiderAssignmentHandler"
# rider API service
App\Service\RiderAPIHandler\ResqRiderAPIHandler:
arguments:
$country_code: "%env(COUNTRY_CODE)%"
App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\ResqRiderAPIHandler"
# map manager
#App\Service\GISManager\Bing: ~
App\Service\GISManager\OpenStreet: ~
#App\Service\GISManager\Google: ~
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing"
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
App\EventListener\JobOrderActiveCacheListener:
arguments:
$jo_cache: "@App\\Service\\JobOrderCache"
$mqtt: "@App\\Service\\MQTTClient"
tags:
- name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postRemove'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%"
App\Service\RiderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
$status_key: "%env(STATUS_RIDER_KEY)%"
# inventory manager
App\Service\InventoryManager:
arguments:
$api_url: "%env(INVENTORY_API_URL)%"
$api_ocp_key: "%env(INVENTORY_API_OCP)%"
$api_auth_prefix: "%env(INVENTORY_API_AUTH_TOKEN_PREFIX)%"
$api_auth_token: "%env(INVENTORY_API_AUTH_TOKEN)%"
# API logging
App\EventSubscriber\LogSubscriber:
arguments:
$api_log_flag: "%env(API_LOGGING)%"

View file

@ -0,0 +1,55 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Filesystem;
class ConfigureResqCommand extends Command
{
public function __construct()
{
parent::__construct();
}
protected function configure()
{
$this->setName('resq:configure')
->setDescription('Copies the configuration files, depending on environment.')
->setHelp('Copies the configuration files, depending on environment.')
->addArgument('dir_name', InputArgument::REQUIRED, 'Name of source subdirectory.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$dir_name = $input->getArgument('dir_name');
$filesystem = new Filesystem();
$current_dir = getcwd();
$source_dir = $current_dir . '/resq_settings/' . $dir_name . '/';
$config_dir = $current_dir . '/config/';
$translations_dir = $current_dir . '/translations/';
// copy services.yaml file
error_log('Copying services.yaml file...');
$filesystem->copy($source_dir . 'services.yaml', $config_dir . 'services.yaml', true);
// copy menu.yaml file
error_log('Copying menu.yaml file...');
$filesystem->copy($source_dir . 'menu.yaml', $config_dir . 'menu.yaml', true);
// copy messages.en.yaml file
error_log('Copying messages.en.yaml file...');
$filesystem->copy($source_dir . 'messages.en.yaml', $translations_dir . 'messages.en.yaml', true);
error_log('Done copying files.');
return 0;
}
}

View file

@ -0,0 +1,173 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use DateTime;
use DateInterval;
use App\Entity\JobOrder;
use App\Entity\Rider;
use App\Entity\CustomerVehicle;
use App\Entity\Invoice;
use App\Entity\InvoiceItem;
use App\Entity\Battery;
use App\Ramcar\CMBServiceType;
use App\Ramcar\TransactionOrigin;
use App\Ramcar\WarrantyClass;
use App\Ramcar\ModeOfPayment;
use App\Ramcar\JOStatus;
use App\Ramcar\InvoiceStatus;
class CreateJOTestDataCommand extends Command
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
parent::__construct();
}
protected function configure()
{
$this->setName('joborder:create_testdata')
->setDescription('Create JO test data, given a rider id.')
->setHelp('Create JO test data, given a rider id.')
->addArgument('rider_id', InputArgument::REQUIRED, 'Rider id.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$rider_id = $input->getArgument('rider_id');
$rider = $this->em->getRepository(Rider::class)->find($rider_id);
// get customer vehicles where plate number is not null
$cv_query = $this->em->createQuery('SELECT cv FROM App\Entity\CustomerVehicle cv WHERE cv.plate_number is not null');
$cv_results = $cv_query->getResult();
// get the first customer vehicle
$cv = current($cv_results);
// get batteries
$battery_results = $this->em->getRepository(Battery::class)->findAll();
// get the first battery
$battery = current($battery_results);
error_log($cv->getPlateNumber());
error_log($battery->getID());
if (empty($rider))
{
error_log('Rider not found.');
}
else
{
// get current date
$current_date = new DateTime();
// for this month JO
$current_year = $current_date->format('Y');
$current_month = $current_date->format('M');
// for last month JO
$date_interval = new DateInterval('P1M');
$last_month_date = $current_date->sub($date_interval);
$last_month_year = $last_month_date->format('Y');
$last_month_month = $last_month_date->format('M');
$time_schedule = $current_date->format('h:i A');
$this->createJobOrders($rider, $cv, $battery, $last_month_year, $last_month_month, $time_schedule);
$this->createJobOrders($rider, $cv, $battery, $current_year, $current_month, $time_schedule);
}
return 0;
}
protected function createJobOrders(Rider $rider, CustomerVehicle $cv, Battery $battery, $year, $month, $time)
{
// insert 15 JOs for last month
for ($i = 1; $i <= 15; $i++)
{
$jo = new JobOrder();
// set customer data
$jo->setCustomerVehicle($cv);
$jo->setCustomer($cv->getCustomer());
// set hub and rider data
$jo->setRider($rider);
$jo->setHub($rider->getHub());
// set JO details
$point = new Point(121.0223, 14.6091);
$s_type = CMBServiceType::BATTERY_REPLACEMENT_NEW;
$source = TransactionOrigin::CALL;
$advance_order = true;
$warranty_class = WarrantyClass::WTY_PRIVATE;
$status = JOStatus::FULFILLED;
$delivery_address = '#1234 Moogle Lane';
$mode_of_payment = ModeOfPayment::CASH;
// set invoice details
$invoice = new Invoice();
$invoice_item = new InvoiceItem();
// set invoice item details
$invoice_item->setBattery($battery)
->setTitle($battery->getModel()->getName() . ' ' . $battery->getSize()->getName())
->setQuantity(1)
->setPrice($battery->getSellingPrice())
->setInvoice($invoice);
$invoice->addItem($invoice_item);
$this->em->persist($invoice_item);
// set invoice details
$invoice->setTotalPrice($battery->getSellingPrice())
->setStatus(InvoiceStatus::DRAFT)
->setVATExclusivePrice($battery->getSellingPrice())
->setDiscount(0.0)
->setTradeIn(0.0)
->setVAT(0.00);
$this->em->persist($invoice);
$jo->setInvoice($invoice);
// for last month
$date_schedule_date = $i . ' ' . $month . ' ' . $year . ' ' . $time;
error_log('Adding JO with date schedule ' . $date_schedule_date);
$date_schedule = DateTime::createFromFormat("d M Y h:i A", $date_schedule_date);
$jo->setDateSchedule($date_schedule)
->setCoordinates($point)
->setAdvanceOrder($advance_order)
->setServiceType($s_type)
->setWarrantyClass($warranty_class)
->setSource($source)
->setStatus($status)
->setDeliveryAddress($delivery_address);
$this->em->persist($jo);
}
$this->em->flush();
}
}

View file

@ -70,8 +70,8 @@ class ImportCMBBatteryDataCommand extends Command
error_log('Processing battery csv file...');
while (($fields = fgetcsv($fh)) !== false)
{
// data starts at row 2
if ($row_num < 2)
// data starts at row 1
if ($row_num < 1)
{
$row_num++;
continue;
@ -90,22 +90,42 @@ class ImportCMBBatteryDataCommand extends Command
// [0] = battery manufacturer
// [1] = battery model
// [2] = battery size
// if only 2, get both
// [0] = battery manufacturer and battery model
// [1] = battery size
// if 4,
// [0] = battery manufacturer
// concatenate [1] and [2] for the battery model
// [1] = battery model
// [2] = extra info
// [3] = battery size
// OR
// [0] = battery manufacturer
// [1] = battery model
// [2] = battery size
// [3] = battery size --> this one would have ()
// if 5,
// [0] = battery manufacturer
// [1] = battery model
// [2] = extra info
// [3] = extra info
// [4] = battery size
$battery_manufacturer = '';
$battery_model = '';
$battery_size = '';
if (count($battery_info) == 3)
{
// sample: Century Marathoner 120-7L
// sample: Century Marathoner M42(60B20L)
$battery_manufacturer = trim($battery_info[0]);
$battery_model = trim($battery_info[1]);
$battery_size = trim($battery_info[2]);
// check for parenthesis in battery_info[2]
if (strpos($battery_info[2], '(') === false)
{
// no parenthesis found
$battery_size = trim($battery_info[2]);
}
else
{
$battery_size = trim(str_replace('(', ' (', $battery_info[2]));
}
}
if (count($battery_info) == 2)
{
@ -117,13 +137,32 @@ class ImportCMBBatteryDataCommand extends Command
if (count($battery_info) == 4)
{
// sample: Motolite Classic Wetcharged DIN100L
// sample: Century Excel NS60LS (60B24LS)
$battery_manufacturer = trim($battery_info[0]);
$battery_model = trim($battery_info[1]) . ' ' . trim($battery_info[2]);
$battery_size = trim($battery_info[3]);
$battery_model = trim($battery_info[1]);
// check for parenthesis in battery_info[3]
if (strpos($battery_info[3], '(') === false)
{
// no parenthesis found
$battery_size = trim($battery_info[3]);
}
else
{
// need to concatenate [2] and [3]
$battery_size = trim($battery_info[2]) . ' ' . trim($battery_info[3]);
}
}
if (count($battery_info) == 5)
{
// sample: Century Marathoner Max Wet NS40ZL
$battery_manufacturer = trim($battery_info[0]);
$battery_model = trim($battery_info[1]);
$battery_size = trim($battery_info[4]);
}
// check if battery size has ()
// if so, trim it to ignore the parenthesis and what's after (.
/*
$pos = stripos($battery_size, '(');
if ($pos == true)
{
@ -133,7 +172,7 @@ class ImportCMBBatteryDataCommand extends Command
else
{
$clean_size = $battery_size;
}
} */
//error_log('battery manufacturer ' . $battery_manufacturer);
//error_log('battery model ' . $battery_model);
@ -143,24 +182,24 @@ class ImportCMBBatteryDataCommand extends Command
// when we add to db for manufacturer, model, and size, we do not use the normalized versions
$normalized_manu = $this->normalizeName($battery_manufacturer);
$normalized_model = $this->normalizeName($battery_model);
$normalized_size = $this->normalizeName($clean_size);
$normalized_size = $this->normalizeName($battery_size);
// save battery manufacturer if not yet in system
if (!isset($this->bmanu_hash[$normalized_manu]))
{
$this->addBatteryManufacturer($battery_manufacturer);
$this->addBatteryManufacturer(strtoupper($battery_manufacturer));
}
// save battery model if not yet in system
if (!isset($this->bmodel_hash[$normalized_model]))
{
$this->addBatteryModel($battery_model);
$this->addBatteryModel(strtoupper($battery_model));
}
// save battery size if not yet in system
if (!isset($this->bsize_hash[$normalized_size]))
{
$this->addBatterySize($clean_size);
$this->addBatterySize(strtoupper($battery_size));
}
// save battery if not yet in system

View file

@ -0,0 +1,365 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
use App\Entity\Battery;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
class ImportCMBBatteryModelSizeCommand extends Command
{
// CENTURY MARATHONER BATTERY
const F_BATT_MARATHONER_SIZE = 4;
const F_BATT_MARATHONER_PRICE = 5;
const F_BATT_MARATHONER_TRADEIN_PRICE = 6;
const F_BATT_MARATHONER_DISCOUNT = 7;
// MOTOLITE CLASSIC BATTERY
const F_BATT_CLASSIC_SIZE = 8;
const F_BATT_CLASSIC_PRICE = 9;
const F_BATT_CLASSIC_TRADEIN_PRICE = 10;
const F_BATT_CLASSIC_DISCOUNT = 11;
// CENTURY EXCEL BATTERY
const F_BATT_EXCEL_SIZE = 12;
const F_BATT_EXCEL_PRICE = 13;
const F_BATT_EXCEL_TRADEIN_PRICE = 14;
const F_BATT_EXCEL_DISCOUNT = 15;
// CENTURY SDFC BATTERY
const F_BATT_SDFC_SIZE = 16;
const F_BATT_SDFC_PRICE = 17;
const F_BATT_SDFC_TRADEIN_PRICE = 18;
const F_BATT_SDFC_DISCOUNT = 19;
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
// array for battery manufacturer
protected $batt_manufacturers = [
'CENTURY',
'MOTOLITE',
];
// array for battery models
protected $batt_models = [
'EXCEL', // CENTURY
'SDFC', // CENTURY
'MARATHONER', // CENTURY
'CLASSIC', // MOTOLITE
];
public function __construct(EntityManagerInterface $om)
{
$this->em = $om;
// load existing battery data
$this->loadBatteryManufacturers();
$this->loadBatteryModels();
$this->loadBatterySizes();
$this->loadBatteries();
parent::__construct();
}
protected function configure()
{
$this->setName('cmbbatterymodelsize:import')
->setDescription('Retrieve from a CSV file battery information.')
->setHelp('Creates battery manufacturers, models, sizes based on data from imported CSV.')
->addArgument('input_file', InputArgument::REQUIRED, 'Path to the CSV file.')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to the output CSV file for entries not added.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// check if battery manufacturers have been created
if (count($this->bmanu_hash) == 0)
{
// create the manufacturers
$this->createBatteryManufacturerData();
// reload the hash
$this->loadBatteryManufacturers();
}
// check if battery models have been created
if (count($this->bmodel_hash) == 0)
{
// create the battery models
$this->createBatteryModelData();
// reload the hash
$this->loadBatteryModels();
}
// get the sizes, vehicles, battery prices from the csv file
$csv_file = $input->getArgument('input_file');
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could not be read.');
}
$output_file = $input->getArgument('output_file');
// attempt to open file
try
{
$out_fh = fopen($output_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $output_file . '" could not be read.');
}
// get entity manager
$em = $this->em;
$row_num = 0;
$not_added = [];
error_log('Processing battery model and size file...');
while (($fields = fgetcsv($fh)) !== false)
{
if ($row_num < 2)
{
$row_num++;
continue;
}
// get battery info from file
$marathoner_size = $this->normalizeName(trim($fields[self::F_BATT_MARATHONER_SIZE]));
$marathoner_price = trim($fields[self::F_BATT_MARATHONER_PRICE]);
$marathoner_tradein_price = trim($fields[self::F_BATT_MARATHONER_TRADEIN_PRICE]);
$classic_size = $this->normalizeName(trim($fields[self::F_BATT_CLASSIC_SIZE]));
$classic_price = trim($fields[self::F_BATT_CLASSIC_PRICE]);
$classic_tradein_price = trim($fields[self::F_BATT_CLASSIC_TRADEIN_PRICE]);
$excel_size = $this->normalizeName(trim($fields[self::F_BATT_EXCEL_SIZE]));
$excel_price = trim($fields[self::F_BATT_EXCEL_PRICE]);
$excel_tradein_price = trim($fields[self::F_BATT_EXCEL_TRADEIN_PRICE]);
$sdfc_size = $this->normalizeName(trim($fields[self::F_BATT_SDFC_SIZE]));
$sdfc_price = trim($fields[self::F_BATT_SDFC_PRICE]);
$sdfc_tradein_price = trim($fields[self::F_BATT_SDFC_TRADEIN_PRICE]);
// add the battery sizes
// check if size is empty or if price and tradein prices are N/A
if (!isset($this->bsize_hash[$marathoner_size]))
{
// ignore blank sizes
if ((strlen($marathoner_size) > 0) &&
(strlen($marathoner_price) > 0))
{
// non-numeric entries are ignored since these are N/A == they don't sell it
if ((is_numeric($marathoner_price)) &&
(is_numeric($marathoner_tradein_price)))
{
$this->addBatterySize($marathoner_size, $marathoner_price, $marathoner_tradein_price);
}
}
}
if (!isset($this->bsize_hash[$classic_size]))
{
if ((strlen($classic_size) > 0) &&
(strlen($classic_price) > 0))
{
// non-numeric entries are ignored since these are N/A == they don't sell it
if ((is_numeric($classic_price)) &&
(is_numeric($classic_tradein_price)))
{
$this->addBatterySize($classic_size, $classic_price, $classic_tradein_price);
}
}
}
if (!isset($this->bsize_hash[$excel_size]))
{
if ((strlen($excel_size) > 0) &&
(strlen($excel_price) > 0))
{
// non-numeric entries are ignored since these are N/A == they don't sell it
if ((is_numeric($excel_price)) &&
(is_numeric($excel_tradein_price)))
{
$this->addBatterySize($excel_size, $excel_price, $excel_tradein_price);
}
}
}
if (!isset($this->bsize_hash[$sdfc_size]))
{
if ((strlen($sdfc_size) > 0) &&
(strlen($sdfc_price) > 0))
{
// non-numeric entries are ignored since these are N/A == they don't sell it
if ((is_numeric($sdfc_price)) &&
(is_numeric($sdfc_tradein_price)))
{
$this->addBatterySize($sdfc_size, $sdfc_price, $sdfc_tradein_price);
}
}
}
$row_num++;
}
// output the battery sizes that were not added
if (count($not_added) > 0)
{
fputcsv($out_fh, [
'Battery Model',
'Battery Size',
'Price',
'Trade In Price',
'Reason',
]);
foreach($not_added as $row)
{
fputcsv($out_fh, $row);
}
}
fclose($out_fh);
}
protected function createBatteryManufacturerData()
{
foreach ($this->batt_manufacturers as $name)
{
$new_bmanu = new BatteryManufacturer();
$new_bmanu->setName($name);
$this->em->persist($new_bmanu);
}
$this->em->flush();
}
protected function createBatteryModelData()
{
foreach ($this->batt_models as $name)
{
$new_bmodel = new BatteryModel();
$new_bmodel->setName($name);
$this->em->persist($new_bmodel);
}
$this->em->flush();
}
protected function addBatterySize($size, $price, $tradein_price)
{
$new_bsize = new BatterySize();
$clean_size = strtoupper($size);
// check if size is M-42, if so, we need to change it to M42
if (strpos($clean_size, 'M-42') !== false)
{
$clean_size = strtoupper(str_replace('-', '', $size));
}
$new_bsize->setName(strtoupper($clean_size));
$new_bsize->setTIPriceMotolite($tradein_price);
$this->em->persist($new_bsize);
// add to hash
$this->bsize_hash[$size] = $new_bsize;
$this->em->flush();
}
protected function addInvalidEntry($model, $size, $price,
$tradein_price, $reason)
{
$entry = [
$model,
$size,
$price,
$tradein_price,
$reason,
];
return $entry;
}
protected function loadBatteryManufacturers()
{
$this->bmanu_hash = [];
$batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll();
foreach ($batt_manufacturers as $batt_manu)
{
$name = $this->normalizeName($batt_manu->getName());
$this->bmanu_hash[$name] = $batt_manu;
}
}
protected function loadBatteryModels()
{
$this->bmodel_hash = [];
$batt_models = $this->em->getRepository(BatteryModel::class)->findAll();
foreach ($batt_models as $batt_model)
{
$name = $this->normalizeName($batt_model->getName());
$this->bmodel_hash[$name] = $batt_model;
}
}
protected function loadBatterySizes()
{
$this->bsize_hash = [];
$batt_sizes = $this->em->getRepository(BatterySize::class)->findAll();
foreach ($batt_sizes as $batt_size)
{
$name = $this->normalizeName($batt_size->getName());
$this->bsize_hash[$name] = $batt_size;
}
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function normalizeName($name)
{
$normalized_key = trim(strtolower($name));
return $normalized_key;
}
}

View file

@ -0,0 +1,373 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
use App\Entity\Battery;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
class ImportCMBBatteryVehicleCompatibilityCommand extends Command
{
// field index in csv file
// vehicle info
const F_VEHICLE_MANUFACTURER = 1;
const F_VEHICLE_MAKE = 2;
const F_VEHICLE_YEAR = 3;
// CENTURY MARATHONER BATTERY
const F_BATT_MARATHONER_SIZE = 4;
const F_BATT_MARATHONER_PRICE = 5;
const F_BATT_MARATHONER_TRADEIN_PRICE = 6;
const F_BATT_MARATHONER_DISCOUNT = 7;
// MOTOLITE CLASSIC BATTERY
const F_BATT_CLASSIC_SIZE = 8;
const F_BATT_CLASSIC_PRICE = 9;
const F_BATT_CLASSIC_TRADEIN_PRICE = 10;
const F_BATT_CLASSIC_DISCOUNT = 11;
// CENTURY EXCEL BATTERY
const F_BATT_EXCEL_SIZE = 12;
const F_BATT_EXCEL_PRICE = 13;
const F_BATT_EXCEL_TRADEIN_PRICE = 14;
const F_BATT_EXCEL_DISCOUNT = 15;
// CENTURY SDFC BATTERY
const F_BATT_SDFC_SIZE = 16;
const F_BATT_SDFC_PRICE = 17;
const F_BATT_SDFC_TRADEIN_PRICE = 18;
const F_BATT_SDFC_DISCOUNT = 19;
// constants for battery manufacturer names
const STR_CENTURY = 'century';
const STR_MOTOLITE = 'motolite';
// constants for battery models
const STR_MARATHONER = 'marathoner';
const STR_CLASSIC = 'classic';
const STR_EXCEL = 'excel';
const STR_SDFC = 'sdfc';
// special case for battery size
const STR_M_42 = 'M-42';
const STR_M42 = 'M42';
// for the model year
const STR_PRESENT = 'Present';
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
protected $vmanu_hash;
protected $vmake_hash;
public function __construct(EntityManagerInterface $om)
{
$this->em = $om;
// load existing battery data
$this->loadBatteryManufacturers();
$this->loadBatteryModels();
$this->loadBatterySizes();
$this->loadBatteries();
// load existing vehicle data
$this->loadVehicleManufacturers();
$this->loadVehicleMakes();
parent::__construct();
}
protected function configure()
{
$this->setName('cmbbatteryvehiclecompatibility:import')
->setDescription('Retrieve from a CSV file vehicle and battery compatibility information.')
->setHelp('Creates vehicles and their compatible batteries based on data from imported CSV.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to output file for vehicles not added.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// get the sizes, vehicles, battery prices from the csv file
$csv_file = $input->getArgument('file');
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be read.');
}
$output_file = $input->getArgument('output_file');
// attempt to open file
try
{
$output_fh = fopen($output_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $output_file . '" could be read.');
}
// get entity manager
$em = $this->em;
$row_num = 0;
error_log('Processing vehicle and battery compatibility file...');
while (($fields = fgetcsv($fh)) !== false)
{
$comp_batteries = [];
if ($row_num < 2)
{
$row_num++;
continue;
}
// get vehicle info from file
$manufacturer = trim(strtolower($fields[self::F_VEHICLE_MANUFACTURER]));
$make = trim(strtolower($fields[self::F_VEHICLE_MAKE]));
$year = trim($fields[self::F_VEHICLE_YEAR]);
// get battery info from file
$marathoner_size = $this->normalizeName(trim($fields[self::F_BATT_MARATHONER_SIZE]));
$marathoner_price = trim($fields[self::F_BATT_MARATHONER_PRICE]);
$marathoner_tradein_price = trim($fields[self::F_BATT_MARATHONER_TRADEIN_PRICE]);
$classic_size = $this->normalizeName(trim($fields[self::F_BATT_CLASSIC_SIZE]));
$classic_price = trim($fields[self::F_BATT_CLASSIC_PRICE]);
$classic_tradein_price = trim($fields[self::F_BATT_CLASSIC_TRADEIN_PRICE]);
$excel_size = $this->normalizeName(trim($fields[self::F_BATT_EXCEL_SIZE]));
$excel_price = trim($fields[self::F_BATT_EXCEL_PRICE]);
$excel_tradein_price = trim($fields[self::F_BATT_EXCEL_TRADEIN_PRICE]);
$sdfc_size = $this->normalizeName(trim($fields[self::F_BATT_SDFC_SIZE]));
$sdfc_price = trim($fields[self::F_BATT_SDFC_PRICE]);
$sdfc_tradein_price = trim($fields[self::F_BATT_SDFC_TRADEIN_PRICE]);
// get the compatible batteries
// get marathoner battery
if (strlen($marathoner_size) > 0)
{
// check if battery in system
if (isset($this->batt_hash[self::STR_CENTURY][self::STR_MARATHONER][$marathoner_size]))
$comp_batteries[] = $this->batt_hash[self::STR_CENTURY][self::STR_MARATHONER][$marathoner_size];
}
// get classic battery
if (strlen($classic_size) > 0)
{
// check if battery in system
if (isset($this->batt_hash[self::STR_MOTOLITE][self::STR_CLASSIC][$classic_size]))
$comp_batteries[] = $this->batt_hash[self::STR_MOTOLITE][self::STR_CLASSIC][$classic_size];
}
// get excel battery
if (strlen($excel_size) > 0)
{
// check if battery in system
if (isset($this->batt_hash[self::STR_CENTURY][self::STR_EXCEL][$excel_size]))
$comp_batteries[] = $this->batt_hash[self::STR_CENTURY][self::STR_EXCEL][$excel_size];
}
// get sdfc battery
if (strlen($sdfc_size) > 0)
{
// check if battery in system
if (isset($this->batt_hash[self::STR_CENTURY][self::STR_SDFC][$sdfc_size]))
$comp_batteries[] = $this->batt_hash[self::STR_CENTURY][self::STR_SDFC][$sdfc_size];
}
// check if vehicle manufacturer has been added
if (!isset($this->vmanu_hash[$manufacturer]))
$this->addVehicleManufacturer($manufacturer);
// check if vehicle make has been added
if (!isset($this->vmake_hash[$manufacturer][$make]))
{
$this->addVehicleMake($manufacturer, $make, $year, $comp_batteries);
}
$row_num++;
}
$em->flush();
}
protected function addVehicleManufacturer($name)
{
// save to db
$vehicle_manufacturer = new VehicleManufacturer();
$vehicle_manufacturer->setName(strtoupper($name));
$this->em->persist($vehicle_manufacturer);
$this->em->flush();
// add to hash
$this->vmanu_hash[$name] = $vehicle_manufacturer;
}
protected function addVehicleMake($manufacturer, $make, $year, $batteries)
{
// save to db
$vehicle = new Vehicle();
$vmanu = $this->vmanu_hash[$manufacturer];
// parse year from and year to
$year_from = '';
$year_to = '';
if (!empty($year))
{
$model_years = explode('-', $year);
$year_from = $model_years[0];
if (!empty($year_to))
$year_to = $model_years[1];
// check if $year_to is the string "Present"
// if so, set to 0, for now
if ($year_to == self::STR_PRESENT)
$year_to = 0;
}
$vehicle->setManufacturer($vmanu)
->setMake(strtoupper($make))
->setModelYearFrom($year_from)
->setModelYearTo($year_to);
// add vehicle to battery
foreach ($batteries as $battery)
{
$battery->addVehicle($vehicle);
$this->em->persist($battery);
}
// add vehicle to manufacturer
$vmanu->addVehicle($vehicle);
$this->em->persist($vmanu);
$this->em->persist($vehicle);
$this->em->flush();
// add to hash
$this->vmake_hash[$manufacturer][$make] = $vehicle;
}
protected function loadBatteryManufacturers()
{
$this->bmanu_hash = [];
$batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll();
foreach ($batt_manufacturers as $batt_manu)
{
$name = $this->normalizeName($batt_manu->getName());
$this->bmanu_hash[$name] = $batt_manu;
}
}
protected function loadBatteryModels()
{
$this->bmodel_hash = [];
$batt_models = $this->em->getRepository(BatteryModel::class)->findAll();
foreach ($batt_models as $batt_model)
{
$name = $this->normalizeName($batt_model->getName());
$this->bmodel_hash[$name] = $batt_model;
}
}
protected function loadBatterySizes()
{
$this->bsize_hash = [];
$batt_sizes = $this->em->getRepository(BatterySize::class)->findAll();
foreach ($batt_sizes as $batt_size)
{
$name = $this->normalizeName($batt_size->getName());
$this->bsize_hash[$name] = $batt_size;
}
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function loadVehicleManufacturers()
{
$this->vmanu_hash = [];
$vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll();
foreach ($vmanus as $vmanu)
{
$name = $this->normalizeName($vmanu->getName());
$this->vmanu_hash[$name] = $vmanu;
}
}
protected function loadVehicleMakes()
{
$this->vmake_hash = [];
$vmakes = $this->em->getRepository(Vehicle::class)->findAll();
foreach ($vmakes as $vmake)
{
$manufacturer = $vmake->getManufacturer()->getName();
$make = $this->normalizeName($vmake->getMake());
$this->vmake_hash[$manufacturer][$make] = $vmake;
}
}
protected function normalizeName($name)
{
// check if name contains M-42. Need to convert to M42
if (strpos($name, self::STR_M_42) !== false)
{
// contains M-42
$changed_name = str_replace(self::STR_M_42, self::STR_M42, $name);
$normalized_key = strtolower($changed_name);
}
else
{
$normalized_key = trim(strtolower($name));
}
$normalized_key = trim(strtolower($name));
return $normalized_key;
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\CMBLegacyJobOrderRow;
class ImportCMBLegacyJobOrderCommand extends Command
{
// field index in csv file
const F_INDEX = 0;
const F_CREATED_DATE = 1;
const F_CASE_NO = 2;
const F_INSURER = 3;
const F_VEHICLE_NO = 4;
const F_CAR_MODEL = 5;
const F_NATURE_OF_CALL = 6;
const F_SERVICE_NEEDED = 7;
const F_LOCATION = 8;
const F_STATE = 9;
const F_DRIVER = 10;
const F_TRUCK = 11;
const F_WORKSHOP_ARRIVAL_TIME = 12;
const F_STATUS = 13;
const F_CUSTOMER_NAME = 14;
const F_CUSTOMER_PHONE_NO = 15;
const F_OUR_REFERENCE = 16;
const F_ODOMETER = 17;
const F_BATT_MODEL = 18;
const F_BATT_SIZE = 19;
const F_BATT_TRADE_IN = 20;
const F_REPLACED_BY = 21;
const F_REMARK = 22;
const F_SATISFACTION = 23;
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
parent::__construct();
}
public function configure()
{
$this->setName('cmblegacyjoborderdata:import')
->setDescription('Retrieve from a CSV file CarFix data.')
->setHelp('Creates legacy job order entries based on data from imported CSV.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('file');
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be read.');
}
// get entity manager
$em = $this->em;
$row_num = 0;
error_log('Processing CarFix data csv file...');
while (($fields = fgetcsv($fh)) !== false)
{
if ($row_num < 1)
{
$row_num++;
continue;
}
// get the information
$entry_num = trim($fields[self::F_INDEX]);
$date_create = trim($fields[self::F_CREATED_DATE]);
$case_number = trim($fields[self::F_CASE_NO]);
$insurer = trim($fields[self::F_INSURER]);
$vehicle_number = trim($fields[self::F_VEHICLE_NO]);
$car_model = trim($fields[self::F_CAR_MODEL]);
$nature_of_call = trim($fields[self::F_NATURE_OF_CALL]);
$service_needed = trim($fields[self::F_SERVICE_NEEDED]);
$location = trim($fields[self::F_LOCATION]);
$state = trim($fields[self::F_STATE]);
$driver = trim($fields[self::F_DRIVER]);
$truck = trim($fields[self::F_TRUCK]);
$workshop_arrival_time = trim($fields[self::F_WORKSHOP_ARRIVAL_TIME]);
$status = trim($fields[self::F_STATUS]);
$customer_name = trim($fields[self::F_CUSTOMER_NAME]);
$customer_mobile = trim($fields[self::F_CUSTOMER_PHONE_NO]);
$reference = trim($fields[self::F_OUR_REFERENCE]);
$odometer = trim($fields[self::F_ODOMETER]);
$batt_model = trim($fields[self::F_BATT_MODEL]);
$batt_size = trim($fields[self::F_BATT_SIZE]);
$trade_in = trim($fields[self::F_BATT_TRADE_IN]);
$replaced_by = trim($fields[self::F_REPLACED_BY]);
$remark = trim($fields[self::F_REMARK]);
$satisfaction = trim($fields[self::F_SATISFACTION]);
$data_entry = $this->processEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction);
$legacy_data = new CMBLegacyJobOrderRow();
$legacy_data->setData($data_entry);
$this->em->persist($legacy_data);
}
$this->em->flush();
$this->em->clear();
return 0;
}
protected function processEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction)
{
$data_entry = [
'entry_num' => $entry_num,
'created_date' => $date_create,
'case_number' => $case_number,
'insurer' => $insurer,
'vehicle_number' => $vehicle_number,
'car_model' => $car_model,
'nature_of_call' => $nature_of_call,
'service_needed' => $service_needed,
'location' => $location,
'state' => $state,
'driver' => $driver,
'truck' => $truck,
'workshop_arrival_time' => $workshop_arrival_time,
'status' => $status,
'customer_name' => $customer_name,
'customer_phone_number' => $customer_mobile,
'reference' => $reference,
'odometer' => $odometer,
'batt_model' => $batt_model,
'batt_size' => $batt_size,
'batt_trade_in' => $trade_in,
'replaced_by' => $replaced_by,
'remark' => $remark,
'satisfaction' => $satisfaction,
];
return $data_entry;
}
}

View file

@ -1,449 +0,0 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
use App\Entity\Battery;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
class ImportCMBVehicleCompatibilityCommand extends Command
{
// field index in csv file
const F_VEHICLE_MANUFACTURER = 1;
const F_VEHICLE_MAKE = 2;
const F_VEHICLE_YEAR = 3;
const F_BATT_SDFC = 4;
const F_BATT_ULTRAMAX = 5;
const F_BATT_MOTOLITE = 6;
const F_BATT_MARATHONER = 7;
const F_BATT_EXCEL = 8;
const STR_CENTURY = 'Century';
const STR_MOTOLITE = 'Motolite';
const STR_MARSHALL = 'Marshall';
const STR_SDFC = 'SDFC';
const STR_MARATHONER = 'Marathoner';
const STR_WETCHARGED = 'Classic WetCharged';
const STR_ULTRAMAX = 'ULTRAMAX';
const STR_EXCEL = 'Excel';
const STR_PRESENT = 'Present';
const STR_M_42 = 'M-42';
const STR_M42 = 'M42';
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
protected $vmanu_hash;
protected $vmake_hash;
public function __construct(EntityManagerInterface $om)
{
$this->em = $om;
// load existing battery data
$this->loadBatteryManufacturers();
$this->loadBatteryModels();
$this->loadBatterySizes();
$this->loadBatteries();
// load existing vehicle data
$this->loadVehicleManufacturers();
$this->loadVehicleMakes();
parent::__construct();
}
protected function configure()
{
$this->setName('cmbvehiclecompatibility:import')
->setDescription('Retrieve from a CSV file battery and vehicle information.')
->setHelp('Creates battery manufacturers, models, sizes, vehicle makes, and models based on data from imported CSV.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('file');
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be read.');
}
// get entity manager
$em = $this->em;
$row_num = 0;
error_log('Processing vehicle compatibility csv file...');
while (($fields = fgetcsv($fh)) !== false)
{
$comp_batteries = [];
if ($row_num < 2)
{
$row_num++;
continue;
}
// initialize size battery array for cases where the battery size has '/'
$sdfc_sizes = [];
$ultramax_sizes = [];
$motolite_sizes = [];
$marathoner_sizes = [];
$excel_sizes = [];
// battery info
$sdfc_size = trim($fields[self::F_BATT_SDFC]);
$ultramax_size = trim($fields[self::F_BATT_ULTRAMAX]);
$motolite_size = trim($fields[self::F_BATT_MOTOLITE]);
$marathoner_size = trim($fields[self::F_BATT_MARATHONER]);
$excel_size = trim($fields[self::F_BATT_EXCEL]);
// check the sizes for '/'
$pos = stripos($sdfc_size, '/');
if ($pos == false)
{
// no '/' in size
$sdfc_sizes[] = $this->normalizeName($sdfc_size);
}
else
{
// we have '/' in size so we have to explode
$sizes = explode('/', $sdfc_size);
foreach ($sizes as $size)
{
$sdfc_sizes[] = $this->normalizeName($size);
}
}
$pos = stripos($motolite_size, '/');
if ($pos == false)
{
// no '/' in size
$motolite_sizes[] = $this->normalizeName($motolite_size);
}
else
{
// we have '/' in size so we have to explode
$sizes = explode('/', $motolite_size);
foreach ($sizes as $size)
{
$motolite_sizes[] = $this->normalizeName($size);
}
}
$pos = stripos($marathoner_size, '/');
if ($pos == false)
{
// no '/' in size
$marathoner_sizes[] = $this->normalizeName($marathoner_size);
}
else
{
// we have '/' in size so we have to explode
$sizes = explode('/', $marathoner_size);
foreach ($sizes as $size)
{
$marathoner_sizes[] = $this->normalizeName($size);
}
}
$pos = stripos($ultramax_size, '/');
if ($pos == false)
{
// no '/' in size
$ultramax_sizes[] = $this->normalizeName($ultramax_size);
}
else
{
// we have '/' in size so we have to explode
$sizes = explode('/', $ultramax_size);
foreach ($sizes as $size)
{
$ultramax_sizes[] = $this->normalizeName($size);
}
}
$pos = stripos($excel_size, '/');
if ($pos == false)
{
// no '/' in size
$excel_sizes[] = $this->normalizeName($excel_size);
}
else
{
// we have '/' in size so we have to explode
$sizes = explode('/', $excel_size);
foreach ($sizes as $size)
{
$excel_sizes[] = $this->normalizeName($size);
}
}
// normalize the battery manufacturers and battery models
$norm_century = $this->normalizeName(self::STR_CENTURY);
$norm_sdfc = $this->normalizeName(self::STR_SDFC);
$norm_motolite = $this->normalizeName(self::STR_MOTOLITE);
$norm_wetcharged = $this->normalizeName(self::STR_WETCHARGED);
$norm_marathoner = $this->normalizeName(self::STR_MARATHONER);
$norm_ultramax = $this->normalizeName(self::STR_ULTRAMAX);
$norm_excel = $this->normalizeName(self::STR_EXCEL);
//foreach($sdfc_sizes as $size)
//{
// error_log('sdfc size ' . $size);
//}
//foreach($motolite_sizes as $size)
//{
// error_log('motolite size ' . $size);
//}
//foreach($marathoner_sizes as $size)
//{
// error_log('marathoner size ' . $size);
//}
// vehicle info
$manufacturer = trim($fields[self::F_VEHICLE_MANUFACTURER]);
$make = trim($fields[self::F_VEHICLE_MAKE]);
$year = trim($fields[self::F_VEHICLE_YEAR]);
// vehicle data
// check if vehicle manufacturer has been added
if (!isset($this->vmanu_hash[$manufacturer]))
$this->addVehicleManufacturer($manufacturer);
// check if vehicle make has been added
if (!isset($this->vmake_hash[$manufacturer][$make]))
{
foreach($sdfc_sizes as $size)
{
if (!(empty($size)))
{
if (isset($this->batt_hash[$norm_century][$norm_sdfc][$size]))
$comp_batteries[] = $this->batt_hash[$norm_century][$norm_sdfc][$size];
else
error_log('Not in the system: ' . $norm_century . ' ' . $norm_sdfc . ' ' . $size);
}
}
foreach($ultramax_sizes as $size)
{
if (!(empty($size)))
{
if (isset($this->batt_hash[$norm_ultramax][$norm_ultramax][$size]))
$comp_batteries[] = $this->batt_hash[$norm_ultramax][$norm_ultramax][$size];
else
error_log('Not in the system: ' . $norm_ultramax . ' ' . $norm_ultramax . ' ' . $size);
}
}
foreach($motolite_sizes as $size)
{
if (!(empty($size)))
{
if (isset($this->batt_hash[$norm_motolite][$norm_wetcharged][$size]))
$comp_batteries[] = $this->batt_hash[$norm_motolite][$norm_wetcharged][$size];
else
error_log('Not in the system: ' . $norm_motolite . ' ' . $norm_wetcharged . ' ' . $size);
}
}
foreach($marathoner_sizes as $size)
{
if (!(empty($size)))
{
if (isset($this->batt_hash[$norm_century][$norm_marathoner][$size]))
$comp_batteries[] = $this->batt_hash[$norm_century][$norm_marathoner][$size];
else
error_log('Not in the system: ' . $norm_century . ' ' . $norm_marathoner . ' ' . $size);
}
}
foreach($excel_sizes as $size)
{
if (!(empty($size)))
{
if (isset($this->batt_hash[$norm_excel][$norm_excel][$size]))
$comp_batteries[] = $this->batt_hash[$norm_excel][$norm_excel][$size];
else
error_log('Not in the system: ' . $norm_excel . ' ' . $norm_excel . ' ' . $size);
}
}
$this->addVehicleMake($manufacturer, $make, $year, $comp_batteries);
}
$row_num++;
}
return 0;
}
protected function addVehicleManufacturer($name)
{
// save to db
$vehicle_manufacturer = new VehicleManufacturer();
$vehicle_manufacturer->setName($name);
$this->em->persist($vehicle_manufacturer);
$this->em->flush();
// add to hash
$this->vmanu_hash[$name] = $vehicle_manufacturer;
}
protected function addVehicleMake($manufacturer, $make, $year, $batteries)
{
// save to db
$vehicle = new Vehicle();
$vmanu = $this->vmanu_hash[$manufacturer];
// parse year from and year to
$year_from = '';
$year_to = '';
if (!empty($year))
{
$model_years = explode('-', $year);
$year_from = $model_years[0];
if (!empty($year_to))
$year_to = $model_years[1];
// check if $year_to is the string "Present"
// if so, set to 0, for now
if ($year_to == self::STR_PRESENT)
$year_to = 0;
}
$vehicle->setManufacturer($vmanu)
->setMake($make)
->setModelYearFrom($year_from)
->setModelYearTo($year_to);
// add vehicle to battery
foreach ($batteries as $battery)
{
$battery->addVehicle($vehicle);
$this->em->persist($battery);
}
// add vehicle to manufacturer
$vmanu->addVehicle($vehicle);
$this->em->persist($vmanu);
$this->em->persist($vehicle);
$this->em->flush();
// add to hash
$this->vmake_hash[$manufacturer][$make] = $vehicle;
}
protected function loadBatteryManufacturers()
{
$this->bmanu_hash = [];
$batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll();
foreach ($batt_manufacturers as $batt_manu)
{
$name = $this->normalizeName($batt_manu->getName());
$this->bmanu_hash[$name] = $batt_manu;
}
}
protected function loadBatteryModels()
{
$this->bmodel_hash = [];
$batt_models = $this->em->getRepository(BatteryModel::class)->findAll();
foreach ($batt_models as $batt_model)
{
$name = $this->normalizeName($batt_model->getName());
$this->bmodel_hash[$name] = $batt_model;
}
}
protected function loadBatterySizes()
{
$this->bsize_hash = [];
$batt_sizes = $this->em->getRepository(BatterySize::class)->findAll();
foreach ($batt_sizes as $batt_size)
{
$name = $this->normalizeName($batt_size->getName());
$this->bsize_hash[$name] = $batt_size;
}
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function loadVehicleManufacturers()
{
$this->vmanu_hash = [];
$vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll();
foreach ($vmanus as $vmanu)
{
$name = $vmanu->getName();
$this->vmanu_hash[$name] = $vmanu;
}
}
protected function loadVehicleMakes()
{
$this->vmake_hash = [];
$vmakes = $this->em->getRepository(Vehicle::class)->findAll();
foreach ($vmakes as $vmake)
{
$manufacturer = $vmake->getManufacturer()->getName();
$make = $vmake->getMake();
$this->vmake_hash[$manufacturer][$make] = $vmake;
}
}
protected function normalizeName($name)
{
// check for M-42. Need to convert to M42
if (strcasecmp($name, self::STR_M_42) == 0)
{
$normalized_key = strtolower(self::STR_M42);
}
else
{
$normalized_key = trim(strtolower($name));
}
return $normalized_key;
}
}

View file

@ -0,0 +1,563 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\CMBLegacyJobOrderRow;
use App\Entity\CMBLegacyJobOrder;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
use App\Entity\Battery;
use App\Entity\Customer;
use App\Entity\CustomerVehicle;
use App\Ramcar\CMBServiceType;
use App\Ramcar\JOStatus;
use App\Ramcar\FuelType;
use App\Ramcar\VehicleStatusCondition;
use DateTime;
class MigrateCMBLegacyJobOrderCommand extends Command
{
/*
id = 'entry_num'
trans_date = 'created_date'
case_number = 'case_number'
insurer = 'insurer'
plate_number = 'vehicle_number'
car_brand and car_make = split the 'car_model'
nature_of_call = nature_of_call
trans_type = 'service_needed'
address = 'location'
address = 'state'
driver = 'driver'
truck = 'truck'
workshop_arrival_time => 'workshop_arrival_time'
status = 'status'
cust_name = 'customer_name'
cust_mobile = 'customer_phone_number'
reference = 'reference'
odometer = 'odometer'
batt_model = 'batt_model'
batt_size = 'batt_size'
is_trade_in = 'batt_trade_in'
replaced_by = 'replaced_by'
remarks = 'remark'
satisfaction => 'satisfaction'
*/
const STR_LAST_NAME = 'LEGACY';
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
protected $vmanu_hash;
protected $vmake_hash;
protected $cv_hash;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
// load existing battery data
$this->loadBatteryManufacturers();
$this->loadBatteryModels();
$this->loadBatterySizes();
$this->loadBatteries();
// load existing vehicle data
$this->loadVehicleManufacturers();
$this->loadVehicleMakes();
// load existing customer vehicle data
$this->loadCustomerVehicles();
parent::__construct();
}
public function configure()
{
$this->setName('cmblegacyjoborderdata:migrate')
->setDescription('Retrieve database cmb legacy job orders and insert into job order.')
->setHelp('Creates job orders based on data from imported CSV.')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to the output CSV file of the entries not added.');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('output_file');
// attempt to open file
try
{
$fh = fopen($csv_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be opened.');
}
// get all legacy job orders
$query = $this->em->createQuery('SELECT legacy_jo_row FROM App\Entity\CMBLegacyJobOrderRow legacy_jo_row');
$result = $query->iterate();
$invalid_entries = [];
$added_entries = 0;
$total_entries = 0;
$total_inv_entries = 0;
foreach ($result as $row)
{
$entry = $row[0];
$jo_entry = $entry->getData();
$total_entries++;
// get the entry information
$entry_num = $jo_entry['entry_num'];
$date_create = $jo_entry['created_date'];
$case_number = $jo_entry['case_number'];
$insurer = $jo_entry['insurer'];
$plate_number = $this->cleanPlateNumber($jo_entry['vehicle_number']);
$car_model = $this->normalizeName($jo_entry['car_model']);
$nature_of_call = $jo_entry['nature_of_call'];
$service_needed = $jo_entry['service_needed'];
$location = $jo_entry['location'];
$state = $jo_entry['state'];
$driver = $jo_entry['driver'];
$truck = $jo_entry['truck'];
$workshop_arrival_time = $jo_entry['workshop_arrival_time'];
$status = $jo_entry['status'];
$customer_name = $jo_entry['customer_name'];
$customer_mobile = $jo_entry['customer_phone_number'];
$reference = $jo_entry['reference'];
$odometer = $jo_entry['odometer'];
$batt_model = $this->normalizeName($jo_entry['batt_model']);
$batt_size = $this->normalizeName($jo_entry['batt_size']);
$batt_trade_in = $jo_entry['batt_trade_in'];
$replaced_by = $jo_entry['replaced_by'];
$remark = $jo_entry['remark'];
$satisfaction = $jo_entry['satisfaction'];
// check vehicle info if valid
$v_status = $this->processVehicleInfo($car_model);
if ($v_status != null)
{
//error_log($v_status . ' ' . $car_model);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, $v_status);
$total_inv_entries++;
// move to next entry
continue;
}
// check battery info if valid
$batt_status = $this->processBatteryInfo($batt_model, $batt_size);
if ($batt_status != null)
{
//error_log($batt_status . ' ' . $batt_model . ' ' . $batt_size);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, $batt_status);
$total_inv_entries++;
// move to next entry
continue;
}
// check if mobile phone number has letters or spaces or special characters
if (!(preg_match('/^\d+$/', $customer_mobile)))
{
// not a valid mobile number
//error_log('invalid number ' . $customer_mobile);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, 'Invalid mobile number');
$total_inv_entries++;
// move to next entry
continue;
}
// create job order
$cmb_legacy_jo = new CMBLegacyJobOrder();
// date_create
$created_date = DateTime::createFromFormat("d-m-Y H:i", $date_create);
$cmb_legacy_jo->setTransDate($created_date);
// parse vehicle info
// get vehicle from hash
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
// delivery address = location + state
$delivery_address = $location . ', ' . $state;
// check if trade in
$trade_in = false;
if (strtolower($batt_trade_in) == 'yes')
$trade_in = true;
$cmb_legacy_jo->setCaseNumber($case_number)
->setInsurer($insurer)
->setNatureOfCall($nature_of_call)
->setTransType($service_needed)
->setCarBrand(strtoupper($v_manufacturer))
->setCarMake(strtoupper($v_model))
->setCustName($customer_name)
->setCustMobile($customer_mobile)
->setAddress($delivery_address)
->setDriver($driver)
->setTruck($truck)
->setWorkshopArrivalTime($workshop_arrival_time)
->setPlateNumber($plate_number)
->setStatus($status)
->setReference($reference)
->setOdometer($odometer)
->setBatteryModel(strtoupper($batt_model))
->setBatterySize(strtoupper($batt_size))
->setIsTradeIn($trade_in)
->setReplacedBy($replaced_by)
->setSatisfaction($satisfaction);
// plate number == vehicle_number. Use as key to cv_hash
// check if plate number has been added
if (!isset($this->cv_hash[$plate_number]))
{
$cust = $this->addCustomer($customer_name, $customer_mobile);
$cv = $this->addCustomerVehicle($plate_number, $car_model, $cust);
}
$this->em->persist($cmb_legacy_jo);
$this->em->detach($row[0]);
$added_entries++;
}
$this->em->flush();
$this->em->clear();
// output the entries that were not added
if (count($invalid_entries) > 0)
{
fputcsv($fh, [
'Entry Num',
'Created Date',
'Case Number',
'Insurer',
'Vehicle Number',
'Car Model',
'Nature of Call',
'Service Needed',
'Location',
'State',
'Driver',
'Truck',
'Workshop Arrival Time',
'Status',
'Customer Name',
'Customer Phone Number',
'Reference',
'Odometer',
'Batt Model',
'Batt Size',
'Batt Trade In',
'Replaced By',
'Remark',
'Satisfaction',
'Reason',
]);
foreach($invalid_entries as $row)
{
fputcsv($fh, $row);
}
}
fclose($fh);
error_log('Processed ' . $total_entries . ' entries.');
error_log('Added ' . $added_entries . ' entries.');
error_log('Did not add ' . $total_inv_entries . ' entries.');
return 0;
}
protected function processVehicleInfo($car_model)
{
// vehicle manufacturer is the first entry
// vehicle model is the 2nd entry + whatever follows
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
//error_log($v_manufacturer . ' ' . $v_model);
// check if manufacturer is in hash
if (!isset($this->vmanu_hash[$v_manufacturer]))
{
//error_log($v_manufacturer . ' invalid.');
return 'Vehicle manufacturer not in system.';
}
// check if manufacturer and make is in hash
if (!isset($this->vmake_hash[$v_manufacturer][$v_model]))
{
//error_log($v_model . ' invalid.');
return 'Vehicle model not in system.';
}
// car model info valid
return null;
}
protected function processBatteryInfo($batt_model, $batt_size)
{
// check if battery model is in hash
if (!isset($this->bmodel_hash[$batt_model]))
return 'Battery model not in system.';
// check if battery size is in hash
if (!isset($this->bsize_hash[$batt_size]))
return 'Battery size not in system.';
// battery info valid
return null;
}
protected function addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, $v_status)
{
$inv_entry = [
'number' => $entry_num,
'created_date' => $date_create,
'case_number' => $case_number,
'insurer' => $insurer,
'vehicle_number' => $plate_number,
'car_model' => $car_model,
'nature_of_call' => $nature_of_call,
'service_needed' => $service_needed,
'location' => $location,
'state' => $state,
'driver' => $driver,
'truck' => $truck,
'workshop_arrival_time' => $workshop_arrival_time,
'status' => $status,
'customer_name' => $customer_name,
'customer_phone_number' => $customer_mobile,
'reference' => $reference,
'odometer' => $odometer,
'batt_model' => $batt_model,
'batt_size' => $batt_size,
'batt_trade_in' => $batt_trade_in,
'replaced_by' => $replaced_by,
'remark' => $remark,
'satisfaction' => $satisfaction,
'reason' => $v_status,
];
return $inv_entry;
}
protected function addCustomer($name, $mobile)
{
$new_cust = new Customer();
$new_cust->setFirstName($name)
->setLastName(self::STR_LAST_NAME)
->setPhoneMobile($mobile);
$this->em->persist($new_cust);
return $new_cust;
}
protected function addCustomerVehicle($plate_num, $car_model, $customer)
{
$new_cv = new CustomerVehicle();
// get vehicle from hash
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
$vehicle = $this->vmake_hash[$v_manufacturer][$v_model];
$new_cv->setCustomer($customer)
->setPlateNumber($plate_num)
->setStatusCondition(VehicleStatusCondition::BRAND_NEW)
->setModelYear('')
->setColor('')
->setFuelType(FuelType::GAS)
->setHasMotoliteBattery(true)
->setVehicle($vehicle);
$this->em->persist($new_cv);
// add customer vehicle to cv_hash
$this->cv_hash[$plate_num] = $new_cv;
return $new_cv;
}
protected function loadBatteryManufacturers()
{
$this->bmanu_hash = [];
$batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll();
foreach ($batt_manufacturers as $batt_manu)
{
$name = $this->normalizeName($batt_manu->getName());
$this->bmanu_hash[$name] = $batt_manu;
}
}
protected function loadBatteryModels()
{
$this->bmodel_hash = [];
$batt_models = $this->em->getRepository(BatteryModel::class)->findAll();
foreach ($batt_models as $batt_model)
{
$name = $this->normalizeName($batt_model->getName());
$this->bmodel_hash[$name] = $batt_model;
}
}
protected function loadBatterySizes()
{
$this->bsize_hash = [];
$batt_sizes = $this->em->getRepository(BatterySize::class)->findAll();
foreach ($batt_sizes as $batt_size)
{
$name = $this->normalizeName($batt_size->getName());
$this->bsize_hash[$name] = $batt_size;
}
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function loadVehicleManufacturers()
{
$this->vmanu_hash = [];
$vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll();
foreach ($vmanus as $vmanu)
{
$name = $this->normalizeName($vmanu->getName());
$this->vmanu_hash[$name] = $vmanu;
}
}
protected function loadVehicleMakes()
{
$this->vmake_hash = [];
$vmakes = $this->em->getRepository(Vehicle::class)->findAll();
foreach ($vmakes as $vmake)
{
$manufacturer = $this->normalizeName($vmake->getManufacturer()->getName());
$make = $this->normalizeName($vmake->getMake());
$this->vmake_hash[$manufacturer][$make] = $vmake;
}
}
protected function loadCustomerVehicles()
{
$this->cv_hash = [];
$cvs = $this->em->getRepository(CustomerVehicle::class)->findAll();
foreach ($cvs as $cv)
{
$plate_number = $this->cleanPlateNumber($cv->getPlateNumber());
$this->cv_hash[$plate_number] = $cv;
}
}
protected function normalizeName($name)
{
$normalized_key = trim(strtolower($name));
return $normalized_key;
}
protected function cleanPlateNumber($plate_number)
{
// remove spaces and make upper case
$clean_plate_number = strtoupper(str_replace(' ' , '', $plate_number));
return $clean_plate_number;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Customer;
class UpdateCMBMigratedCustomerCommand extends Command
{
// last name to set
const STR_LAST_NAME = 'LEGACY';
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
parent::__construct();
}
protected function configure()
{
$this->setName('cmbcustomer:updatecustomer')
->setDescription('Set customer last name.')
->setHelp('Set the customer last name. ');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
error_log('Updating customer last name...');
// get all customers
$cust_results = $this->em->getRepository(Customer::class)->findBy(['last_name' => '']);
foreach ($cust_results as $cust)
{
$cust->setLastName(self::STR_LAST_NAME);
}
$this->em->flush();
$this->em->clear();
return 0;
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\CustomerVehicle;
use App\Entity\CMBLegacyJobOrder;
use App\Entity\Battery;
class UpdateCMBMigratedCustomerVehicleBatteryCommand extends Command
{
// constants for battery manufacturer names
const STR_CENTURY = 'century';
const STR_MOTOLITE = 'motolite';
// constants for battery models
const STR_MARATHONER = 'marathoner';
const STR_CLASSIC = 'classic';
const STR_EXCEL = 'excel';
const STR_SDFC = 'sdfc';
protected $em;
protected $batt_hash;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
// load existing batteries
$this->loadBatteries();
parent::__construct();
}
protected function configure()
{
$this->setName('cmbcustomervehicle:updatebattery')
->setDescription('Update customer vehicle battery information.')
->setHelp('Updates the customer vehicle battery based on migrated data. ')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to the output CSV file of the entries not updated.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('output_file');
// attempt to open file
try
{
$fh = fopen($csv_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be opened.');
}
error_log('Updating customer vehicles...');
// get all customer vehicles
$cv_results = $this->em->getRepository(CustomerVehicle::class)->findAll();
$not_updated = [];
foreach ($cv_results as $cv)
{
$plate_number = $cv->getPlateNumber();
// find cmb legacy job order using plate number
$cmb_legacy_results = $this->em->getRepository(CMBLegacyJobOrder::class)->findBy(['plate_number' => $plate_number], ['trans_date' => 'DESC']);
if ($cmb_legacy_results != null)
{
// get the latest entry
$cmb_legacy = current($cmb_legacy_results);
// get battery model and size
$battery_model = $this->normalizeName($cmb_legacy->getBatteryModel());
$battery_size = $this->normalizeName($cmb_legacy->getBatterySize());
// figure out manufacturer using model
// century = marathoner, excel, sdfc
// motolite = classic
$battery_manu = self::STR_CENTURY;
if ($battery_model == self::STR_CLASSIC)
$battery_manu = self::STR_MOTOLITE;
// get battery
$cv_battery = null;
if (isset($this->batt_hash[$battery_manu][$battery_model][$battery_size]))
$cv_battery = $this->batt_hash[$battery_manu][$battery_model][$battery_size];
if ($cv_battery != null)
{
//error_log('found battery for ' . $plate_number . ' battery ' . $cv_battery->getModel()->getName() . ' ' . $cv_battery->getSize()->getName());
$cv->setCurrentBattery($cv_battery);
}
else
{
//error_log('no battery for ' . $battery_model . ' ' . $battery_size);
$not_updated[] = $this->addNotUpdatedEntry($cv, $battery_manu, $battery_model, $battery_size);
}
}
}
$this->em->flush();
$this->em->clear();
// output the entries that were not added
if (count($not_updated) > 0)
{
fputcsv($fh, [
'Customer Vehicle ID',
'Plate Number',
'Battery Manufacturer',
'Battery Model',
'Battery Size',
'Reason',
]);
foreach($not_updated as $row)
{
fputcsv($fh, $row);
}
}
return 0;
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function addNotUpdatedEntry($cv, $manu, $model, $size)
{
$entry = [
$cv->getID(),
$cv->getPlateNumber(),
strtoupper($manu),
strtoupper($model),
strtoupper($size),
'Battery not in system.',
];
return $entry;
}
protected function normalizeName($name)
{
$normalized_key = trim(strtolower($name));
return $normalized_key;
}
}

View file

@ -0,0 +1,317 @@
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Ramcar\NewAPIResult;
use App\Service\RiderAPIHandlerInterface;
// Rider API controller for CMB
class CMBRAPIController extends Controller
{
public function register(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->register($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function login(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->login($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function logout(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->logout($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function acceptJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->acceptJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function cancelJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->cancelJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function arrive(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->arrive($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function performJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->performJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function hubArrive(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->hubArrive($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function payment(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->payment($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function available(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->available($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getPromos(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getPromos($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getBatteries(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getBatteries($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function changeService(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->changeService($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getJobOrderHistory(Request $req, $period, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getJobOrderHistory($req, $period);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getAssignedJobOrders(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getAssignedJobOrders($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function setJobOrderInTransit(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->setJobOrderInTransit($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function generateInvoice(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->generateInvoice($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function goOnline(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->goOnline($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function goOffline(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->goOffline($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function startJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->startJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function completeJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->completeJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function setActiveJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->setActiveJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function rejectJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->rejectJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function setOdometer(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->setOdometer($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function uploadFinishPhotos(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->uploadFinishPhotos($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getStatus(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getStatus($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getOngoingJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getOngoingJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getPaymentMethods(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getPaymentMethods($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function getCancelReasons(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->getCancelReasons($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
public function verifyJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler)
{
$data = $rapi_handler->verifyJobOrder($req);
$res = $this->generateResultFromHandler($data);
return $res->getReturnResponse();
}
protected function generateResultFromHandler($data)
{
$res = new NewAPIResult();
if (isset($data['error']))
{
$message = $data['error'];
$title = $data['title'];
$res->setError(true)
->setErrorTitle($title)
->setErrorMessage($message);
}
else
{
$res->setData($data);
}
return $res;
}
}

View file

@ -346,7 +346,7 @@ class HubController extends Controller
$riders = [];
// TODO: remove this later when we don't get all available riders
$riders_limit = 5;
$riders_limit = 50;
$num_riders = 0;
foreach ($available_riders as $rider)

View file

@ -228,7 +228,7 @@ class JobOrderController extends Controller
}
/**
* @Menu(selected="jo_all")
* @Menu(selected="jo_/viewall")
*/
public function listAll(JobOrderHandlerInterface $jo_handler)
{
@ -284,13 +284,19 @@ class JobOrderController extends Controller
$rows[$key]['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $jo_id]);
// $rows[$key]['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $jo_id]);
$rows[$key]['meta']['edit_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $tier_params['edit_route']), ['id' => $jo_id]);
$rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]);
//$rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]);
$rows[$key]['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $jo_id]);
}
else if ($tier == 'behind_schedule')
{
$rows[$key]['meta']['edit_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $tier_params['edit_route']), ['id' => $jo_id]);
//$rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]);
}
else
{
// $rows[$key]['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $jo_id]);
$rows[$key]['meta']['update_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $tier_params['edit_route']), ['id' => $jo_id]);
$rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]);
$rows[$key]['meta']['edit_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $tier_params['edit_route']), ['id' => $jo_id]);
//$rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]);
$rows[$key]['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $jo_id]);
}
@ -764,7 +770,10 @@ class JobOrderController extends Controller
// use invoice object values in a json friendly array
$invoice = [
'discount' => number_format($iobj->getDiscount(), 2),
// TODO: CMB needs to have no decimal places for discount.
// Resq requires the two decimal places since discount is computed.
//'discount' => number_format($iobj->getDiscount(), 2),
'discount' => number_format($iobj->getDiscount(), 0),
'trade_in' => number_format($iobj->getTradeIn(), 2), // TODO: computations not done yet for this on invoice creator
'price' => number_format($iobj->getVATExclusivePrice(), 2),
'vat' => number_format($iobj->getVAT(), 2),
@ -867,14 +876,14 @@ class JobOrderController extends Controller
return $this->render($template, $params);
}
public function oneStepSubmit(Request $req, JobOrderHandlerInterface $jo_handler)
public function oneStepSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient)
{
$this->denyAccessUnlessGranted('jo_onestep.form', null, 'No access.');
// initialize error list
$error_array = [];
$id = -1;
$error_array = $jo_handler->processOneStepJobOrder($req, $id);
$error_array = $jo_handler->processOneStepJobOrder($req, $id, $mclient);
// check if any errors were found
if (!empty($error_array)) {
@ -913,12 +922,12 @@ class JobOrderController extends Controller
return $this->render($template, $params);
}
public function oneStepEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id)
public function oneStepEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id, MQTTClient $mclient)
{
$this->denyAccessUnlessGranted('jo_onestep.edit', null, 'No access.');
$error_array = [];
$error_array = $jo_handler->processOneStepJobOrder($req, $id);
$error_array = $jo_handler->processOneStepJobOrder($req, $id, $mclient);
// check if any errors were found
if (!empty($error_array)) {
@ -1065,6 +1074,157 @@ class JobOrderController extends Controller
]);
}
/**
* @Menu(selected="jo_advance_order_form")
*/
public function advanceOrderForm(EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler,
GISManagerInterface $gis)
{
$this->denyAccessUnlessGranted('jo_advance_order.form', null, 'No access.');
$params = $jo_handler->initializeAdvanceOrderForm();
$params['submit_url'] = $this->generateUrl('jo_advance_order_submit');
$params['return_url'] = $this->generateUrl('jo_advance_order_form');
$params['map_js_file'] = $gis->getJSJOFile();
$params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll();
$params['vmakes'] = $em->getRepository(Vehicle::class)->findAll();
$params['hubs'] = $em->getRepository(Hub::class)->findAll();
$template = $params['template'];
// response
return $this->render($template, $params);
}
public function advanceOrderSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient)
{
$this->denyAccessUnlessGranted('jo_advance_order.form', null, 'No access.');
// initialize error list
$error_array = [];
$id = -1;
$error_array = $jo_handler->processAdvanceOrderJobOrder($req, $id, $mclient);
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @Menu(selected="jo_advance_order_edit_form")
*/
public function advanceOrderEditForm($id, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler,
GISManagerInterface $gis, MapTools $map_tools)
{
$this->denyAccessUnlessGranted('jo_advance_order.edit', null, 'No access.');
$params = $jo_handler->initializeAdvanceOrderEditForm($id, $map_tools);
$params['submit_url'] = $this->generateUrl('jo_advance_order_edit_submit', ['id' => $id]);
$params['return_url'] = $this->generateUrl('jo_open');
$params['map_js_file'] = $gis->getJSJOFile();
$params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll();
$params['vmakes'] = $em->getRepository(Vehicle::class)->findAll();
$template = $params['template'];
// response
return $this->render($template, $params);
}
public function advanceOrderEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id, MQTTClient $mclient)
{
$this->denyAccessUnlessGranted('jo_advance_order.edit', null, 'No access.');
$error_array = [];
$error_array = $jo_handler->processAdvanceOrderJobOrder($req, $id, $mclient);
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @Menu(selected="jo_assign_advance_order")
*/
public function listAssignAdvanceOrder(JobOrderHandlerInterface $jo_handler)
{
$this->denyAccessUnlessGranted('jo_assign_advance_order.list', null, 'No access.');
$template = $jo_handler->getTwigTemplate('jo_list_assign_advanceorder');
$params = $jo_handler->getOtherParameters();
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
return $this->render($template, $params);
}
/**
* @Menu(selected="jo_assign_advance_order_form")
*/
public function assignAdvanceOrderForm($id, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler,
GISManagerInterface $gis, MapTools $map_tools)
{
$this->denyAccessUnlessGranted('jo_assign_advance_order.form', null, 'No access.');
$params = $jo_handler->initializeAssignAdvanceOrderForm($id, $map_tools);
$params['submit_url'] = $this->generateUrl('jo_assign_advance_order_submit', ['id' => $id]);
$params['return_url'] = $this->generateUrl('jo_assign_advance_order');
$params['map_js_file'] = $gis->getJSJOFile();
$params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll();
$params['vmakes'] = $em->getRepository(Vehicle::class)->findAll();
$params['hubs'] = $em->getRepository(Hub::class)->findAll();
$template = $params['template'];
// response
return $this->render($template, $params);
}
public function assignAdvanceOrderSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id, MQTTClient $mclient)
{
$this->denyAccessUnlessGranted('jo_assign_advance_order.form', null, 'No access.');
// initialize error list
$error_array = [];
$error_array = $jo_handler->processAssignAdvanceOrderJobOrder($req, $id, $mclient);
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @Menu(selected="jo_hub_view")
*/
@ -1209,6 +1369,21 @@ class JobOrderController extends Controller
}
/**
* @Menu(selected="jo_behind_schedule")
*/
public function listBehindSchedule(JobOrderHandlerInterface $jo_handler)
{
$this->denyAccessUnlessGranted('jo_behind_schedule.list', null, 'No access.');
$template = $jo_handler->getTwigTemplate('jo_behind_schedule');
$params = $jo_handler->getOtherParameters();
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
return $this->render($template, $params);
}
protected function autoAssignHubAndRider($jo, EntityManagerInterface $em,
MapTools $map_tools, InventoryManager $im)
{

View file

@ -0,0 +1,111 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Doctrine\ORM\EntityManagerInterface;
use Catalyst\MenuBundle\Annotation\Menu;
use DateTime;
use DateInterval;
use App\Entity\Notification;
class NotificationController extends AbstractController
{
// TODO: security
public function ajaxList(EntityManagerInterface $em)
{
/*
$date = new DateTime();
$date->sub(new DateInterval('PT10M'));
$notifs = [
[
'id' => 1,
'type' => 'jo_new',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Sample incoming job order',
'link' => '#',
], [
'id' => 2,
'type' => 'rider_accept',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Sample rider has accepted job order',
'link' => '#',
], [
'id' => 3,
'type' => 'jo_cancel',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Customer has cancelled job order.',
'link' => '#',
], [
'id' => 3,
'type' => 'rider_reject',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Rider has rejected job order. Job order needs to be reassigned.',
'link' => '#',
],
];
$sample_data = [
'count' => 4,
'notifications' => $notifs,
];
*/
$notifs = $em->getRepository(Notification::class)->findBy(['user_id' => 0]);
$notif_data = [];
$unread_count = 0;
foreach ($notifs as $notif)
{
if (!($notif->isRead()))
$unread_count++;
$notif_data[] = [
'id' => $notif->getID(),
'type' => 'jo_new',
'is_read' => $notif->isRead(),
'is_fresh' => $notif->isFresh(),
'date' => $notif->getDateCreate()->format('Y-m-d\TH:i:s.000P'),
'text' => $notif->getMessage(),
'link' => $notif->getURL(),
];
}
$sample_data = [
'count' => count($notif_data),
'unread_count' => $unread_count,
'notifications' => $notif_data,
];
$res = new JsonResponse($sample_data);
return $res;
}
// TODO: security
public function ajaxUpdate(EntityManagerInterface $em, Request $req)
{
$notif_id = $req->request->get('id');
error_log($notif_id);
$notif = $em->getRepository(Notification::class)->find($notif_id);
if ($notif != null)
{
// TODO: fresh is if unread and still within x hours
// but for now fresh and unread are both the same
$notif->setIsRead(true);
$notif->setIsFresh(false);
$em->persist($notif);
$em->flush();
}
$res = new JsonResponse();
return $res;
}
}

View file

@ -596,4 +596,38 @@ class RiderController extends Controller
return $this->redirecttoRoute('rider_update', ['id' => $rider->getID()]);
}
/**
* @ParamConverter("rider", class="App\Entity\Rider")
*/
public function ajaxAvailable(EntityManagerInterface $em, Rider $rider)
{
$jo = $rider->getRiderActiveJobOrder();
if ($jo == null || $jo->isClosed())
$avail = 'available';
else
$avail = 'unavailable';
$response = new Response(
$avail,
Response::HTTP_OK,
['content-type' => 'text/plain']
);
return $response;
}
/**
* @ParamConverter("rider", class="App\Entity\Rider")
*/
public function ajaxRiderName(EntityManagerInterface $em, Rider $rider)
{
$rider_name = '';
if ($rider != null)
$rider_name = $rider->getFullName();
return $this->json([
'rider_name' => $rider_name,
]);
}
}

View file

@ -0,0 +1,395 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="cmb_legacy_job_order")
*/
class CMBLegacyJobOrder
{
// legacy internal id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="datetime")
*/
protected $trans_date;
/**
* @ORM\Column(type="string", length=30, nullable=true)
*/
protected $case_number;
/**
* @ORM\Column(type="string", length=30, nullable=true)
*/
protected $insurer;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $plate_number;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $trans_type;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $car_brand;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $car_make;
/**
* @ORM\Column(type="string", length=30, nullable=true)
*/
protected $nature_of_call;
/**
* @ORM\Column(type="string", length=400, nullable=true)
*/
protected $address;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $driver;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $truck;
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $workshop_arrival_time;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $status;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
protected $cust_name;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $cust_mobile;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $reference;
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $odometer;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $batt_model;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $batt_size;
/**
* @ORM\Column(type="boolean", options={"default":false})
*/
protected $flag_trade_in;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $replaced_by;
/**
* @ORM\Column(type="string", length=4000, nullable=true)
*/
protected $remarks;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $satisfaction;
public function __construct()
{
$this->trans_date = new DateTime();
}
public function setID($id)
{
$this->id = $id;
return $this;
}
public function getID()
{
return $this->id;
}
public function setTransDate(DateTime $trans_date)
{
$this->trans_date = $trans_date;
return $this;
}
public function getTransDate()
{
return $this->trans_date;
}
public function setTransType($trans_type)
{
$this->trans_type = $trans_type;
return $this;
}
public function getTransType()
{
return $this->trans_type;
}
public function setCaseNumber($case_number)
{
$this->case_number = $case_number;
return $this;
}
public function getCaseNumber()
{
return $this->case_number;
}
public function setInsurer($insurer)
{
$this->insurer = $insurer;
return $this;
}
public function getInsurer()
{
return $this->insurer;
}
public function setNatureOfCall($nature_of_call)
{
$this->nature_of_call = $nature_of_call;
return $this;
}
public function getNatureOfCall()
{
return $this->nature_of_call;
}
public function setPlateNumber($plate_number)
{
$this->plate_number = $plate_number;
return $this;
}
public function getPlateNumber()
{
return $this->plate_number;
}
public function setCarBrand($car_brand)
{
$this->car_brand = $car_brand;
return $this;
}
public function getCarBrand()
{
return $this->car_brand;
}
public function setCarMake($car_make)
{
$this->car_make = $car_make;
return $this;
}
public function getCarMake()
{
return $this->car_make;
}
public function setAddress($address)
{
$this->address = $address;
return $this;
}
public function getAddress()
{
return $this->address;
}
public function setDriver($driver)
{
$this->driver = $driver;
return $this;
}
public function getDriver()
{
return $this->driver;
}
public function setTruck($truck)
{
$this->truck = $truck;
return $this;
}
public function getTruck()
{
return $this->truck;
}
public function setWorkshopArrivalTime($workshop_arrival_time)
{
$this->workshop_arrival_time = $workshop_arrival_time;
return $this;
}
public function getWorkshopArrivalTime()
{
return $this->workshop_arrival_time;
}
public function setStatus($status)
{
$this->status = $status;
return $this;
}
public function getStatus()
{
return $this->status;
}
public function setCustName($cust_name)
{
$this->cust_name = $cust_name;
return $this;
}
public function getCustName()
{
return $this->cust_name;
}
public function setCustMobile($cust_mobile)
{
$this->cust_mobile = $cust_mobile;
return $this;
}
public function getCustMobile()
{
return $this->cust_mobile;
}
public function setReference($reference)
{
$this->reference = $reference;
return $this;
}
public function getReference()
{
return $this->reference;
}
public function setOdometer($odometer)
{
$this->odometer = $odometer;
return $this;
}
public function getOdometer()
{
return $this->odometer;
}
public function setBatteryModel($batt_model)
{
$this->batt_model = $batt_model;
return $this;
}
public function getBatteryModel()
{
return $this->batt_model;
}
public function setBatterySize($batt_size)
{
$this->batt_size = $batt_size;
return $this;
}
public function getBatterySize()
{
return $this->batt_size;
}
public function setIsTradeIn($flag_trade_in = true)
{
$this->flag_trade_in = $flag_trade_in;
return $this;
}
public function isTradeIn()
{
return $this->flag_trade_in;
}
public function setReplacedBy($replaced_by)
{
$this->replaced_by = $replaced_by;
return $this;
}
public function getReplacedBy()
{
return $this->replaced_by;
}
public function setSatisfaction($satisfaction)
{
$this->satisfaction = $satisfaction;
return $this;
}
public function getSatisfaction()
{
return $this->satisfaction;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="cmb_legacy_job_order_row")
*/
class CMBLegacyJobOrderRow
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// data from csv file
/**
* @ORM\Column(type="json")
*/
protected $data;
public function __construct()
{
$this->data = [];
}
public function addData($id, $value)
{
$this->data[$id] = $value;
return $this;
}
public function setData(array $data_array)
{
$this->data = $data_array;
return $this;
}
public function getDataById($id)
{
// return null if we don't have it
if (!isset($this->data[$id]))
return null;
return $this->data[$id];
}
public function getData()
{
return $this->data;
}
}

View file

@ -34,7 +34,7 @@ class Customer
// first name
/**
* @ORM\Column(type="string", length=80)
* @ORM\Column(type="string", length=100)
* @Assert\NotBlank()
*/
protected $first_name;
@ -42,7 +42,6 @@ class Customer
// last name
/**
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $last_name;
@ -66,7 +65,7 @@ class Customer
// mobile phone
/**
* @ORM\Column(type="string", length=30)
* @ORM\Column(type="string", length=50)
*/
protected $phone_mobile;

140
src/Entity/JOExtra.php Normal file
View file

@ -0,0 +1,140 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity
* @ORM\Table(name="jo_extra")
*/
class JOExtra
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $image_1_filename;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $image_2_filename;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $image_3_filename;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $image_4_filename;
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $other_images;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $cust_signature;
public function __construct()
{
$this->other_images = new ArrayCollection();
}
public function getID()
{
return $this->id;
}
public function setImage1Filename($image_filename)
{
$this->image_1_filename = $image_filename;
return $this;
}
public function getImage1Filename()
{
return $this->image_1_filename;
}
public function setImage2Filename($image_filename)
{
$this->image_2_filename = $image_filename;
return $this;
}
public function getImage2Filename()
{
return $this->image_2_filename;
}
public function setImage3Filename($image_filename)
{
$this->image_3_filename = $image_filename;
return $this;
}
public function getImage3Filename()
{
return $this->image_3_filename;
}
public function setImage4Filename($image_filename)
{
$this->image_4_filename = $image_filename;
return $this;
}
public function getImage4Filename()
{
return $this->image_4_filename;
}
public function getOtherImages()
{
return $this->other_images;
}
public function setOtherImages(array $images)
{
$this->other_images = new ArrayCollection();
foreach ($images as $image_filename)
{
$this->other_images->add($image_filename);
}
return $this;
}
public function clearOtherImages()
{
$this->other_images = new ArrayCollection();
return $this;
}
public function setCustomerSignature($sig_file)
{
$this->cust_signature = $sig_file;
return $this;
}
public function getCustomerSignature()
{
return $this->cust_signature;
}
}

View file

@ -12,6 +12,7 @@ use DateTime;
use App\Ramcar\ModeOfPayment;
use App\Ramcar\JOStatus;
use App\Ramcar\ServiceType;
use App\Ramcar\CMBCancelReason;
/**
* @ORM\Entity
@ -334,6 +335,30 @@ class JobOrder
*/
protected $phone_mobile;
// link to JOExtra
/**
* @ORM\OneToOne(targetEntity="JOExtra")
*/
protected $jo_extra;
// date that the status last changed
/**
* @ORM\Column(type="datetime", nullable=true)
*/
protected $date_status_change;
// insurer/responsible party handling the onsite job
/**
* @ORM\Column(type="string", length=80, nullable=true)
*/
protected $responsible_party;
// rider plate number since rider can change vehicles
/**
* @ORM\Column(type="string", length=10, nullable=true)
*/
protected $rider_plate_num;
public function __construct()
{
$this->date_create = new DateTime();
@ -583,6 +608,7 @@ class JobOrder
{
// TODO: validate status
$this->status = $status;
$this->date_status_change = new DateTime();
return $this;
}
@ -596,6 +622,11 @@ class JobOrder
return JOStatus::getName($this->status);
}
public function getDateStatusChange()
{
return $this->date_status_change;
}
public function setDeliveryInstructions($delivery_instructions)
{
$this->delivery_instructions = $delivery_instructions;
@ -674,6 +705,15 @@ class JobOrder
return $this->cancel_reason;
}
public function getCancelReasonName()
{
$reason = CMBCancelReason::getName($this->cancel_reason);
if ($reason == 'Unknown')
$reason = $this->cancel_reason;
return $reason;
}
public function getTickets()
{
return $this->tickets;
@ -796,6 +836,11 @@ class JobOrder
$this->makeRiderAvailable();
}
public function perform()
{
$this->setStatus(JOStatus::PERFORMED);
}
public function fulfill()
{
$this->setStatus(JOStatus::FULFILLED)
@ -962,5 +1007,38 @@ class JobOrder
return $this->phone_mobile;
}
public function setJOExtra(JOExtra $jo_extra)
{
$this->jo_extra = $jo_extra;
return $this;
}
public function getJOExtra()
{
return $this->jo_extra;
}
public function setResponsibleParty($responsible_party)
{
$this->responsible_party = $responsible_party;
return $this;
}
public function getResponsibleParty()
{
return $this->responsible_party;
}
public function setRiderPlateNum($rider_plate_num)
{
$this->rider_plate_num = $rider_plate_num;
return $this;
}
public function getRiderPlateNum()
{
return $this->rider_plate_num;
}
}

148
src/Entity/Notification.php Normal file
View file

@ -0,0 +1,148 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="notification", indexes={@ORM\Index(columns={"user_id"})})
*/
class Notification
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// date / time created
/**
* @ORM\Column(type="datetime")
*/
protected $date_create;
// index by user for fast fetching
// user id (don't link, don't need to)
/**
* @ORM\Column(type="integer")
*/
protected $user_id;
// icon
/**
* @ORM\Column(type="string", length=25)
*/
protected $icon;
// message text
/**
* @ORM\Column(type="string", length=300)
*/
protected $message;
/**
* @ORM\Column(type="string", length=180)
*/
protected $url;
// has user read the notification
/**
* @ORM\Column(type="boolean")
*/
protected $flag_read;
// is this still a fresh notification for the user
/**
* @ORM\Column(type="boolean")
*/
protected $flag_fresh;
public function __construct()
{
$this->date_create = new DateTime();
$this->flag_read = false;
$this->flag_fresh = true;
}
public function getID()
{
return $this->id;
}
public function getDateCreate()
{
return $this->date_create;
}
public function setUserID($user_id)
{
$this->user_id = $user_id;
return $this;
}
public function getUserID()
{
return $this->user_id;
}
public function setIcon($icon)
{
$this->icon = $icon;
return $this;
}
public function getIcon()
{
return $this->icon;
}
public function setMessage($message)
{
$this->message = $message;
return $this;
}
public function getMessage()
{
return $this->message;
}
public function setURL($url)
{
$this->url = $url;
return $this;
}
public function getURL()
{
return $this->url;
}
public function setIsRead($bool = true)
{
$this->flag_read = $bool;
return $this;
}
public function isRead()
{
return $this->flag_read;
}
public function setIsFresh($bool = true)
{
$this->flag_fresh = $bool;
return $this;
}
public function isFresh()
{
return $this->flag_fresh;
}
}

View file

@ -347,6 +347,11 @@ class Rider
return $this->job_orders->matching($criteria)[0];
}
public function getRiderActiveJobOrder()
{
return $this->active_job_order;
}
public function getOpenJobOrders()
{
$active_status = [

View file

@ -0,0 +1,22 @@
<?php
namespace App\Ramcar;
class CMBCancelReason extends NameValue
{
const ACCIDENT = 'accident';
const BREAKDOWN = 'breakdown';
const FUEL_RUNOUT = 'fuel_runout';
const WRONG_BATTERY_SIZE = 'wrong_battery_size';
const CUSTOMER_CANCEL = 'customer_cancel';
const OTHERS = 'others';
const COLLECTION = [
'accident' => 'Accident',
'breakdown' => 'Breakdown',
'fuel_runout' => 'Fuel Runout',
'wrong_battery_size' => 'Wrong Battery Size',
'customer_cancel' => 'Cancelled by Customer',
'others' => 'Others'
];
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Ramcar;
class CMBJOEventType extends NameValue
{
const CREATE = 'create';
const HUB_ASSIGN = 'hub_assign';
const RIDER_ASSIGN = 'rider_assign';
const CANCEL = 'cancel';
const FULFILL = 'fulfill';
const OPEN_EDIT = 'open_edit';
const REQUEUE = 'requeue';
const RIDER_ACCEPT = 'accept';
const RIDER_IN_TRANSIT = 'rider_in_transit';
const RIDER_ARRIVE = 'arrive';
const RIDER_START = 'rider_start';
const RIDER_EDIT = 'rider_edit';
const PAID = 'paid';
const PERFORM = 'perform';
const RIDER_FINISH = 'finish';
const RIDER_UPLOAD_PHOTO = 'rider_upload_photo';
const COLLECTION = [
'create' => 'Created',
'hub_assign' => 'Assigned to Hub',
'rider_assign' => 'Assigned Rider',
'cancel' => 'Cancelled',
'fulfill' => 'Fulfilled',
'open_edit' => 'Open Edit',
'requeue' => 'Requeue',
'accept' => 'Rider Accept',
'rider_in_transit' => 'Rider in Transit',
'arrive' => 'Rider Arrive',
'rider_start' => 'Rider Start',
'rider_edit' => 'Rider Edit',
'paid' => 'Paid',
'perform' => 'Perform',
'finish' => 'Finish',
'rider_upload_photo' => 'Rider Upload Photo',
];
}

View file

@ -5,12 +5,20 @@ namespace App\Ramcar;
class CMBModeOfPayment extends NameValue
{
const CASH = 'cash';
const SHOPEE = 'shopee';
const LAZADA = 'lazada';
const CREDIT_CARD = 'credit_card';
const BANK_TRANSFER = 'bank_transfer';
const ONLINE_TRANSFER = 'online_transfer';
const E_WALLET = 'e_wallet';
const DEBIT_CARD = 'debit_card';
const COLLECTION = [
'cash' => 'Cash',
'shopee' => 'Shopee',
'lazada' => 'Lazada',
'credit_card' => 'Credit Card',
'bank_transfer' => 'Bank Transfer',
'online_transfer' => 'Online Transfer',
'e_wallet' => 'E-Wallet',
'debit_card' => 'Debit Card',
];
}

View file

@ -6,11 +6,13 @@ class CMBServiceType extends NameValue
{
const BATTERY_REPLACEMENT_NEW = 'battery_new';
const BATTERY_REPLACEMENT_WARRANTY = 'battery_warranty';
const WARRANTY_CLAIM = 'warranty_claim';
const JUMPSTART = 'jumpstart';
const COLLECTION = [
'battery_new' => 'Battery Sales',
'battery_warranty' => 'Under Warranty',
'battery_warranty' => 'Warranty Replacement',
'warranty_claim' => 'Warranty Claim',
'jumpstart' => 'Jumpstart',
];
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Ramcar;
class CMBTransactionOrigin extends NameValue
{
const CALL = 'call';
const ONLINE = 'online';
const FACEBOOK = 'facebook';
const VIP = 'vip';
const MOBILE_APP = 'mobile_app';
const WALK_IN = 'walk_in';
const WHATSAPP = 'whatsapp';
// TODO: for now, resq also gets the walk-in option
const COLLECTION = [
'call' => 'Hotline',
'online' => 'Online',
'facebook' => 'Facebook',
'vip' => 'VIP',
'mobile_app' => 'Mobile App',
'walk_in' => 'Walk-in',
'whatsapp' => 'Whatsapp',
];
}

View file

@ -4,12 +4,14 @@ namespace App\Ramcar;
class CMBWarrantyClass extends NameValue
{
const WTY_PASSENGER = 'passenger';
const WTY_COMMERCIAL = 'commercial';
const WTY_PASSENGER = 'passenger';
const WTY_COMMERCIAL = 'commercial';
const WTY_GRAB = 'grab';
const COLLECTION = [
'passenger' => 'Passenger',
'commercial' => 'Commercial',
'grab' => 'Grab',
];
}

View file

@ -4,13 +4,15 @@ namespace App\Ramcar;
class JOStatus extends NameValue
{
const PENDING = 'pending'; // NOTE: JO has no hub assigned
const RIDER_ASSIGN = 'rider_assign'; // NOTE: JO has hub assigned but no rider assigned
const ASSIGNED = 'assigned'; // NOTE: JO has hub and rider assigned
const IN_TRANSIT = 'in_transit'; // NOTE: JO's rider is on his way
const IN_PROGRESS = 'in_progress'; // NOTE: JO fulfillment in progress
const CANCELLED = 'cancelled'; // NOTE: JO is cancelled
const FULFILLED = 'fulfilled'; // NOTE: JO is fulfilled
const PENDING = 'pending'; // JO has no hub assigned
const RIDER_ASSIGN = 'rider_assign'; // JO has hub assigned but no rider assigned
const ASSIGNED = 'assigned'; // JO has hub and rider assigned
const IN_TRANSIT = 'in_transit'; // Rider is on his way
const IN_PROGRESS = 'in_progress'; // Rider has arrived at customer's location and fulfillment is in progress
const PERFORMED = 'performed'; // Rider has finished performing JO task / service
const PAID = 'paid'; // Rider has finished collecting payment for JO
const CANCELLED = 'cancelled'; // JO is cancelled
const FULFILLED = 'fulfilled'; // JO is fulfilled
const COLLECTION = [
'pending' => 'For Dispatch',
@ -18,6 +20,8 @@ class JOStatus extends NameValue
'assigned' => 'Assigned',
'in_transit' => 'In Transit',
'in_progress' => 'In Progress',
'performed' => 'Service Performed',
'paid' => 'Customer Paid',
'cancelled' => 'Cancelled',
'fulfilled' => 'Completed',
];

View file

@ -0,0 +1,92 @@
<?php
namespace App\Ramcar;
use Symfony\Component\HttpFoundation\Response;
class NewAPIResult
{
protected $err_title;
protected $err_flag;
protected $err_message;
protected $data;
public function __construct()
{
$this->err_title = '';
$this->err_flag = false;
$this->err_message = '';
$this->data = [];
}
public function setError($flag = true)
{
$this->err_flag = $flag;
return $this;
}
public function isError()
{
return $this->err_flag;
}
public function setErrorMessage($message)
{
$this->err_message = $message;
return $this;
}
public function getErrorMessage()
{
return $this->err_message;
}
public function setData($data)
{
$this->data = $data;
return $this;
}
public function getData()
{
return $this->data;
}
public function setErrorTitle($title)
{
$this->err_title = $title;
return $this;
}
public function getErrorTitle()
{
return $this->err_title;
}
public function getReturnResponse()
{
if ($this->isError())
$status = 'error';
else
$status = 'success';
if (count($this->data) == 0)
$this->data = new \stdClass();
$return_data = [
'error' => [
'title' => $this->err_title,
'status' => $status,
'message' => $this->err_message
],
'data' => $this->data
];
// $json_data = json_encode($return_data, JSON_NUMERIC_CHECK);
$json_data = json_encode($return_data);
$json = new Response($json_data);
return $json;
}
}

View file

@ -189,7 +189,7 @@ class CMBCustomerHandler implements CustomerHandlerInterface
$cust_vehicle = new CustomerVehicle();
$cust_vehicle->setName($vehicle->name)
->setVehicle($vobj)
->setPlateNumber($vehicle->plate_number)
->setPlateNumber(trim($vehicle->plate_number))
->setModelYear($vehicle->model_year)
->setColor('')
->setStatusCondition('')
@ -416,8 +416,14 @@ class CMBCustomerHandler implements CustomerHandlerInterface
// add filters to count query
if (!empty($term)) {
$tquery->where('q.plate_number like :search')
->setParameter('search', $term . '%');
//$tquery->where('q.plate_number like :search')
// ->setParameter('search', $term . '%');
// TODO: this is really slow. Need to optimize
$tquery->innerJoin('q.customer', 'c')
->where('q.plate_number like :search')
->orWhere('c.phone_mobile = :number')
->setParameter('search', $term . '%')
->setParameter('number', $term);
/*
$tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1')
->setParameter('search', $term . '*');
@ -448,8 +454,14 @@ class CMBCustomerHandler implements CustomerHandlerInterface
// add filters if needed
if (!empty($term)) {
$query->where('q.plate_number like :search')
->setParameter('search', $term . '%');
// TODO: this is really slow. Need to optimize
$query->innerJoin('q.customer', 'cust')
->where('q.plate_number like :search')
->orWhere('cust.phone_mobile = :number')
->setParameter('search', $term . '%')
->setParameter('number', $term);
//$query->where('q.plate_number like :search')
// ->setParameter('search', $term . '%');
/*
$query->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1')
->setParameter('search', $term . '*');
@ -594,7 +606,7 @@ class CMBCustomerHandler implements CustomerHandlerInterface
protected function generateYearOptions()
{
$start_year = 1950;
return range($start_year, date("Y") + 1);
return range($start_year, date("Y"));
}

View file

@ -86,6 +86,10 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface
case CMBServiceType::BATTERY_REPLACEMENT_WARRANTY:
$this->processWarranty($total, $criteria, $invoice);
break;
case CMBServiceType::WARRANTY_CLAIM:
// TODO: this will change once we confirm what needs to be computed
$this->processOtherServices($total, $invoice, $stype);
break;
//case ServiceType::POST_RECHARGED:
// $this->processRecharge($total, $invoice);
// break;
@ -270,7 +274,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface
}
// check if discount is greater than 50 or negative number
if (($discount > 50) || ($discount < 0))
if (($discount > 60) || ($discount < 0))
return 'Invalid discount specified';
$criteria->setDiscount($discount);
@ -363,9 +367,10 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface
if (!isset($con_batts[$batt_id]))
$con_batts[$batt->getID()] = [
'batt' => $batt,
'qty' => 0
'qty' => $qty
];
$con_batts[$batt_id]['qty']++;
else
$con_batts[$batt_id]['qty'] += $qty;
// no trade-in

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@ interface JobOrderHandlerInterface
public function generateJobOrder(Request $req, int $id);
// process one step job order
public function processOneStepJobOrder(Request $req, int $id);
//public function processOneStepJobOrder(Request $req, int $id, MQTTClient $mclient);
// dispatch job order
public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient);

View file

@ -0,0 +1,48 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RedisClientProvider;
use App\Entity\Notification;
class NotificationManager
{
protected $redis;
protected $redis_mqtt_key;
protected $em;
const NOTIF_KEY = 'user/{user_id}/notification';
public function __construct(RedisClientProvider $redis_prov, EntityManagerInterface $em, $redis_mqtt_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->redis_mqtt_key = $redis_mqtt_key;
$this->em = $em;
}
// set user_id to 0 for all
public function sendNotification($user_id, $msg, $url)
{
// send mqtt
$chan = $this->getChannel($user_id);
$data = $chan . '|' . $msg;
$this->redis->lpush($this->redis_mqtt_key, $data);
// create notif
$notif = new Notification();
$notif->setUserID($user_id)
->setIcon('')
->setMessage($msg)
->setURL($url);
// save to db
$this->em->persist($notif);
$this->em->flush();
}
protected function getChannel($user_id)
{
return str_replace('{user_id}', $user_id, self::NOTIF_KEY );
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,8 @@
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RedisClientProvider;
use App\Entity\Rider;
@ -10,12 +12,14 @@ class RiderCache
protected $redis;
protected $loc_key;
protected $status_key;
protected $em;
public function __construct(RedisClientProvider $redis_prov, $loc_key, $status_key)
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis_prov, $loc_key, $status_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->loc_key = $loc_key;
$this->status_key = $status_key;
$this->em = $em;
}
public function addActiveRider($id, $lat, $lng)
@ -46,10 +50,15 @@ class RiderCache
$lng = $data[1][0];
$lat = $data[1][1];
$locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
// get rider details so we can check for availability
$rider = $this->getRiderDetails($id);
if ($rider != null)
{
$locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
}
}
// error_log(print_r($all_riders, true));
@ -73,4 +82,17 @@ class RiderCache
{
$this->redis->hincrby($this->status_key, $id, -1);
}
protected function getRiderDetails($id)
{
$rider = $this->em->getRepository(Rider::class)->find($id);
if ($rider == null)
return null;
// return only if available
if ($rider->isAvailable())
return $rider;
return null;
}
}

View file

@ -0,0 +1,307 @@
<?php
namespace App\Service\WarrantyHandler;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\WarrantyHandlerInterface;
use App\Entity\Warranty;
use App\Entity\Battery;
use App\Entity\BatterySize;
use App\Entity\SAPBattery;
use App\Entity\BatteryModel;
use App\Entity\CustomerVehicle;
use App\Ramcar\CMBWarrantyClass;
use DateTime;
use DateInterval;
class CMBWarrantyHandler implements WarrantyHandlerInterface
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number,
$batt_list, DateTime $date_purchase, $warranty_class)
{
// new warranty
$warranty = new Warranty();
foreach ($batt_list as $battery)
{
// get the battery model and battery size
$model_id = $battery->getModel()->getID();
$size_id = $battery->getSize()->getID();
$bty_model = $this->em->getRepository(BatteryModel::class)->find($model_id);
$bty_size = $this->em->getRepository(BatterySize::class)->find($size_id);
if ($bty_model != null)
{
$warranty->setBatteryModel($bty_model);
}
if ($bty_size != null)
{
$warranty->setBatterySize($bty_size);
}
}
// compute expiry date
$date_expire = null;
if ((!empty($warranty_class)) &&
(count($batt_list) != 0))
{
$period = $this->getWarrantyPeriod($batt_list, $warranty_class);
$date_expire = $this->computeDateExpire($date_purchase, $period);
$warranty->setDateExpire($date_expire);
}
// set and save values
if (trim($serial) == '')
$warranty->setSerial(null);
else
$warranty->setSerial($serial);
$warranty->setPlateNumber($plate_number)
->setFirstName($first_name)
->setLastName($last_name)
->setMobileNumber($mobile_number)
->setDatePurchase($date_purchase)
->setWarrantyClass($warranty_class);
$this->em->persist($warranty);
$this->em->flush();
// update customer vehicle with warranty info
$this->updateCustomerVehicle($serial, $batt_list, $plate_number, $date_expire);
$this->em->clear();
}
public function updateCustomerVehicle($serial, $batteries, $plate_number, $date_expire)
{
// find customer vehicle using plate number
// error_log('Finding customer vehicle with plate number ' . $plate_number);
$cv_q = $this->em->createQuery('select count(cv) from App\Entity\CustomerVehicle cv where cv.plate_number = :plate_number')
->setParameter('plate_number', $plate_number);
$cv_result = $cv_q->getSingleScalarResult();
$battery_id = null;
if ($cv_result != 0)
{
if (!empty($batteries))
{
// set current battery to the first battery in list.
// there are cases where multiple batteries linked to an SAP code.
$battery = $batteries[0];
$battery_id = $battery->getID();
}
//error_log('Serial/Warranty Code = ' . $serial);
$q = $this->em->createQuery('update App\Entity\CustomerVehicle cv
set cv.curr_battery = :batt_id,
cv.warranty_code = :serial,
cv.warranty_expiration = :expiry_date
where cv.plate_number = :plate_number')
->setParameters([
'batt_id' => $battery_id,
'serial' => $serial,
'expiry_date' => $date_expire,
'plate_number' => $plate_number]);
$q->execute();
}
}
public function updateWarranty(Warranty $warr, $first_name, $last_name, $mobile_number, $batt_list, DateTime $date_purchase)
{
// TODO: add serial and plate number to update
// TODO: check if data from existing warranty matches the new data
// check if details are complete
if (empty($warr->getFirstName()))
{
if (!empty($first_name))
{
$warr->setFirstName($first_name);
}
}
if (empty($warr->getLastName()))
{
if (!empty($last_name))
{
$warr->setLastName($last_name);
}
}
if (empty($warr->getMobileNumber()))
{
if (!empty($mobile_number))
{
$warr->setMobileNumber($mobile_number);
}
}
if ((empty($warr->getBatteryModel())) ||
(empty($warr->getBatterySize())))
{
if (count($batt_list) != 0)
{
foreach ($batt_list as $battery)
{
// get the battery model and battery size
$model_id = $battery->getModel()->getID();
$size_id = $battery->getSize()->getID();
$bty_model = $this->em->getRepository(BatteryModel::class)->find($model_id);
$bty_size = $this->em->getRepository(BatterySize::class)->find($size_id);
if ($bty_model != null)
{
$warranty->setBatteryModel($bty_model);
}
if ($bty_size != null)
{
$warranty->setBatterySize($bty_size);
}
}
}
}
$purchase_date = $warr->getDatePurchase();
if (empty($purchase_date))
{
if (!empty($date_purchase))
{
$warr->setDatePurchase($date_purchase);
}
$purchase_date = $date_purchase;
}
if (empty($warr->getDateExpire()))
{
$batteries = [];
if (count($batt_list) == 0)
{
$batteries = $this->getBatteriesForWarranty($warr);
}
else
{
$batteries = $batt_list;
}
if (!empty($batteries))
{
$period = $this->getWarrantyPeriod($batteries, $warr->getWarrantyClass());
if (!empty($purchase_date))
{
$expire_date = $this->computeDateExpire($purchase_date, $period);
$warr->setDateExpire($expire_date);
}
}
}
$this->em->persist($warr);
$this->em->flush();
$this->em->clear();
}
public function computeDateExpire($date_create, $warranty_period)
{
$expire_date = clone $date_create;
$expire_date->add(new DateInterval('P'.$warranty_period.'M'));
return $expire_date;
}
public function getWarrantyPeriod($batteries, $warranty_class)
{
// set to -1 to show that we haven't set a warranty period yet
// cannot set initial value to 0 because warranty tnv can be 0
$least_warranty = -1;
$warr_period = 0;
foreach ($batteries as $battery)
{
// if multiple batteries, get the smallest warranty period
// check warranty class to get warranty period
if ($warranty_class == CMBWarrantyClass::WTY_PASSENGER)
{
$warr_period = $battery->getWarrantyPrivate();
//error_log('Warranty Period for Private: ' . $warr_period);
}
if ($warranty_class == CMBWarrantyClass::WTY_COMMERCIAL)
{
$warr_period = $battery->getWarrantyCommercial();
//error_log('Warranty Period for Commercial: ' . $warr_period);
}
if ($warranty_class == CMBWarrantyClass::WTY_GRAB)
{
$warr_period = $battery->getWarrantyTnv();
//error_log('Warranty Period for TNV: ' . $warr_period);
}
if ($least_warranty < 0)
{
// set least warranty to the first obtained warranty period
$least_warranty = $warr_period;
}
if ($least_warranty > $warr_period)
{
$least_warranty = $warr_period;
}
}
$warranty_period = $least_warranty;
return $warranty_period;
}
public function getBatteriesForWarranty($warr)
{
// find battery via sku/sap battery first
// if sku is null, use battery model and battery size to find battery
// if all three are null, do nothing
$batteries = null;
$batt_model = $warr->getBatteryModel();
$batt_size = $warr->getBatterySize();
$warranty_class = $warr->getWarrantyClass();
if (empty($warranty_class))
{
//error_log('Warranty class is empty for warranty id ' . $warr->getID());
return null;
}
if ($batt_model == null)
{
//error_log('Battery model is null for warranty id ' . $warr->getID());
return null;
}
if ($batt_size == null)
{
//error_log('Battery size is null for warranty id ' . $warr->getID());
return null;
}
// find battery using battery model and battery size
$batteries = $this->em->getRepository(Battery::class)->findBy(['model' => $batt_model, 'size' => $batt_size]);
if (empty($batteries))
{
error_log('Battery not found for warranty id ' . $warr->getID());
return null;
}
return $batteries;
}
public function cleanPlateNumber($plate)
{
// remove spaces and make upper case
return strtoupper(str_replace(' ', '', $plate));
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Service;
use App\Entity\Warranty;
use DateTime;
interface WarrantyHandlerInterface
{
public function createWarranty(string $serial, string $plate_number, string $first_name, string$last_name,
string $mobile_number, array $batt_list, DateTime $date_purchase,
string $warranty_class);
public function updateCustomerVehicle(string $serial, array $batteries, string $plate_number, DateTime $date_expire);
public function updateWarranty(Warranty $warr, string $first_name, string $last_name, string $mobile_number,
array $batt_list, DateTime $date_purchase);
public function computeDateExpire(DateTime $date_create, int $warranty_period);
public function getWarrantyPeriod(array $batteries, string $warranty_class);
public function getBatteriesForWarranty(Warranty $warr);
public function cleanPlateNumber(string $plate);
}

View file

@ -448,217 +448,50 @@
<div id="m_header_topbar" class="m-topbar m-stack m-stack--ver m-stack--general">
<div class="m-stack__item m-topbar__nav-wrapper">
<ul class="m-topbar__nav m-nav m-nav--inline">
<!--
<li class="m-nav__item m-topbar__notifications m-topbar__notifications--img m-dropdown m-dropdown--large m-dropdown--header-bg-fill m-dropdown--arrow m-dropdown--align-center m-dropdown--mobile-full-width" data-dropdown-toggle="click" data-dropdown-persistent="true">
<a href="#" class="m-nav__link m-dropdown__toggle" id="m_topbar_notification_icon">
<span class="m-nav__link-badge m-badge m-badge--dot m-badge--dot-small m-badge--danger"></span>
<span class="m-nav__link-icon">
<i class="flaticon-music-2"></i>
</span>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--center"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__header m--align-center" style="background: url(assets/app/media/img/misc/notification_bg.jpg); background-size: cover;">
<span class="m-dropdown__header-title">
9 New
</span>
<span class="m-dropdown__header-subtitle">
User Notifications
</span>
</div>
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<ul class="nav nav-tabs m-tabs m-tabs-line m-tabs-line--brand" role="tablist">
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link active" data-toggle="tab" href="#topbar_notifications_notifications" role="tab">
Alerts
</a>
</li>
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link" data-toggle="tab" href="#topbar_notifications_events" role="tab">
Events
</a>
</li>
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link" data-toggle="tab" href="#topbar_notifications_logs" role="tab">
Logs
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="topbar_notifications_notifications" role="tabpanel">
<div class="m-scrollable" data-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>
<span class="m-list-timeline__text">
12 new users registered
</span>
<span class="m-list-timeline__time">
Just now
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
System shutdown
<span class="m-badge m-badge--success m-badge--wide">
pending
</span>
</span>
<span class="m-list-timeline__time">
14 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
New invoice received
</span>
<span class="m-list-timeline__time">
20 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
DB overloaded 80%
<span class="m-badge m-badge--info m-badge--wide">
settled
</span>
</span>
<span class="m-list-timeline__time">
1 hr
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
System error -
<a href="#" class="m-link">
Check
</a>
</span>
<span class="m-list-timeline__time">
2 hrs
</span>
</div>
<div class="m-list-timeline__item m-list-timeline__item--read">
<span class="m-list-timeline__badge"></span>
<span href="" class="m-list-timeline__text">
New order received
<span class="m-badge m-badge--danger m-badge--wide">
urgent
</span>
</span>
<span class="m-list-timeline__time">
7 hrs
</span>
</div>
<div class="m-list-timeline__item m-list-timeline__item--read">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
Production server down
</span>
<span class="m-list-timeline__time">
3 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
Production server up
</span>
<span class="m-list-timeline__time">
5 hrs
</span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="topbar_notifications_events" role="tabpanel">
<div class="m-scrollable" m-scrollabledata-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-success"></span>
<a href="" class="m-list-timeline__text">
New order received
</a>
<span class="m-list-timeline__time">
Just now
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-danger"></span>
<a href="" class="m-list-timeline__text">
New invoice received
</a>
<span class="m-list-timeline__time">
20 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-success"></span>
<a href="" class="m-list-timeline__text">
Production server up
</a>
<span class="m-list-timeline__time">
5 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
New order received
</a>
<span class="m-list-timeline__time">
7 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
System shutdown
</a>
<span class="m-list-timeline__time">
11 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
Production server down
</a>
<span class="m-list-timeline__time">
3 hrs
</span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="topbar_notifications_logs" role="tabpanel">
<div class="m-stack m-stack--ver m-stack--general" style="min-height: 180px;">
<div class="m-stack__item m-stack__item--center m-stack__item--middle">
<span class="">
All caught up!
<br>
No new logs.
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
-->
<li class="m-nav__item m-topbar__notifications m-topbar__notifications--img m-dropdown m-dropdown--large m-dropdown--header-bg-fill m-dropdown--arrow m-dropdown--align-right m-dropdown--mobile-full-width" data-dropdown-toggle="click" data-dropdown-persistent="true">
<a href="#" class="m-nav__link m-dropdown__toggle" id="m_topbar_notification_icon">
<span class="m-nav__link-badge m-badge m-badge--dot m-badge--dot-small m-badge--danger"></span>
<span class="m-nav__link-icon">
<i class="flaticon-music-2"></i>
</span>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--right m-dropdown__arrow--adjust"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__header m--align-center" style="background: url(/assets/app/media/img/misc/notification_bg.jpg); background-size: cover;">
<span class="m-dropdown__header-title">
<span id="notif-count">-</span> New
</span>
<span class="m-dropdown__header-subtitle">
User Notifications
</span>
</div>
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<div class="m-scrollable" data-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div id="notif-body" class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>
<span class="m-list-timeline__text">
<a href="#">This is a notification</a>
</span>
<span class="m-list-timeline__time">
Just now
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li class="m-nav__item m-topbar__user-profile m-topbar__user-profile--img m-dropdown m-dropdown--medium m-dropdown--arrow m-dropdown--header-bg-fill m-dropdown--align-right m-dropdown--mobile-full-width m-dropdown--skin-light" data-dropdown-toggle="click">
<a href="#" class="m-nav__link m-dropdown__toggle">
<span class="m-topbar__userpic">
@ -718,6 +551,8 @@
</div>
</div>
</li>
</ul>
</div>
</div>
@ -770,6 +605,26 @@
<!--end::Page Snippets -->
<!--begin::Extra Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="/assets/js/moment.min.js" type="text/javascript"></script>
<script src="/assets/js/notification.js" type="text/javascript"></script>
<script>
// notifications
var notif = new NotificationHandler({
'notif_ajax_url': '{{ url('notification_ajax_list') }}',
'notif_ajax_update_url': '{{ url('notification_ajax_update') }}',
'icons': {
'jo_new': 'flaticon-placeholder-3 kt-font-brand',
'jo_cancel': 'fa fa-times-circle kt-font-danger',
'rider_accept': 'fa fa-motorcycle kt-font-success',
'rider_reject': 'fa fa-exclamation-triangle kt-font-danger'
},
'default_icon': 'fa fa-asterisk kt-font-brand',
});
notif.clearAll();
notif.loadAll();
notif.listen('{{ app.user.id }}', '{{ mqtt_host }}', {{ mqtt_port }}, {{ ssl_enable }});
</script>
{% block scripts %}{% endblock %}
<!--end::Extra Scripts -->
</body>

View file

@ -663,9 +663,18 @@
// display create vehicle form
$("#add-vehicle").click(function() {
$("#vehicle-form").data('mode', 'create');
$("#vehicle-form-title").html("Add Vehicle");
$("#vehicle-form-modal").modal('show');
// check if there are already 2 vehicles in list
if (vehicleRows.length == 2)
{
swal({
text: 'Customer is limited to only 2 vehicles..',
type: 'info',
});
} else {
$("#vehicle-form").data('mode', 'create');
$("#vehicle-form-title").html("Add Vehicle");
$("#vehicle-form-modal").modal('show');
}
});
// find vehicle row by index

View file

@ -12,7 +12,7 @@
{% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.1.0/paho-mqtt.min.js" type="text/javascript"></script>
<script src="{{ asset('assets/js/dashboard_map.js') }}"></script>
<script src="{{ asset('assets/js/map_mqtt.js') }}"></script>
{% if dashboard_enable == 'true' %}
@ -35,7 +35,9 @@ function initMap(r_markers, c_markers, icons) {
'zoom': 13,
'rider_popup_url': '/riders/[id]/popup',
'cust_popup_url': '/job-order/[id]/popup',
'icons': icons
'icons': icons,
'rider_name_url': '/riders/[id]/name',
'rider_availability_url': '{{ absolute_url('/riders/[id]/available')|raw }}'
};
var dashmap = new DashboardMap(options, r_markers, c_markers);
@ -45,7 +47,7 @@ function initMap(r_markers, c_markers, icons) {
return dashmap;
}
function initEventHandler(dashmap) {
function initEventHandler(dashmap, icons, ssl) {
var options = {
'track_jo': true,
'track_rider': true,
@ -54,11 +56,12 @@ function initEventHandler(dashmap) {
'rider_status': 'rider/+/status',
'jo_location': 'jo/+/location',
'jo_status': 'jo/+/status',
'jo_origin': 'jo/+/origin'
'jo_origin': 'jo/+/origin',
'rider_availability': 'rider/+/availability',
},
};
var event_handler = new MapEventHandler(options, dashmap);
var event_handler = new MapEventHandler(options, dashmap, ssl);
event_handler.connect('{{ app.user.getID }}', '{{ mqtt_host }}', {{ mqtt_port }});
}
@ -94,8 +97,13 @@ var icons = {
var r_markers = {};
var c_markers = {};
var ssl = false;
{% if ssl_enable == 'true' %}
ssl = true;
{% endif %}
var dashmap = initMap(r_markers, c_markers, icons);
initEventHandler(dashmap, icons);
initEventHandler(dashmap, icons, ssl);
{% endif %}
</script>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -23,9 +23,15 @@
<span class="m-portlet__head-icon">
<i class="flaticon-transport"></i>
</span>
<h3 class="m-portlet__head-text">
One-step Job Order
</h3>
{% if mode == "onestep" %}
<h3 class="m-portlet__head-text">
One-step Job Order
</h3>
{% else %}
<h3 class="m-portlet__head-text">
One-step Job Order #{{ obj.getID }} - {{ obj.getStatusText }}
</h3>
{% endif %}
</div>
</div>
</div>
@ -84,7 +90,7 @@
</h3>
<span class="m-switch m-switch--icon block-switch">
<label>
<input type="checkbox" name="new_customer" id="flag-new-customer" value="1"{{ obj.isAdvanceOrder ? ' checked' }}>
<input type="checkbox" name="new_customer" id="flag-new-customer" value="1">
<label class="switch-label">New Customer</label>
<span></span>
</label>
@ -92,57 +98,51 @@
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_first_name">First Name</label>
<input type="text" name="customer_first_name" id="customer-first-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getFirstName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_first_name"></div>
<label data-field="first_name">First Name</label>
<input type="text" name="first_name" id="customer-first-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getFirstName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="first_name"></div>
</div>
<div class="col-lg-6">
<label data-field="customer_last_name">Last Name</label>
<input type="text" name="customer_last_name" id="customer-last-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getLastName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_last_name"></div>
<label data-field="last_name">Last Name</label>
<input type="text" name="last_name" id="customer-last-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getLastName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="last_name"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_phone_mobile">Mobile Phone</label>
<label data-field="phone_mobile">Mobile Phone</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_mobile" id="customer-phone-mobile" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneMobile|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_mobile"></div>
<input type="text" name="phone_mobile" id="customer-phone-mobile" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneMobile|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="phone_mobile"></div>
</div>
</div>
<div class="col-lg-6">
<label data-field="customer_phone_landline">Landline</label>
<label data-field="phone_landline">Second Number</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_landline" id="customer-phone-landline" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneLandline|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_landline"></div>
<input type="text" name="phone_landline" id="customer-phone-landline" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneLandline|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="phone_landline"></div>
</div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_phone_office">Office Phone</label>
<label data-field="phone_office">Office Phone</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_office" id="customer-phone-office" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneOffice|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_office"></div>
<input type="text" name="phone_office" id="customer-phone-office" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneOffice|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="phone_office"></div>
</div>
</div>
<div class="col-lg-6">
<label data-field="customer_phone_fax">Fax</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_fax" id="customer-phone-fax" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneFax|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_fax"></div>
</div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_customer_notes">Customer Notes</label>
<textarea name="customer_customer_notes" id="customer-customer-notes" class="form-control m-input cust_field" data-vehicle-field="1" rows="4" disabled>{{ obj.getCustomer ? obj.getCustomer.getCustomerNotes }}</textarea>
<div class="form-control-feedback hide" data-field="customer_customer_notes"></div>
<label data-field="customer_notes">Customer Notes</label>
<textarea name="customer_notes" id="customer-customer-notes" class="form-control m-input cust_field" data-vehicle-field="1" rows="4" disabled>{{ obj.getCustomer ? obj.getCustomer.getCustomerNotes }}</textarea>
<div class="form-control-feedback hide" data-field="customer_notes"></div>
</div>
</div>
</div>
@ -178,17 +178,26 @@
</div>
<div class="col-lg-3">
<label data-field="cv_year">Model Year</label>
<input type="text" name="cv_year" id="cv-year" class="form-control m-input cv_field" value="{{ obj.getCustomerVehicle ? obj.getCustomerVehicle.getModelYear }}" data-vehicle-field="1" disabled>
<select name="cv_year" class="form-control m-input cv_field" id="cv-year" data-required="0" disabled>
<option value="">Select a year</option>
{% for year in model_years %}
{% if obj.getCustomerVehicle %}
<option value="{{ year }}"{{ obj.getCustomerVehicle.getModelYear == year ? ' selected' }}>{{ year }}</option>
{% else %}
<option value="{{ year }}">{{ year }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="cv_year"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-3">
<label data-field="cv_plate">Plate #
<label data-field="plate_number">Plate #
<span style="color:red"> *</span>
</label>
<input type="text" name="cv_plate" id="cv-plate" class="form-control m-input cv_field" value="{{ obj.getCustomerVehicle.getPlateNumber|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="cv_plate"></div>
<input type="text" name="plate_number" id="cv-plate" class="form-control m-input cv_field" value="{{ obj.getCustomerVehicle.getPlateNumber|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="plate_number"></div>
</div>
<!--
<div class="col-lg-3">
@ -278,7 +287,7 @@
<div class="col-lg-3">
<label data-field="date_schedule">Scheduled Date</label>
<div class="input-group date dp">
<input type="text" name="date_schedule_date" class="form-control m-input" data-default-value="{{ obj.getDateSchedule|default("now")|date('Y-m-d') }}" value="{{ obj.getDateSchedule|default("now")|date('d M Y') }}" readonly placeholder="Select a date" disabled>
<input type="text" id="date-schedule-date" name="date_schedule_date" class="form-control m-input" data-default-value="{{ obj.getDateSchedule|default("now")|date('Y-m-d') }}" value="{{ obj.getDateSchedule|default("now")|date('d M Y') }}" readonly placeholder="Select a date" disabled>
<span class="input-group-addon">
<i class="la la-calendar glyphicon-th"></i>
</span>
@ -288,7 +297,7 @@
<div class="col-lg-3">
<label data-field="date_schedule">Scheduled Time</label>
<div class="input-group">
<input type="text" name="date_schedule_time" class="form-control m-input tp" data-default-value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" readonly placeholder="Select a time" disabled>
<input type="text" id="date-schedule-time" name="date_schedule_time" class="form-control m-input tp" data-default-value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" readonly placeholder="Select a time" disabled>
<span class="input-group-addon">
<i class="la la-clock-o glyphicon-th"></i>
</span>
@ -296,18 +305,6 @@
<div class="form-control-feedback hide" data-field="date_schedule_time"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="tier1_notes">Tier 1 Notes</label>
<textarea name="tier1_notes" class="form-control m-input" rows="15">{{ obj.getTier1Notes }}</textarea>
<div class="form-control-feedback hide" data-field="tier1_notes"></div>
</div>
<div class="col-lg-6">
<label data-field="tier2_notes">Tier 2 Notes</label>
<textarea name="tier2_notes" class="form-control m-input" rows="15">{{ obj.getTier2Notes }}</textarea>
<div class="form-control-feedback hide" data-field="tier2_notes"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<div class="col-lg-12 form-group-inner">
@ -324,8 +321,22 @@
<label data-field="delivery_instructions">{% trans %}delivery_instructions_label{% endtrans %}</label>
<textarea name="delivery_instructions" class="form-control m-input" rows="4">{{ obj.getDeliveryInstructions }}</textarea>
</div>
<br>
<div class="col-lg-12 form-group-inner">
<label data-field="cancel_reason">Cancellation Reason</label>
<input type="text" id="cancel-reason" name="cancel_reason" class="form-control m-input" value="{{ obj.getCancelReasonName|default('') }}" disabled>
<div class="form-control-feedback hide" data-field="cancel_reason"></div>
</div>
</div>
<div class="col-lg-6">
<div class="col-lg-12 form-group-inner">
<label>
Responsible Party
<span style="color:red"> *</span>
</label>
<input type="text" name="responsible_party" class="form-control m-input" value="{{ obj.getResponsibleParty|default('') }}" >
<div class="form-control-feedback hide" data-field="responsible_party"></div>
</div>
<div class="col-lg-12 form-group-inner">
<label>Prepared By</label>
<input type="text" name="created_by" class="form-control m-input" value="{{ obj.getCreatedBy.getFullName|default('') }}" disabled>
@ -415,7 +426,6 @@
<th>Branch</th>
<th>Contact Numbers</th>
<th>Distance in KM</th>
<th>Action</th>
</tr>
</thead>
<tbody id="nearest_hubs">
@ -425,7 +435,6 @@
<td>{{ hub.hub.getName }}</td>
<td>{{ hub.hub.getBranch }}</td>
<td>{{ hub.hub.getContactNumbers }}</td>
<td></span></td>
<td></td>
{% endfor %}
{% endif %} -->
@ -462,14 +471,13 @@
<th>Last Name</th>
<th>Contact No.</th>
<th>Plate Number</th>
<th>Action</th>
</tr>
</thead>
<tbody id="riders">
{% if mode in ['onestep-edit', 'view-all', 'update-fulfillment'] %}
{% set avail_riders = obj.getHub.getAvailableRiders|default([]) %}
<tr class="placeholder-row{{ obj.getHub and avail_riders|length > 0 ? ' hide' }}">
<td colspan="5">
<td colspan="4">
No riders available.
</td>
</tr>
@ -495,6 +503,100 @@
</div>
</div>
{% if mode in ['onestep-edit'] %}
<div class="m-form__seperator m-form__seperator--dashed"></div>
<div class="m-form__section">
<div class="m-form__heading">
<h3 class="m-form__heading-title">
Other Information
</h3>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-3">
<label data-field="odometer">Odometer Reading</label>
<input type="text" name="odometer" id="odometer" class="form-control m-input other_info_field" value="{{ odometer }}" disabled>
</div>
<div class="col-lg-3">
<label data-field="email">Email Address</label>
<input type="text" name="email" id="email" class="form-control m-input other_info_field" value="{{ email }}" disabled>
</div>
<div class="col-lg-3">
<label> Customer Signature </label>
<div class="portrait-box" style="background-image: url('{{ signature ? '/uploads/jo_extra/' ~ signature : '/assets/images/user.gif' }}');" ></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-12">
<label> Pictures</label>
</div>
{% for key, picture in jo_pictures %}
{% if key == 'image_1' %}
<div class="col-lg-2">
<a href="#" class="image-pop">
<img id="image-1-source" src="{{ asset('/uploads/jo_extra/' ~ picture) }}" style="width: 150px; height: 150px;" />
</a>
<br>
<label>Windscreen Sticker</label>
</div>
{% endif %}
{% if key == 'image_2' %}
<div class="col-lg-2">
<a href="#" class="image-pop">
<img id="image-2-source" src="{{ asset('/uploads/jo_extra/' ~ picture) }}" style="width: 150px; height: 150px;" />
</a>
<br>
<label>Battery</label>
</div>
{% endif %}
{% if key == 'image_3' %}
<div class="col-lg-2">
<a href="#" class="image-pop">
<img id="image-3-source" src="{{ asset('/uploads/jo_extra/' ~ picture) }}" style="width: 150px; height: 150px;" />
</a>
<br>
<label>Odometer Reading</label>
</div>
{% endif %}
{% if key == 'image_4' %}
<div class="col-lg-2">
<a href="#" class="image-pop">
<img id="image-4-source" src="{{ asset('/uploads/jo_extra/' ~ picture) }}" style="width: 150px; height: 150px;" />
</a>
<br>
<label>Proof of Payment</label>
</div>
{% endif %}
{% endfor %}
</div>
<!-- <div class="form-group m-form__group row">
<div class="col-lg-12">
<label> Other Images</label>
</div>
{% for key, picture in jo_pictures %}
{% if key == 'other_images' %}
{% for pic in jo_pictures['other_images'] %}
<div class="col-lg-2">
<div class="portrait-box" style="background-image: url('{{ '/uploads/jo_extra/' ~ pic }}');" ></div>
</div>
{% endfor %}
{% endif %}
{% endfor %}
</div> -->
</div>
<!-- modal for images -->
<div class="modal fade" id="image-modal" tabindex="-1" role="dialog" aria-labelledby="imageModal" aria-hidden="true">
<div class="modal-dialog" data-dismiss="modal">
<div class="modal-content">
<div class="modal-body">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<img src="" class="image-preview" style="width: 100%;" >
</div>
</div>
</div>
</div>
{% endif %}
<div class="m-form__seperator m-form__seperator--dashed"></div>
<div class="m-form__section" id="sc-section">
<div class="m-form__heading">
@ -544,7 +646,7 @@
<input type="text" name="or_name" id="or_name" class="form-control m-input" value="{{ obj.getORName|default('') }}">
<div class="form-control-feedback hide" data-field="or_name"></div>
</div>
<div class="col-lg-6">
<div style="display:none" class="col-lg-6">
<label data-field="promo_detail">Employee ID / Card Number / Referred By</label>
<input type="text" name="promo_detail" id="promo_detail" class="form-control m-input" value="{{ obj.getPromoDetail|default('') }}">
<div class="form-control-feedback hide" data-field="promo_detail"></div>
@ -554,10 +656,29 @@
<div class="col-lg-6">
<label>Discount</label>
{% if ftags.invoice_edit %}
<input type="number" id="invoice-discount" name="invoice_discount" class="form-control m-input min = "0" max="50" value="{{ obj.getInvoice ? obj.getInvoice.getDiscount }}">
<select class="form-control m-input" id="invoice-discount" name="invoice_discount">
<option value=""></option>
{% for discount in discounts %}
{% if obj.getInvoice %}
<option value="{{ discount }}"{{ obj.getInvoice.getDiscount|number_format(2) == discount ? ' selected' }}>{{ discount }}</option>
{% else %}
<option value="{{ discount }}">{{ discount }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_discount"></div>
{% else %}
<input type="number" id="invoice-discount" name="invoice_discount" class="form-control m-input min="0" max="50" value="{{ obj.getInvoice ? obj.getInvoice.getDiscount }}" disabled>
<select class="form-control m-input" id="invoice-discount" name="invoice_discount">
<option value=""></option>
{% for discount in discounts %}
{% if obj.getInvoice %}
<option value="{{ discount }}"{{ obj.getInvoice.getDiscount|number_format(2) == discount ? ' selected' }} disabled>{{ discount }}</option>
{% else %}
<option value="{{ discount }}" disabled>{{ discount }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_discount"></div>
{% endif %}
</div>
<div class="col-lg-6">
@ -753,6 +874,7 @@ $(function() {
{% if mode in ['onestep'] %}
// get nearest hubs ajax
var hub_table = '';
var rider_table = '';
$.getJSON("{{ url('hub_nearest') }}?lat=" + lat + "&long=" + lng, function(data) {
var hubs = data['hubs'];
for (i in hubs) {
@ -765,18 +887,29 @@ $(function() {
hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>';
hub_table += '</tr>';
}
$('#nearest_hubs').html(hub_table);
});
// clear rider table
rider_table += '<tr>';
rider_table += '<td>' + '</td>';
rider_table += '<td>' + '</td>';
rider_table += '<td>' + '</td>';
rider_table += '<td>' + '</td>';
rider_table += '</tr>';
$('#riders').html(rider_table);
{% endif %}
{% if mode in ['onestep-edit'] %}
// get nearest hubs ajax
var hub_table = '';
var rider_table = '';
$.getJSON("{{ url('hub_nearest') }}?lat=" + lat + "&long=" + lng, function(data) {
var hubs = data['hubs'];
for (i in hubs) {
@ -794,13 +927,26 @@ $(function() {
hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>';
hub_table += '</tr>';
}
$('#nearest_hubs').html(hub_table);
$('#nearest_hubs').html(hub_table);
});
if (selected_rider == '') {
// clear rider table
rider_table += '<tr>';
rider_table += '<td>' + '</td>';
rider_table += '<td>' + '</td>';
rider_table += '<td>' + '</td>';
rider_table += '<td>' + '</td>';
rider_table += '</tr>';
$('#riders').html(rider_table);
}
{% endif %}
{% if mode in ['view-all', 'update-fulfillment'] %}
@ -820,7 +966,6 @@ $(function() {
hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>';
hub_table += '</tr>';
}
}
@ -855,6 +1000,9 @@ $(function() {
});
osm_map.on('click', function(e) {
// clear selected hub and riders
selected_hub = '';
selected_rider = '';
selectPoint(e.latlng.lat, e.latlng.lng);
});
@ -911,6 +1059,7 @@ $(function() {
// clear rider field
$('#rider-field').val('');
$('#rider-plate-number').val('');
selected_rider = '';
// get riders of hub
@ -931,7 +1080,6 @@ $(function() {
rider_table += '<td>' + rider['last_name'] + '</td>';
rider_table += '<td>' + rider['contact_num'] + '</td>';
rider_table += '<td>' + rider['plate_num'] + '</td>';
rider_table += '<td></td>';
rider_table += '</tr>';
}
@ -961,6 +1109,8 @@ $(function() {
// set rider
selected_rider = id;
$('#rider-field').val(selected_rider);
//var rider_plate_num = $(this).find('td:nth-child(4)').text();
//$('#rider-plate-number').val(rider_plate_num);
});
{% endif %}
});
@ -1029,6 +1179,7 @@ $(function() {
{% endif %}
$("#row-form").submit(function(e) {
e.preventDefault();
if (form_in_process) {
alert("Cannot submit form twice. First submission still in progress.");
return false;
@ -1173,7 +1324,6 @@ $(function() {
$("#customer-phone-mobile").val(vdata.customer.phone_mobile);
$("#customer-phone-landline").val(vdata.customer.phone_landline);
$("#customer-phone-office").val(vdata.customer.phone_office);
$("#customer-phone-fax").val(vdata.customer.phone_fax);
$("#customer-customer-notes").val(vdata.customer.customer_notes);
$("#cv-mfg").val(vdata.vehicle.mfg_id);
// get vehicles for this manufacturer
@ -1191,6 +1341,7 @@ $(function() {
} else {
$("#current-battery, #warranty-expiration").val("No current battery").css('color', '#f4516c');
}
$("#or_name").val(vdata.customer.first_name + ' ' + vdata.customer.last_name);
})
}).focus();
{% endif %}
@ -1414,7 +1565,8 @@ $(function() {
// update invoice when promo is changed
$("#invoice-discount").change(function() {
generateInvoice();
console.log('discount ' + $("#invoice-discount").val());
generateInvoice();
});
// trigger update when service type is changed
@ -1758,6 +1910,59 @@ $(function() {
generateInvoice();
});
$("#customer-first-name, #customer-last-name").change(function() {
// autopopulate OR name with customer name
var cust_name = $("#customer-first-name").val() + ' ' + $("#customer-last-name").val();
$("#or_name").val(cust_name);
});
$('.dp').on('changeDate', function() {
// check date schedule if it's earlier
// if so, show an alert to let user know
var date_time = $('#date-schedule-date').val() + ' ' + $('#date-schedule-time').val()
var date_schedule = new Date(date_time);
var current_date = new Date('{{ current_date|date('Y-m-d g:i A') }}');
// check if date is earlier than current
if (date_schedule < current_date)
{
swal({
title: 'Warning!',
text: 'Schedule date and time is before today\'s date and time.',
type: 'warning',
});
}
});
$('.tp').on('changeTime.timepicker', function(e) {
// set the schedule time what the user selects
$('#date-schedule-time').val(e.time.value);
});
$('.tp').on('hide.timepicker', function(e) {
// check time schedule along with date if it's earlier than current date
var date_time = $('#date-schedule-date').val() + ' ' + $('#date-schedule-time').val()
var date_schedule = new Date(date_time);
var current_date = new Date('{{ current_date|date('Y-m-d g:i A') }}');
// check if date is earlier than current
if (date_schedule < current_date)
{
swal({
title: 'Warning!',
text: 'Schedule date and time is before today\'s date and time.',
type: 'warning',
});
}
});
// image modal
$('.image-pop').on('click', function() {
$('.image-preview').attr('src', $(this).find('img').attr('src'));
$('#image-modal').modal('show');
});
});
</script>
{% endblock %}

View file

@ -23,9 +23,15 @@
<span class="m-portlet__head-icon">
<i class="flaticon-transport"></i>
</span>
<h3 class="m-portlet__head-text">
Walk-in Job Order
</h3>
{% if mode == "walk-in" %}
<h3 class="m-portlet__head-text">
Walk-in Job Order
</h3>
{% else %}
<h3 class="m-portlet__head-text">
Walk-in Job Order #{{ obj.getID }} - {{ obj.getStatusText }}
</h3>
{% endif %}
</div>
</div>
</div>
@ -84,7 +90,7 @@
</h3>
<span class="m-switch m-switch--icon block-switch">
<label>
<input type="checkbox" name="new_customer" id="flag-new-customer" value="1"{{ obj.isAdvanceOrder ? ' checked' }}>
<input type="checkbox" name="new_customer" id="flag-new-customer" value="1">
<label class="switch-label">New Customer</label>
<span></span>
</label>
@ -92,57 +98,51 @@
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_first_name">First Name</label>
<input type="text" name="customer_first_name" id="customer-first-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getFirstName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_first_name"></div>
<label data-field="first_name">First Name</label>
<input type="text" name="first_name" id="customer-first-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getFirstName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="first_name"></div>
</div>
<div class="col-lg-6">
<label data-field="customer_last_name">Last Name</label>
<input type="text" name="customer_last_name" id="customer-last-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getLastName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_last_name"></div>
<label data-field="last_name">Last Name</label>
<input type="text" name="last_name" id="customer-last-name" class="form-control m-input cust_field" value="{{ obj.getCustomer.getLastName|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="last_name"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_phone_mobile">Mobile Phone</label>
<label data-field="phone_mobile">Mobile Phone</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_mobile" id="customer-phone-mobile" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneMobile|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_mobile"></div>
<input type="text" name="phone_mobile" id="customer-phone-mobile" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneMobile|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="phone_mobile"></div>
</div>
</div>
<div class="col-lg-6">
<label data-field="customer_phone_landline">Landline</label>
<label data-field="phone_landline">Second Number</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_landline" id="customer-phone-landline" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneLandline|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_landline"></div>
<input type="text" name="phone_landline" id="customer-phone-landline" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneLandline|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="phone_landline"></div>
</div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_phone_office">Office Phone</label>
<label data-field="phone_office">Office Phone</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_office" id="customer-phone-office" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneOffice|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_office"></div>
<input type="text" name="phone_office" id="customer-phone-office" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneOffice|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="phone_office"></div>
</div>
</div>
<div class="col-lg-6">
<label data-field="customer_phone_fax">Fax</label>
<div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_fax" id="customer-phone-fax" class="form-control m-input cust_field" value="{{ obj.getCustomer.getPhoneFax|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_fax"></div>
</div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_customer_notes">Customer Notes</label>
<textarea name="customer_customer_notes" id="customer-customer-notes" class="form-control m-input cust_field" data-vehicle-field="1" rows="4" disabled>{{ obj.getCustomer ? obj.getCustomer.getCustomerNotes }}</textarea>
<div class="form-control-feedback hide" data-field="customer_customer_notes"></div>
<label data-field="customer_notes">Customer Notes</label>
<textarea name="customer_notes" id="customer-customer-notes" class="form-control m-input cust_field" data-vehicle-field="1" rows="4" disabled>{{ obj.getCustomer ? obj.getCustomer.getCustomerNotes }}</textarea>
<div class="form-control-feedback hide" data-field="customer_notes"></div>
</div>
</div>
</div>
@ -178,17 +178,26 @@
</div>
<div class="col-lg-3">
<label data-field="cv_year">Model Year</label>
<input type="text" name="cv_year" id="cv-year" class="form-control m-input cv_field" value="{{ obj.getCustomerVehicle ? obj.getCustomerVehicle.getModelYear }}" data-vehicle-field="1" disabled>
<select name="cv_year" class="form-control m-input cv_field" id="cv-year" data-required="0" disabled>
<option value="">Select a year</option>
{% for year in model_years %}
{% if obj.getCustomerVehicle %}
<option value="{{ year }}"{{ obj.getCustomerVehicle.getModelYear == year ? ' selected' }}>{{ year }}</option>
{% else %}
<option value="{{ year }}">{{ year }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="cv_year"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-3">
<label data-field="cv_plate">Plate #
<label data-field="plate_number">Plate #
<span style="color:red"> *</span>
</label>
<input type="text" name="cv_plate" id="cv-plate" class="form-control m-input cv_field" value="{{ obj.getCustomerVehicle.getPlateNumber|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="cv_plate"></div>
<input type="text" name="plate_number" id="cv-plate" class="form-control m-input cv_field" value="{{ obj.getCustomerVehicle.getPlateNumber|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="plate_number"></div>
</div>
<!--
<div class="col-lg-3">
@ -278,7 +287,7 @@
<div class="col-lg-3">
<label data-field="date_schedule">Scheduled Date</label>
<div class="input-group date dp">
<input type="text" name="date_schedule_date" class="form-control m-input" data-default-value="{{ obj.getDateSchedule|default("now")|date('Y-m-d') }}" value="{{ obj.getDateSchedule|default("now")|date('d M Y') }}" readonly placeholder="Select a date" disabled>
<input type="text" id="date-schedule-date" name="date_schedule_date" class="form-control m-input" data-default-value="{{ obj.getDateSchedule|default("now")|date('Y-m-d') }}" value="{{ obj.getDateSchedule|default("now")|date('d M Y') }}" readonly placeholder="Select a date" disabled>
<span class="input-group-addon">
<i class="la la-calendar glyphicon-th"></i>
</span>
@ -288,7 +297,7 @@
<div class="col-lg-3">
<label data-field="date_schedule">Scheduled Time</label>
<div class="input-group">
<input type="text" name="date_schedule_time" class="form-control m-input tp" data-default-value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" readonly placeholder="Select a time" disabled>
<input type="text" id="date-schedule-time" name="date_schedule_time" class="form-control m-input tp" data-default-value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" value="{{ obj.getDateSchedule|default("now")|date('g:i A') }}" readonly placeholder="Select a time" disabled>
<span class="input-group-addon">
<i class="la la-clock-o glyphicon-th"></i>
</span>
@ -296,18 +305,6 @@
<div class="form-control-feedback hide" data-field="date_schedule_time"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="tier1_notes">Tier 1 Notes</label>
<textarea name="tier1_notes" class="form-control m-input" rows="15">{{ obj.getTier1Notes }}</textarea>
<div class="form-control-feedback hide" data-field="tier1_notes"></div>
</div>
<div class="col-lg-6">
<label data-field="tier2_notes">Tier 2 Notes</label>
<textarea name="tier2_notes" class="form-control m-input" rows="15">{{ obj.getTier2Notes }}</textarea>
<div class="form-control-feedback hide" data-field="tier2_notes"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<div class="col-lg-12 form-group-inner">
@ -323,14 +320,28 @@
</select>
<div class="form-control-feedback hide" data-field="mode_of_payment"></div>
</div>
</div>
<div class="col-lg-6">
<br>
<div class="col-lg-12 form-group-inner">
<label>Prepared By</label>
<input type="text" name="created_by" class="form-control m-input" value="{{ obj.getCreatedBy.getFullName|default('') }}" disabled>
<div class="form-control-feedback hide" data-field="date_transaction"></div>
</div>
</div>
<div class="col-lg-6">
<div class="col-lg-12 form-group-inner">
<label>
Responsible Party
<span style="color:red"> *</span>
</label>
<input type="text" name="responsible_party" class="form-control m-input" value="{{ obj.getResponsibleParty|default('') }}" >
<div class="form-control-feedback hide" data-field="responsible_party"></div>
</div>
<br>
<div class="col-lg-12 form-group-inner">
<label data-field="cancel_reason">Cancellation Reason</label>
<input type="text" id="cancel-reason" name="cancel_reason" class="form-control m-input" value="{{ obj.getCancelReasonName|default('') }}" disabled>
<div class="form-control-feedback hide" data-field="cancel_reason"></div>
</div>
</div>
</div>
</div>
@ -357,8 +368,6 @@
<th>Hub</th>
<th>Branch</th>
<th>Contact Numbers</th>
<th>Distance in KM</th>
<th>Action</th>
</tr>
</thead>
<tbody id="nearest_hubs">
@ -367,16 +376,12 @@
<td>{{ obj.getHub.getName }}</td>
<td>{{ obj.getHub.getBranch }}</td>
<td>{{ obj.getHub.getContactNumbers }}</td>
<td></span></td>
<td></td>
{% else %}
{% for hub in hubs %}
<tr data-id="{{ hub.getID }}"{{ obj.getHub and obj.getHub.getID == hub.getID ? ' class="m-table__row--primary"' }}>
<td>{{ hub.getName }}</td>
<td>{{ hub.getBranch }}</td>
<td>{{ hub.getContactNumbers }}</td>
<td></span></td>
<td></td>
{% endfor %}
{% endif %}
</tbody>
@ -398,7 +403,7 @@
<input type="text" name="or_name" id="or_name" class="form-control m-input" value="{{ obj.getORName|default('') }}">
<div class="form-control-feedback hide" data-field="or_name"></div>
</div>
<div class="col-lg-6">
<div style="display:none" class="col-lg-6">
<label data-field="promo_detail">Employee ID / Card Number / Referred By</label>
<input type="text" name="promo_detail" id="promo_detail" class="form-control m-input" value="{{ obj.getPromoDetail|default('') }}">
<div class="form-control-feedback hide" data-field="promo_detail"></div>
@ -408,10 +413,28 @@
<div class="col-lg-6">
<label>Discount</label>
{% if ftags.invoice_edit %}
<input type="number" id="invoice-discount" name="invoice_discount" class="form-control m-input min = "0" max="50" value="{{ obj.getInvoice ? obj.getInvoice.getDiscount }}">
<select class="form-control m-input" id="invoice-discount" name="invoice_discount">
<option value=""></option>
{% for discount in discounts %}
{% if obj.getInvoice %}
<option value="{{ discount }}"{{ obj.getInvoice.getDiscount|number_format(0) == discount ? ' selected' }}>{{ discount }}</option>
{% else %}
<option value="{{ discount }}">{{ discount }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_discount"></div>
{% else %}
<input type="number" id="invoice-discount" name="invoice_discount" class="form-control m-input min="0" max="50" value="{{ obj.getInvoice ? obj.getInvoice.getDiscount }}" disabled>
<select class="form-control m-input" id="invoice-discount" name="invoice_discount">
<option value=""></option>
{% for discount in discounts %}
{% if obj.getInvoice %}
<option value="{{ discount }}"{{ obj.getInvoice.getDiscount == discount ? ' selected' }} disabled>{{ discount }}</option>
{% else %}
<option value="{{ discount }}" disabled>{{ discount }}</option>
{% endif %}
{% endfor %}
</select>
{% endif %}
</div>
<div class="col-lg-6">
@ -708,7 +731,6 @@ var vdata = false;
$("#customer-phone-mobile").val(vdata.customer.phone_mobile);
$("#customer-phone-landline").val(vdata.customer.phone_landline);
$("#customer-phone-office").val(vdata.customer.phone_office);
$("#customer-phone-fax").val(vdata.customer.phone_fax);
$("#customer-customer-notes").val(vdata.customer.customer_notes);
$("#cv-mfg").val(vdata.vehicle.mfg_id);
@ -727,6 +749,8 @@ var vdata = false;
} else {
$("#current-battery, #warranty-expiration").val("No current battery").css('color', '#f4516c');
}
$("#or_name").val(vdata.customer.first_name + ' ' + vdata.customer.last_name);
})
}).focus();
{% endif %}
@ -1093,6 +1117,54 @@ var vdata = false;
});
});
});
$("#customer-first-name, #customer-last-name").change(function() {
// autopopulate OR name with customer name
var cust_name = $("#customer-first-name").val() + ' ' + $("#customer-last-name").val();
$("#or_name").val(cust_name);
});
$('.dp').on('changeDate', function() {
// check date schedule if it's earlier
// if so, show an alert to let user know
var date_time = $('#date-schedule-date').val() + ' ' + $('#date-schedule-time').val()
var date_schedule = new Date(date_time);
var current_date = new Date('{{ current_date|date('Y-m-d g:i A') }}');
// check if date is earlier than current
if (date_schedule < current_date)
{
swal({
title: 'Warning!',
text: 'Schedule date and time is before today\'s date and time.',
type: 'warning',
});
}
});
$('.tp').on('changeTime.timepicker', function(e) {
// set the schedule time what the user selects
$('#date-schedule-time').val(e.time.value);
});
$('.tp').on('hide.timepicker', function(e) {
// check time schedule along with date if it's earlier than current date
var date_time = $('#date-schedule-date').val() + ' ' + $('#date-schedule-time').val()
var date_schedule = new Date(date_time);
var current_date = new Date('{{ current_date|date('Y-m-d g:i A') }}');
// check if date is earlier than current
if (date_schedule < current_date)
{
swal({
title: 'Warning!',
text: 'Schedule date and time is before today\'s date and time.',
type: 'warning',
});
}
});
});
</script>
{% endblock %}

View file

@ -94,8 +94,12 @@
},
rows: {
beforeTemplate: function (row, data, index) {
if (data.flag_advance) {
$(row).addClass('m-table__row--danger');
if (data.status == 'In Progress') {
$(row).addClass('m-table__row--is_in_progress');
}
if (data.status == 'Assigned') {
$(row).addClass('m-table__row--is_assigned');
}
}
},
@ -104,6 +108,27 @@
field: 'id',
title: 'JO Number'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '<a href="' + row.meta.edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="View"><i class="la la-edit"></i></a>';
actions += '<a href="' + row.meta.pdf_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="PDF" target="_blank"><i class="la la-file-o"></i></a>';
return actions;
},
},
{
field: 'type',
title: 'Schedule'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
},
{
field: 'plate_number',
title: 'Plate #'
@ -125,12 +150,12 @@
title: 'Customer Area'
},
{
field: 'type',
title: 'Schedule'
field: 'date_start',
title: 'Start Date'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
field: 'date_finish',
title: 'Finish Date'
},
{
field: 'rider_name',
@ -145,21 +170,8 @@
title: 'Status'
},
{
field: 'processor',
title: 'Dispatcher'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '<a href="' + row.meta.update_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="View"><i class="la la-edit"></i></a>';
actions += '<a href="' + row.meta.pdf_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="PDF" target="_blank"><i class="la la-file-o"></i></a>';
return actions;
},
field: 'creator',
title: 'Agent'
}
],
search: {

View file

@ -0,0 +1,210 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Advanced Order Job Orders
</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-12">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input m-input--solid" placeholder="Search..." id="data-rows-search">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<div>
<select class="form-control m-input" id="rider_list" name="rider_list">
<option value="">All Riders</option>
{% for rider in riders %}
<option value="{{ rider.getID }}">{{ rider.getFirstName ~ ' ' ~ rider.getLastName }} </option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<div class="input-daterange input-group" id="date-range">
<input role="presentation" type="text" class="form-control m-input" id="date_start" name="date_start" placeholder="Start date" />
<div class="input-group-append">
<span class="input-group-text"><i class="la la-ellipsis-h"></i></span>
</div>
<input role="presentation" type="text" class="form-control" id="date_end" name="date_end" placeholder="End date" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--begin: Datatable -->
<div id="data-rows"></div>
<!--end: Datatable -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
$("#date-range").datepicker({
orientation: "bottom"
});
var options = {
data: {
type: 'remote',
source: {
read: {
url: '{{ url('jo_assign_advance_order_rows') }}',
method: 'POST'
}
},
saveState: {
cookie: false,
webstorage: false
},
pageSize: 10,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
rows: {
beforeTemplate: function (row, data, index) {
if (data.status == 'In Progress') {
$(row).addClass('m-table__row--is_in_progress');
}
if (data.status == 'Assigned') {
$(row).addClass('m-table__row--is_assigned');
}
}
},
columns: [
{
field: 'id',
title: 'JO #'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
{% if is_granted('jo_assign_advance_order.form') %}
var actions = '<a href="' + row.meta.edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="Edit"><i class="la la-edit"></i></a>';
{% endif %}
return actions;
},
},
{
field: 'type',
title: 'Schedule'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
},
{
field: 'plate_number',
title: 'Plate #'
},
{
field: 'car_model',
title: 'Car Model'
},
{
field: 'customer_name',
title: 'Customer'
},
{
field: 'service_type',
title: 'Service Type',
},
{
field: 'delivery_address',
title: 'Area'
},
{
field: 'date_start',
title: 'Start Date'
},
{
field: 'rider_name',
title: 'Rider'
},
{
field: 'rider_plate_number',
title: 'Rider Plate #'
},
{
field: 'status',
title: 'Status'
},
{
field: 'creator',
title: 'Agent'
}
],
search: {
onEnter: false,
input: $('#data-rows-search'),
delay: 400
}
};
var table = $("#data-rows").mDatatable(options);
// auto refresh table
setInterval(function() {
table.reload();
}, {{ table_refresh_rate }});
$("#rider_list").on("change", function() {
table.search($(this).val(), "rider");
});
$("#date_start").on("change", function() {
var date_start = $(this).val();
var date_end = $("[name='date_end']").val();
var date_array = [date_start, date_end];
table.search(date_array, "schedule_date");
});
$("#date_end").on("change", function() {
console.log($(this).val());
var date_end = $(this).val();
var date_start = $("[name='date_start']").val();
var date_array = [date_start, date_end];
table.search(date_array, "schedule_date");
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,199 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Job Orders (Behind Schedule)
</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-12">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input m-input--solid" placeholder="Search..." id="data-rows-search">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<div>
<select class="form-control m-input" id="rider_list" name="rider_list">
<option value="">All Riders</option>
{% for rider in riders %}
<option value="{{ rider.getID }}">{{ rider.getFirstName ~ ' ' ~ rider.getLastName }} </option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<div class="input-daterange input-group" id="date-range">
<input role="presentation" type="text" class="form-control m-input" id="date_start" name="date_start" placeholder="Start date" />
<div class="input-group-append">
<span class="input-group-text"><i class="la la-ellipsis-h"></i></span>
</div>
<input role="presentation" type="text" class="form-control" id="date_end" name="date_end" placeholder="End date" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--begin: Datatable -->
<div id="data-rows"></div>
<!--end: Datatable -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
$("#date-range").datepicker({
orientation: "bottom"
});
var options = {
data: {
type: 'remote',
source: {
read: {
url: '{{ url('jo_behind_schedule_rows') }}',
method: 'POST'
}
},
saveState: {
cookie: false,
webstorage: false
},
pageSize: 10,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
columns: [
{
field: 'id',
title: 'JO #'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
{% if is_granted('jo_onestep.edit') or is_granted('jo_advance_order.edit') or is_granted('jo_assign_advance_order.edit') %}
var actions = '<a href="' + row.meta.edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="Edit"><i class="la la-edit"></i></a>';
{% endif %}
return actions;
},
},
{
field: 'type',
title: 'Schedule'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
},
{
field: 'plate_number',
title: 'Plate #'
},
{
field: 'car_model',
title: 'Car Model'
},
{
field: 'customer_name',
title: 'Customer'
},
{
field: 'service_type',
title: 'Service Type',
},
{
field: 'delivery_address',
title: 'Area'
},
{
field: 'date_start',
title: 'Start Date'
},
{
field: 'rider_name',
title: 'Rider'
},
{
field: 'rider_plate_number',
title: 'Rider Plate #'
},
{
field: 'status',
title: 'Status'
},
{
field: 'processor',
title: 'Dispatcher'
}
],
search: {
onEnter: false,
input: $('#data-rows-search'),
delay: 400
}
};
var table = $("#data-rows").mDatatable(options);
// auto refresh table
setInterval(function() {
table.reload();
}, {{ table_refresh_rate }});
$("#rider_list").on("change", function() {
table.search($(this).val(), "rider");
});
$("#date_start").on("change", function() {
var date_start = $(this).val();
var date_end = $("[name='date_end']").val();
var date_array = [date_start, date_end];
table.search(date_array, "schedule_date");
});
$("#date_end").on("change", function() {
console.log($(this).val());
var date_end = $(this).val();
var date_start = $("[name='date_start']").val();
var date_array = [date_start, date_end];
table.search(date_array, "schedule_date");
});
});
</script>
{% endblock %}

View file

@ -93,12 +93,12 @@
},
rows: {
beforeTemplate: function (row, data, index) {
if (data.flag_advance) {
$(row).addClass('m-table__row--danger');
if (data.status == 'In Progress') {
$(row).addClass('m-table__row--is_in_progress');
}
if (data.is_mobile) {
$(row).addClass('m-table__row--is_mobile');
if (data.status == 'Assigned') {
$(row).addClass('m-table__row--is_assigned');
}
}
},
@ -107,6 +107,32 @@
field: 'id',
title: 'JO #'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '';
{% if is_granted('jo_onestep.edit') or is_granted('jo_advance_order.edit') or is_granted('jo_assign_advance_order.edit') %}
actions += '<a href="' + row.meta.edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="Edit"><i class="la la-edit"></i></a>';
{% endif %}
{% if is_granted('jo_pdf.list') %}
actions += '<a href="' + row.meta.pdf_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="PDF" target="_blank"><i class="la la-file-o"></i></a>';
{% endif %}
return actions;
},
},
{
field: 'type',
title: 'Schedule'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
},
{
field: 'plate_number',
title: 'Plate #'
@ -128,12 +154,8 @@
title: 'Area'
},
{
field: 'type',
title: 'Schedule'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
field: 'date_start',
title: 'Start Date'
},
{
field: 'rider_name',
@ -148,22 +170,8 @@
title: 'Status'
},
{
field: 'processor',
title: 'Dispatcher'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
{% if is_granted('jo_onestep.edit') %}
var actions = '<a href="' + row.meta.onestep_edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="Edit"><i class="fa fa-file"></i></a>';
{% endif %}
return actions;
},
field: 'creator',
title: 'Agent'
}
],
search: {

View file

@ -11,7 +11,22 @@ battery_size_tradein_other: Trade-in Other
add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Res-Q for CMB Job Order
country_code_prefix: '+60'
delivery_instructions_label: 'CarFix Details'
delivery_instructions_label: 'Other Details'
pdf_company_name: ENTITY LOGISTICS SDN BHD(1045869-H)
pdf_company_address: 26/1 Lebuh Persekutuan 47301 Petaling Jaya Selangor
pdf_company_number_email: 'Tel: 03-7872 9072 E-Mail: enquiry@entitylogistic.com.my'
pdf_customer_details_header: Customers Details
pdf_footer_scan_to_pay: Scan To Pay
pdf_footer_payments: 'Note: All payments are made payable to'
pdf_footer_company_name: Entity Logistic Sdn Bhd
pdf_footer_bank: 'Malayan Banking: 512307611410'
pdf_footer_return_policy: Goods Sold are not returnable or exchangeable
pdf_footer_warranty_private_label: 'Petrol vehicles / private use : -'
pdf_footer_warranty_private_text_1: 'Warranty 12 months + 6 months extended or 25,000 km (whichever'
pdf_footer_warranty_private_text_2: 'comes first) and on selected models'
pdf_footer_warranty_commercial_label: 'Diesel vehicles / commercial use : -'
pdf_footer_warranty_commercial_text: 'Warranty 6 months extended or 20,000 km (whichever comes first)'
pdf_footer_document: 'THIS IS A COMPUTER GENERATED DOCUMENT - NO SIGNATURE REQUIRED'
# images
image_logo_login: /assets/images/black-text-logo-01.png
@ -19,7 +34,11 @@ icon_login: /assets/images/battery-assist-bm-logo-32x32.png
icon_base_32x32: /assets/images/black-text-logo-01-32x32.png
icon_base_16x16: /assets/images/black-text-logo-01-16x16.png
image_dashboard: /assets/images/century_logo.png
image_jo_pdf: /public/assets/images/black-text-logo-01-115x115.png
image_jo_pdf_century_logo: /public/assets/images/pdf_images/275x75-century-logo-placeholder.png
image_jo_pdf_qr_logo_1: /public/assets/images/pdf_images/110x110-qr-codes-logo-placeholder.png
image_jo_pdf_qr_logo_2: /public/assets/images/pdf_images/110x110-qr-codes-logo-placeholder.png
image_jo_pdf_qr_code_1: /public/assets/images/pdf_images/120x120-qr-codes.png
image_jo_pdf_qr_code_2: /public/assets/images/pdf_images/120x120-qr-codes.png
# default point for maps
default_lat: 3.084216

View file

@ -1,27 +1,46 @@
# text
title_login: Motolite Res-Q | Login
block_title: Motolite Res-Q
title_login: Res-Q for CMB | Login
block_title: Res-Q for CMB
control_panel_sign_in: Sign-in to Control Panel
alt_image_logo_login: Res-Q
alt_image_dashboard: Motolite
copyright: Motolite Res-Q
alt_image_logo_login: Res-Q for CMB
alt_image_dashboard: Res-Q for CMB
copyright: Res-Q for CMB
battery_size_tradein_brand: Trade-in Motolite
battery_size_tradein_premium: Trade-in Premium
battery_size_tradein_other: Trade-in Other
add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Motolite Res-Q Job Order
country_code_prefix: '+63'
delivery_instructions_label: Delivery Instructions
jo_title_pdf: Res-Q for CMB Job Order
country_code_prefix: '+60'
delivery_instructions_label: 'Other Details'
pdf_company_name: ENTITY LOGISTICS SDN BHD(1045869-H)
pdf_company_address: 26/1 Lebuh Persekutuan 47301 Petaling Jaya Selangor
pdf_company_number_email: 'Tel: 03-7872 9072 E-Mail: enquiry@entitylogistic.com.my'
pdf_customer_details_header: Customers Details
pdf_footer_scan_to_pay: Scan To Pay
pdf_footer_payments: 'Note: All payments are made payable to'
pdf_footer_company_name: Entity Logistic Sdn Bhd
pdf_footer_bank: 'Malayan Banking: 512307611410'
pdf_footer_return_policy: Goods Sold are not returnable or exchangeable
pdf_footer_warranty_private_label: 'Petrol vehicles / private use : -'
pdf_footer_warranty_private_text_1: 'Warranty 12 months + 6 months extended or 25,000 km (whichever'
pdf_footer_warranty_private_text_2: 'comes first) and on selected models'
pdf_footer_warranty_commercial_label: 'Diesel vehicles / commercial use : -'
pdf_footer_warranty_commercial_text: 'Warranty 6 months extended or 20,000 km (whichever comes first)'
pdf_footer_document: 'THIS IS A COMPUTER GENERATED DOCUMENT - NO SIGNATURE REQUIRED'
# images
image_logo_login: /assets/images/logo-resq.png
icon_login: /assets/demo/default/media/img/logo/favicon.ico
icon_base_32x32: /assets/images/favicon/favicon-32x32.png
icon_base_16x16: /assets/images/favicon/favicon-16x16.png
image_dashboard: /assets/images/logo-motolite.png
image_jo_pdf: /public/assets/images/logo-resq.png
image_logo_login: /assets/images/black-text-logo-01.png
icon_login: /assets/images/battery-assist-bm-logo-32x32.png
icon_base_32x32: /assets/images/black-text-logo-01-32x32.png
icon_base_16x16: /assets/images/black-text-logo-01-16x16.png
image_dashboard: /assets/images/century_logo.png
image_jo_pdf_century_logo: /public/assets/images/pdf_images/pdf_cmb_logo.png
image_jo_pdf_qr_logo_1: /public/assets/images/pdf_images/touchngo_logo.png
image_jo_pdf_qr_logo_2: /public/assets/images/pdf_images/heart_logo.png
image_jo_pdf_qr_code_1: /public/assets/images/pdf_images/touchngo_qr.png
image_jo_pdf_qr_code_2: /public/assets/images/pdf_images/heart_qr.png
# default point for maps
default_lat: 14.6091
default_long: 121.0223
default_region: ph
default_lat: 3.084216
default_long: 101.6129996
default_region: my

15
utils/clear_jo_data.sql Normal file
View file

@ -0,0 +1,15 @@
delete from jo_event;
delete from invoice_item;
delete from invoice;
delete from ticket;
set foreign_key_checks = 0;
delete from job_order;
set foreign_key_checks = 1;
delete from mobile_session;
delete from customer_vehicle;
delete from customer;
delete from warranty;
update rider set active_jo_id = null;

View file

@ -0,0 +1,18 @@
delete from rider_session where rider_id=12;
delete from rider_session where rider_id=2;
delete from rider_session where rider_id=13;
delete from rider_session where rider_id=11;
delete from rider_schedule where rider_id=12;
delete from rider_schedule where rider_id=2;
delete from rider_schedule where rider_id=13;
delete from rider_schedule where rider_id=11;
delete from rider where id=12;
delete from rider where id=2;
delete from rider where id=13;
delete from rider where id=11;
delete from hub where id=1;
delete from hub where id=2;
delete from hub where id=4;