diff --git a/config/acl.yaml b/config/acl.yaml index 90b2f06d..c6ccc243 100644 --- a/config/acl.yaml +++ b/config/acl.yaml @@ -215,6 +215,20 @@ access_keys: - id: rider.delete label: Delete + - id: servicecharge + label: Service Charge + acls: + - id: service_charge.menu + label: Menu + - id: service_charge.list + label: List + - id: service_charge.add + label: Add + - id: service_charge.update + label: Update + - id: service_charge.delete + label: Delete + - id: joborder label: Job Order acls: @@ -246,6 +260,10 @@ access_keys: label: One-step Process - id: jo_onestep.edit label: One-step Process Edit + - id: jo_walkin.form + label: Walk-in + - id: jo_walkin.edit + label: Walk-in Edit - id: support label: Customer Support Access diff --git a/config/cmb.menu.yaml b/config/cmb.menu.yaml new file mode 100644 index 00000000..9f28b315 --- /dev/null +++ b/config/cmb.menu.yaml @@ -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 diff --git a/config/menu.yaml b/config/menu.yaml index a64dd8b4..9f28b315 100644 --- a/config/menu.yaml +++ b/config/menu.yaml @@ -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 @@ -102,21 +106,9 @@ main_menu: acl: jo_onestep.form label: One-step Process parent: joborder - - 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 + - id: jo_walkin_form + acl: jo_walkin.form + label: Walk-in parent: joborder - id: jo_open acl: jo_open.list diff --git a/config/resq.menu.yaml b/config/resq.menu.yaml new file mode 100644 index 00000000..b0096cf5 --- /dev/null +++ b/config/resq.menu.yaml @@ -0,0 +1,183 @@ +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: 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 diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index bfd41df9..d5cd8dfc 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -206,3 +206,23 @@ jo_tracker: controller: App\Controller\JobOrderController::tracker methods: [GET] +jo_walkin_form: + path: /job-order/walk-in + controller: App\Controller\JobOrderController::walkInForm + methods: [GET] + +jo_walkin_submit: + path: /job-order/walk-in + controller: App\Controller\JobOrderController::walkInSubmit + methods: [POST] + +jo_walkin_edit_form: + path: /job-order/walk-in/{id} + controller: App\Controller\JobOrderController::walkInEditForm + methods: [GET] + +jo_walkin_edit_submit: + path: /job-order/walk-in/{id} + controller: App\Controller\JobOrderController::walkInEditSubmit + methods: [POST] + diff --git a/config/routes/rider.yaml b/config/routes/rider.yaml index 70ddd91d..1934a1b1 100644 --- a/config/routes/rider.yaml +++ b/config/routes/rider.yaml @@ -41,3 +41,18 @@ rider_ajax_popup: path: /riders/{id}/popup controller: App\Controller\RiderController::popupInfo methods: [GET] + +rider_active_jo: + path: /riders/{id}/activejo/{jo_id} + controller: App\Controller\RiderController::riderActiveJO + methods: [GET] + +rider_priority_up_jo: + path: /riders/{id}/priority_up/{jo_id} + controller: App\Controller\RiderController::priorityUpJO + methods: [GET] + +rider_priority_down_jo: + path: /riders/{id}/priority_down/{jo_id} + controller: App\Controller\RiderController::priorityDownJO + methods: [GET] diff --git a/config/routes/service_charge.yaml b/config/routes/service_charge.yaml new file mode 100644 index 00000000..c6206371 --- /dev/null +++ b/config/routes/service_charge.yaml @@ -0,0 +1,34 @@ +service_charge_list: + path: /service_charges + controller: App\Controller\ServiceChargeController::index + +service_charge_rows: + path: /service_charges/rows + controller: App\Controller\ServiceChargeController::rows + methods: [POST] + +service_charge_create: + path: /service_charges/create + controller: App\Controller\ServiceChargeController::addForm + methods: [GET] + +service_charge_create_submit: + path: /service_charges/create + controller: App\Controller\ServiceChargeController::addSubmit + methods: [POST] + +service_charge_update: + path: /service_charges/{id} + controller: App\Controller\ServiceChargeController::updateForm + methods: [GET] + +service_charge_update_submit: + path: /service_charges/{id} + controller: App\Controller\ServiceChargeController::updateSubmit + methods: [POST] + +service_charge_delete: + path: /service_charges/{id} + controller: App\Controller\ServiceChargeController::destroy + methods: [DELETE] + diff --git a/initial_sql/sql_insert_service_charge_data.sql b/initial_sql/sql_insert_service_charge_data.sql new file mode 100644 index 00000000..bab3fdf5 --- /dev/null +++ b/initial_sql/sql_insert_service_charge_data.sql @@ -0,0 +1 @@ +INSERT INTO `service_charge` VALUES(1,'Bangi',20),(2,'Banting',30),(3,'Bdr Saujana Utama',20),(4,'Bdr Seri Coalfields',30),(5,'Bdr Baru Bangi',20),(6,'Bdr Saujana Putra',20),(7,'Bukit Beruntung',30),(8,'Cyberjaya',20),(9,'Dengkil',30),(10,'Hulu Langat',20),(11,'Jenjarom',30),(12,'Klia',30),(13,'Meru',20),(14,'Port Klang',20),(15,'Pulau Indah',30),(16,'Puncak Alam',20),(17,'Putrajaya',20),(18,'Rawang',30),(19,'Salak Tinggi',30),(20,'Semenyih',20),(21,'Sepang',30),(22,'Serendah',30),(23,'Sungai Buloh',20),(24,'Teluk Panglima Garang',30),(25,'Uitm Puncak Alam',20),(26,'12am - 7am',10),(27,'Out of define Klg Valley',20),(28,'Airport',35),(29,'Jump start',50),(30,'Product warranty service charge - existing BA customer',20),(31,'Product warranty service charge - non BA customer',40); diff --git a/public/assets/js/dashboard_map.js b/public/assets/js/dashboard_map.js index 4691a11d..6cba7d68 100644 --- a/public/assets/js/dashboard_map.js +++ b/public/assets/js/dashboard_map.js @@ -51,6 +51,32 @@ class DashboardMap { return this.map; } + switchRiderStatus(rider_id, rider_status) { + console.log('switching rider ' + rider_id + ' to ' + rider_status); + + // find the marker + console.log(this.rider_markers); + if (this.rider_markers.hasOwnProperty(rider_id)) { + var marker = this.rider_markers[rider_id]; + } else { + // TODO: call ajax to get location and create marker + console.log('marker not found for rider'); + return true; + } + + // add it to proper layer group + console.log(rider_status); + if (rider_status == 'available') { + this.layer_groups.rider_active_jo.removeLayer(marker); + this.layer_groups.rider_available.addLayer(marker); + marker.setIcon(this.options.icons.rider_available); + } else if (rider_status == 'jo') { + this.layer_groups.rider_available.removeLayer(marker); + this.layer_groups.rider_active_jo.addLayer(marker); + marker.setIcon(this.options.icons.rider_active_jo); + } + } + putMarker(id, lat, lng, markers, icon, layer_group, popup_url) { var my = this; // existing marker @@ -131,6 +157,19 @@ class DashboardMap { ); } + removeRiderMarker(id) { + console.log('removing rider marker for ' + id); + var markers = this.rider_markers; + + if (!markers.hasOwnProperty(id)) { + console.log('no such marker to remove'); + return; + } + + this.layer_groups.rider_active_jo.removeLayer(markers[id]); + this.layer_groups.rider_available.removeLayer(markers[id]); + } + loadLocations(location_url) { console.log(this.rider_markers); var my = this; diff --git a/public/assets/js/map_mqtt.js b/public/assets/js/map_mqtt.js index 6eac1f65..8cf3a6af 100644 --- a/public/assets/js/map_mqtt.js +++ b/public/assets/js/map_mqtt.js @@ -27,18 +27,26 @@ class MapEventHandler { console.log('mqtt connected!'); var my = icontext.invocationContext; - // subscribe to rider locations if (my.options.track_rider) { + // subscribe to rider locations console.log('subscribing to ' + my.options.channels.rider_location); my.mqtt.subscribe(my.options.channels.rider_location); + + // subscribe to rider status + console.log('subscribing to ' + my.options.channels.rider_status); + my.mqtt.subscribe(my.options.channels.rider_status); } - // subscribe to jo locations if (my.options.track_jo) { + // subscribe to jo locations console.log('subscribing to ' + my.options.channels.jo_location); my.mqtt.subscribe(my.options.channels.jo_location); + + // subscribe to jo status + console.log('subscribing to ' + my.options.channels.jo_status); my.mqtt.subscribe(my.options.channels.jo_status); } + } onMessage(msg) { @@ -75,8 +83,24 @@ 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); break; + case "status": + console.log("got status for rider " + chan_split[1] + " - " + payload); + switch (payload) { + case 'available': + this.dashmap.switchRiderStatus(chan_split[1], 'available'); + break; + case 'jo': + console.log('jo status'); + this.dashmap.switchRiderStatus(chan_split[1], 'jo'); + break; + case 'logout': + this.dashmap.removeRiderMarker(chan_split[1]); + break; + } + break; } } @@ -102,6 +126,7 @@ class MapEventHandler { this.dashmap.putCustomerMarker(id, lat, lng); break; case "status": + console.log("got status for jo " + payload); switch (payload) { case 'cancel': case 'fulfill': diff --git a/src/Controller/CustomerController.php b/src/Controller/CustomerController.php index 7421fec0..9b62df8c 100644 --- a/src/Controller/CustomerController.php +++ b/src/Controller/CustomerController.php @@ -181,7 +181,8 @@ class CustomerController extends Controller public function getCustomerVehicles(Request $req, CustomerHandlerInterface $cust_handler) { - if (!$this->isGranted('jo_in.list')) { + if ((!$this->isGranted('jo_onestep.form')) || + (!$this->isGranted('jo_walkin.form'))) { $exception = $this->createAccessDeniedException('No access.'); throw $exception; } diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index f70d5484..1d1d84c1 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -12,6 +12,7 @@ use App\Entity\Battery; use App\Entity\JobOrder; use App\Entity\VehicleManufacturer; use App\Entity\Vehicle; +use App\Entity\Hub; use App\Service\InvoiceGeneratorInterface; use App\Service\JobOrderHandlerInterface; @@ -275,12 +276,14 @@ class JobOrderController extends Controller { $rows[$key]['meta']['reassign_hub_url'] = $this->generateUrl('jo_open_hub_form', ['id' => $jo_id]); $rows[$key]['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $jo_id]); - $rows[$key]['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $jo_id]); + // $rows[$key]['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $jo_id]); + $rows[$key]['meta']['edit_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $tier_params['edit_route']), ['id' => $jo_id]); $rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]); } else { - $rows[$key]['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $jo_id]); + // $rows[$key]['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $jo_id]); + $rows[$key]['meta']['update_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $tier_params['edit_route']), ['id' => $jo_id]); $rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]); $rows[$key]['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $jo_id]); } @@ -429,7 +432,7 @@ class JobOrderController extends Controller * @Menu(selected="jo_fulfill") */ public function fulfillmentForm(JobOrderHandlerInterface $jo_handler, $id, - GISManagerInterface $gis) + GISManagerInterface $gis, EntityManagerInterface $em) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); @@ -442,6 +445,8 @@ class JobOrderController extends Controller throw $this->createNotFoundException($e->getMessage()); } + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + $params['vmakes'] = $em->getRepository(Vehicle::class)->findAll(); $params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_fulfill'); $params['map_js_file'] = $gis->getJSJOFile(); @@ -604,7 +609,7 @@ class JobOrderController extends Controller * @Menu(selected="jo_all") */ public function allForm($id, JobOrderHandlerInterface $jo_handler, - GISManagerInterface $gis) + GISManagerInterface $gis, EntityManagerInterface $em) { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); @@ -617,6 +622,8 @@ class JobOrderController extends Controller throw $this->createNotFoundException($e->getMessage()); } + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + $params['vmakes'] = $em->getRepository(Vehicle::class)->findAll(); $params['return_url'] = $this->generateUrl('jo_all'); $params['submit_url'] = ''; $params['map_js_file'] = $gis->getJSJOFile(); @@ -691,6 +698,7 @@ class JobOrderController extends Controller $items = $req->request->get('items'); $promo_id = $req->request->get('promo'); $cvid = $req->request->get('cvid'); + $service_charges = $req->request->get('service_charges', []); $em = $this->getDoctrine()->getManager(); @@ -734,11 +742,7 @@ class JobOrderController extends Controller } */ - // TODO: this snippet should be in the invoice generator - $error = $ic->invoicePromo($criteria, $promo_id); - - if (!$error) - $error = $ic->invoiceBatteries($criteria, $items); + $error = $ic->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); if ($error) { @@ -880,7 +884,6 @@ class JobOrderController extends Controller return $this->json([ 'success' => 'Changes have been saved!' ]); - } /** @@ -962,4 +965,93 @@ class JobOrderController extends Controller return $this->render('job-order/tracker.html.twig', $params); } + + /** + * @Menu(selected="jo_walkin_form") + */ + public function walkInForm(EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler) + { + $this->denyAccessUnlessGranted('jo_walkin.form', null, 'No access.'); + + $params = $jo_handler->initializeWalkinForm(); + $params['submit_url'] = $this->generateUrl('jo_walkin_submit'); + $params['return_url'] = $this->generateUrl('jo_walkin_form'); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + $params['vmakes'] = $em->getRepository(Vehicle::class)->findAll(); + $params['hubs'] = $em->getRepository(Hub::class)->findAll(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function walkInSubmit(Request $req, JobOrderHandlerInterface $jo_handler) + { + $this->denyAccessUnlessGranted('jo_walkin.form', null, 'No access.'); + + // initialize error list + $error_array = []; + $id = -1; + $error_array = $jo_handler->processWalkinJobOrder($req, $id); + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @Menu(selected="jo_walkin_edit_form") + */ + public function walkInEditForm($id, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler) + { + $this->denyAccessUnlessGranted('jo_walkin.edit', null, 'No access.'); + + $params = $jo_handler->initializeWalkinEditForm($id); + $params['submit_url'] = $this->generateUrl('jo_walkin_edit_submit', ['id' => $id]); + $params['return_url'] = $this->generateUrl('jo_open'); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + $params['vmakes'] = $em->getRepository(Vehicle::class)->findAll(); + $params['hubs'] = $em->getRepository(Hub::class)->findAll(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function walkInEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) + { + $this->denyAccessUnlessGranted('jo_walkin.edit', null, 'No access.'); + + $error_array = []; + $error_array = $jo_handler->processWalkinJobOrder($req, $id); + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + } diff --git a/src/Controller/RiderController.php b/src/Controller/RiderController.php index 838b558b..43f5d6d6 100644 --- a/src/Controller/RiderController.php +++ b/src/Controller/RiderController.php @@ -7,7 +7,10 @@ use App\Entity\Rider; use App\Entity\RiderSchedule; use App\Entity\Hub; use App\Entity\User; +use App\Entity\JobOrder; + use App\Service\FileUploader; +use App\Service\MQTTClient; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; @@ -18,6 +21,8 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; + use Catalyst\MenuBundle\Annotation\Menu; use DateTime; @@ -510,4 +515,85 @@ class RiderController extends Controller return $this->render('rider/popup.html.twig', [ 'rider' => $rider ]); } + + /** + * @ParamConverter("rider", class="App\Entity\Rider") + */ + public function riderActiveJO(EntityManagerInterface $em, MQTTClient $mclient, Rider $rider, $jo_id) + { + $jo = $em->getRepository(JobOrder::class)->find($jo_id); + $rider->setActiveJobOrder($jo); + $em->flush(); + + // TODO: trigger what needs triggering in rider app + $payload = [ + 'event' => 'cancelled', + 'reason' => 'Reprioritization', + 'jo_id' => $jo->getID(), + ]; + $mclient->sendRiderEvent($jo, $payload); + + + return $this->redirecttoRoute('rider_update', ['id' => $rider->getID()]); + } + + /** + * @ParamConverter("rider", class="App\Entity\Rider") + * @ParamConverter("jo", class="App\Entity\JobOrder", options={"id": "jo_id"}) + */ + public function priorityUpJO(EntityManagerInterface $em, Rider $rider, JobOrder $jo) + { + $jos = $rider->getOpenJobOrders(); + + // set new priority + $old_prio = $jo->getPriority(); + $new_prio = $old_prio - 1; + $jo->setPriority($new_prio); + + // go through all rider open JOs and set priority when needed + foreach ($jos as $rider_jo) + { + // check if it's the same + if ($rider_jo->getID() == $jo->getID()) + continue; + + // if priority is the same as old priority, move it down + if ($new_prio == $rider_jo->getPriority()) + $rider_jo->setPriority($rider_jo->getPriority() + 1); + } + + $em->flush(); + + return $this->redirecttoRoute('rider_update', ['id' => $rider->getID()]); + } + + /** + * @ParamConverter("rider", class="App\Entity\Rider") + * @ParamConverter("jo", class="App\Entity\JobOrder", options={"id": "jo_id"}) + */ + public function priorityDownJO(EntityManagerInterface $em, Rider $rider, JobOrder $jo) + { + $jos = $rider->getOpenJobOrders(); + + // set new priority + $old_prio = $jo->getPriority(); + $new_prio = $old_prio + 1; + $jo->setPriority($new_prio); + + // go through all rider open JOs and set priority when needed + foreach ($jos as $rider_jo) + { + // check if it's the same + if ($rider_jo->getID() == $jo->getID()) + continue; + + // if priority is the same as old priority, move it down + if ($new_prio == $rider_jo->getPriority()) + $rider_jo->setPriority($rider_jo->getPriority() - 1); + } + + $em->flush(); + + return $this->redirecttoRoute('rider_update', ['id' => $rider->getID()]); + } } diff --git a/src/Controller/RoleController.php b/src/Controller/RoleController.php index 754ab40c..da4de74c 100644 --- a/src/Controller/RoleController.php +++ b/src/Controller/RoleController.php @@ -248,7 +248,7 @@ class RoleController extends Controller if (!$row->isSuperAdmin()) { // clear first - $row->clearACLAttributes(); + $row->clearACLAccess(); // then add $acl_attribs = $req->request->get('acl'); diff --git a/src/Controller/ServiceChargeController.php b/src/Controller/ServiceChargeController.php new file mode 100644 index 00000000..7208e306 --- /dev/null +++ b/src/Controller/ServiceChargeController.php @@ -0,0 +1,268 @@ +denyAccessUnlessGranted('service_charge.list', null, 'No access.'); + + return $this->render('service-charge/list.html.twig'); + } + + public function rows(Request $req) + { + $this->denyAccessUnlessGranted('service_charge.list', null, 'No access.'); + + // build query + $qb = $this->getDoctrine() + ->getRepository(ServiceCharge::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + + // add fitlers to count query + $this->setQueryFilters($datatable, $tquery); + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q'); + + // add filters to query + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.id', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['name'] = $orow->getName(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('service_charge.update')) + $row['meta']['update_url'] = $this->generateUrl('service_charge_update', ['id' => $row['id']]); + if ($this->isGranted('service.delete')) + $row['meta']['delete_url'] = $this->generateUrl('service_charge_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + /** + * @Menu(selected="service_charge_list") + */ + public function addForm() + { + $this->denyAccessUnlessGranted('service_charge.add', null, 'No access.'); + + $params = []; + $params['obj'] = new ServiceCharge(); + $params['mode'] = 'create'; + + // response + return $this->render('service-charge/form.html.twig', $params); + } + + public function addSubmit(Request $req, ValidatorInterface $validator, EntityManagerInterface $em) + { + $this->denyAccessUnlessGranted('service_charge.add', null, 'No access.'); + + // create new object + $row = new ServiceCharge(); + + // set and save values + $row->setName($req->request->get('name')); + $row->setAmount($req->request->get('amount')); + + // validate + $errors = $validator->validate($row); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } else { + // validated! save the entity + $em->persist($row); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + } + + /** + * @Menu(selected="service_charge_list") + * @ParamConverter("sc", class="App\Entity\ServiceCharge") + */ + public function updateForm(ServiceCharge $sc) + { + $this->denyAccessUnlessGranted('service_charge.update', null, 'No access.'); + + $params = []; + $params['mode'] = 'update'; + + if ($sc == null) + throw $this->createNotFoundException('The item does not exist'); + + $params['obj'] = $sc; + + // response + return $this->render('service-charge/form.html.twig', $params); + } + + /** + * @ParamConverter("sc", class="App\Entity\ServiceCharge") + */ + public function updateSubmit(Request $req, ValidatorInterface $validator, + ServiceCharge $sc, EntityManagerInterface $em) + { + $this->denyAccessUnlessGranted('service_charge.update', null, 'No access.'); + + // make sure this row exists + if ($sc == null) + throw $this->createNotFoundException('The item does not exist'); + + // set and save values + $sc->setName($req->request->get('name')); + $sc->setAmount($req->request->get('amount')); + + // validate + $errors = $validator->validate($sc); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } else { + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + } + + /** + * @Menu(selected="service_list") + * @ParamConverter("sc", class="App\Entity\ServiceCharge") + */ + public function destroy(ServiceCharge $sc, EntityManagerInterface $em) + { + $this->denyAccessUnlessGranted('service_charge.delete', null, 'No access.'); + + $params = []; + + if ($sc == null) + throw $this->createNotFoundException('The item does not exist'); + + // delete this row + $em->remove($sc); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } + + protected function setQueryFilters($datatable, &$query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } + +} diff --git a/src/Entity/JobOrder.php b/src/Entity/JobOrder.php index 36834cc1..8f5e78e7 100644 --- a/src/Entity/JobOrder.php +++ b/src/Entity/JobOrder.php @@ -280,6 +280,20 @@ class JobOrder */ protected $hub_rejections; + // priority order for riders + // NOTE: this is a workaround since changeing rider to jo rider assignment with details requires + // too many changes and may break too many things. + /** + * @ORM\Column(type="integer", options={"default": 0})) + */ + protected $priority; + + // meta + /** + * @ORM\Column(type="json") + */ + protected $meta; + public function __construct() { $this->date_create = new DateTime(); @@ -297,6 +311,9 @@ class JobOrder $this->trade_in_type = null; $this->flag_rider_rating = false; $this->flag_coolant = false; + + $this->priority = 0; + $this->meta = []; } public function getID() @@ -802,4 +819,30 @@ class JobOrder { return $this->hub_rejections; } + + public function setPriority($priority) + { + $this->priority = $priority; + return $this; + } + + public function getPriority() + { + return $this->priority; + } + + public function addMeta($id, $value) + { + $this->meta[$id] = $value; + return $this; + } + + public function getMeta($id) + { + // return null if we don't have it + if (!isset($this->meta[$id])) + return null; + + return $this->meta[$id]; + } } diff --git a/src/Entity/Rider.php b/src/Entity/Rider.php index f9331c03..502d93bc 100644 --- a/src/Entity/Rider.php +++ b/src/Entity/Rider.php @@ -61,9 +61,17 @@ class Rider // job orders that the rider has done /** * @ORM\OneToMany(targetEntity="JobOrder", mappedBy="rider") + * @ORM\OrderBy({"priority" = "ASC"}) */ protected $job_orders; + // rider's active job order since we now support multiple job orders per rider + /** + * @ORM\OneToOne(targetEntity="JobOrder") + * @ORM\JoinColumn(name="active_jo_id", referencedColumnName="id") + */ + protected $active_job_order; + // picture of rider /** * @ORM\Column(type="string", nullable=true) @@ -122,6 +130,8 @@ class Rider $this->flag_active = true; $this->username = null; $this->password = ''; + + $this->active_job_order = null; } public function getID() @@ -300,8 +310,30 @@ class Rider return $this->password; } + public function setActiveJobOrder(JobOrder $jo = null) + { + $this->active_job_order = $jo; + return $this; + } + public function getActiveJobOrder() { + // check if we have set a custom active + if ($this->active_job_order != null) + { + switch ($this->active_job_order->getStatus()) + { + // if jo is open, return it + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + return $this->active_job_order; + } + + // if active jo is not open, get the next open one + } + + // no custom active job order $active_status = [ JOStatus::ASSIGNED, JOStatus::IN_TRANSIT, @@ -315,6 +347,20 @@ class Rider return $this->job_orders->matching($criteria)[0]; } + public function getOpenJobOrders() + { + $active_status = [ + JOStatus::ASSIGNED, + JOStatus::IN_TRANSIT, + JOStatus::IN_PROGRESS, + ]; + + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->in('status', $active_status)); + + return $this->job_orders->matching($criteria); + } + public function getSessions() { return $this->sessions; diff --git a/src/Entity/ServiceCharge.php b/src/Entity/ServiceCharge.php new file mode 100644 index 00000000..98f96c77 --- /dev/null +++ b/src/Entity/ServiceCharge.php @@ -0,0 +1,66 @@ +amount = 0; + } + + public function getID() + { + return $this->id; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setAmount($amount) + { + $this->amount = $amount; + return $this; + } + + public function getAmount() + { + return $this->amount; + } +} diff --git a/src/Entity/Warranty.php b/src/Entity/Warranty.php index 113fbb93..d2143966 100644 --- a/src/Entity/Warranty.php +++ b/src/Entity/Warranty.php @@ -149,7 +149,7 @@ class Warranty return $this->id; } - public function setSerial($serial) + public function setSerial($serial = null) { $this->serial = $serial; return $this; diff --git a/src/EventListener/JobOrderActiveCacheListener.php b/src/EventListener/JobOrderActiveCacheListener.php index 541ce158..89eedd01 100644 --- a/src/EventListener/JobOrderActiveCacheListener.php +++ b/src/EventListener/JobOrderActiveCacheListener.php @@ -35,6 +35,7 @@ class JobOrderActiveCacheListener $this->processActiveJO($jo); break; // inactive + // NOTE: should never really get here since it's creation case JOStatus::CANCELLED: $this->processInactiveJO($jo, 'cancel'); break; @@ -84,22 +85,55 @@ class JobOrderActiveCacheListener $coords = $jo->getCoordinates(); // TODO: do we put the key in config? + // send jo location $this->mqtt->publish( 'jo/' . $jo->getID() . '/location', $coords->getLatitude() . ':' . $coords->getLongitude() ); + + // TODO: do we still need to send jo status? + + // send rider status + $rider = $jo->getRider(); + if ($rider != null) + { + $this->mqtt->publish( + 'rider/' . $rider->getID() . '/status', + 'jo' + ); + } } protected function processInactiveJO($jo, $status = 'cancel') { + error_log('got inactive jo, sending mqtt message for ' . $jo->getID()); // remove from redis cache $this->jo_cache->removeActiveJobOrder($jo); - // TODO: publich to mqtt + // publish to mqtt + // send jo status $this->mqtt->publish( 'jo/' . $jo->getID() . '/status', $status ); + + // send rider status + $rider = $jo->getRider(); + if ($rider != null) + { + // check if rider has any queued jobs + $open_jos = $rider->getOpenJobOrders(); + if (count($open_jos) > 0) + $rider_status = 'jo'; + else + $rider_status = 'available'; + + // send status + $this->mqtt->publish( + 'rider/' . $rider->getID() . '/status', + $rider_status + ); + } } } diff --git a/src/Ramcar/InvoiceCriteria.php b/src/Ramcar/InvoiceCriteria.php index 5ea5e623..6665226d 100644 --- a/src/Ramcar/InvoiceCriteria.php +++ b/src/Ramcar/InvoiceCriteria.php @@ -5,6 +5,7 @@ namespace App\Ramcar; use App\Entity\Battery; use App\Entity\Promo; use App\Entity\CustomerVehicle; +use App\Entity\ServiceCharge; class InvoiceCriteria { @@ -12,6 +13,8 @@ class InvoiceCriteria protected $promos; protected $cv; protected $flag_coolant; + protected $discount; + protected $service_charges; // entries are battery and trade-in combos protected $entries; @@ -23,6 +26,8 @@ class InvoiceCriteria $this->entries = []; $this->cv = null; $this->flag_coolant = false; + $this->discount = 0; + $this->service_charges = []; } public function setServiceType($stype) @@ -125,4 +130,27 @@ class InvoiceCriteria { return $this->flag_coolant; } + + public function setDiscount($discount) + { + $this->discount = $discount; + return $this; + } + + public function getDiscount() + { + return $this->discount; + } + + public function addServiceCharge(ServiceCharge $service_charge) + { + $this->service_charges[] = $service_charge; + return $this; + } + + public function getServiceCharges() + { + return $this->service_charges; + } + } diff --git a/src/Ramcar/TransactionOrigin.php b/src/Ramcar/TransactionOrigin.php index baf412f1..c4d69ad2 100644 --- a/src/Ramcar/TransactionOrigin.php +++ b/src/Ramcar/TransactionOrigin.php @@ -9,12 +9,15 @@ class TransactionOrigin extends NameValue const FACEBOOK = 'facebook'; const VIP = 'vip'; const MOBILE_APP = 'mobile_app'; + const WALK_IN = 'walk_in'; + // 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', ]; } diff --git a/src/Service/CustomerHandler/CMBCustomerHandler.php b/src/Service/CustomerHandler/CMBCustomerHandler.php index d8107514..371f674b 100644 --- a/src/Service/CustomerHandler/CMBCustomerHandler.php +++ b/src/Service/CustomerHandler/CMBCustomerHandler.php @@ -320,7 +320,9 @@ class CMBCustomerHandler implements CustomerHandlerInterface $nerror_array = []; $verror_array = []; - // TODO: validate mobile numbers + if (!($this->validateMobileNumber($req->request->get('phone_mobile')))) + $error_array['phone_mobile'] = 'Invalid mobile phone number.'; + // TODO: validate vehicles // custom validation for vehicles @@ -685,6 +687,18 @@ class CMBCustomerHandler implements CustomerHandlerInterface } } + public function validateMobileNumber($mobile_number) + { + if (empty($mobile_number)) + return true; + if (strlen($mobile_number) != 9) + return false; + if(preg_match('/^\d+$/',$mobile_number)) + return true; + + return false; + } + // check if datatable filter is present and append to query protected function setQueryFilters($datatable, &$query) { if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { diff --git a/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php b/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php index f25348a4..534600fb 100644 --- a/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php @@ -17,8 +17,8 @@ use App\Ramcar\FuelType; use App\Entity\Invoice; use App\Entity\InvoiceItem; use App\Entity\Battery; -use App\Entity\Promo; use App\Entity\User; +use App\Entity\ServiceCharge; use App\Service\InvoiceGeneratorInterface; @@ -106,9 +106,12 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface // break; } - // TODO: check if any promo is applied - // apply discounts - $promos = $criteria->getPromos(); + // process service charges if any + $service_charges = $criteria->getServiceCharges(); + if (count($service_charges) > 0) + { + $this->processServiceCharges($total, $criteria, $invoice); + } // get current user $user = $this->security->getUser(); @@ -131,7 +134,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface } // generate invoice criteria - public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array) + public function generateInvoiceCriteria($jo, $discount, $invoice_items, &$error_array) { $em = $this->em; @@ -140,7 +143,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface $criteria->setServiceType($jo->getServiceType()) ->setCustomerVehicle($jo->getCustomerVehicle()); - $ierror = $this->invoicePromo($criteria, $promo_id); + $ierror = $this->validateDiscount($criteria, $discount); if (!$ierror && !empty($invoice_items)) { @@ -158,11 +161,19 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface $ierror = $this->invoiceBatteries($criteria, $invoice_items); } + // get the meta for service charges + $service_charges = $jo->getMeta('service_charges'); + if (!empty($service_charges)) + { + $service_charges = $jo->getMeta('service_charges'); + + $this->invoiceServiceCharges($criteria, $service_charges); + } + if ($ierror) { $error_array['invoice'] = $ierror; } - else { // generate the invoice @@ -193,6 +204,25 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface } } + // prepare draft for invoice + public function generateDraftInvoice($criteria, $discount, $service_charges, $items) + { + $ierror = $this->validateDiscount($criteria, $discount); + + if (!$ierror) + { + // process service charges + $ierror = $this->invoiceServiceCharges($criteria, $service_charges); + + if (!$ierror) + { + $ierror = $this->invoiceBatteries($criteria, $items); + } + } + + return $ierror; + } + protected function getTaxAmount($price) { $vat_ex_price = $this->getTaxExclusivePrice($price); @@ -224,27 +254,26 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface return 0; } - public function invoicePromo(InvoiceCriteria $criteria, $promo_id) + public function validateDiscount(InvoiceCriteria $criteria, $discount) { + // return error if there's a problem, false otherwise // check service type $stype = $criteria->getServiceType(); if ($stype != CMBServiceType::BATTERY_REPLACEMENT_NEW) return null; - - if (empty($promo_id)) + // check if discount is blank or 0 + if ((empty($discount)) || ($discount == 0)) { return false; } - // check if this is a valid promo - $promo = $this->em->getRepository(Promo::class)->find($promo_id); + // check if discount is greater than 50 or negative number + if (($discount > 50) || ($discount < 0)) + return 'Invalid discount specified'; - if (empty($promo)) - return 'Invalid promo specified.'; - - $criteria->addPromo($promo); + $criteria->setDiscount($discount); return false; } @@ -292,6 +321,28 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface return null; } + public function invoiceServiceCharges(InvoiceCriteria $criteria, $service_charges) + { + if (!empty($service_charges)) + { + foreach ($service_charges as $service_charge) + { + // check if valid service charge + $sc = $this->em->getRepository(ServiceCharge::class)->find($service_charge['id']); + + if (empty($sc)) + { + $error = 'Invalid service charge specified.'; + return $error; + } + + $criteria->addServiceCharge($sc); + } + } + + return null; + } + protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice) { @@ -389,33 +440,14 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface protected function processDiscount(&$total, InvoiceCriteria $criteria, Invoice $invoice) { - $promos = $criteria->getPromos(); - if (count($promos) < 1) - return; - - // NOTE: only get first promo because only one is applicable anyway - $promo = $promos[0]; - - $rate = $promo->getDiscountRate(); - $apply_to = $promo->getDiscountApply(); - - switch ($apply_to) - { - case DiscountApply::SRP: - $discount = round($total['sell_price'] * $rate, 2); - break; - case DiscountApply::OPL: - // $discount = round($total['sell_price'] * 0.6 / 0.7 * $rate, 2); - $discount = round($total['sell_price'] * (1 - 1.5 / 0.7 * $rate), 2); - break; - } + $discount = $criteria->getDiscount(); // if discount is higher than 0, display in invoice if ($discount > 0) { $item = new InvoiceItem(); $item->setInvoice($invoice) - ->setTitle('Promo discount') + ->setTitle('Discount') ->setQuantity(1) ->setPrice(-1 * $discount); $invoice->addItem($item); @@ -425,7 +457,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface $total['total_price'] -= $discount; // process - $invoice->setPromo($promo); + $invoice->setDiscount($discount); } protected function processJumpstart(&$total, $invoice) @@ -629,4 +661,24 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface $total['vat'] = $vat; } + protected function processServiceCharges(&$total, InvoiceCriteria $criteria, Invoice $invoice) + { + $service_charges = $criteria->getServiceCharges(); + + foreach ($service_charges as $service_charge) + { + $amount = $service_charge->getAmount(); + $title = 'Service Charge - ' . $service_charge->getName(); + + $total['total_price'] += $amount; + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($title) + ->setQuantity(1) + ->setPrice($amount); + + $invoice->addItem($item); + } + } } diff --git a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php index c5e9c81c..2999d622 100644 --- a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php @@ -18,6 +18,7 @@ use App\Entity\Invoice; use App\Entity\InvoiceItem; use App\Entity\User; use App\Entity\Battery; +use App\Entity\Promo; use App\Service\InvoiceGeneratorInterface; @@ -193,6 +194,20 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface } } + // prepare draft for invoice + public function generateDraftInvoice($criteria, $promo_id, $service_charges, $items) + { + $ierror = $this->invoicePromo($criteria, $promo_id); + + if (!$ierror) + { + $ierror = $this->invoiceBatteries($criteria, $items); + } + + return $ierror; + } + + protected function getTaxAmount($price) { $vat_ex_price = $this->getTaxExclusivePrice($price); @@ -226,7 +241,7 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface return 0; } - public function invoicePromo(InvoiceCriteria $criteria, $promo_id) + protected function invoicePromo(InvoiceCriteria $criteria, $promo_id) { // return error if there's a problem, false otherwise // check service type diff --git a/src/Service/InvoiceGeneratorInterface.php b/src/Service/InvoiceGeneratorInterface.php index 85407c30..3e1c5f88 100644 --- a/src/Service/InvoiceGeneratorInterface.php +++ b/src/Service/InvoiceGeneratorInterface.php @@ -15,4 +15,7 @@ interface InvoiceGeneratorInterface // generate invoice criteria public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, array &$error_array); + // prepare draft for invoice + public function generateDraftInvoice(InvoiceCriteria $criteria, int $promo_id, array $service_charges, array $items); + } diff --git a/src/Service/JobOrderHandler/CMBJobOrderHandler.php b/src/Service/JobOrderHandler/CMBJobOrderHandler.php index 40f428ee..d490ce56 100644 --- a/src/Service/JobOrderHandler/CMBJobOrderHandler.php +++ b/src/Service/JobOrderHandler/CMBJobOrderHandler.php @@ -26,6 +26,7 @@ use App\Entity\Rider; use App\Entity\JORejection; use App\Entity\Warranty; use App\Entity\Customer; +use App\Entity\ServiceCharge; use App\Ramcar\InvoiceCriteria; use App\Ramcar\CMBServiceType; @@ -42,6 +43,7 @@ use App\Ramcar\JORejectionReason; use App\Service\InvoiceGeneratorInterface; use App\Service\JobOrderHandlerInterface; use App\Service\RiderAssignmentHandlerInterface; +use App\Service\CustomerHandlerInterface; use App\Service\WarrantyHandler; use App\Service\MQTTClient; use App\Service\APNSClient; @@ -66,13 +68,15 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface protected $rah; protected $country_code; protected $wh; + protected $cust_handler; protected $template_hash; public function __construct(Security $security, EntityManagerInterface $em, InvoiceGeneratorInterface $ic, ValidatorInterface $validator, TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah, - string $country_code, WarrantyHandler $wh) + string $country_code, WarrantyHandler $wh, + CustomerHandlerInterface $cust_handler) { $this->em = $em; $this->ic = $ic; @@ -82,6 +86,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $this->rah = $rah; $this->country_code = $country_code; $this->wh = $wh; + $this->cust_handler = $cust_handler; $this->loadTemplates(); } @@ -160,6 +165,25 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface // process rows $rows = []; foreach ($obj_rows as $orow) { + // get car model + $cv = $orow->getCustomerVehicle(); + + $cv_manufacturer = $cv->getVehicle()->getManufacturer()->getName(); + $cv_make = $cv->getVehicle()->getMake(); + $year = $cv->getModelYear(); + + $car_model = $cv_manufacturer . ' ' . $cv_make . ' ' . $year; + + // get rider information + $rider_name = ''; + $rider_plate_number = ''; + $rider = $orow->getRider(); + if (!empty($rider)) + { + $rider_name = $rider->getFullName(); + $rider_plate_number = $rider->getPlateNumber(); + } + // add row data $row['id'] = $orow->getID(); $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); @@ -171,6 +195,9 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $row['flag_advance'] = $orow->isAdvanceOrder(); $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; + $row['car_model'] = $car_model; + $row['rider_name'] = $rider_name; + $row['rider_plate_number'] = $rider_plate_number; $processor = $orow->getProcessedBy(); if ($processor == null) @@ -347,13 +374,13 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface // call service to generate job order and invoice $invoice_items = $req->request->get('invoice_items', []); - $promo_id = $req->request->get('invoice_promo'); + $discount = $req->request->get('invoice_discount'); $invoice_change = $req->request->get('invoice_change', 0); // check if invoice changed if ($invoice_change) { - $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + $this->ic->generateInvoiceCriteria($jo, $discount, $invoice_items, $error_array); } // validate @@ -409,15 +436,21 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface } // check if new customer - if ($req->request->get('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.'; } - $new_cust = new Customer(); - $new_cv = new CustomerVehicle(); + // validate mobile phone + $valid_mobile = $this->cust_handler->validateMobileNumber($req->request->get('customer_phone_mobile')); + if (!($valid_mobile)) + $error_array['customer_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.'; // find the vehicle using vid $new_vehicle = $em->getRepository(Vehicle::class)->find($req->request->get('vid')); @@ -426,8 +459,10 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $error_array['cv_mfg'] = 'Invalid manufacturer specified.'; $error_array['cv_make'] = 'Invalid make specified.'; } - else + if (empty($error_array)) { + $new_cust = new Customer(); + $new_cv = new CustomerVehicle(); $new_cust->setLastName($req->request->get('customer_last_name')) ->setFirstName($req->request->get('customer_first_name')) @@ -514,6 +549,29 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface } } + // 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 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)) + $error_array['invoice_discount'] = 'Invalid discount specified'; + + // get list of service charges + $service_charges = $req->request->get('service_charges', []); + if (empty($error_array)) { // get current user @@ -541,9 +599,13 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface ->setModeOfPayment($req->request->get('mode_of_payment')) ->setLandmark($req->request->get('landmark')) ->setHub($hub) - ->setRider($rider); + ->setRider($rider) + ->setPriority($priority); - // check if user is null, meaning call to create came from API + $jo->addMeta('discount', $discount); + $jo->addMeta('service_charges', $service_charges); + + // check if user is null, meaning call to create came from API if ($user != null) { $jo->setCreatedBy($user); @@ -563,13 +625,13 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface // call service to generate job order and invoice $invoice_items = $req->request->get('invoice_items', []); - $promo_id = $req->request->get('invoice_promo'); + $discount = $req->request->get('invoice_discount'); $invoice_change = $req->request->get('invoice_change', 0); // check if invoice changed if ($invoice_change) { - $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + $this->ic->generateInvoiceCriteria($jo, $discount, $invoice_items, $error_array); } // validate @@ -918,7 +980,11 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface // create the warranty if new battery only if ($this->checkIfNewBattery($obj)) { - $serial = $req->request->get('warranty_code') ; + if (empty($req->request->get('warranty_code'))) + $serial = null; + else + $serial = $req->request->get('warranty_code'); + $warranty_class = $obj->getWarrantyClass(); $first_name = $obj->getCustomer()->getFirstName(); $last_name = $obj->getCustomer()->getLastName(); @@ -1361,12 +1427,13 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface { $params['obj'] = new JobOrder(); $params['mode'] = 'onestep'; + $params['jo_service_charges'] = []; $this->fillDropdownParameters($params); $this->fillFormTags($params); // get template to display - $params['template'] = $this->getTwigTemplate('jo_onestep'); + $params['template'] = $this->getTwigTemplate('jo_onestep_form'); // return params return $params; @@ -1382,6 +1449,22 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $params['cvid'] = $obj->getCustomerVehicle()->getID(); $params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID(); + // get service charges + $sc_array = []; + $jo_service_charges = $obj->getMeta('service_charges'); + if (!(empty($jo_service_charges))) + { + foreach ($jo_service_charges as $jo_sc_id) + { + // find service charge + $sc_obj = $em->getRepository(ServiceCharge::class)->find($jo_sc_id); + + $sc_array[] = $sc_obj; + } + } + + $params['jo_service_charges'] = $sc_array; + $this->fillDropdownParameters($params); $this->fillFormTags($params); @@ -1490,7 +1573,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface { $em = $this->em; - $params['mode'] = 'update-all'; + $params['mode'] = 'view-all'; // get row data $obj = $em->getRepository(JobOrder::class)->find($id); @@ -1502,8 +1585,12 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $this->fillDropdownParameters($params); $this->fillFormTags($params); - // get template to display - $params['template'] = $this->getTwigTemplate('jo_all_form'); + // get template to display + // check transaction origin if walkin + if ($obj->getSource() == TransactionOrigin::WALK_IN) + $params['template'] = $this->getTwigTemplate('jo_walkin_form'); + else + $params['template'] = $this->getTwigTemplate('jo_onestep_form'); $params['obj'] = $obj; $params['status_cancelled'] = JOStatus::CANCELLED; @@ -2022,13 +2109,6 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $pdf->Cell($label_width, $line_height, 'Plate Number:'); $pdf->MultiCell($val_width, $line_height, $cv ? $cv->getPlateNumber() : '', 0, 'L'); - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Vehicle Color:'); - $pdf->MultiCell(0, $line_height, $cv ? $cv->getColor() : '', 0, 'L'); - // get Y after right cell $y2 = $pdf->GetY(); @@ -2345,6 +2425,311 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface return false; } + public function initializeWalkinForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'walk-in'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_walkin_form'); + + // return params + return $params; + } + + public function processWalkinJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + $em = $this->em; + + $jo = $em->getRepository(JobOrder::class)->find($id); + if (empty($jo)) + { + // new job order + $jo = new JobOrder(); + } + + // 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')); + if (!($valid_mobile)) + $error_array['customer_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.'; + + // find the vehicle using vid + $new_vehicle = $em->getRepository(Vehicle::class)->find($req->request->get('vid')); + if (empty($new_vehicle)) + { + $error_array['cv_mfg'] = 'Invalid manufacturer specified.'; + $error_array['cv_make'] = 'Invalid make specified.'; + } + + if (empty($error_array)) + { + $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_cv->setCustomer($new_cust) + ->setVehicle($new_vehicle) + ->setPlateNumber($req->request->get('cv_plate')) + ->setModelYear($req->request->get('cv_year')) + ->setColor('') + ->setStatusCondition('') + ->setFuelType('') + ->setActive() + ->setWarrantyCode($req->request->get('warranty_code')); + + if (($req->request->get('service_type')) == CMBServiceType::BATTERY_REPLACEMENT_NEW) + { + $new_cv->setHasMotoliteBattery(true); + } + else + { + $new_cv->setHasMotoliteBattery(false); + } + + // link JO to new customer + $jo->setCustomer($new_cust); + $jo->setCustomerVehicle($new_cv); + + $em->persist($new_cust); + $em->persist($new_cv); + } + } + else + { + // check if customer vehicle is set + if (empty($req->request->get('customer_vehicle'))) { + $error_array['customer_vehicle'] = 'No vehicle selected.'; + } else + { + // get customer vehicle + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); + + if (empty($cust_vehicle)) { + $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; + } + else + { + $jo->setCustomerVehicle($cust_vehicle); + $jo->setCustomer($cust_vehicle->getCustomer()); + + // save serial into cv + $cust_vehicle->setWarrantyCode($req->request->get('warranty_code')); + + $em->persist($cust_vehicle); + } + } + } + + // check if hub is selected + if (empty($req->request->get('hub_id'))) + $error_array['hub'] = 'No hub selected.'; + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub_id')); + + if (empty($hub)) + $error_array['hub'] = 'Invalid hub specified.'; + + // get hub coordinates + $hub_coordinates = $hub->getCoordinates(); + } + + // 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)) + $error_array['invoice_discount'] = 'Invalid discount specified'; + + if (empty($error_array)) + { + // get current user + $user = $this->security->getUser(); + + $stype = $req->request->get('service_type'); + + // set and save values + $jo->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::FULFILLED) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setORName($req->request->get('or_name')) + ->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); + + $jo->addMeta('discount', $discount); + + // check if user is null, meaning call to create came from API + if ($user != null) + { + $jo->setCreatedBy($user); + } + + // check if reference JO is set and validate + if (!empty($req->request->get('ref_jo'))) { + // get reference JO + $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); + + if (empty($ref_jo)) { + $error_array['ref_jo'] = 'Invalid reference job order specified.'; + } else { + $jo->setReferenceJO($ref_jo); + } + } + + // call service to generate job order and invoice + $invoice_items = $req->request->get('invoice_items', []); + $discount = $req->request->get('invoice_discount'); + $invoice_change = $req->request->get('invoice_change', 0); + + // check if invoice changed + if ($invoice_change) + { + $this->ic->generateInvoiceCriteria($jo, $discount, $invoice_items, $error_array); + } + + // validate + $errors = $this->validator->validate($jo); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if errors are found + if (empty($error_array)) + { + // validated, no error. save the job order + $em->persist($jo); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // save to customer vehicle battery record + $this->updateVehicleBattery($jo); + + // save serial to customer vehicle + $cust_vehicle = $jo->getCustomerVehicle(); + $cust_vehicle->setWarrantyCode($req->request->get('warranty_code')); + + $em->persist($cust_vehicle); + + // create the warranty if new battery only + if ($this->checkIfNewBattery($jo)) + { + if (empty($req->request->get('warranty_code'))) + $serial = null; + else + $serial = $req->request->get('warranty_code'); + + $warranty_class = $jo->getWarrantyClass(); + $first_name = $jo->getCustomer()->getFirstName(); + $last_name = $jo->getCustomer()->getLastName(); + $mobile_number = $jo->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($jo->getDateFulfill() == null) + $date_purchase = $jo->getDateCreate(); + else + $date_purchase = $jo->getDateFulfill(); + + // validate plate number + // $plate_number = $this->wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + $plate_number = Warranty::cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + if ($plate_number != false) + { + $batt_list = array(); + $invoice = $jo->getInvoice(); + if (!empty($invoice)) + { + // get battery + $invoice_items = $invoice->getItems(); + foreach ($invoice_items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + { + $batt_list[] = $item->getBattery(); + } + } + } + + $this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); + } + } + + $em->flush(); + } + } + + return $error_array; + } + + public function initializeWalkinEditForm($id) + { + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $obj; + $params['mode'] = 'walk-in-edit'; + $params['cvid'] = $obj->getCustomerVehicle()->getID(); + $params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_walkin_edit_form'); + + // return params + return $params; + } + protected function fillDropdownParameters(&$params) { $em = $this->em; @@ -2352,6 +2737,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface // db loaded $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); $params['promos'] = $em->getRepository(Promo::class)->findAll(); + $params['service_charges'] = $em->getRepository(ServiceCharge::class)->findAll(); // list of hubs $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); @@ -2422,6 +2808,18 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $params['ftags']['invoice_edit'] = true; $params['ftags']['preset_vehicle'] = true; break; + case 'walk-in': + $params['ftags']['vehicle_dropdown'] = true; + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'walk-in-edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + } } @@ -2434,22 +2832,16 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface // $this->template_hash = [ // 'blah' => 'blah', // ]; - $this->template_hash['jo_incoming_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_open_edit_form'] = 'job-order/cmb.form.html.twig'; $this->template_hash['jo_incoming_vehicle_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_processing_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_assigning_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_fulfillment_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_open_hub_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_open_rider_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_all_form'] = 'job-order/cmb.form.html.twig'; - $this->template_hash['jo_list_processing'] = 'job-order/list.processing.html.twig'; - $this->template_hash['jo_list_assigning'] = 'job-order/list.assigning.html.twig'; - $this->template_hash['jo_list_fulfillment'] = 'job-order/list.fulfillment.html.twig'; - $this->template_hash['jo_list_open'] = 'job-order/list.open.html.twig'; - $this->template_hash['jo_list_all'] = 'job-order/list.all.html.twig'; - $this->template_hash['jo_onestep'] = 'job-order/cmb.form.onestep.html.twig'; + $this->template_hash['jo_fulfillment_form'] = 'job-order/cmb.form.onestep.html.twig'; + $this->template_hash['jo_all_form'] = 'job-order/cmb.form.onestep.html.twig'; + $this->template_hash['jo_list_fulfillment'] = 'job-order/cmb.list.fulfillment.html.twig'; + $this->template_hash['jo_list_open'] = 'job-order/cmb.list.open.html.twig'; + $this->template_hash['jo_list_all'] = 'job-order/cmb.list.all.html.twig'; + $this->template_hash['jo_onestep_form'] = 'job-order/cmb.form.onestep.html.twig'; $this->template_hash['jo_onestep_edit_form'] = 'job-order/cmb.form.onestep.html.twig'; + $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'; } protected function checkTier($tier) @@ -2641,4 +3033,17 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface ->setParameter('status', $status); } } + + public function getEditRoute($jo_id, $tier = null) + { + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if (empty($jo)) + throw new NotFoundHttpException('The item does not exist'); + + // check transaction origin + if ($jo->getSource() == TransactionOrigin::WALK_IN) + return 'jo_walkin_edit_form'; + else + return 'jo_onestep_edit_form'; + } } diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index c6a6fa01..a12e09d5 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -314,6 +314,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } } + // TODO: check status before saving since JO might already + // have a status that needs to be retained + if (empty($error_array)) { // get current user $user = $this->security->getUser(); @@ -2587,4 +2590,12 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setParameter('status', $status); } } + + public function getEditRoute($jo_id, $tier) + { + if (empty($tier)) + return 'jo_open_edit_form'; + + return $tier; + } } diff --git a/src/Service/JobOrderHandlerInterface.php b/src/Service/JobOrderHandlerInterface.php index 5593d10c..590a0926 100644 --- a/src/Service/JobOrderHandlerInterface.php +++ b/src/Service/JobOrderHandlerInterface.php @@ -98,4 +98,7 @@ interface JobOrderHandlerInterface // check if service type is new battery public function checkIfNewBattery(JobOrder $jo); + + // return the edit route, based on tier and form + public function getEditRoute(int $jo_id, $tier); } diff --git a/src/Service/WarrantyHandler.php b/src/Service/WarrantyHandler.php index 281464bf..6d01eb10 100644 --- a/src/Service/WarrantyHandler.php +++ b/src/Service/WarrantyHandler.php @@ -73,8 +73,11 @@ class WarrantyHandler } // set and save values - $warranty->setSerial($serial) - ->setPlateNumber($plate_number) + if (trim($serial) == '') + $warranty->setSerial(null); + else + $warranty->setSerial($serial); + $warranty->setPlateNumber($plate_number) ->setFirstName($first_name) ->setLastName($last_name) ->setMobileNumber($mobile_number) diff --git a/templates/home.html.twig b/templates/home.html.twig index b7ac2c24..19e99011 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -51,6 +51,7 @@ function initEventHandler(dashmap) { 'track_rider': true, 'channels': { 'rider_location': 'rider/+/location', + 'rider_status': 'rider/+/status', 'jo_location': 'jo/+/location', 'jo_status': 'jo/+/status' }, diff --git a/templates/job-order/cmb.form.onestep.html.twig b/templates/job-order/cmb.form.onestep.html.twig index e0714676..7d2344f2 100644 --- a/templates/job-order/cmb.form.onestep.html.twig +++ b/templates/job-order/cmb.form.onestep.html.twig @@ -184,7 +184,9 @@