Compare commits

...

81 commits

Author SHA1 Message Date
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
71 changed files with 7792 additions and 1159 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,8 @@ access_keys:
label: Autoassign Test
- id: jo_hub.list
label: Hub View
- id: jo_behind_schedule.list
label: View Behind Schedule
- id: support
label: Customer Support Access

View file

@ -118,6 +118,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,13 @@ 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"
# map manager
#App\Service\GISManager\Bing: ~
App\Service\GISManager\OpenStreet: ~
@ -229,6 +232,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,13 @@ 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
parent: joborder
- id: jo_assign
acl: jo_assign.list
label: Rider Assignment
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 +118,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 +181,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,14 @@ 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"

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,40 @@ 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"
# map manager
#App\Service\GISManager\Bing: ~
@ -219,6 +221,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;
}
}

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,6 +311,7 @@ class DashboardMap {
$.each(riders, function(id, data) {
var lat = data.latitude;
var lng = data.longitude;
var name = '';
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng);
@ -262,4 +322,31 @@ class DashboardMap {
// 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,8 @@
class MapEventHandler {
constructor(options, dashmap) {
constructor(options, dashmap, ssl) {
this.options = options;
this.dashmap = dashmap;
this.ssl = ssl;
}
connect(user_id, host, port) {
@ -11,7 +12,7 @@ class MapEventHandler {
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
// useSSL: true,
useSSL: this.ssl,
timeout: 3,
invocationContext: this,
onSuccess: this.onConnect.bind(this),
@ -35,6 +36,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) {
@ -55,7 +60,7 @@ class MapEventHandler {
onMessage(msg) {
// console.log(msg);
console.log('received message');
// console.log('received message');
var channel = msg.destinationName;
var chan_split = channel.split('/');
@ -73,13 +78,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 +134,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');
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

@ -286,6 +286,11 @@ class JobOrderController extends Controller
$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 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]);
@ -764,7 +769,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 +875,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 +921,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)) {
@ -1209,6 +1217,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

@ -334,6 +334,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 +607,7 @@ class JobOrder
{
// TODO: validate status
$this->status = $status;
$this->date_status_change = new DateTime();
return $this;
}
@ -596,6 +621,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;
@ -796,6 +826,11 @@ class JobOrder
$this->makeRiderAvailable();
}
public function perform()
{
$this->setStatus(JOStatus::PERFORMED);
}
public function fulfill()
{
$this->setStatus(JOStatus::FULFILLED)
@ -962,5 +997,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,18 @@ 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 COLLECTION = [
'cash' => 'Cash',
'shopee' => 'Shopee',
'lazada' => 'Lazada',
'credit_card' => 'Credit Card',
'bank_transfer' => 'Bank Transfer',
'online_transfer' => 'Online Transfer',
'e_wallet' => 'E-Wallet',
];
}

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,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

View file

@ -31,12 +31,12 @@ use App\Entity\ServiceCharge;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\CMBServiceType;
use App\Ramcar\CMBTradeInType;
use App\Ramcar\JOEventType;
use App\Ramcar\CMBJOEventType;
use App\Ramcar\JOStatus;
use App\Ramcar\CMBWarrantyClass;
use App\Ramcar\DiscountApply;
use App\Ramcar\CMBModeOfPayment;
use App\Ramcar\TransactionOrigin;
use App\Ramcar\CMBTransactionOrigin;
use App\Ramcar\FacilitatedType;
use App\Ramcar\JORejectionReason;
@ -177,13 +177,35 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// get rider information
$rider_name = '';
$rider_plate_number = '';
$rider_id = '';
$rider = $orow->getRider();
if (!empty($rider))
{
$rider_id = $rider->getID();
$rider_name = $rider->getFullName();
$rider_plate_number = $rider->getPlateNumber();
}
// get time when JO was started by rider
$start_event_type = CMBJOEventType::RIDER_START;
$date_start = '';
$start_results = $this->em->getRepository(JOEvent::class)->findby(['type_id' => $start_event_type, 'rider' => $rider_id, 'job_order' => $orow->getID()], ['date_happen' => 'DESC']);
if ($start_results != null)
{
$jo_event = current($start_results);
$date_start = $jo_event->getDateHappen()->format('d M Y g:i A');
}
// get time when JO was finished by rider
$finish_event_type = CMBJOEventType::RIDER_FINISH;
$date_finish = '';
$finish_results = $this->em->getRepository(JOEvent::class)->findby(['type_id' => $finish_event_type, 'rider' => $rider_id, 'job_order' => $orow->getID()], ['date_happen' => 'DESC']);
if ($finish_results != null)
{
$jo_event = current($finish_results);
$date_finish = $jo_event->getDateHappen()->format('d M Y g:i A');
}
// add row data
$row['id'] = $orow->getID();
$row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName();
@ -194,16 +216,18 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$row['status'] = $statuses[$orow->getStatus()];
$row['flag_advance'] = $orow->isAdvanceOrder();
$row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber();
$row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP;
$row['is_mobile'] = $orow->getSource() == CMBTransactionOrigin::MOBILE_APP;
$row['car_model'] = $car_model;
$row['rider_name'] = $rider_name;
$row['rider_plate_number'] = $rider_plate_number;
$row['date_start'] = $date_start;
$row['date_finish'] = $date_finish;
$processor = $orow->getProcessedBy();
if ($processor == null)
$row['processor'] = '';
$creator = $orow->getCreatedBy();
if ($creator == null)
$row['creator'] = '';
else
$row['processor'] = $orow->getProcessedBy()->getFullName();
$row['creator'] = $orow->getCreatedBy()->getFullName();
$assignor = $orow->getAssignedBy();
if ($assignor == null)
@ -350,7 +374,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setPromoDetail($req->request->get('promo_detail', ''))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'));
@ -400,7 +424,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CREATE)
->setTypeID(CMBJOEventType::CREATE)
->setJobOrder($jo);
if ($user != null)
@ -416,7 +440,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
return $error_array;
}
public function processOneStepJobOrder(Request $req, $id)
public function processOneStepJobOrder(Request $req, $id, MQTTClient $mclient)
{
// initialize error list
$error_array = [];
@ -424,33 +448,40 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$em = $this->em;
$jo = $em->getRepository(JobOrder::class)->find($id);
$old_jo_status = null;
$old_rider = null;
if (empty($jo))
{
// new job order
$jo = new JobOrder();
}
else
{
// need to get old values of rider and status to see if we need to change JO status or not
$old_rider = $jo->getRider();
$old_jo_status = $jo->getStatus();
}
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if responsible party is present
if (empty($req->request->get('responsible_party')))
$error_array['responsible_party'] = 'Responsible party is required.';
// check if new customer
if ($req->request->get('new_customer', false))
{
if (empty($req->request->get('customer_customer_notes')))
{
$error_array['customer_customer_notes'] = 'Customer notes cannot be null.';
}
// validate mobile phone
$valid_mobile = $this->cust_handler->validateMobileNumber($req->request->get('customer_phone_mobile'));
$valid_mobile = $this->cust_handler->validateMobileNumber($req->request->get('phone_mobile'));
if (!($valid_mobile))
$error_array['customer_phone_mobile'] = 'Invalid mobile phone number.';
$error_array['phone_mobile'] = 'Invalid mobile phone number.';
// check if plate number is in request
if (empty($req->request->get('cv_plate')))
$error_array['cv_plate'] = 'Plate number is required.';
if (empty(trim($req->request->get('plate_number'))))
$error_array['plate_number'] = 'Plate number is required.';
// find the vehicle using vid
$new_vehicle = $em->getRepository(Vehicle::class)->find($req->request->get('vid'));
@ -464,17 +495,17 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$new_cust = new Customer();
$new_cv = new CustomerVehicle();
$new_cust->setLastName($req->request->get('customer_last_name'))
->setFirstName($req->request->get('customer_first_name'))
->setPhoneMobile($req->request->get('customer_phone_mobile'))
->setPhoneLandline($req->request->get('customer_phone_landline'))
->setPhoneOffice($req->request->get('customer_phone_office'))
->setPhoneFax($req->request->get('customer_phone_fax'))
->setCustomerNotes($req->request->get('customer_customer_notes'));
$new_cust->setLastName($req->request->get('last_name'))
->setFirstName($req->request->get('first_name'))
->setPhoneMobile($req->request->get('phone_mobile'))
->setPhoneLandline($req->request->get('phone_landline'))
->setPhoneOffice($req->request->get('phone_office'))
->setPhoneFax($req->request->get('phone_fax'))
->setCustomerNotes($req->request->get('customer_notes'));
$new_cv->setCustomer($new_cust)
->setVehicle($new_vehicle)
->setPlateNumber($req->request->get('cv_plate'))
->setPlateNumber(trim($req->request->get('plate_number')))
->setModelYear($req->request->get('cv_year'))
->setColor('')
->setStatusCondition('')
@ -526,6 +557,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
}
// check if hub AND rider is selected
$rider_plate_number = '';
if ((empty($req->request->get('hub_id'))) &&
(empty($req->request->get('rider_id')))) {
$error_array['hub'] = 'No hub selected.';
@ -544,36 +576,47 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
if (empty($rider)) {
$error_array['rider'] = 'Invalid rider specified.';
} else {
// check if rider is still available
if (!($rider->isAvailable()))
$error_array['rider'] = 'Selected rider is unavailable.';
$rider_plate_number = $rider->getPlateNumber();
}
}
}
}
// set priority based on rider's existing open job orders
$rider_jos = $rider->getOpenJobOrders();
// get maximum priority then add 1
// NOTE: this can be a bit buggy due to concurrency issues
// ideally have to lock jo table, but that isn't feasible right now
$priority = 0;
foreach ($rider_jos as $rider_jo)
{
if ($priority < $rider_jo->getPriority())
$priority = $rider_jo->getPriority() + 1;
}
// delivery address
if (empty($req->request->get('delivery_address')))
$error_array['delivery_address'] = 'Delivery address is required.';
// get discount and set to meta
$discount = $req->request->get('invoice_discount', []);
// check if discount is greater than 50 or negative number
if (($discount > 50) || ($discount < 0))
$discount = $req->request->get('invoice_discount');
if (($discount > 60) || ($discount < 0))
{
$error_array['invoice_discount'] = 'Invalid discount specified';
}
// get list of service charges
$service_charges = $req->request->get('service_charges', []);
if (empty($error_array))
{
// set priority based on rider's existing open job orders
$rider_jos = $rider->getOpenJobOrders();
// get maximum priority then add 1
// NOTE: this can be a bit buggy due to concurrency issues
// ideally have to lock jo table, but that isn't feasible right now
$priority = 0;
foreach ($rider_jos as $rider_jo)
{
if ($priority < $rider_jo->getPriority())
$priority = $rider_jo->getPriority() + 1;
}
// get current user
$user = $this->security->getUser();
@ -589,22 +632,47 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
->setServiceType($stype)
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setStatus(JOStatus::ASSIGNED)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setPromoDetail($req->request->get('promo_detail', ''))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'))
->setHub($hub)
->setRider($rider)
->setPriority($priority);
->setPriority($priority)
->setResponsibleParty($req->request->get('responsible_party', ''))
->setRiderPlateNum($rider_plate_number);
$jo->addMeta('discount', $discount);
$jo->addMeta('service_charges', $service_charges);
// TODO: what happens if hub and rider are changed
// and JO is already in_transit or in_progress?
// retain old jo status if it's an update JO
// check old rider if it is also a reassignment
// old_rider should be null if JO has been rejected
if (($old_rider == null) && ($old_jo_status == null))
$jo->setStatus(JOStatus::ASSIGNED);
else
{
error_log('not a new JO');
$new_rider = $jo->getRider();
if ($new_rider != $old_rider)
{
// reassignment
$jo->setStatus(JOStatus::ASSIGNED);
}
else
{
if ($old_jo_status != null)
$jo->setStatus($old_jo_status);
}
}
// check if user is null, meaning call to create came from API
if ($user != null)
{
@ -651,7 +719,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CREATE)
->setTypeID(CMBJOEventType::CREATE)
->setJobOrder($jo);
if ($user != null)
@ -661,6 +729,21 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$em->persist($event);
$em->flush();
// check if JO has been reassigned
if ($old_rider != $jo->getRider())
//if ($old_jo_status != $jo->getStatus())
{
error_log('JO has been reassigned');
// TODO: refactor later
$channel = 'rider/' . $rider->getID() . '/events';
$payload = [
'event' => 'new_jo',
'jo_id' => $jo->getID(),
];
$mclient->publish($channel, json_encode($payload));
}
}
}
@ -775,7 +858,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::HUB_ASSIGN)
->setTypeID(CMBJOEventType::HUB_ASSIGN)
->setJobOrder($obj);
if ($user != null)
@ -874,7 +957,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_ASSIGN)
->setTypeID(CMBJOEventType::RIDER_ASSIGN)
->setJobOrder($obj);
if ($user != null)
@ -945,7 +1028,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::FULFILL)
->setTypeID(CMBJOEventType::FULFILL)
->setJobOrder($obj);
// get current user
@ -1043,7 +1126,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CANCEL)
->setTypeID(CMBJOEventType::CANCEL)
->setJobOrder($obj);
// get current user
@ -1153,7 +1236,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// add event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::HUB_ASSIGN)
->setTypeID(CMBJOEventType::HUB_ASSIGN)
->setJobOrder($obj);
if ($user != null)
@ -1353,7 +1436,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// add event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_ASSIGN)
->setTypeID(CMBJOEventType::RIDER_ASSIGN)
->setJobOrder($obj);
if ($user != null)
@ -1425,9 +1508,19 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
public function initializeOneStepForm()
{
$params['obj'] = new JobOrder();
$new_jo = new JobOrder();
// set time schedule
$date_schedule = new DateTime();
// add 45 minutes to time
$date_schedule->add(new DateInterval('PT45M'));
$new_jo->setDateSchedule($date_schedule);
$params['obj'] = $new_jo;
$params['mode'] = 'onestep';
$params['jo_service_charges'] = [];
$params['current_date'] = new DateTime();
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
@ -1448,6 +1541,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$params['mode'] = 'onestep-edit';
$params['cvid'] = $obj->getCustomerVehicle()->getID();
$params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID();
$params['current_date'] = new DateTime();
// get service charges
$sc_array = [];
@ -1465,6 +1559,53 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$params['jo_service_charges'] = $sc_array;
// get odometer
$odometer = $obj->getMeta('odometer');
$params['odometer'] = $odometer;
// get customer email used in JO
$email = $obj->getMeta('customer_email');
$params['email'] = $email;
// get images if any
$jo_extra = $obj->getJOExtra();
$pic_array = [];
$params['signature'] = null;
if ($jo_extra != null)
{
$img_1 = $jo_extra->getImage1Filename();
$img_2 = $jo_extra->getImage2Filename();
$img_3 = $jo_extra->getImage3Filename();
$img_4 = $jo_extra->getImage4Filename();
$other_images = $jo_extra->getOtherImages();
$cust_signature = $jo_extra->getCustomerSignature();
if ($img_1 != null)
$pic_array['image_1'] = $img_1;
if ($img_2 != null)
$pic_array['image_2'] = $img_2;
if ($img_3 != null)
$pic_array['image_3'] = $img_3;
if ($img_4 != null)
$pic_array['image_4'] = $img_4;
if ($other_images != null)
{
foreach ($other_images as $img)
{
$pic_array['other_images'][] = $img;
}
}
$params['signature'] = $cust_signature;
}
$params['jo_pictures'] = $pic_array;
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
@ -1587,7 +1728,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// get template to display
// check transaction origin if walkin
if ($obj->getSource() == TransactionOrigin::WALK_IN)
if ($obj->getSource() == CMBTransactionOrigin::WALK_IN)
$params['template'] = $this->getTwigTemplate('jo_walkin_form');
else
$params['template'] = $this->getTwigTemplate('jo_onestep_form');
@ -2427,8 +2568,19 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
public function initializeWalkinForm()
{
$params['obj'] = new JobOrder();
$new_jo = new JobOrder();
// set time schedule
$date_schedule = new DateTime();
// add 45 minutes to time
$date_schedule->add(new DateInterval('PT45M'));
$new_jo->setDateSchedule($date_schedule);
$params['obj'] = $new_jo;
$params['mode'] = 'walk-in';
$params['discounts'] = $this->generateDiscountOptions();
$params['current_date'] = new DateTime();
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
@ -2454,22 +2606,21 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$jo = new JobOrder();
}
// check if responsible party is present
if (empty($req->request->get('responsible_party')))
$error_array['responsible_party'] = 'Responsible party is required.';
// check if new customer
if ($req->request->get('new_customer', false))
{
if (empty($req->request->get('customer_customer_notes')))
{
$error_array['customer_customer_notes'] = 'Customer notes cannot be null.';
}
// validate mobile phone
$valid_mobile = $this->cust_handler->validateMobileNumber($req->request->get('customer_phone_mobile'));
$valid_mobile = $this->cust_handler->validateMobileNumber($req->request->get('phone_mobile'));
if (!($valid_mobile))
$error_array['customer_phone_mobile'] = 'Invalid mobile phone number.';
$error_array['phone_mobile'] = 'Invalid mobile phone number.';
// check if plate number is in request
if (empty($req->request->get('cv_plate')))
$error_array['cv_plate'] = 'Plate number is required.';
if (empty(trim($req->request->get('plate_number'))))
$error_array['plate_number'] = 'Plate number is required.';
// find the vehicle using vid
$new_vehicle = $em->getRepository(Vehicle::class)->find($req->request->get('vid'));
@ -2484,17 +2635,17 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$new_cust = new Customer();
$new_cv = new CustomerVehicle();
$new_cust->setLastName($req->request->get('customer_last_name'))
->setFirstName($req->request->get('customer_first_name'))
->setPhoneMobile($req->request->get('customer_phone_mobile'))
->setPhoneLandline($req->request->get('customer_phone_landline'))
->setPhoneOffice($req->request->get('customer_phone_office'))
->setPhoneFax($req->request->get('customer_phone_fax'))
->setCustomerNotes($req->request->get('customer_customer_notes'));
$new_cust->setLastName($req->request->get('last_name'))
->setFirstName($req->request->get('first_name'))
->setPhoneMobile($req->request->get('phone_mobile'))
->setPhoneLandline($req->request->get('phone_landline'))
->setPhoneOffice($req->request->get('phone_office'))
->setPhoneFax($req->request->get('phone_fax'))
->setCustomerNotes($req->request->get('customer_notes'));
$new_cv->setCustomer($new_cust)
->setVehicle($new_vehicle)
->setPlateNumber($req->request->get('cv_plate'))
->setPlateNumber(trim($req->request->get('plate_number')))
->setModelYear($req->request->get('cv_year'))
->setColor('')
->setStatusCondition('')
@ -2563,8 +2714,8 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// get discount and set to meta
$discount = $req->request->get('invoice_discount');
// check if discount is greater than 50 or negative number
if (($discount > 50) || ($discount < 0))
// check if discount is greater than 60 or negative number
if (($discount > 60) || ($discount < 0))
$error_array['invoice_discount'] = 'Invalid discount specified';
if (empty($error_array))
@ -2584,13 +2735,14 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setPromoDetail($req->request->get('promo_detail', ''))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'))
->setDeliveryAddress('Walk-in')
->setLandmark('Walk-in')
->setCoordinates($hub_coordinates)
->setHub($hub);
->setHub($hub)
->setResponsibleParty($req->request->get('responsible_party', ''));
$jo->addMeta('discount', $discount);
@ -2640,7 +2792,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CREATE)
->setTypeID(CMBJOEventType::CREATE)
->setJobOrder($jo);
if ($user != null)
@ -2719,6 +2871,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$params['mode'] = 'walk-in-edit';
$params['cvid'] = $obj->getCustomerVehicle()->getID();
$params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID();
$params['current_date'] = new DateTime();
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
@ -2756,7 +2909,10 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$params['trade_in_types'] = CMBTradeInType::getCollection();
$params['facilitated_types'] = FacilitatedType::getCollection();
$params['facilitated_hubs'] = $fac_hubs;
$params['sources'] = TransactionOrigin::getCollection();
$params['sources'] = CMBTransactionOrigin::getCollection();
$params['model_years'] = $this->generateYearOptions();
$params['discounts'] = $this->generateDiscountOptions();
}
protected function initFormTags(&$params)
@ -2843,6 +2999,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$this->template_hash['jo_walkin_form'] = 'job-order/cmb.form.walkin.html.twig';
$this->template_hash['jo_walkin_edit_form'] = 'job-order/cmb.form.walkin.html.twig';
$this->template_hash['jo_popup'] = 'job-order/cmb.popup.html.twig';
$this->template_hash['jo_behind_schedule'] = 'job-order/cmb.list.behindschedule.html.twig';
}
protected function checkTier($tier)
@ -2898,6 +3055,14 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$unlock_route = '';
$jo_status = '';
break;
case 'behind_schedule':
$tier_key = 'jo_behind_schedule';
$tier_name = 'Behind Schedule';
$rows_route = 'jo_behind_schedule_rows';
$edit_route = '';
$unlock_route = '';
$jo_status = JOStatus::ASSIGNED;
break;
default:
throw new AccessDeniedHttpException('No access.');
}
@ -3028,6 +3193,53 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
->setParameter('date_start', $date_start)
->setParameter('date_end', $date_end);
}
break;
case 'behind_schedule':
if (isset($datatable['query']['data-rows-search']))
{
$query->innerJoin('q.cus_vehicle', 'cv')
->innerJoin('q.customer', 'c')
->where('cv.plate_number like :filter')
->orWhere('c.phone_mobile like :filter')
->orWhere('c.first_name like :filter or c.last_name like :filter')
->setParameter('filter', $datatable['query']['data-rows-search'] . '%');
}
if (isset($datatable['query']['rider']))
{
$query->innerJoin('q.rider', 'r')
->andWhere('r.id = :rider_id')
->setParameter('rider_id', $datatable['query']['rider']);
}
if (isset($datatable['query']['schedule_date']))
{
$start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00';
$end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00';
$date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start);
$date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end);
$query->andWhere('q.date_schedule >= :date_start')
->andWhere('q.date_schedule <= :date_end')
->setParameter('date_start', $date_start)
->setParameter('date_end', $date_end);
}
// status should be assigned
$query->andWhere('q.status = :status')
->setParameter('status', $status);
$current_datetime = new DateTime();
$other_date = new DateTime();
$interval = new DateInterval('PT15M');
$other_date->add($interval);
//error_log('current datetime ' . $current_datetime->format('Y-m-d H:i:s'));
//error_log('other_date ' . $other_date->format('Y-m-d H:i:s'));
$query->andWhere('q.date_schedule < :current_datetime OR q.date_schedule <= :other_date')
->setParameter('current_datetime', $current_datetime)
->setParameter('other_date', $other_date);
break;
default:
$query->where('q.status = :status')
@ -3042,9 +3254,21 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
throw new NotFoundHttpException('The item does not exist');
// check transaction origin
if ($jo->getSource() == TransactionOrigin::WALK_IN)
if ($jo->getSource() == CMBTransactionOrigin::WALK_IN)
return 'jo_walkin_edit_form';
else
return 'jo_onestep_edit_form';
}
protected function generateDiscountOptions()
{
$discount_start = 0;
return range($discount_start, 60);
}
protected function generateYearOptions()
{
$start_year = 1950;
return range($start_year, date("Y"));
}
}

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

@ -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

@ -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>

View file

@ -92,57 +92,57 @@
</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">Landline</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>
<label data-field="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>
<input type="text" name="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="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>
@ -324,8 +333,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="rider_plate_number">Rider Plate Number</label>
<input type="text" id="rider-plate-number" name="rider_plate_number" class="form-control m-input" value="{{ obj.getRiderPlateNum|default('') }}" >
<div class="form-control-feedback hide" data-field="rider_plate_number"></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 +438,6 @@
<th>Branch</th>
<th>Contact Numbers</th>
<th>Distance in KM</th>
<th>Action</th>
</tr>
</thead>
<tbody id="nearest_hubs">
@ -425,7 +447,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 +483,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 +515,73 @@
</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">
<div class="portrait-box" style="background-image: url('{{ '/uploads/jo_extra/' ~ picture }}');" ></div>
</div>
{% endif %}
{% if key == 'image_2' %}
<div class="col-lg-2">
<div class="portrait-box" style="background-image: url('{{ '/uploads/jo_extra/' ~ picture }}');" ></div>
</div>
{% endif %}
{% if key == 'image_3' %}
<div class="col-lg-2">
<div class="portrait-box" style="background-image: url('{{ '/uploads/jo_extra/' ~ picture }}');" ></div>
</div>
{% endif %}
{% if key == 'image_4' %}
<div class="col-lg-2">
<div class="portrait-box" style="background-image: url('{{ '/uploads/jo_extra/' ~ picture }}');" ></div>
</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>
{% 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 +631,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 +641,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 +859,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 +872,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 +912,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 +951,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 +985,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 +1044,7 @@ $(function() {
// clear rider field
$('#rider-field').val('');
$('#rider-plate-number').val('');
selected_rider = '';
// get riders of hub
@ -931,7 +1065,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 +1094,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 +1164,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;
@ -1191,6 +1327,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 +1551,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 +1896,53 @@ $(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',
});
}
});
});
</script>
{% endblock %}

View file

@ -92,57 +92,57 @@
</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">Landline</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>
<label data-field="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>
<input type="text" name="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="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>
@ -323,13 +332,22 @@
</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>
</div>
@ -357,8 +375,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 +383,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 +410,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 +420,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">
@ -727,6 +757,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 +1125,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');
}
}
},
@ -132,6 +136,14 @@
field: 'date_schedule',
title: 'Scheduled Date'
},
{
field: 'date_start',
title: 'Start Date'
},
{
field: 'date_finish',
title: 'Finish Date'
},
{
field: 'rider_name',
title: 'Rider'
@ -145,8 +157,8 @@
title: 'Status'
},
{
field: 'processor',
title: 'Dispatcher'
field: 'creator',
title: 'Agent'
},
{
field: 'Actions',

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: '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: 'type',
title: 'Schedule'
},
{
field: 'date_start',
title: 'Start Date'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
},
{
field: 'rider_name',
title: 'Rider'
},
{
field: 'rider_plate_number',
title: 'Rider Plate #'
},
{
field: 'status',
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;
},
}
],
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');
}
}
},
@ -131,6 +131,10 @@
field: 'type',
title: 'Schedule'
},
{
field: 'date_start',
title: 'Start Date'
},
{
field: 'date_schedule',
title: 'Scheduled Date'
@ -148,8 +152,8 @@
title: 'Status'
},
{
field: 'processor',
title: 'Dispatcher'
field: 'creator',
title: 'Agent'
},
{
field: 'Actions',

View file

@ -1,27 +1,27 @@
# 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: 'CarFix Details'
# 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: /public/assets/images/black-text-logo-01-115x115.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;