diff --git a/.env.dist b/.env.dist index f170c3c9..a9fe4b6a 100644 --- a/.env.dist +++ b/.env.dist @@ -50,3 +50,6 @@ GEOFENCE_ENABLE=settotrueorfalse CVU_MFG_ID=insertmfgidforunknownvehicles CVU_BRAND_ID=insertbrandidforunknownvehicles +# country code prefix +COUNTRY_CODE=+insertcountrycodehere + diff --git a/.gitignore b/.gitignore index 90038562..bd8a680c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /sql/ /pem/ /migration/ +/kml/ ###< symfony/framework-bundle ### *.swp diff --git a/config/acl.yaml b/config/acl.yaml index 57a1c427..90b2f06d 100644 --- a/config/acl.yaml +++ b/config/acl.yaml @@ -242,6 +242,10 @@ access_keys: label: Edit - id: joborder.cancel label: Cancel + - id: jo_onestep.form + label: One-step Process + - id: jo_onestep.edit + label: One-step Process Edit - id: support label: Customer Support Access diff --git a/config/menu.yaml b/config/menu.yaml index b0096cf5..a64dd8b4 100644 --- a/config/menu.yaml +++ b/config/menu.yaml @@ -98,6 +98,10 @@ main_menu: 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_in acl: jo_in.list label: Incoming diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index a11fab53..d912927a 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -4,3 +4,5 @@ twig: strict_variables: '%kernel.debug%' globals: gmaps_api_key: "%env(GMAPS_API_KEY)%" + mqtt_host: "%env(MQTT_WS_HOST)%" + mqtt_port: "%env(MQTT_WS_PORT)%" diff --git a/config/routes.yaml b/config/routes.yaml index fe5c04a3..e3cc0585 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -3,7 +3,3 @@ # controller: App\Controller\DefaultController::index # # -home: - path: / - controller: App\Controller\HomeController::index - diff --git a/config/routes/home.yaml b/config/routes/home.yaml new file mode 100644 index 00000000..da111860 --- /dev/null +++ b/config/routes/home.yaml @@ -0,0 +1,8 @@ +home: + path: / + controller: App\Controller\HomeController::index + +rider_locations: + path: /rider_locations + controller: App\Controller\HomeController::getRiderLocations + diff --git a/config/routes/hub.yaml b/config/routes/hub.yaml index 1634e7d7..d28498ec 100644 --- a/config/routes/hub.yaml +++ b/config/routes/hub.yaml @@ -34,3 +34,12 @@ hub_delete: controller: App\Controller\HubController::destroy methods: [DELETE] +hub_nearest: + path: /ajax/nearest_hubs + controller: App\Controller\HubController::nearest + methods: [GET] + +hub_riders: + path: /ajax/hubs/riders + controller: App\Controller\HubController::getHubRiders + methods: [GET] diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index 05cb08c8..139b208f 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -175,3 +175,29 @@ jo_reject_hub: path: /job-order/{id}/reject-hub controller: App\Controller\JobOrderController::rejectHubSubmit methods: [POST] + +jo_onestep_form: + path: /job-order/onestep + controller: App\Controller\JobOrderController::oneStepForm + methods: [GET] + +jo_onestep_submit: + path: /job-order/onestep + controller: App\Controller\JobOrderController::oneStepSubmit + methods: [POST] + +jo_onestep_edit_form: + path: /job-order/onestep/{id}/edit + controller: App\Controller\JobOrderController::oneStepEditForm + methods: [GET] + +jo_onestep_edit_submit: + path: /job-order/onestep/{id}/edit + controller: App\Controller\JobOrderController::oneStepEditSubmit + methods: [POST] + +jo_ajax_popup: + path: /job-order/{id}/popup + controller: App\Controller\JobOrderController::popupInfo + methods: [GET] + diff --git a/config/routes/rider.yaml b/config/routes/rider.yaml index 61a4522e..70ddd91d 100644 --- a/config/routes/rider.yaml +++ b/config/routes/rider.yaml @@ -36,3 +36,8 @@ rider_delete: path: /riders/{id} controller: App\Controller\RiderController::destroy methods: [DELETE] + +rider_ajax_popup: + path: /riders/{id}/popup + controller: App\Controller\RiderController::popupInfo + methods: [GET] diff --git a/config/services.yaml b/config/services.yaml index c8916c9b..e3a9c6f1 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -108,6 +108,11 @@ services: $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" @@ -151,3 +156,52 @@ services: $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" + #App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator" + + # job order generator + #App\Service\JobOrderHandler\ResqJobOrderHandler: + # arguments: + # $country_code: "%env(COUNTRY_CODE)%" + + App\Service\JobOrderHandler\CMBJobOrderHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + #job order generator interface + App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler" + #App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\ResqJobOrderHandler" + + # customer generator + App\Service\CustomerHandler\CMBCustomerHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + #App\Service\CustomerHandler\ResqCustomerHandler: + # arguments: + # $country_code: "%env(COUNTRY_CODE)%" + + # customer generator interface + App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\CMBCustomerHandler" + #App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\ResqCustomerHandler" + + # rider assignment + App\Service\RiderAssignmentHandler\CMBRiderAssignmentHandler: ~ + + # rider assignment interface + App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\CMBRiderAssignmentHandler" + + # 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" + diff --git a/migration/sql_delete_battery_vehicle_data.sql b/migration/sql_delete_battery_vehicle_data.sql new file mode 100644 index 00000000..7df32e17 --- /dev/null +++ b/migration/sql_delete_battery_vehicle_data.sql @@ -0,0 +1,8 @@ +DELETE FROM battery; +DELETE FROM battery_manufacturer; +DELETE FROM battery_manufacturer; +DELETE FROM battery_model; +DELETE FROM battery_size; +DELETE FROM vehicle; +DELETE FROM vehicle_manufacturer; +DELETE FROM battery_vehicle; diff --git a/public/assets/css/style.css b/public/assets/css/style.css index f8e6318d..051d2143 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -295,3 +295,40 @@ span.has-danger, .btn-icon { margin-right: .5em; } + +.marker-pin { + width: 30px; + height: 30px; + border-radius: 50% 50% 50% 0; + background: #c30b82; + position: absolute; + transform: rotate(-45deg); + left: 50%; + top: 50%; + margin: -15px 0 0 -15px; +} + +.marker-pin::after { + content: ''; + width: 24px; + height: 24px; + margin: 3px 0 0 3px; + background: #fff; + position: absolute; + border-radius: 50%; + } + +.map-div-icon i { + position: absolute; + width: 22px; + font-size: 22px; + left: 0; + right: 0; + margin: 10px auto; + text-align: center; +} + +.map-div-icon i.awesome { + margin: 12px auto; + font-size: 17px; + diff --git a/public/assets/images/battery-assist-bm-logo-16x16.png b/public/assets/images/battery-assist-bm-logo-16x16.png new file mode 100644 index 00000000..fe2a6769 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-16x16.png differ diff --git a/public/assets/images/battery-assist-bm-logo-32x32.png b/public/assets/images/battery-assist-bm-logo-32x32.png new file mode 100644 index 00000000..fa0c4c85 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-32x32.png differ diff --git a/public/assets/images/battery-assist-bm-logo-50x50.png b/public/assets/images/battery-assist-bm-logo-50x50.png new file mode 100644 index 00000000..4b086bd8 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-50x50.png differ diff --git a/public/assets/images/battery-assist-bm-logo-edited.png b/public/assets/images/battery-assist-bm-logo-edited.png new file mode 100644 index 00000000..9f1b7bf3 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-edited.png differ diff --git a/public/assets/images/battery-assist-bm-logo.png b/public/assets/images/battery-assist-bm-logo.png new file mode 100644 index 00000000..47311393 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo.png differ diff --git a/public/assets/images/black-text-logo-01-115x115.png b/public/assets/images/black-text-logo-01-115x115.png new file mode 100644 index 00000000..6cb04ea0 Binary files /dev/null and b/public/assets/images/black-text-logo-01-115x115.png differ diff --git a/public/assets/images/black-text-logo-01-125x125.png b/public/assets/images/black-text-logo-01-125x125.png new file mode 100644 index 00000000..f79218f7 Binary files /dev/null and b/public/assets/images/black-text-logo-01-125x125.png differ diff --git a/public/assets/images/black-text-logo-01-16x16.png b/public/assets/images/black-text-logo-01-16x16.png new file mode 100644 index 00000000..4ac80d27 Binary files /dev/null and b/public/assets/images/black-text-logo-01-16x16.png differ diff --git a/public/assets/images/black-text-logo-01-32x32.png b/public/assets/images/black-text-logo-01-32x32.png new file mode 100644 index 00000000..4ac80d27 Binary files /dev/null and b/public/assets/images/black-text-logo-01-32x32.png differ diff --git a/public/assets/images/black-text-logo-01.png b/public/assets/images/black-text-logo-01.png new file mode 100644 index 00000000..8286a75e Binary files /dev/null and b/public/assets/images/black-text-logo-01.png differ diff --git a/public/assets/images/century_logo.png b/public/assets/images/century_logo.png new file mode 100644 index 00000000..1457c03b Binary files /dev/null and b/public/assets/images/century_logo.png differ diff --git a/src/Command/ImportCMBBatteryDataCommand.php b/src/Command/ImportCMBBatteryDataCommand.php new file mode 100644 index 00000000..125b150d --- /dev/null +++ b/src/Command/ImportCMBBatteryDataCommand.php @@ -0,0 +1,315 @@ +em = $om; + + // load existing batteries and sizes + $this->loadBatteryManufacturers(); + $this->loadBatteryModels(); + $this->loadBatteries(); + $this->loadBatterySizes(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('cmbbatterydata:import') + ->setDescription('Import a CSV file with battery data.') + ->setHelp('Adds the battery data based on 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; + + // loop through the rows + $row_num = 0; + error_log('Processing battery csv file...'); + while (($fields = fgetcsv($fh)) !== false) + { + // data starts at row 2 + if ($row_num < 2) + { + $row_num++; + continue; + } + + // battery info + $code = trim($fields[self::F_BATT_CODE]); + $desc = trim($fields[self::F_BATT_DESC]); + $price = trim($fields[self::F_BATT_PRICE]); + + $clean_price = trim($price, '$'); + + $battery_info = explode(' ', $desc); + + // if battery_info has 3 elements, get the last two + // [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 + // [3] = battery size + $battery_manufacturer = ''; + $battery_model = ''; + $battery_size = ''; + if (count($battery_info) == 3) + { + // sample: Century Marathoner 120-7L + $battery_manufacturer = trim($battery_info[0]); + $battery_model = trim($battery_info[1]); + $battery_size = trim($battery_info[2]); + } + if (count($battery_info) == 2) + { + // sample: Marshall DIN55R + $battery_manufacturer = trim($battery_info[0]); + $battery_model = trim($battery_info[0]); + $battery_size = trim($battery_info[1]); + } + if (count($battery_info) == 4) + { + // sample: Motolite Classic Wetcharged DIN100L + $battery_manufacturer = trim($battery_info[0]); + $battery_model = trim($battery_info[1]) . ' ' . trim($battery_info[2]); + $battery_size = trim($battery_info[3]); + } + + // check if battery size has () + // if so, trim it to ignore the parenthesis and what's after (. + $pos = stripos($battery_size, '('); + if ($pos == true) + { + $sizes = explode('(', $battery_size); + $clean_size = trim($sizes[0]); + } + else + { + $clean_size = $battery_size; + } + + //error_log('battery manufacturer ' . $battery_manufacturer); + //error_log('battery model ' . $battery_model); + //error_log('battery size ' . $battery_size); + + // normalize the manufacturer, model and size for the hash + // 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); + + // save battery manufacturer if not yet in system + if (!isset($this->bmanu_hash[$normalized_manu])) + { + $this->addBatteryManufacturer($battery_manufacturer); + } + + // save battery model if not yet in system + if (!isset($this->bmodel_hash[$normalized_model])) + { + $this->addBatteryModel($battery_model); + } + + // save battery size if not yet in system + if (!isset($this->bsize_hash[$normalized_size])) + { + $this->addBatterySize($clean_size); + } + + // save battery if not yet in system + if (!isset($this->batt_hash[$normalized_manu][$normalized_model][$normalized_size])) + { + $this->addBattery($normalized_manu, $normalized_model, $normalized_size, $code, $clean_price); + } + + } + } + + protected function addBatteryManufacturer($name) + { + $batt_manufacturer = new BatteryManufacturer(); + + $batt_manufacturer->setName($name); + + $this->em->persist($batt_manufacturer); + $this->em->flush(); + + // add new manufacturer to hash + $normalized_name = $this->normalizeName($name); + $this->bmanu_hash[$normalized_name] = $batt_manufacturer; + } + + protected function addBatteryModel($name) + { + $batt_model = new BatteryModel(); + + $batt_model->setName($name); + + $this->em->persist($batt_model); + $this->em->flush(); + + // add new model to hash + $normalized_name = $this->normalizeName($name); + $this->bmodel_hash[$normalized_name] = $batt_model; + } + + protected function addBatterySize($name) + { + if (!empty($name)) + { + // save to db + $batt_size = new BatterySize(); + + $batt_size->setName($name); + + $this->em->persist($batt_size); + $this->em->flush(); + + // add new size into hash + $normalized_name = $this->normalizeName($name); + $this->bsize_hash[$normalized_name] = $batt_size; + } + } + + protected function addBattery($manufacturer, $brand, $size, $code, $price) + { + // save to db + $bmanu = $this->bmanu_hash[$manufacturer]; + $bmodel = $this->bmodel_hash[$brand]; + $bsize = $this->bsize_hash[$size]; + + $battery = new Battery(); + $battery->setManufacturer($bmanu) + ->setModel($bmodel) + ->setSize($bsize) + ->setWarrantyPrivate(21) + ->setWarrantyCommercial(6) + ->setWarrantyTnv(12) + ->setProductCode($code) + ->setSAPCode($code) + ->setSellingPrice($price); + + $this->em->persist($battery); + $this->em->flush(); + + // insert into hash + $this->batt_hash[$brand][$brand][$size] = $battery; + + // add battery into battery manufacturer, battery model, and battery size + $bmanu->addBattery($battery); + $bmodel->addBattery($battery); + $bsize->addBattery($battery); + + $this->em->persist($bmanu); + $this->em->persist($bmodel); + $this->em->persist($bsize); + + $this->em->flush(); + } + + 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; + } + +} diff --git a/src/Command/ImportCMBBatteryTradeInPriceCommand.php b/src/Command/ImportCMBBatteryTradeInPriceCommand.php new file mode 100644 index 00000000..657b492d --- /dev/null +++ b/src/Command/ImportCMBBatteryTradeInPriceCommand.php @@ -0,0 +1,113 @@ +em = $om; + + // load existing sizes + $this->loadBatterySizes(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('cmbbatterydata:importtradeinprice') + ->setDescription('Import a CSV file with trade in prices.') + ->setHelp('Adds the battery tradein prices to existing batteries based on 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; + + // loop through the rows + $row_num = 0; + error_log('Processing battery tradein price csv file...'); + while (($fields = fgetcsv($fh)) !== false) + { + // data starts at row 2 + if ($row_num < 2) + { + $row_num++; + continue; + } + + // tradein price info + // battery price info + $desc = trim($fields[self::F_SIZE_DESC]); + $price = trim($fields[self::F_TRADEIN_PRICE]); + + $clean_price = trim($price, '$'); + + $size_info = explode(' ', $desc); + + $size = $size_info[1]; + + if (isset($this->bsize_hash[$size])) + { + $battery_size = $this->bsize_hash[$size]; + + // use TIPriceMotolite + $battery_size->setTIPriceMotolite($clean_price); + + $this->em->persist($battery_size); + $this->em->flush(); + } + else + { + error_log('Cannot find battery size ' . $size); + } + + + } + } + + protected function loadBatterySizes() + { + $this->bsize_hash = []; + + $batt_sizes = $this->em->getRepository(BatterySize::class)->findAll(); + foreach ($batt_sizes as $batt_size) + { + $name = $batt_size->getName(); + $this->bsize_hash[$name] = $batt_size; + } + } + + +} diff --git a/src/Command/ImportCMBVehicleCompatibilityCommand.php b/src/Command/ImportCMBVehicleCompatibilityCommand.php new file mode 100644 index 00000000..f6910d06 --- /dev/null +++ b/src/Command/ImportCMBVehicleCompatibilityCommand.php @@ -0,0 +1,447 @@ +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++; + } + } + + 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; + } + +} diff --git a/src/Command/SeedRiderSessionsCommand.php b/src/Command/SeedRiderSessionsCommand.php new file mode 100644 index 00000000..c52e0169 --- /dev/null +++ b/src/Command/SeedRiderSessionsCommand.php @@ -0,0 +1,59 @@ +em = $om; + $this->redis = $redis->getRedisClient(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('rider:session:seed') + ->setDescription('Seed current rider sessions') + ->setHelp('Seed current rider sessions'); + } + + + protected function execute(InputInterface $input, OutputInterface $output) + { + // get all rider sessions + $r_sessions = $this->em->getRepository(RiderSession::class)->findAll(); + foreach ($r_sessions as $session) + { + // get session id + $session_id = $session->getID(); + + // get rider id + if ($session->getRider() != null) + { + $rider_id = $session->getRider()->getID(); + + // key for redis + $redis_key = 'rider.id.' . $session_id; + //$output->writeln('key: ' . $redis_key); + + // set to redis cache + $this->redis->set($redis_key, $rider_id); + } + } + } +} diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 197021c5..9765ad8f 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -24,7 +24,7 @@ use App\Ramcar\TransactionOrigin; use App\Ramcar\TradeInType; use App\Ramcar\JOEventType; -use App\Service\InvoiceCreator; +use App\Service\InvoiceGeneratorInterface; use App\Service\RisingTideGateway; use App\Service\MQTTClient; use App\Service\GeofenceTracker; @@ -817,7 +817,7 @@ class APIController extends Controller return $res->getReturnResponse(); } - public function requestJobOrder(Request $req, InvoiceCreator $ic, GeofenceTracker $geo) + public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo) { // check required parameters and api key $required_params = [ @@ -979,7 +979,7 @@ class APIController extends Controller $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator - $invoice = $ic->processCriteria($icrit); + $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); $em->persist($jo); @@ -1026,7 +1026,7 @@ class APIController extends Controller return $res->getReturnResponse(); } - public function getEstimate(Request $req, InvoiceCreator $ic) + public function getEstimate(Request $req, InvoiceGeneratorInterface $ic) { // $this->debugRequest($req); @@ -1126,7 +1126,7 @@ class APIController extends Controller $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator - $invoice = $ic->processCriteria($icrit); + $invoice = $ic->generateInvoice($icrit); // make invoice json data $data = [ diff --git a/src/Controller/CustomerController.php b/src/Controller/CustomerController.php index 1f3849ad..7421fec0 100644 --- a/src/Controller/CustomerController.php +++ b/src/Controller/CustomerController.php @@ -2,134 +2,54 @@ namespace App\Controller; -use App\Ramcar\CustomerClassification; -use App\Ramcar\FuelType; -use App\Ramcar\VehicleStatusCondition; use App\Ramcar\CrudException; +use App\Service\CustomerHandlerInterface; use App\Entity\Customer; -use App\Entity\CustomerVehicle; -use App\Entity\MobileNumber; -use App\Entity\Vehicle; -use App\Entity\VehicleManufacturer; -use App\Entity\Battery; -use App\Entity\BatteryManufacturer; -use Doctrine\ORM\Query; +use Doctrine\ORM\EntityManagerInterface; + use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Catalyst\MenuBundle\Annotation\Menu; -use DateTime; - class CustomerController extends Controller { + /** * @Menu(selected="customer_list") */ - public function index() + public function index(CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.list', null, 'No access.'); - return $this->render('customer/list.html.twig'); + $params = $cust_handler->initializeCustomerIndexForm(); + + $template = $params['template']; + + return $this->render($template); } - public function rows(Request $req) + public function rows(Request $req, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.list', null, 'No access.'); - // build query - $tqb = $this->getDoctrine() - ->getRepository(Customer::class) - ->createQueryBuilder('q'); - - $qb = $this->getDoctrine() - ->getRepository(Customer::class) - ->createQueryBuilder('q'); - - // get datatable params - $datatable = $req->request->get('datatable'); - - // count total records - $tquery = $tqb->select('COUNT(q)'); - - // add filters 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.first_name', 'asc'); - } - - // get rows for this page - $obj_rows = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); + $params = $cust_handler->getCustomers($req); + $meta = $params['meta']; + $rows = $params['rows']; // process rows - $rows = []; - foreach ($obj_rows as $orow) { - // add row data - $row['id'] = $orow->getID(); - $row['title'] = $orow->getTitle(); - $row['first_name'] = $orow->getFirstName(); - $row['last_name'] = $orow->getLastName(); - $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); - $row['flag_mobile_app'] = $orow->hasMobileApp(); - $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; - $row['flag_active'] = $orow->isActive(); - $row['flag_csat'] = $orow->isCSAT(); - - // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events - $row['mobile_numbers'] = implode("
", $orow->getMobileNumberList()); - $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); - - // add row metadata - $row['meta'] = [ - 'update_url' => '', - 'delete_url' => '' - ]; - + foreach ($rows as $key => $data) { // add crud urls - if ($this->isGranted('customer.update')) - $row['meta']['update_url'] = $this->generateUrl('customer_update', ['id' => $row['id']]); - if ($this->isGranted('customer.delete')) - $row['meta']['delete_url'] = $this->generateUrl('customer_delete', ['id' => $row['id']]); + $cust_id = $rows[$key]['id']; - $rows[] = $row; + if ($this->isGranted('customer.update')) + $rows[$key]['meta']['update_url'] = $this->generateUrl('customer_update', ['id' => $cust_id]); + if ($this->isGranted('customer.delete')) + $rows[$key]['meta']['delete_url'] = $this->generateUrl('customer_delete', ['id' => $cust_id]); } // response @@ -139,347 +59,119 @@ class CustomerController extends Controller ]); } - protected function fillDropdownParameters(&$params) - { - $em = $this->getDoctrine()->getManager(); - - $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); - $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); - - $params['classifications'] = CustomerClassification::getCollection(); - $params['fuel_types'] = FuelType::getCollection(); - $params['status_conditions'] = VehicleStatusCondition::getCollection(); - - $params['years'] = $this->generateYearOptions(); - $params['batteries'] = $em->getRepository(Battery::class)->findAll(); - } - /** * @Menu(selected="customer_list") */ - public function addForm() + public function addForm(CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.add', null, 'No access.'); - $params['obj'] = new Customer(); - $params['mode'] = 'create'; + $params = $cust_handler->initializeAddCustomerForm(); - // get dropdown parameters - $this->fillDropdownParameters($params); + $template = $params['template']; // response - return $this->render('customer/form.html.twig', $params); + return $this->render($template, $params); } - protected function setObject($obj, $req) - { - // set and save values - $obj->setTitle($req->request->get('title')) - ->setFirstName($req->request->get('first_name')) - ->setLastName($req->request->get('last_name')) - ->setCustomerClassification($req->request->get('customer_classification')) - ->setCustomerNotes($req->request->get('customer_notes')) - ->setEmail($req->request->get('email')) - ->setIsCSAT($req->request->get('flag_csat') ? true : false) - ->setActive($req->request->get('flag_active') ? true : false); - - // phone numbers - $obj->setPhoneMobile($req->request->get('phone_mobile')) - ->setPhoneLandline($req->request->get('phone_landline')) - ->setPhoneOffice($req->request->get('phone_office')) - ->setPhoneFax($req->request->get('phone_fax')); - } - - - public function addSubmit(Request $req, ValidatorInterface $validator) + public function addSubmit(Request $req, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.add', null, 'No access.'); - // create new row - $em = $this->getDoctrine()->getManager(); - $row = new Customer(); + $result = $cust_handler->addCustomer($req); - $this->setObject($row, $req); - - // initialize error lists - $error_array = []; - $nerror_array = []; - $verror_array = []; - - // error_log(print_r($req->request->all(), true)); - - // custom validation for vehicles - $vehicles = json_decode($req->request->get('vehicles')); - - if (!empty($vehicles)) { - foreach ($vehicles as $vehicle) { - // check if vehicle exists - $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); - - if (empty($vobj)) { - $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; - } else { - $cust_vehicle = new CustomerVehicle(); - $cust_vehicle->setName($vehicle->name) - ->setVehicle($vobj) - ->setPlateNumber($vehicle->plate_number) - ->setModelYear($vehicle->model_year) - ->setColor($vehicle->color) - ->setStatusCondition($vehicle->status_condition) - ->setFuelType($vehicle->fuel_type) - ->setActive($vehicle->flag_active) - ->setCustomer($row); - - // if specified, check if battery exists - if ($vehicle->battery) { - // check if battery exists - $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); - - if (empty($bobj)) { - $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; - } else { - // check if warranty expiration was specified - $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); - if (!$warr_ex) - $warr_ex = null; - - $cust_vehicle->setHasMotoliteBattery(true) - ->setCurrentBattery($bobj) - ->setWarrantyCode($vehicle->warranty_code) - ->setWarrantyExpiration($warr_ex); - } - } else { - $cust_vehicle->setHasMotoliteBattery(false); - } - - $verrors = $validator->validate($cust_vehicle); - - // add errors to list - foreach ($verrors as $error) { - if (!isset($verror_array[$vehicle->index])) - $verror_array[$vehicle->index] = []; - - $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); - } - - // add to entity - if (!isset($verror_array[$vehicle->index])) { - $row->addVehicle($cust_vehicle); - } - } - } - } - - // validate - $errors = $validator->validate($row); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // check if any errors were found - if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) { - // return validation failure response - return $this->json([ - 'success' => false, - 'errors' => $error_array, - 'nerrors' => $nerror_array, - 'verrors' => $verror_array - ], 422); - } else { - // validated! save the entity - $em->persist($row); - $em->flush(); + if (isset($result['id'])) + { + $id = $result['id']; // return successful response return $this->json([ 'success' => 'Changes have been saved!', - 'id' => $row->getID() + 'id' => $id ]); - } - } - - /** - * @Menu(selected="customer_list") - */ - public function updateForm($id) - { - $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); - - $params['mode'] = 'update'; - - // get row data - $em = $this->getDoctrine()->getManager(); - $row = $em->getRepository(Customer::class)->find($id); - - // make sure this row exists - if (empty($row)) - throw $this->createNotFoundException('The item does not exist'); - - // get dropdown parameters - $this->fillDropdownParameters($params); - - $params['obj'] = $row; - - // response - return $this->render('customer/form.html.twig', $params); - } - - protected function updateVehicles($em, Customer $cust, $vehicles) - { - $vehicle_ids = []; - - foreach ($vehicles as $vehicle) - { - // check if customer vehicle exists - if (!empty($vehicle->id)) - { - $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); - if ($cust_vehicle == null) - throw new CrudException("Could not find customer vehicle."); - - } - // this is a new vehicle - else - { - $cust_vehicle = new CustomerVehicle(); - $cust_vehicle->setCustomer($cust); - $cust->addVehicle($cust_vehicle); - $em->persist($cust_vehicle); - } - - // vehicle, because they could have changed vehicle type - $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); - if ($vobj == null) - throw new CrudException("Could not find vehicle."); - - // TODO: validate details - - $cust_vehicle->setName($vehicle->name) - ->setVehicle($vobj) - ->setPlateNumber($vehicle->plate_number) - ->setModelYear($vehicle->model_year) - ->setColor($vehicle->color) - ->setStatusCondition($vehicle->status_condition) - ->setFuelType($vehicle->fuel_type) - ->setActive($vehicle->flag_active); - - // if specified, check if battery exists - if ($vehicle->battery) - { - // check if battery exists - $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); - if ($bobj == null) - throw new CrudException("Could not find battery."); - - // check if warranty expiration was specified - $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); - if (!$warr_ex) - $warr_ex = null; - - $cust_vehicle->setHasMotoliteBattery(true) - ->setCurrentBattery($bobj) - ->setWarrantyCode($vehicle->warranty_code) - ->setWarrantyExpiration($warr_ex); - } - else - { - $cust_vehicle->setHasMotoliteBattery(false); - } - - - // add to list of vehicles to keep - $vehicle_ids[$cust_vehicle->getID()] = true; - } - - // cleanup - // delete all vehicles not in list - $cvs = $cust->getVehicles(); - foreach ($cvs as $cv) - { - if (!isset($vehicle_ids[$cv->getID()])) - { - $cust->removeVehicle($cv); - $em->remove($cv); - } - } - } - - public function updateSubmit(Request $req, ValidatorInterface $validator, $id) - { - $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); - - // get row data - $em = $this->getDoctrine()->getManager(); - $cust = $em->getRepository(Customer::class)->find($id); - - // make sure this row exists - if (empty($cust)) - throw $this->createNotFoundException('The item does not exist'); - - $this->setObject($cust, $req); - - // initialize error lists - $error_array = []; - $nerror_array = []; - $verror_array = []; - - // TODO: validate mobile numbers - // TODO: validate vehicles - - // custom validation for vehicles - $vehicles = json_decode($req->request->get('vehicles')); - $this->updateVehicles($em, $cust, $vehicles); - - // validate - $errors = $validator->validate($cust); - - // add errors to list - foreach ($errors as $error) - { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // check if any errors were found - if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) - { - // return validation failure response - return $this->json([ - 'success' => false, - 'errors' => $error_array, - 'nerrors' => $nerror_array, - 'verrors' => $verror_array - ], 422); } else { - // validated! save the entity. do a persist anyway to save child entities - $em->persist($cust); - $em->flush(); + $error_array = $result['error_array']; + $nerror_array = $result['nerror_array']; + $verror_array = $result['verror_array']; + + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array, + 'nerrors' => $nerror_array, + 'verrors' => $verror_array + ], 422); + } + } + + /** + * @Menu(selected="customer_list") + */ + public function updateForm($id, CustomerHandlerInterface $cust_handler) + { + $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); + + $params = $cust_handler->initializeUpdateCustomerForm($id); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function updateSubmit(Request $req, CustomerHandlerInterface $cust_handler, $id) + { + $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); + + try + { + $result = $cust_handler->updateCustomer($req, $id); + } + catch (CrudException $e) + { + throw new CrudException($e->getMessage()); + } + + if (isset($result['id'])) + { + $id = $result['id']; // return successful response return $this->json([ 'success' => 'Changes have been saved!', - 'id' => $cust->getID() + 'id' => $id ]); - } + } + else + { + $error_array = $result['error_array']; + $nerror_array = $result['nerror_array']; + $verror_array = $result['verror_array']; + + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array, + 'nerrors' => $nerror_array, + 'verrors' => $verror_array + ], 422); + } } - public function destroy($id) + public function destroy($id, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.delete', null, 'No access.'); - // get row data - $em = $this->getDoctrine()->getManager(); - $row = $em->getRepository(Customer::class)->find($id); - - if (empty($row)) - throw $this->createNotFoundException('The item does not exist'); - - // delete this row - $em->remove($row); - $em->flush(); + try + { + $cust_handler->deleteCustomer($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } // response $response = new Response(); @@ -487,113 +179,17 @@ class CustomerController extends Controller $response->send(); } - protected function generateYearOptions() - { - $start_year = 1950; - return range($start_year, date("Y") + 1); - } - - public function getCustomerVehicles(Request $req) + public function getCustomerVehicles(Request $req, CustomerHandlerInterface $cust_handler) { if (!$this->isGranted('jo_in.list')) { $exception = $this->createAccessDeniedException('No access.'); throw $exception; } - // get search term - $term = $req->query->get('search'); + $results = $cust_handler->getCustomerVehicles($req); - // get querybuilder - $qb = $this->getDoctrine() - ->getRepository(CustomerVehicle::class) - ->createQueryBuilder('q'); - - /* - // build expression now since we're reusing it - $vehicle_label = $qb->expr()->concat( - 'q.plate_number', - $qb->expr()->literal(' - '), - 'c.first_name', - $qb->expr()->literal(' '), - 'c.last_name', - $qb->expr()->literal(' (+63'), - 'c.phone_mobile', - $qb->expr()->literal(')') - ); - */ - - // count total records - $tquery = $qb->select('COUNT(q)'); - - // add filters to count query - if (!empty($term)) { - $tquery->where('q.plate_number like :search') - ->setParameter('search', $term . '%'); - /* - $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') - ->setParameter('search', $term . '*'); - */ - /* - $tquery->where('q.plate_number LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - */ - } - - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // pagination vars - $page = $req->query->get('page') ?? 1; - $perpage = 20; - $offset = ($page - 1) * $perpage; - $pages = ceil($total / $perpage); - $has_more_pages = $page < $pages ? true : false; - - // build main query - $query = $qb->select('q'); - /* - ->addSelect($vehicle_label . ' as vehicle_label') - ->addSelect('c.first_name as cust_first_name') - ->addSelect('c.last_name as cust_last_name'); - */ - - // add filters if needed - if (!empty($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 . '*'); - */ - /* - $query->where('q.plate_number LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - */ - } - - - // get rows - $query_obj = $query->orderBy('q.plate_number', 'asc') - ->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery(); - // error_log($query_obj->getSql()); - - $obj_rows = $query_obj->getResult(); - - // build vehicles array - $vehicles = []; - - // get country code from services.yaml - $country_code = $this->getParameter('country_code'); - - foreach ($obj_rows as $cv) { - $cust = $cv->getCustomer(); - $vehicles[] = [ - 'id' => $cv->getID(), - 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' (' . $country_code . $cust->getPhoneMobile() . ')', - ]; - } + $vehicles = $results['vehicles']; + $has_more_pages = $results['has_more_pages']; // response return $this->json([ @@ -605,85 +201,26 @@ class CustomerController extends Controller ]); } - public function getCustomerVehicleInfo(Request $req) + public function getCustomerVehicleInfo(Request $req, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - // get id - $id = $req->query->get('id'); + $result = $cust_handler->getCustomerVehicleInfo($req); - // get row data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(CustomerVehicle::class)->find($id); - - // make sure this row exists - if (empty($obj)) { + if ($result == null) + { return $this->json([ 'success' => false, 'error' => 'The item does not exist' ]); } - - $customer = $obj->getCustomer(); - $vehicle = $obj->getVehicle(); - $battery = $obj->getCurrentBattery(); - - // build response - $row = [ - 'customer' => [ - 'id' => $customer->getID(), - 'first_name' => $customer->getFirstName(), - 'last_name' => $customer->getLastName(), - 'customer_notes' => $customer->getCustomerNotes(), - 'phone_mobile' => $customer->getPhoneMobile(), - 'phone_landline' => $customer->getPhoneLandline(), - 'phone_office' => $customer->getPhoneOffice(), - 'phone_fax' => $customer->getPhoneFax(), - ], - 'vehicle' => [ - 'id' => $vehicle->getID(), - 'mfg_name' => $vehicle->getManufacturer()->getName(), - 'make' => $vehicle->getMake(), - 'model_year_from' => $vehicle->getModelYearFrom(), - 'model_year_to' => $vehicle->getModelYearTo(), - 'model_year' => $obj->getModelYear(), - 'color' => $obj->getColor(), - 'plate_number' => $obj->getPlateNumber(), - //'fuel_type' => $obj->getFuelType(), - //'status_condition' => $obj->getStatusCondition(), - ] - ]; - - if (!empty($battery)) { - $row['battery'] = [ - 'id' => $battery->getID(), - 'mfg_name' => $battery->getManufacturer()->getName(), - 'model_name' => $battery->getModel()->getName(), - 'size_name' => $battery->getSize()->getName(), - 'prod_code' => $battery->getProductCode(), - 'warranty_code' => $obj->getWarrantyCode(), - 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", - 'has_motolite_battery' => $obj->hasMotoliteBattery(), - 'is_active' => $obj->isActive() - ]; - } - - // response - return $this->json([ - 'success' => true, - 'data' => $row - ]); - } - - // 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'])) { - $query->join('q.vehicles', 'cv') - ->where('q.first_name LIKE :filter') - ->orWhere('q.last_name LIKE :filter') - ->orWhere('q.customer_classification LIKE :filter') - ->orWhere('cv.plate_number LIKE :filter') - ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + else + { + // response + return $this->json([ + 'success' => true, + 'data' => $result + ]); } } } diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index 96380a23..3a3e5173 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -5,13 +5,86 @@ namespace App\Controller; use Catalyst\MenuBundle\Annotation\Menu; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Doctrine\ORM\EntityManagerInterface; + +use App\Service\RiderTracker; +use App\Service\GISManagerInterface; + +use App\Entity\Rider; + + class HomeController extends Controller { /** * @Menu(selected="home") */ - public function index() + public function index(EntityManagerInterface $em, RiderTracker $rider_tracker, + GISManagerInterface $gis_manager) { - return $this->render('home.html.twig'); + // get map + $params['map_js_file'] = $gis_manager->getJSInitFile(); + + return $this->render('home.html.twig', $params); + } + + public function getRiderLocations(EntityManagerInterface $em, RiderTracker $rider_tracker) + { + // TODO: get active riders from cache + // TODO: get active JOs from cache + // get all riders + $riders = $em->getRepository(Rider::class)->findAll(); + + $locations = []; + foreach ($riders as $rider) + { + // get location for each rider + $rider_id = $rider->getID(); + $coordinates = $rider_tracker->getRiderLocation($rider_id); + + $lat = $coordinates->getLatitude(); + $long = $coordinates->getLongitude(); + + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + { + $has_jo = false; + $clat = 0; + $clong = 0; + $jo_data = []; + } + else + { + $has_jo = true; + $cust_loc = $jo->getCoordinates(); + $clat = $cust_loc->getLatitude(); + $clong = $cust_loc->getLongitude(); + $jo_id = $jo->getID(); + $jo_data = [ + 'id' => $jo_id, + 'status' => $jo->getStatusText(), + 'stype' => $jo->getServiceTypeName(), + 'url' => $this->generateUrl('jo_all_form', ['id' => $jo_id]), + 'plate' => $jo->getCustomerVehicle()->getPlateNumber(), + 'cname' => $jo->getCustomer()->getNameDisplay(), + ]; + } + + + // use rider map label as key + $rider_map_label = $rider->getMapLabel(); + $locations[$rider_id] = [ + 'label' => $rider->getMapLabel(), + 'loc' => [$lat, $long], + 'has_jo' => $has_jo, + 'cust_loc' => [$clat, $clong], + 'jo' => $jo_data, + ]; + + } + + return $this->json([ + 'riders' => $locations, + ]); + } } diff --git a/src/Controller/HubController.php b/src/Controller/HubController.php index eb1faa73..ffce16f6 100644 --- a/src/Controller/HubController.php +++ b/src/Controller/HubController.php @@ -17,6 +17,9 @@ use DateTime; use Catalyst\MenuBundle\Annotation\Menu; +use App\Service\MapTools; +use App\Service\RiderTracker; + class HubController extends Controller { /** @@ -287,4 +290,86 @@ class HubController extends Controller $response->setStatusCode(Response::HTTP_OK); $response->send(); } + + public function nearest(MapTools $map_tools, Request $req) + { + // get lat / long + $lat = $req->query->get('lat'); + $long = $req->query->get('long'); + + // get nearest hubs according to position + $point = new Point($long, $lat); + $result = $map_tools->getClosestHubs($point, 10, date("H:i:s")); + + $hubs = []; + foreach ($result as $hub_res) + { + $hub = $hub_res['hub']; + $coords = $hub->getCoordinates(); + $hubs[] = [ + 'id' => $hub->getID(), + 'long' => $coords->getLongitude(), + 'lat' => $coords->getLatitude(), + 'label' => $hub->getFullName(), + 'name' => $hub->getName(), + 'branch' => $hub->getBranch(), + 'cnum' => $hub->getContactNumbers(), + ]; + } + + return $this->json([ + 'hubs' => $hubs, + ]); + } + + public function getHubRiders(Request $req, RiderTracker $rider_tracker) + { + // get hub id + $hub_id = $req->query->get('id'); + + // get hub + $em = $this->getDoctrine()->getManager(); + $hub = $em->getRepository(Hub::class)->find($hub_id); + + // make sure this row exists + if (empty($hub)) + throw $this->createNotFoundException('The item does not exist'); + + //TODO: get available riders sort by proximity, show 10 + $available_riders = $hub->getAvailableRiders(); + + $riders = []; + + // TODO: remove this later when we don't get all available riders + $riders_limit = 5; + $num_riders = 0; + + foreach ($available_riders as $rider) + { + if ($num_riders > $riders_limit) + break; + + // get location for each rider + $rider_id = $rider->getID(); + $coordinates = $rider_tracker->getRiderLocation($rider_id); + + $lat = $coordinates->getLatitude(); + $long = $coordinates->getLongitude(); + + $riders[] = [ + 'id' => $rider->getID(), + 'first_name' => $rider->getFirstName(), + 'last_name' => $rider->getLastName(), + 'contact_num' => $rider->getContactNumber(), + 'plate_num' => $rider->getPlateNumber(), + 'location' => [$lat, $long], + ]; + + $num_riders++; + } + + return $this->json([ + 'riders' => $riders, + ]); + } } diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 2036ffe0..039ea37d 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -2,128 +2,39 @@ namespace App\Controller; -use App\Ramcar\ServiceType; use App\Ramcar\JOStatus; -use App\Ramcar\WarrantyClass; -use App\Ramcar\DiscountApply; -use App\Ramcar\TradeInType; use App\Ramcar\InvoiceCriteria; -use App\Ramcar\InvoiceStatus; -use App\Ramcar\ModeOfPayment; -use App\Ramcar\TransactionOrigin; -use App\Ramcar\JOEventType; -use App\Ramcar\FacilitatedType; -use App\Ramcar\JORejectionReason; -use App\Entity\JobOrder; -use App\Entity\BatteryManufacturer; -use App\Entity\Customer; use App\Entity\CustomerVehicle; -//use App\Entity\Outlet; -use App\Entity\Hub; use App\Entity\Promo; -use App\Entity\Rider; use App\Entity\Battery; -use App\Entity\JOEvent; -use App\Entity\JORejection; +use App\Entity\JobOrder; -use App\Service\InvoiceCreator; +use App\Service\InvoiceGeneratorInterface; +use App\Service\JobOrderHandlerInterface; +use App\Service\GISManagerInterface; use App\Service\MapTools; -use App\Service\HubCounter; use App\Service\MQTTClient; use App\Service\APNSClient; -use App\Service\WarrantyHandler; - -use Doctrine\ORM\Query; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\LockMode; -use Doctrine\ORM\PessimisticLockException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Contracts\Translation\TranslatorInterface; + +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Catalyst\MenuBundle\Annotation\Menu; -use CrEOF\Spatial\PHP\Types\Geometry\Point; - -use Mosquitto\Client as MosquittoClient; -use DateTime; -use DateInterval; - -use FPDF; - class JobOrderController extends Controller { - public function getJobOrders(Request $req) + public function getJobOrders(Request $req, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - // get search term - $term = $req->query->get('search'); + $params = $jo_handler->getJobOrders($req); - // get querybuilder - $qb = $this->getDoctrine() - ->getRepository(JobOrder::class) - ->createQueryBuilder('q'); - - // build expression now since we're reusing it - $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); - - // count total records - $tquery = $qb->select('COUNT(q)') - ->join('q.customer', 'c') - ->join('q.cus_vehicle', 'v'); - - // add filters to count query - if (!empty($term)) { - $tquery->where($jo_label . ' LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - } - - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // pagination vars - $page = $req->query->get('page') ?? 1; - $perpage = 20; - $offset = ($page - 1) * $perpage; - $pages = ceil($total / $perpage); - $has_more_pages = $page < $pages ? true : false; - - // build main query - $query = $qb->select('q') - ->addSelect($jo_label . ' as jo_label') - ->addSelect('c.first_name as cust_first_name') - ->addSelect('c.last_name as cust_last_name') - ->addSelect('v.plate_number as vehicle_plate_number'); - - // add filters if needed - if (!empty($term)) { - $query->where($jo_label . ' LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - } - - // get rows - $obj_rows = $query->orderBy('q.id', 'asc') - ->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); - - // build job order array - $job_orders = []; - - foreach ($obj_rows as $jo) { - $service_type = ServiceType::getName($jo[0]->getServiceType()); - - $job_orders[] = [ - 'id' => $jo[0]->getID(), - 'text' => $jo['jo_label'] . ' - ' . $service_type - ]; - } + $job_orders = $params['job_orders']; + $has_more_pages = $params['has_more_pages']; // response return $this->json([ @@ -135,233 +46,50 @@ class JobOrderController extends Controller ]); } - protected function fillDropdownParameters(&$params) - { - $em = $this->getDoctrine()->getManager(); - - // db loaded - $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); - // $params['customers'] = $em->getRepository(Customer::class)->findAll(); - $params['promos'] = $em->getRepository(Promo::class)->findAll(); - - // list of hubs - $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); - $fac_hubs = []; - foreach ($hubs as $hub) - { - $fac_hubs[$hub->getID()] = $hub->getName() . ' - ' . $hub->getBranch(); - } - - // name values - $params['service_types'] = ServiceType::getCollection(); - $params['warranty_classes'] = WarrantyClass::getCollection(); - $params['modes_of_payment'] = ModeOfPayment::getCollection(); - $params['statuses'] = JOStatus::getCollection(); - $params['discount_apply'] = DiscountApply::getCollection(); - $params['trade_in_types'] = TradeInType::getCollection(); - $params['facilitated_types'] = FacilitatedType::getCollection(); - $params['facilitated_hubs'] = $fac_hubs; - $params['sources'] = TransactionOrigin::getCollection(); - } - - protected function initFormTags(&$params) - { - // default to editing, as we have more forms editing than creating - $params['ftags'] = [ - 'title' => 'Job Order Form', - 'vehicle_dropdown' => false, - 'invoice_edit' => false, - 'set_map_coordinate' => true, - 'preset_vehicle' => false, - 'ticket_table' => true, - 'cancel_button' => true, - ]; - } - - protected function fillFormTags(&$params) - { - $this->initFormTags($params); - - switch ($params['mode']) - { - case 'create': - $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 'create_vehicle': - $params['ftags']['set_map_coordinate'] = false; - $params['ftags']['invoice_edit'] = true; - $params['ftags']['preset_vehicle'] = true; - $params['ftags']['ticket_table'] = false; - $params['ftags']['cancel_button'] = false; - break; - case 'open_edit': - $params['ftags']['invoice_edit'] = true; - $params['ftags']['preset_vehicle'] = true; - break; - } - } - /** * @Menu(selected="jo_in") */ - public function incomingForm() + public function incomingForm(JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - $params['obj'] = new JobOrder(); - $params['mode'] = 'create'; + $params = $jo_handler->initializeIncomingForm(); + $params['submit_url'] = $this->generateUrl('jo_in_submit'); $params['return_url'] = $this->generateUrl('jo_in'); + $params['map_js_file'] = $gis->getJSJOFile(); - $em = $this->getDoctrine()->getManager(); - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_in") */ - public function openEditForm($id) + public function openEditForm($id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - - $params['obj'] = $jo; - $params['mode'] = 'open_edit'; + $params = $jo_handler->initializeOpenEditForm($id); $params['submit_url'] = $this->generateUrl('jo_open_edit_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_open'); - $params['cvid'] = $jo->getCustomerVehicle()->getID(); - $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); + $params['map_js_file'] = $gis->getJSJOFile(); - $em = $this->getDoctrine()->getManager(); - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function openEditSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic, $id) + public function openEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) { $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - $user = $this->getUser(); - - // initialize error list $error_array = []; - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // 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.'; - } - - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - $stype = $req->request->get('service_type'); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($stype) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->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')) - ->setModeOfPayment($req->request->get('mode_of_payment')) - ->setLandmark($req->request->get('landmark')); - - // did they change invoice? - $invoice_items = $req->request->get('invoice_items', []); - $invoice_change = $req->request->get('invoice_change', 0); - if ($invoice_change) - { - // instantiate invoice criteria - $criteria = new InvoiceCriteria(); - $criteria->setServiceType($stype) - ->setCustomerVehicle($obj->getCustomerVehicle()); - - $ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo')); - - // check for trade in so we can mark it for mobile app - foreach ($invoice_items as $item) - { - // get first trade-in - if (!empty($item['trade_in'])) - { - $obj->setTradeInType($item['trade_in']); - break; - } - } - - if (!$ierror) - $ierror = $this->invoiceBatteries($em, $criteria, $invoice_items); - - if ($ierror) - { - $error_array['invoice'] = $ierror; - } - else - { - // generate the invoice - $iobj = $ic->processCriteria($criteria); - $iobj->setStatus(InvoiceStatus::DRAFT) - ->setCreatedBy($this->getUser()); - - // validate - $ierrors = $validator->validate($iobj); - - // add errors to list - foreach ($ierrors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // remove previous invoice - $old_invoice = $obj->getInvoice(); - $em->remove($old_invoice); - $em->flush(); - - // add invoice to JO - $obj->setInvoice($iobj); - - // persist invoice - $em->persist($iobj); - } - } - - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } + $error_array = $jo_handler->generateJobOrder($req, $id); // check if any errors were found if (!empty($error_array)) { @@ -372,17 +100,6 @@ class JobOrderController extends Controller ], 422); } - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::OPEN_EDIT) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -392,164 +109,36 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_in") */ - public function incomingVehicleForm($cvid) + public function incomingVehicleForm($cvid, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - $params['mode'] = 'create_vehicle'; - $params['submit_url'] = $this->generateUrl('jo_in_submit'); - $params['return_url'] = $this->generateUrl('jo_in'); - $params['cvid'] = $cvid; - - $em = $this->getDoctrine()->getManager(); - - // get customer vehicle - $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); - $params['vid'] = $cv->getVehicle()->getID(); - - // make sure this customer vehicle exists - if (empty($cv)) + try { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); + $params = $jo_handler->initializeIncomingVehicleForm($cvid); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - $jo = new JobOrder(); - $jo->setCustomerVehicle($cv) - ->setCustomer($cv->getCustomer()); + $params['submit_url'] = $this->generateUrl('jo_in_submit'); + $params['return_url'] = $this->generateUrl('jo_in'); - $params['obj'] = $jo; - $this->fillDropdownParameters($params); - $this->fillFormTags($params); + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function incomingSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic) + public function incomingSubmit(Request $req, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); // initialize error list $error_array = []; - - // create new row - $em = $this->getDoctrine()->getManager(); - $obj = new JobOrder(); - - // 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 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.'; - } - } - - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - $stype = $req->request->get('service_type'); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setCreatedBy($this->getUser()) - ->setServiceType($stype) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setCustomer($cust_vehicle->getCustomer()) - ->setCustomerVehicle($cust_vehicle) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::PENDING) - ->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')) - ->setModeOfPayment($req->request->get('mode_of_payment')) - ->setLandmark($req->request->get('landmark')); - - // 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 { - $obj->setReferenceJO($ref_jo); - } - } - - // instantiate invoice criteria - $criteria = new InvoiceCriteria(); - $criteria->setServiceType($stype) - ->setCustomerVehicle($cust_vehicle); - - $ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo')); - $invoice_items = $req->request->get('invoice_items'); - - if (!$ierror && !empty($invoice_items)) - { - // check for trade in so we can mark it for mobile app - foreach ($invoice_items as $item) - { - // get first trade-in - if (!empty($item['trade_in'])) - { - $obj->setTradeInType($item['trade_in']); - break; - } - } - - $ierror = $this->invoiceBatteries($em, $criteria, $invoice_items); - } - - if ($ierror) - { - $error_array['invoice'] = $ierror; - } - else - { - // generate the invoice - $iobj = $ic->processCriteria($criteria); - $iobj->setStatus(InvoiceStatus::DRAFT) - ->setCreatedBy($this->getUser()); - - // validate - $ierrors = $validator->validate($iobj); - - // add errors to list - foreach ($ierrors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // add invoice to JO - $obj->setInvoice($iobj); - - // save - $em->persist($iobj); - } - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } + $id = -1; + $error_array = $jo_handler->generateJobOrder($req, $id); // check if any errors were found if (!empty($error_array)) { @@ -560,156 +149,81 @@ class JobOrderController extends Controller ], 422); } - // validated! save the entity - $em->persist($obj); - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::CREATE) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - $em->flush(); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } - protected function checkTier($tier) - { - // check specified tier - switch ($tier) { - case 'proc': - $tier_key = 'jo_proc'; - $tier_name = 'Dispatch'; - $rows_route = 'jo_proc_rows'; - $edit_route = 'jo_proc_form'; - $unlock_route = 'jo_proc_unlock'; - $jo_status = JOStatus::PENDING; - break; - case 'assign': - $tier_key = 'jo_assign'; - $tier_name = 'Assigning'; - $rows_route = 'jo_assign_rows'; - $edit_route = 'jo_assign_form'; - $unlock_route = 'jo_assign_unlock'; - $jo_status = JOStatus::RIDER_ASSIGN; - break; - case 'fulfill': - $tier_key = 'jo_fulfill'; - $tier_name = 'Fullfillment'; - $rows_route = 'jo_fulfill_rows'; - $edit_route = 'jo_fulfill_form'; - $unlock_route = ''; - $jo_status = [ - JOStatus::ASSIGNED, - JOStatus::IN_PROGRESS - ]; - break; - case 'open': - $tier_key = 'jo_open'; - $tier_name = 'Open'; - $rows_route = 'jo_open_rows'; - $edit_route = ''; - $unlock_route = ''; - $jo_status = [ - JOStatus::PENDING, - JOStatus::RIDER_ASSIGN, - JOStatus::ASSIGNED, - JOStatus::IN_PROGRESS, - JOStatus::IN_TRANSIT, - ]; - break; - case 'all': - $tier_key = 'jo_open'; - $tier_name = 'Open'; - $rows_route = 'jo_open_rows'; - $edit_route = 'jo_all_form'; - $unlock_route = ''; - $jo_status = ''; - break; - default: - $exception = $this->createAccessDeniedException('No access.'); - throw $exception; - } - - // check acl - $this->denyAccessUnlessGranted($tier_key . '.list', null, 'No access.'); - - // return params if allowed access - return [ - 'key' => $tier_key, - 'name' => $tier_name, - 'rows_route' => $rows_route, - 'edit_route' => $edit_route, - 'unlock_route' => $unlock_route, - 'jo_status' => $jo_status - ]; - } - /** * @Menu(selected="jo_proc") */ - public function listProcessing() + public function listProcessing(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_processing'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.processing.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_assign") */ - public function listAssigning() + public function listAssigning(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_assigning'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.assigning.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_fulfill") */ - public function listFulfillment() + public function listFulfillment(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_fulfillment'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.fulfillment.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_open") */ - public function listOpen() + public function listOpen(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_open'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); $params['statuses'] = JOStatus::getCollection(); - return $this->render('job-order/list.open.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_all") */ - public function listAll() + public function listAll(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_all'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.all.html.twig', $params); + return $this->render($template, $params); } /* @@ -729,125 +243,42 @@ class JobOrderController extends Controller } */ - public function getRows(Request $req, $tier) + public function getRows(Request $req, $tier, JobOrderHandlerInterface $jo_handler) { - // check which job order tier is being called for and confirm access - $tier_params = $this->checkTier($tier); - - // get current user - $user = $this->getUser(); - $hubs = $user->getHubs(); - - // get query builder - $qb = $this->getDoctrine() - ->getRepository(JobOrder::class) - ->createQueryBuilder('q'); - - // get datatable params - $datatable = $req->request->get('datatable'); - - // count total records - $tquery = $qb->select('COUNT(q)'); - - $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); - - $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 - $qb = $this->getDoctrine() - ->getRepository(JobOrder::class) - ->createQueryBuilder('q'); - $query = $qb->select('q'); - - $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); - - // 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.date_schedule', 'asc'); + try + { + $params = $jo_handler->getRows($req, $tier); + } + catch (AccessDeniedHttpException $e) + { + throw $this->createAccessDeniedException($e->getMessage()); } - // get rows for this page - $query_obj = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery(); - - // error_log($query_obj->getSQL()); - - $obj_rows = $query_obj->getResult(); - /* - $obj_rows = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); - */ - - $statuses = JOStatus::getCollection(); - $service_types = ServiceType::getCollection(); - - // process rows - $rows = []; - foreach ($obj_rows as $orow) { - // add row data - $row['id'] = $orow->getID(); - $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); - $row['delivery_address'] = $orow->getDeliveryAddress(); - $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); - $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; - $row['service_type'] = $service_types[$orow->getServiceType()]; - $row['status'] = $statuses[$orow->getStatus()]; - $row['flag_advance'] = $orow->isAdvanceOrder(); - $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); - $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; - - $processor = $orow->getProcessedBy(); - if ($processor == null) - $row['processor'] = ''; - else - $row['processor'] = $orow->getProcessedBy()->getFullName(); - - $assignor = $orow->getAssignedBy(); - if ($assignor == null) - $row['assignor'] = ''; - else - $row['assignor'] = $orow->getAssignedBy()->getFullName(); + $rows = $params['rows']; + $meta = $params['meta']; + $tier_params = $params['tier_params']; + foreach ($rows as $key => $data) { // add crud urls + $jo_id = $rows[$key]['id']; + if ($tier == 'open') { - $row['meta']['reassign_hub_url'] = $this->generateUrl('jo_open_hub_form', ['id' => $row['id']]); - $row['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $row['id']]); - $row['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $row['id']]); + $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']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]); } else { - $row['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $row['id']]); - $row['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $row['id']]); + $rows[$key]['meta']['update_url'] = $this->generateUrl($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]); } if ($tier_params['unlock_route'] != '') - $row['meta']['unlock_url'] = $this->generateUrl($tier_params['unlock_route'], ['id' => $row['id']]); + $rows[$key]['meta']['unlock_url'] = $this->generateUrl($tier_params['unlock_route'], ['id' => $jo_id]); - $rows[] = $row; } // response @@ -860,231 +291,50 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_proc") */ - public function processingForm(MapTools $map_tools, $id) + public function processingForm(MapTools $map_tools, $id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - // manual transaction since we're locking - $em->getConnection()->beginTransaction(); - try { - - // lock and get data - $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); - - // make sure this job order exists - if (empty($obj)) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); - } - - // check status - if ($obj->getStatus() != JOStatus::PENDING) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not have a pending status'); - } - - // check if we are the processor - $processor = $obj->getProcessedBy(); - $user = $this->getUser(); - // TODO: go back to list page and display alert / flash that says they cannot access it because they - // are not the processor - if ($processor != null && $processor->getID() != $user->getID()) - { - $em->getConnection()->rollback(); - throw $this->createAccessDeniedException('Not the processor'); - } - - // make this user be the processor - $obj->setProcessedBy($user); - $em->flush(); - - $em->getConnection()->commit(); + $params = $jo_handler->initializeProcessingForm($id, $map_tools); } - catch(PessimisticLockException $e) + catch (AccessDeniedHttpException $e) { - throw $this->createAccessDeniedException('Not the processor'); + throw $this->createAccessDeniedException($e->getMessage()); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - // NOTE: we are able to lock, everything should be fine now - - $params['mode'] = 'update-processing'; - $params['status_cancelled'] = JOStatus::CANCELLED; - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - // get rejections - $rejections = $obj->getHubRejections(); - - // get rejection reasons - $params['rejection_reasons'] = JORejectionReason::getCollection(); - - // get closest hubs - $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); - - $params['hubs'] = []; - - // format duration and distance into friendly time - foreach ($hubs as $hub) { - // duration - $seconds = $hub['duration']; - - if (!empty($seconds) && $seconds > 0) { - $hours = floor($seconds / 3600); - $minutes = ceil(($seconds / 60) % 60); - - $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); - } else { - $hub['duration'] = false; - } - - // distance - $meters = $hub['distance']; - - if (!empty($meters) && $meters > 0) { - $hub['distance'] = round($meters / 1000) . " km"; - } else { - $hub['distance'] = false; - } - - // counters - $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); - $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); - - // check for rejection - $hub['flag_rejected'] = false; - $hub_id = $hub['hub']->getID(); - - foreach ($rejections as $robj) - { - if ($robj->getHub()->getID() === $hub_id) - { - $hub['flag_rejected'] = true; - break; - } - } - - $params['hubs'][] = $hub; - } - - $params['obj'] = $obj; - $params['submit_url'] = $this->generateUrl('jo_proc_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_proc_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_proc'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function processingSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) + public function processingSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - $processor = $obj->getProcessedBy(); - $user = $this->getUser(); - - // check if we're the one processing, return error otherwise - if ($processor == null) - throw $this->createAccessDeniedException('Not the processor'); - - if ($processor != null && $processor->getID() != $user->getID()) - throw $this->createAccessDeniedException('Not the processor'); - // initialize error list $error_array = []; - - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if cancelled already - if (!$obj->canDispatch()) + try { - throw $this->createNotFoundException('Could not dispatch. Job Order is not pending.'); - // TODO: have this handled better, so UI shows the error - // $error_array['dispatch'] = 'Could not dispatch. Job Order is not pending.'; + $error_array = $jo_handler->dispatchJobOrder($req, $id, $mclient); } - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) + catch (AccessDeniedHttpException $e) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + throw $this->createAccessDeniedException($e->getMessage()); } - - // check if hub is set - if (empty($req->request->get('hub'))) + catch (NotFoundHttpException $e) { - $error_array['hub'] = 'No hub selected.'; - } - else - { - // get hub - $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); - - if (empty($hub)) - { - $error_array['hub'] = 'Invalid hub specified.'; - } - } - - // check facilitated type - $fac_type = $req->request->get('facilitated_type'); - if (!empty($fac_type)) - { - if (!FacilitatedType::validate($fac_type)) - $fac_type = null; - } - else - $fac_type = null; - - // check facilitated by - $fac_by_id = $req->request->get('facilitated_by'); - $fac_by = null; - if (!empty($fac_by_id)) - { - $fac_by = $em->getRepository(Hub::class)->find($fac_by_id); - if (empty($fac_by)) - $fac_by = null; - } - - - if (empty($error_array)) - { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::RIDER_ASSIGN) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setFacilitatedType($fac_type) - ->setFacilitatedBy($fac_by) - ->setHub($hub); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -1097,23 +347,6 @@ class JobOrderController extends Controller ], 422); } - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::HUB_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - - // send event to mobile app - $payload = [ - 'event' => 'outlet_assign' - ]; - $mclient->sendEvent($obj, $payload); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -1123,149 +356,49 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_assign") */ - public function assigningForm(MapTools $map_tools, $id) + public function assigningForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - // manual transaction since we're locking - $em->getConnection()->beginTransaction(); - - $params['mode'] = 'update-assigning'; - try { - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); - } - - // check status - if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not have an assigning status'); - } - - // check if super user - $user = $this->getUser(); - if ($user->isSuperAdmin()) - { - // do nothing, just allow page to be accessed - } - else - { - // check if hub is assigned to current user - $user_hubs = $this->getUser()->getHubs(); - if (!in_array($obj->getHub()->getID(), $user_hubs)) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order is not on a hub assigned to this user'); - } - - // check if we are the assignor - $assignor = $obj->getAssignedBy(); - - if ($assignor != null && $assignor->getID() != $user->getID()) - { - $em->getConnection()->rollback(); - throw $this->createAccessDeniedException('Not the assignor'); - } - - // make this user be the assignor - $obj->setAssignedBy($user); - $em->flush(); - } - - $em->getConnection()->commit(); + $params = $jo_handler->initializeAssignForm($id); } - catch (PessimisticLockException $e) + catch (AccessDeniedHttpException $e) { - throw $this->createAccessDeniedException('Not the assignor'); + throw $this->createAccessDeniedException($e->getMessage()); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['submit_url'] = $this->generateUrl('jo_assign_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_assign_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_assign'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); + } - public function assigningSubmit(Request $req, ValidatorInterface $validator, MQTTCLient $mclient, APNSClient $aclient, $id) + public function assigningSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTCLient $mclient, APNSClient $aclient, $id) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); // initialize error list $error_array = []; - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if we can assign - if (!$obj->canAssign()) - throw $this->createNotFoundException('Cannot assign rider to this job order.'); - - - // 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.'; + try + { + $error_array = $jo_handler->assignJobOrder($req, $id, $mclient, $aclient); } - - // check if rider is set - if (empty($req->request->get('rider'))) { - $error_array['rider'] = 'No rider selected.'; - } else { - // get rider - $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); - - if (empty($rider)) { - $error_array['rider'] = 'Invalid rider specified.'; - } - } - - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->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')) - ->setAssignedBy($this->getUser()) - ->setDateAssign(new DateTime()) - ->setRider($rider); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -1277,31 +410,6 @@ class JobOrderController extends Controller ], 422); } - // set rider unavailable - $rider->setAvailable(false); - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - - // send event to mobile app - $payload = [ - 'event' => 'driver_assigned' - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); - - // sned push notification - $aclient->sendPush($obj, "A RESQ rider is on his way to you."); - - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -1311,149 +419,46 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_fulfill") */ - public function fulfillmentForm(MapTools $map_tools, $id) + public function fulfillmentForm(JobOrderHandlerInterface $jo_handler, $id, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - $params['mode'] = 'update-fulfillment'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) + try { - throw $this->createNotFoundException('The job order does not exist'); + $params = $jo_handler->initializeFulfillmentForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - // check status - if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) - { - throw $this->createNotFoundException('The job order does not have a fulfillment status'); - } - - // check if hub is assigned to current user - $user_hubs = $this->getUser()->getHubs(); - if (!in_array($obj->getHub()->getID(), $user_hubs)) - { - throw $this->createNotFoundException('The job order is not on a hub assigned to this user'); - } - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_fulfill'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - protected function updateVehicleBattery(JobOrder $jo) - { - // check if new battery - if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) - return; - - // customer vehicle - $cv = $jo->getCustomerVehicle(); - if ($cv == null) - return; - - // invoice - $invoice = $jo->getInvoice(); - if ($invoice == null) - return; - - // invoice items - $items = $invoice->getItems(); - if (count($items) <= 0) - return; - - // get first battery from invoice - $battery = null; - foreach ($items as $item) - { - $battery = $item->getBattery(); - if ($battery != null) - break; - } - - // no battery in order - if ($battery == null) - return; - - // warranty expiration - $warr = $jo->getWarrantyClass(); - if ($warr == WarrantyClass::WTY_PRIVATE) - $warr_months = $battery->getWarrantyPrivate(); - else if ($warr == WarrantyClass::WTY_COMMERCIAL) - $warr_months = $battery->getWarrantyCommercial(); - else if ($warr == WarrantyClass::WTY_TNV) - $warr_months = 12; - - $warr_date = new DateTime(); - $warr_date->add(new DateInterval('P' . $warr_months . 'M')); - - // update customer vehicle battery - $cv->setCurrentBattery($battery) - ->setHasMotoliteBattery(true) - ->setWarrantyExpiration($warr_date); - } - - public function fulfillmentSubmit(Request $req, ValidatorInterface $validator, - MQTTClient $mclient, $id, WarrantyHandler $wh) + public function fulfillmentSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); // initialize error list $error_array = []; - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // 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.'; + try + { + $error_array = $jo_handler->fulfillJobOrder($req, $id, $mclient); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - // ->setStatus(JOStatus::FULFILLED) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')); - // ->setDateFulfill(new DateTime()); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } - - $obj->fulfill(); // check if any errors were found if (!empty($error_array)) { @@ -1464,290 +469,6 @@ class JobOrderController extends Controller ], 422); } - /* - // set rider available - $rider = $obj->getRider(); - if ($rider != null) - $rider->setAvailable(); - */ - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::FULFILL) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // save to customer vehicle battery record - $this->updateVehicleBattery($obj); - - // create warranty - if ($obj->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) - { - $serial = null; - $warranty_class = $obj->getWarrantyClass(); - $first_name = $obj->getCustomer()->getFirstName(); - $last_name = $obj->getCustomer()->getLastName(); - $mobile_number = $obj->getCustomer()->getPhoneMobile(); - - // check if date fulfilled is null - if ($obj->getDateFulfill() == null) - $date_purchase = $obj->getDateCreate(); - else - $date_purchase = $obj->getDateFulfill(); - - $plate_number = $wh->cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); - - $batt_list = array(); - $invoice = $obj->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(); - } - } - } - - $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); - } - - // validated! save the entity - $em->flush(); - - // get rider - $rider = $obj->getRider(); - - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; - if ($rider->getImageFile() != null) - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); - - // send to mqtt - $payload = [ - 'event' => 'fulfilled', - 'jo_id' => $obj->getID(), - 'driver_image' => $image_url, - 'driver_name' => $rider->getFullName(), - 'driver_id' => $rider->getID(), - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); - - // return successful response - return $this->json([ - 'success' => 'Changes have been saved!' - ]); - } - - protected function sendEvent(JobOrder $job_order, $payload) - { - $sessions = $job_order->getCustomer()->getMobileSessions(); - if (count($sessions) == 0) - return; - - $client = new MosquittoClient(); - $client->connect('localhost', 1883); - - foreach ($sessions as $sess) - { - $phone_num = $sess->getPhoneNumber(); - $channel = 'motolite.control.' . $phone_num; - $client->publish($channel, json_encode($payload)); - } - - $client->disconnect(); - } - - /** - * @Menu(selected="jo_open") - */ - public function openHubForm(MapTools $map_tools, $id) - { - $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - - $em = $this->getDoctrine()->getManager(); - - $params['mode'] = 'update-reassign-hub'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - { - throw $this->createNotFoundException('The job order does not exist'); - } - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - // get rejections - $rejections = $obj->getHubRejections(); - - // get rejection reasons - $params['rejection_reasons'] = JORejectionReason::getCollection(); - - // get closest hubs - $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); - - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['hubs'] = []; - - // format duration and distance into friendly time - foreach ($hubs as $hub) { - // duration - $seconds = $hub['duration']; - - if (!empty($seconds) && $seconds > 0) { - $hours = floor($seconds / 3600); - $minutes = ceil(($seconds / 60) % 60); - - $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); - } else { - $hub['duration'] = false; - } - - // distance - $meters = $hub['distance']; - - if (!empty($meters) && $meters > 0) { - $hub['distance'] = round($meters / 1000) . " km"; - } else { - $hub['distance'] = false; - } - - // counters - $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); - $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); - - // check for rejection - $hub['flag_rejected'] = false; - $hub_id = $hub['hub']->getID(); - - foreach ($rejections as $robj) - { - if ($robj->getHub()->getID() === $hub_id) - { - $hub['flag_rejected'] = true; - break; - } - } - - $params['hubs'][] = $hub; - } - - $params['obj'] = $obj; - $params['submit_url'] = $this->generateUrl('jo_open_hub_submit', ['id' => $obj->getID()]); - $params['return_url'] = $this->generateUrl('jo_open'); - - // response - return $this->render('job-order/form.html.twig', $params); - } - - public function openHubSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) - { - $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - $user = $this->getUser(); - - // initialize error list - $error_array = []; - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // 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 hub is set - if (empty($req->request->get('hub'))) { - $error_array['hub'] = 'No hub selected.'; - } else { - // get hub - $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); - - if (empty($hub)) { - $error_array['hub'] = 'Invalid hub specified.'; - } - } - - if (empty($error_array)) - { - // rider mqtt event - // NOTE: need to send this before saving because rider will be cleared - $rider_payload = [ - 'event' => 'cancelled', - 'reason' => 'Reassigned', - 'jo_id' => $obj->getID(), - ]; - $mclient->sendRiderEvent($obj, $rider_payload); - - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::RIDER_ASSIGN) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setHub($hub) - ->setProcessedBy($this->getUser()) - ->clearRider(); - - // validate - $errors = $validator->validate($obj); - - // 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); - } - - // add event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::HUB_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - - // user mqtt event - $payload = [ - 'event' => 'outlet_assign' - ]; - $mclient->sendEvent($obj, $payload); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -1757,112 +478,44 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_open") */ - public function openRiderForm($id) + public function openHubForm(MapTools $map_tools, $id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - $params['mode'] = 'update-reassign-rider'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) + try { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); + $params = $jo_handler->initializeHubForm($id, $map_tools); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - // check status - if ($obj->getStatus() == JOStatus::PENDING) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not have an assigned hub'); - } - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['submit_url'] = $this->generateUrl('jo_open_rider_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_open_hub_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_open'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function openRiderSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) + public function openHubSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); // initialize error list $error_array = []; - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // 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.'; + try + { + $error_array = $jo_handler->setHub($req, $id, $mclient); } - - // check if rider is set - if (empty($req->request->get('rider'))) { - $error_array['rider'] = 'No rider selected.'; - } else { - // get rider - $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); - - if (empty($rider)) { - $error_array['rider'] = 'Invalid rider specified.'; - } - } - - if (empty($error_array)) { - // rider mqtt event - // NOTE: need to send this before saving because rider will be cleared - $rider_payload = [ - 'event' => 'cancelled', - 'reason' => 'Reassigned', - 'jo_id' => $obj->getID(), - ]; - $mclient->sendRiderEvent($obj, $rider_payload); - - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->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')) - ->setAssignedBy($this->getUser()) - ->setDateAssign(new DateTime()) - ->setAssignedBy($this->getUser()) - ->setRider($rider); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -1874,23 +527,63 @@ class JobOrderController extends Controller ], 422); } - // add event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } - // validated! save the entity - $em->flush(); + /** + * @Menu(selected="jo_open") + */ + public function openRiderForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) + { + $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - // send event to mobile app - $payload = [ - 'event' => 'driver_assigned' - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); + try + { + $params = $jo_handler->initializeRiderForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } + + $params['submit_url'] = $this->generateUrl('jo_open_rider_submit', ['id' => $id]); + $params['return_url'] = $this->generateUrl('jo_open'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function openRiderSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + { + $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); + + // initialize error list + $error_array = []; + + try + { + $error_array = $jo_handler->setRider($req, $id, $mclient); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } + + // 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([ @@ -1901,423 +594,47 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_all") */ - public function allForm($id) + public function allForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); + try + { + $params = $jo_handler->initializeAllForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } - $params['mode'] = 'update-all'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - throw $this->createNotFoundException('The job order does not exist'); - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; $params['return_url'] = $this->generateUrl('jo_all'); $params['submit_url'] = ''; + $params['map_js_file'] = $gis->getJSJOFile(); - // timeline stuff (descending by time) - $params['timeline'] = [ - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 4", - 'color' => "#f4516c" - ], - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 3", - 'color' => "#34bfa3" - ], - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 2", - 'color' => "#716aca" - ], - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 1", - 'color' => "#ffb822" - ], - ]; + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function pdfForm(Request $req, $id, TranslatorInterface $translator) + public function pdfForm(Request $req, $id, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_pdf.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - throw $this->createNotFoundException('The job order does not exist'); - - // set output filename - $filename = 'job-order-' . $obj->getID() . '.pdf'; - - // translate the title and the logo for the pdf - $translated_title = $translator->trans('jo_title_pdf'); - $translated_logo = $translator->trans('image_jo_pdf'); - - // get the country code from services.yaml - $country_code = $this->getParameter('country_code'); - - // generate the pdf - $pdf = new FPDF('P', 'mm', 'letter'); - $pdf->AddPage(); - $pdf->setTitle($translated_title . ' #' . $obj->getID()); - $pdf->SetFillColor(211, 211, 211); - - // style defaults - $margin = 10; - $page_width = $pdf->GetPageWidth() - ($margin * 2); - $table_col_width = $page_width / 12; - $line_height = 5; - $jo_line_height = 10; - $table_line_height = 7; - $font_face = 'Arial'; - $body_font_size = 9; - $header_font_size = 9; - $jo_font_size = 16; - $col1_x = $margin; - $col2_x = 120; - $label_width = 40; - $val_width = 60; - - // insert the logo - $image_path = $this->get('kernel')->getProjectDir() . $translated_logo; - $pdf->Image($image_path, $col1_x, 10); - - // insert JO number - $pdf->SetFont($font_face, 'B', $jo_font_size); - $pdf->SetX($col2_x); - $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); - $pdf->SetTextColor(9, 65, 150); - $pdf->Cell(0, $jo_line_height, $obj->getID()); - - // insert customer info - $customer = $obj->getCustomer(); - $pdf->SetFont($font_face, '', $body_font_size); - $pdf->SetTextColor(0, 0, 0); - - $pdf->Ln($line_height * 7); - - // get current Y - $y = $pdf->GetY(); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Customer Name:'); - $pdf->MultiCell($val_width, $line_height, $customer ? $customer->getFirstName() . ' ' . $customer->getLastName() : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); - $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneMobile() ? $country_code . $customer->getPhoneMobile() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Delivery Date:'); - $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Landline:'); - $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneLandline() ? $country_code . $customer->getPhoneLandline() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Office Phone:'); - $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneOffice() ? $country_code . $customer->getPhoneOffice() : '', 0, 'L'); - - $pdf->SetX($col2_x); - $pdf->Cell($label_width, $line_height, 'Fax:'); - $pdf->MultiCell($val_width, $line_height, $customer && $customer->getPhoneFax() ? $country_code . $customer->getPhoneFax() : '', 0, 'L'); - - // insert vehicle info - $cv = $obj->getCustomerVehicle(); - $vehicle = $cv->getVehicle(); - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Vehicle Details'); - $pdf->Ln($line_height * 2); - - // get current Y - $y = $pdf->GetY(); - - $pdf->SetFont($font_face, '', $body_font_size); - $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(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Brand:'); - $pdf->MultiCell($val_width, $line_height, $vehicle && $vehicle->getManufacturer() ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Model / Year:'); - $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Make:'); - $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); - - // insert battery info - $battery = $cv->getCurrentBattery(); - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Battery Details'); - $pdf->Ln($line_height * 2); - - $pdf->SetFont($font_face, '', $body_font_size); - - // get current Y - $y = $pdf->GetY(); - - $pdf->Cell($label_width, $line_height, 'Current Battery:'); - $pdf->MultiCell($val_width, $line_height, $battery && $battery->getManufacturer() && $battery->getModel() && $battery->getSize() ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Serial Number:'); - $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); - $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); - - // insert transaction details - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Transaction Details'); - $pdf->Ln($line_height * 2); - - $pdf->SetFont($font_face, '', $body_font_size); - - // get current Y - $y = $pdf->GetY(); - - $pdf->Cell($label_width, $line_height, 'Warranty Class:'); - $pdf->MultiCell($val_width, $line_height, WarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); - $pdf->MultiCell(0, $line_height, ModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->Cell($label_width, $line_height, 'Delivery Address:'); - $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Landmark:'); - $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); - $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("g:i A") : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Dispatched By:'); - $pdf->MultiCell(0, $line_height, $obj->getProcessedBy() ? $obj->getProcessedBy()->getFullName() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - // insert delivery instructions - $pdf->SetY($y); - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell(0, $line_height, 'Delivery Instructions'); - $pdf->Ln(); - - $pdf->SetFont($font_face, '', $body_font_size); - $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); - - // insert invoice details - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Invoice Details'); - $pdf->Ln(); - - // invoice table headers - $invoice = $obj->getInvoice(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); - $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); - $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); - $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); - $pdf->SetFont($font_face, '', $body_font_size); - - // build invoice items table - if ($invoice && !empty($invoice->getItems())) + try { - foreach ($invoice->getItems() as $item) - { - $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); - $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); - $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); - $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); - } + $proj_path = $this->get('kernel')->getProjectDir(); + $params = $jo_handler->generatePDFForm($req, $id, $proj_path); } - else + catch (NotFoundHttpException $e) { - $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); + throw $this->createNotFoundException($e->getMessage()); } - $pdf->Ln($line_height * 2); - - // get current Y - $y = $pdf->GetY(); - - // insert invoice footer details - $pdf->Cell($label_width, $line_height, 'Transaction Type:'); - $pdf->MultiCell($val_width, $line_height, ServiceType::getName($obj->getServiceType()), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); - $pdf->SetFont($font_face, ''); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVATExclusivePrice(), 2) : '', 0, 'R'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'OR Name:'); - $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'TAX:'); - $pdf->SetFont($font_face, ''); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVAT(), 2) : '', 0, 'R'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); - $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); - $pdf->SetFont($font_face, ''); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getDiscount(), 2) : '', 0, 'R'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Discount Type:'); - $pdf->MultiCell($val_width, $line_height, $invoice && $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getTotalPrice(), 2) : '', 0, 'R'); - $pdf->SetFont($font_face, ''); + $pdf = $params['obj']; + $filename = $params['filename']; // return response return new Response($pdf->Output('I', $filename), 200, [ @@ -2325,7 +642,7 @@ class JobOrderController extends Controller ]); } - public function cancelJobOrder(Request $req, MQTTClient $mclient, $id) + public function cancelJobOrder(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('joborder.cancel', null, 'No access.'); @@ -2340,190 +657,23 @@ class JobOrderController extends Controller ], 422); } - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - /* - // cancel job order - $obj->setStatus(JOStatus::CANCELLED) - ->setDateCancel(new DateTime()) - ->setCancelReason($cancel_reason); - - // set rider available - $rider = $obj->getRider(); - if ($rider != null) - $rider->setAvailable(); - */ - - $obj->cancel($cancel_reason); - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::CANCEL) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // save - $em->flush(); - - // send mobile app event - $payload = [ - 'event' => 'cancelled', - 'reason' => $cancel_reason, - 'jo_id' => $obj->getID(), - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); + try + { + $jo_handler->cancelJobOrder($req, $id, $mclient); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } // return successful response return $this->json([ 'success' => 'Job order has been cancelled!' ]); + } - // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter - // check if datatable filter is present and append to query - protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status) - { - switch ($tier) - { - case 'fulfill': - $query->where('q.status IN (:statuses)') - ->andWhere('q.hub IN (:hubs)') - ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) - ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); - break; - case 'assign': - $query->where('q.status = :status') - ->andWhere('q.hub IN (:hubs)') - ->setParameter('status', $status) - ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); - break; - case 'open': - if (isset($datatable['query']['data-rows-search'])) - { - $query->innerJoin('q.cus_vehicle', 'cv') - ->innerJoin('q.customer', 'c') - ->where('q.status IN (:statuses)') - ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') - ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) - ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); - } - else - { - $query->where('q.status IN (:statuses)') - ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); - } - break; - case 'all': - 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'] . '%'); - } - break; - default: - $query->where('q.status = :status') - ->setParameter('status', $status); - } - - // get only pending rows - /* - $query->where($qb->expr()->orX( - $qb->expr()where('q.status', 'pending'); - )); - - - // apply filters - if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { - $query->where('q.delivery_address LIKE :filter') - ->orWhere($qb->expr()->concat('c.first_name', $qb->expr()->literal(' '), 'c.last_name') . ' LIKE :filter') - ->orWhere('cv.plate_number LIKE :filter') - ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); - } - */ - } - - protected function invoicePromo($em, InvoiceCriteria $criteria, $promo_id) - { - // return error if there's a problem, false otherwise - - // check service type - $stype = $criteria->getServiceType(); - if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW) - return null; - - - if (empty($promo_id)) - return false; - - // check if this is a valid promo - $promo = $em->getRepository(Promo::class)->find($promo_id); - - if (empty($promo)) - return 'Invalid promo specified.'; - - $criteria->addPromo($promo); - return false; - } - - protected function invoiceBatteries($em, InvoiceCriteria $criteria, $items) - { - // check service type - $stype = $criteria->getServiceType(); - if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) - return null; - - // return error if there's a problem, false otherwise - if (!empty($items)) - { - foreach ($items as $item) - { - // error_log('ITEMS'); - // check if this is a valid battery - $battery = $em->getRepository(Battery::class)->find($item['battery']); - - if (empty($battery)) - { - $error = 'Invalid battery specified.'; - return $error; - } - - // quantity - $qty = $item['quantity']; - if ($qty < 1) - continue; - - /* - // add to criteria - $criteria->addBattery($battery, $qty); - */ - - // if this is a trade in, add trade in - if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in'])) - $trade_in = $item['trade_in']; - else - $trade_in = null; - - $criteria->addEntry($battery, $trade_in, $qty); - } - } - - return null; - } - - public function generateInvoice(Request $req, InvoiceCreator $ic) + public function generateInvoice(Request $req, InvoiceGeneratorInterface $ic) { // error_log('generating invoice...'); $error = false; @@ -2573,11 +723,11 @@ class JobOrderController extends Controller } */ - - $error = $this->invoicePromo($em, $criteria, $promo_id); + // TODO: this snippet should be in the invoice generator + $error = $ic->invoicePromo($criteria, $promo_id); if (!$error) - $error = $this->invoiceBatteries($em, $criteria, $items); + $error = $ic->invoiceBatteries($criteria, $items); if ($error) { @@ -2589,7 +739,7 @@ class JobOrderController extends Controller } // generate the invoice - $iobj = $ic->processCriteria($criteria); + $iobj = $ic->generateInvoice($criteria); // use invoice object values in a json friendly array $invoice = [ @@ -2618,117 +768,45 @@ class JobOrderController extends Controller ]); } - public function unlockProcessor($id) + public function unlockProcessor($id, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_proc.unlock', null, 'No access.'); - // clear lock - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - if ($jo == null) - return $this->redirectToRoute('jo_proc'); - - $jo->setProcessedBy(null); - - $em->flush(); + // call unlockProcessor in job order service + $jo_handler->unlockProcessor($id); // redirect to list return $this->redirectToRoute('jo_proc'); } - public function unlockAssignor($id) + public function unlockAssignor($id, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_assign.unlock', null, 'No access.'); - // clear lock - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - if ($jo == null) - return $this->redirectToRoute('jo_assign'); - - $jo->setAssignedBy(null); - $em->flush(); + // call unlockAssignor in job order service + $jo_handler->unlockAssignor($id); // redirect to list return $this->redirectToRoute('jo_assign'); } - public function rejectHubSubmit(Request $req, ValidatorInterface $validator, $id) + public function rejectHubSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); - // get object data - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - $processor = $jo->getProcessedBy(); - $user = $this->getUser(); - - // check if we're the one processing, return error otherwise - if ($processor == null) - throw $this->createAccessDeniedException('Not the processor'); - - if ($processor != null && $processor->getID() != $user->getID()) - throw $this->createAccessDeniedException('Not the processor'); - // initialize error list $error_array = []; - - // make sure job order exists - if (empty($jo)) - throw $this->createNotFoundException('The item does not exist'); - - // check if hub is set - if (empty($req->request->get('hub'))) + try { - $error_array['hub'] = 'No hub selected.'; + $error_array = $jo_handler->rejectHub($req, $id); } - else + catch (AccessDeniedHttpException $e) { - // get hub - $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); - - if (empty($hub)) - { - $error_array['hub'] = 'Invalid hub specified.'; - } + throw $this->createAccessDeniedException($e->getMessage()); } - - // check if this hub has already been rejected on this job order - $robj = $em->getRepository(JORejection::class)->findOneBy([ - 'job_order' => $jo, - 'hub' => $hub - ]); - - if (!empty($robj)) - $error_array['hub'] = 'This hub has already been rejected for the current job order.'; - - // check if reason is set - if (empty($req->request->get('reason'))) - $error_array['reason'] = 'No reason selected.'; - else if (!JORejectionReason::validate($req->request->get('reason'))) - $error_array['reason'] = 'Invalid reason specified.'; - - if (empty($error_array)) + catch (NotFoundHttpException $e) { - // coordinates - $obj = new JORejection(); - - // set and save values - $obj->setDateCreate(new DateTime()) - ->setHub($hub) - ->setUser($this->getUser()) - ->setJobOrder($jo) - ->setReason($req->request->get('reason')) - ->setRemarks($req->request->get('remarks')) - ->setContactPerson($req->request->get('contact_person')); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -2741,14 +819,107 @@ class JobOrderController extends Controller ], 422); } - // validated! save the entity - $em->persist($obj); - $em->flush(); - // return successful response return $this->json([ 'success' => 'Changes have been saved!', 'request' => $req->request->all() ]); } + + /** + * @Menu(selected="jo_onestep_form") + */ + public function oneStepForm(JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) + { + $this->denyAccessUnlessGranted('jo_onestep.form', null, 'No access.'); + + $params = $jo_handler->initializeOneStepForm(); + $params['submit_url'] = $this->generateUrl('jo_onestep_submit'); + $params['return_url'] = $this->generateUrl('jo_onestep_form'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function oneStepSubmit(Request $req, JobOrderHandlerInterface $jo_handler) + { + $this->denyAccessUnlessGranted('jo_onestep.form', null, 'No access.'); + + // initialize error list + $error_array = []; + $id = -1; + $error_array = $jo_handler->generateOneStepJobOrder($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_onestep_edit_form") + */ + public function oneStepEditForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis, MapTools $map_tools) + { + $this->denyAccessUnlessGranted('jo_onestep.edit', null, 'No access.'); + + $params = $jo_handler->initializeOneStepEditForm($id, $map_tools); + $params['submit_url'] = $this->generateUrl('jo_onestep_edit_submit', ['id' => $id]); + $params['return_url'] = $this->generateUrl('jo_open'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function oneStepEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) + { + $this->denyAccessUnlessGranted('jo_onestep.edit', null, 'No access.'); + + $error_array = []; + $error_array = $jo_handler->generateOneStepJobOrder($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!' + ]); + } + + /** + * @ParamConverter("jo", class="App\Entity\JobOrder") + */ + public function popupInfo(JobOrder $jo) + { + if ($jo == null) + return new Response('No job order data'); + + return $this->render('job-order/popup.html.twig', [ 'jo' => $jo ]); + } } diff --git a/src/Controller/RAPIController.php b/src/Controller/RAPIController.php index a1d709a6..c5d629c8 100644 --- a/src/Controller/RAPIController.php +++ b/src/Controller/RAPIController.php @@ -16,18 +16,18 @@ use CrEOF\Spatial\PHP\Types\Geometry\Point; use App\Ramcar\APIResult; use App\Ramcar\JOStatus; use App\Ramcar\InvoiceCriteria; -use App\Ramcar\ServiceType; +use App\Ramcar\CMBServiceType; use App\Ramcar\WarrantyClass; use App\Ramcar\APIRiderStatus; use App\Ramcar\TransactionOrigin; -use App\Ramcar\TradeInType; +use App\Ramcar\CMBTradeInType; use App\Ramcar\InvoiceStatus; use App\Ramcar\ModeOfPayment; use App\Ramcar\JOEventType; -use App\Service\InvoiceCreator; +use App\Service\InvoiceGeneratorInterface; use App\Service\MQTTClient; -use App\Service\WarrantyHandler; +use App\Service\RedisClientProvider; use App\Entity\RiderSession; use App\Entity\Customer; @@ -43,9 +43,10 @@ use App\Entity\RiderRating; use App\Entity\Rider; use App\Entity\User; use App\Entity\JOEvent; +use App\Entity\Warranty; use DateTime; - +use DateInterval; // Rider API controller class RAPIController extends Controller @@ -136,7 +137,7 @@ class RAPIController extends Controller return $res; } - public function register(Request $req) + public function register(Request $req, RedisClientProvider $redis) { $res = new APIResult(); @@ -179,6 +180,12 @@ class RAPIController extends Controller // save $em->persist($sess); $em->flush(); + + // create redis entry for the session + $redis_client = $redis->getRedisClient(); + $redis_key = 'rider.id.' . $sess->getID(); + error_log('redis_key: ' . $redis_key); + $redis_client->set($redis_key, ''); } catch (DBALException $e) { @@ -202,7 +209,7 @@ class RAPIController extends Controller return $res->getReturnResponse(); } - public function login(Request $req, EncoderFactoryInterface $ef) + public function login(Request $req, EncoderFactoryInterface $ef, RedisClientProvider $redis) { $required_params = [ 'user', @@ -248,6 +255,13 @@ class RAPIController extends Controller $em->flush(); + // update redis rider.id. with the rider id + $redis_client = $redis->getRedisClient(); + $redis_key = 'rider.id.' . $this->session->getID(); + $rider_id = $rider->getID(); + + $redis_client->set($redis_key, $rider_id); + $hub = $rider->getHub(); if ($hub == null) $hub_data = null; @@ -266,7 +280,8 @@ class RAPIController extends Controller // data $data = [ - 'hub' => $hub_data + 'hub' => $hub_data, + 'rider_id' => $rider_id, ]; $res->setData($data); @@ -591,7 +606,7 @@ class RAPIController extends Controller return $res->getReturnResponse(); } - public function payment(Request $req, MQTTClient $mclient, WarrantyHandler $wh) + public function payment(Request $req, MQTTClient $mclient) { $em = $this->getDoctrine()->getManager(); $required_params = ['jo_id']; @@ -619,46 +634,6 @@ class RAPIController extends Controller // TODO: tag rider as unavailable - // save to customer vehicle battery record - // TODO: this has to move to JOHandler - $this->updateVehicleBattery($obj); - - // create warranty - if ($obj->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) - { - $serial = null; - $warranty_class = $obj->getWarrantyClass(); - $first_name = $obj->getCustomer()->getFirstName(); - $last_name = $obj->getCustomer()->getLastName(); - $mobile_number = $obj->getCustomer()->getPhoneMobile(); - - // check if date fulfilled is null - if ($obj->getDateFulfill() == null) - $date_purchase = $obj->getDateCreate(); - else - $date_purchase = $obj->getDateFulfill(); - - $plate_number = $wh->cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); - - $batt_list = array(); - $invoice = $obj->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(); - } - } - } - - $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); - } - $em->flush(); // send mqtt event (fulfilled) @@ -675,6 +650,12 @@ class RAPIController extends Controller ]; $mclient->sendEvent($jo, $payload); + // create the warranty if new battery only + if ($jo->getServiceType () == CMBServiceType::BATTERY_REPLACEMENT_NEW) + { + $this->createWarranty($jo); + } + return $res->getReturnResponse(); } @@ -780,7 +761,7 @@ class RAPIController extends Controller error_log(print_r($all, true)); } - public function changeService(Request $req, InvoiceCreator $ic) + public function changeService(Request $req, InvoiceGeneratorInterface $ic) { $this->debugRequest($req); @@ -793,7 +774,7 @@ class RAPIController extends Controller // check service type $stype_id = $req->request->get('stype_id'); - if (!ServiceType::validate($stype_id)) + if (!CMBServiceType::validate($stype_id)) { $res->setError(true) ->setErrorMessage('Invalid service type - ' . $stype_id); @@ -855,7 +836,7 @@ class RAPIController extends Controller // check trade in $trade_in = $req->request->get('trade_in'); - if (!TradeInType::validate($trade_in)) + if (!CMBTradeInType::validate($trade_in)) $trade_in = null; // check mode of payment @@ -880,8 +861,7 @@ class RAPIController extends Controller error_log('adding entry for battery - ' . $battery->getID()); } - $invoice = $ic->processCriteria($crit); - $invoice->setStatus(InvoiceStatus::DRAFT); + $invoice = $ic->generateInvoice($crit); // remove previous invoice $old_invoice = $jo->getInvoice(); @@ -910,55 +890,79 @@ class RAPIController extends Controller return $res->getReturnResponse(); } - protected function updateVehicleBattery(JobOrder $jo) + protected function createWarranty($jo) { - // check if new battery - if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) - return; + $em = $this->getDoctrine()->getManager(); + $warranty = new Warranty(); - // customer vehicle - $cv = $jo->getCustomerVehicle(); - if ($cv == null) - return; + $warranty_class = $jo->getWarrantyClass(); + $first_name = $jo->getCustomer()->getFirstName(); + $last_name = $jo->getCustomer()->getLastName(); + $mobile_number = $jo->getCustomer()->getPhoneMobile(); - // invoice - $invoice = $jo->getInvoice(); - if ($invoice == null) - return; + // check if date fulfilled is null + if ($jo->getDateFulfill() == null) + $date_create = $jo->getDateCreate(); + else + $date_create = $jo->getDateFulfill(); - // invoice items - $items = $invoice->getItems(); - if (count($items) <= 0) - return; + // normalize the plate number + $plate_number = $this->normalizePlateNumber($jo->getCustomerVehicle()->getPlateNumber()); - // get first battery from invoice - $battery = null; - foreach ($items as $item) + // get battery and its warranty periods + $warranty_period = 0; + $invoice_items = $jo->getInvoice()->getItems(); + foreach ($invoice_items as $item) { - $battery = $item->getBattery(); - if ($battery != null) - break; + if ($item->getBattery() != null) + { + $battery = $item->getBattery(); + $warranty->setBatteryModel($battery->getModel()); + $warranty->setBatterySize($battery->getSize()); + + if ($warranty_class == WarrantyClass::WTY_PRIVATE) + $warranty_period = $battery->getWarrantyPrivate(); + else if ($warranty_class == WarrantyClass::WTY_COMMERCIAL) + $warranty_period = $battery->getWarrantyCommercial(); + else if ($warranty_class == WarrantyClass::WTY_TNV) + $warranty_period = $battery->getWarrantyTnv(); + } } - // no battery in order - if ($battery == null) - return; + // compute expiry date + $expiry_date = $this->computeDateExpire($date_create, $warranty_period); - // warranty expiration - $warr = $jo->getWarrantyClass(); - if ($warr == WarrantyClass::WTY_PRIVATE) - $warr_months = $battery->getWarrantyPrivate(); - else if ($warr == WarrantyClass::WTY_COMMERCIAL) - $warr_months = $battery->getWarrantyCommercial(); - else if ($warr == WarrantyClass::WTY_TNV) - $warr_months = 12; + $warranty->setWarrantyClass($warranty_class) + ->setFirstName($first_name) + ->setLastName($last_name) + ->setMobileNumber($mobile_number) + ->setDatePurchase($date_create) + ->setDateExpire($expiry_date) + ->setPlateNumber($plate_number); - $warr_date = new DateTime(); - $warr_date->add(new DateInterval('P' . $warr_months . 'M')); - - // update customer vehicle battery - $cv->setCurrentBattery($battery) - ->setHasMotoliteBattery(true) - ->setWarrantyExpiration($warr_date); + $em->persist($warranty); + $em->flush(); } + + protected function normalizePlateNumber($plate_number) + { + // make it upper case + $plate_number = trim(strtoupper($plate_number)); + + // remove special characters and spaces + $plate_number = preg_replace('/[^A-Za-z0-9]/', '', $plate_number); + + //error_log('plate number ' . $plate_number); + + return $plate_number; + } + + protected function computeDateExpire($date_create, $warranty_period) + { + $expire_date = clone $date_create; + $expire_date->add(new DateInterval('P'.$warranty_period.'M')); + return $expire_date; + } + + } diff --git a/src/Controller/RiderController.php b/src/Controller/RiderController.php index a62eebd1..838b558b 100644 --- a/src/Controller/RiderController.php +++ b/src/Controller/RiderController.php @@ -10,6 +10,7 @@ use App\Entity\User; use App\Service\FileUploader; use Doctrine\ORM\Query; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; @@ -500,4 +501,13 @@ class RiderController extends Controller ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); } } + + public function popupInfo(EntityManagerInterface $em, $id) + { + $rider = $em->getRepository(Rider::class)->find($id); + if ($rider == null) + return new Response('No rider data'); + + return $this->render('rider/popup.html.twig', [ 'rider' => $rider ]); + } } diff --git a/src/Entity/Battery.php b/src/Entity/Battery.php index e05bff81..4c78fb08 100644 --- a/src/Entity/Battery.php +++ b/src/Entity/Battery.php @@ -60,14 +60,13 @@ class Battery // product code /** - * @ORM\Column(type="string", length=80) - * @Assert\NotBlank() + * @ORM\Column(type="string", length=80, nullable=true) */ protected $prod_code; // sap code /** - * @ORM\Column(type="string", length=80) + * @ORM\Column(type="string", length=80, nullable=true) */ protected $sap_code; diff --git a/src/Entity/Customer.php b/src/Entity/Customer.php index d9dd9b98..b96dfae9 100644 --- a/src/Entity/Customer.php +++ b/src/Entity/Customer.php @@ -231,6 +231,11 @@ class Customer return $this->last_name; } + public function getNameDisplay() + { + return $this->first_name . ' ' . $this->last_name; + } + public function setCustomerClassification($customer_classification) { $this->customer_classification = $customer_classification; diff --git a/src/Entity/CustomerVehicle.php b/src/Entity/CustomerVehicle.php index 3e4c70a4..a5525a38 100644 --- a/src/Entity/CustomerVehicle.php +++ b/src/Entity/CustomerVehicle.php @@ -67,14 +67,12 @@ class CustomerVehicle // vehicle status (new / second-hand) /** * @ORM\Column(type="string", length=15) - * @Assert\NotBlank() */ protected $status_condition; // fuel type - diesel, gas /** * @ORM\Column(type="string", length=15) - * @Assert\NotBlank() */ protected $fuel_type; diff --git a/src/Entity/Rider.php b/src/Entity/Rider.php index 0f5a3116..f9331c03 100644 --- a/src/Entity/Rider.php +++ b/src/Entity/Rider.php @@ -319,4 +319,10 @@ class Rider { return $this->sessions; } + + public function getMapLabel() + { + $map_label = $this->first_name .' ' . $this->last_name; + return $map_label; + } } diff --git a/src/Ramcar/CMBModeOfPayment.php b/src/Ramcar/CMBModeOfPayment.php new file mode 100644 index 00000000..c7ae9908 --- /dev/null +++ b/src/Ramcar/CMBModeOfPayment.php @@ -0,0 +1,16 @@ + 'Cash', + 'credit_card' => 'Credit Card', + 'bank_transfer' => 'Bank Transfer', + ]; +} diff --git a/src/Ramcar/CMBServiceType.php b/src/Ramcar/CMBServiceType.php new file mode 100644 index 00000000..63d18f96 --- /dev/null +++ b/src/Ramcar/CMBServiceType.php @@ -0,0 +1,16 @@ + 'Battery Sales', + 'battery_warranty' => 'Under Warranty', + 'jumpstart' => 'Jumpstart', + ]; +} diff --git a/src/Ramcar/CMBTradeInType.php b/src/Ramcar/CMBTradeInType.php new file mode 100644 index 00000000..8813f441 --- /dev/null +++ b/src/Ramcar/CMBTradeInType.php @@ -0,0 +1,13 @@ + 'Yes', + ]; +} + diff --git a/src/Ramcar/CMBWarrantyClass.php b/src/Ramcar/CMBWarrantyClass.php new file mode 100644 index 00000000..1e2c69b4 --- /dev/null +++ b/src/Ramcar/CMBWarrantyClass.php @@ -0,0 +1,15 @@ + 'Passenger', + 'commercial' => 'Commercial', + ]; + +} diff --git a/src/Service/CustomerHandler/CMBCustomerHandler.php b/src/Service/CustomerHandler/CMBCustomerHandler.php new file mode 100644 index 00000000..fb6a888e --- /dev/null +++ b/src/Service/CustomerHandler/CMBCustomerHandler.php @@ -0,0 +1,698 @@ +em = $em; + $this->validator = $validator; + $this->country_code = $country_code; + + $this->loadTemplates(); + } + + // initialize form to display customer list + public function initializeCustomerIndexForm() + { + $params['template'] = $this->getTwigTemplate('cust_list'); + + return $params; + } + + // get customers + public function getCustomers(Request $req) + { + // build query + $tqb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + $qb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $tqb->select('COUNT(q)'); + + // add filters 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.first_name', '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['title'] = $orow->getTitle(); + $row['first_name'] = $orow->getFirstName(); + $row['last_name'] = $orow->getLastName(); + $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); + $row['flag_mobile_app'] = $orow->hasMobileApp(); + $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; + $row['flag_active'] = $orow->isActive(); + $row['flag_csat'] = $orow->isCSAT(); + + // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events + // prepend the country code before each mobile number + $mobile_number_list = []; + $mobile_numbers = $orow->getMobileNumberList(); + foreach ($mobile_numbers as $mobile_number) + { + $mobile_number_list[] = $this->country_code . $mobile_number; + } + $row['mobile_numbers'] = implode("
", $mobile_number_list); + $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + + return $params; + } + + // initialize add customer form + public function initializeAddCustomerForm() + { + $params['obj'] = new Customer(); + $params['mode'] = 'create'; + + // get dropdown parameters + $this->fillDropdownParameters($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('cust_add_form'); + + // return params + return $params; + } + + // add new customer and customer vehicle, if any + public function addCustomer(Request $req) + { + // create new row + $em = $this->em; + $row = new Customer(); + + $this->setObject($row, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + + if (!empty($vehicles)) + { + foreach ($vehicles as $vehicle) + { + // check if vehicle exists + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + + if (empty($vobj)) + { + $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; + } + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor('') + ->setStatusCondition('') + ->setFuelType('') + ->setActive($vehicle->flag_active) + ->setCustomer($row); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + + if (empty($bobj)) + { + $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; + } + else + { + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + $verrors = $this->validator->validate($cust_vehicle); + + // add errors to list + foreach ($verrors as $error) + { + if (!isset($verror_array[$vehicle->index])) + $verror_array[$vehicle->index] = []; + + $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); + } + + // add to entity + if (!isset($verror_array[$vehicle->index])) + { + $row->addVehicle($cust_vehicle); + } + } + } + } + + // validate + $errors = $this->validator->validate($row); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + $result = []; + // check if any errors were found + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + } + else + { + // validated! save the entity + $em->persist($row); + $em->flush(); + + $result = [ + 'id' => $row->getID(), + ]; + } + + return $result; + } + + // initialize update customer form + public function initializeUpdateCustomerForm($id) + { + $params['mode'] = 'update'; + + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // get dropdown parameters + $this->fillDropdownParameters($params); + // get template to display + $params['template'] = $this->getTwigTemplate('cust_update_form'); + + $params['obj'] = $row; + + return $params; + } + + // update customer and customer vehicle + public function updateCustomer(Request $req, $id) + { + // get row data + $em = $this->em; + $cust = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($cust)) + throw $this->createNotFoundException('The item does not exist'); + + $this->setObject($cust, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // TODO: validate mobile numbers + // TODO: validate vehicles + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + try + { + $this->updateVehicles($em, $cust, $vehicles); + } + catch (CrudException $e) + { + throw new CrudException($e->getMessage()); + } + + // validate + $errors = $this->validator->validate($cust); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + $result = []; + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + + } + else + { + // validated! save the entity. do a persist anyway to save child entities + $em->persist($cust); + $em->flush(); + + $result = [ + 'id' => $cust->getID(), + ]; + } + + return $result; + } + + // delete customer + public function deleteCustomer(int $id) + { + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // delete this row + $em->remove($row); + $em->flush(); + } + + // get customer vehicles + public function getCustomerVehicles(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em + ->getRepository(CustomerVehicle::class) + ->createQueryBuilder('q'); + + /* + // build expression now since we're reusing it + $vehicle_label = $qb->expr()->concat( + 'q.plate_number', + $qb->expr()->literal(' - '), + 'c.first_name', + $qb->expr()->literal(' '), + 'c.last_name', + $qb->expr()->literal(' (+63'), + 'c.phone_mobile', + $qb->expr()->literal(')') + ); + */ + + // count total records + $tquery = $qb->select('COUNT(q)'); + + // add filters to count query + if (!empty($term)) { + $tquery->where('q.plate_number like :search') + ->setParameter('search', $term . '%'); + /* + $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') + ->setParameter('search', $term . '*'); + */ + /* + $tquery->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q'); + /* + ->addSelect($vehicle_label . ' as vehicle_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name'); + */ + + // add filters if needed + if (!empty($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 . '*'); + */ + /* + $query->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + // get rows + $query_obj = $query->orderBy('q.plate_number', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + // error_log($query_obj->getSql()); + + $obj_rows = $query_obj->getResult(); + + // build vehicles array + $vehicles = []; + + foreach ($obj_rows as $cv) { + $cust = $cv->getCustomer(); + $vehicles[] = [ + 'id' => $cv->getID(), + 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' ('. $this->country_code . $cust->getPhoneMobile() . ')', + ]; + } + + $results = [ + 'vehicles' => $vehicles, + 'has_more_pages' => $has_more_pages, + ]; + + return $results; + } + + // get customer vehicle info + public function getCustomerVehicleInfo(Request $req) + { + // get id + $id = $req->query->get('id'); + + // get row data + $em = $this->em; + $obj = $em->getRepository(CustomerVehicle::class)->find($id); + + // make sure this row exists + if (empty($obj)) { + return null; + } + + $customer = $obj->getCustomer(); + $vehicle = $obj->getVehicle(); + $battery = $obj->getCurrentBattery(); + + // build response + $row = [ + 'customer' => [ + 'id' => $customer->getID(), + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'customer_notes' => $customer->getCustomerNotes(), + 'phone_mobile' => $customer->getPhoneMobile(), + 'phone_landline' => $customer->getPhoneLandline(), + 'phone_office' => $customer->getPhoneOffice(), + 'phone_fax' => $customer->getPhoneFax(), + ], + 'vehicle' => [ + 'id' => $vehicle->getID(), + 'mfg_name' => $vehicle->getManufacturer()->getName(), + 'make' => $vehicle->getMake(), + 'model_year_from' => $vehicle->getModelYearFrom(), + 'model_year_to' => $vehicle->getModelYearTo(), + 'model_year' => $obj->getModelYear(), + 'color' => $obj->getColor(), + 'plate_number' => $obj->getPlateNumber(), + ] + ]; + + if (!empty($battery)) { + $row['battery'] = [ + 'id' => $battery->getID(), + 'mfg_name' => $battery->getManufacturer()->getName(), + 'model_name' => $battery->getModel()->getName(), + 'size_name' => $battery->getSize()->getName(), + 'prod_code' => $battery->getProductCode(), + 'warranty_code' => $obj->getWarrantyCode(), + 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", + 'has_motolite_battery' => $obj->hasMotoliteBattery(), + 'is_active' => $obj->isActive() + ]; + } + + return $row; + } + + protected function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + protected function setObject($obj, $req) + { + // set and save values + $obj->setTitle($req->request->get('title')) + ->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setCustomerClassification($req->request->get('customer_classification')) + ->setCustomerNotes($req->request->get('customer_notes')) + ->setEmail($req->request->get('email')) + ->setIsCSAT($req->request->get('flag_csat') ? true : false) + ->setActive($req->request->get('flag_active') ? true : false); + + // phone numbers + $obj->setPhoneMobile($req->request->get('phone_mobile')) + ->setPhoneLandline($req->request->get('phone_landline')) + ->setPhoneOffice($req->request->get('phone_office')) + ->setPhoneFax($req->request->get('phone_fax')); + } + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + + $params['classifications'] = CustomerClassification::getCollection(); + + $params['years'] = $this->generateYearOptions(); + $params['batteries'] = $em->getRepository(Battery::class)->findAll(); + } + + protected function generateYearOptions() + { + $start_year = 1950; + return range($start_year, date("Y") + 1); + } + + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for customer to hash + $this->template_hash['cust_add_form'] = 'customer/cmb.form.html.twig'; + $this->template_hash['cust_update_form'] = 'customer/cmb.form.html.twig'; + $this->template_hash['cust_list'] = 'customer/list.html.twig'; + } + + protected function updateVehicles($em, Customer $cust, $vehicles) + { + $vehicle_ids = []; + + foreach ($vehicles as $vehicle) + { + // check if customer vehicle exists + if (!empty($vehicle->id)) + { + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); + if ($cust_vehicle == null) + throw new CrudException("Could not find customer vehicle."); + + } + // this is a new vehicle + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setCustomer($cust); + $cust->addVehicle($cust_vehicle); + $em->persist($cust_vehicle); + } + + // vehicle, because they could have changed vehicle type + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + if ($vobj == null) + throw new CrudException("Could not find vehicle."); + + // TODO: validate details + + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor('') + ->setStatusCondition('') + ->setFuelType('') + ->setActive($vehicle->flag_active); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + if ($bobj == null) + throw new CrudException("Could not find battery."); + + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + // add to list of vehicles to keep + $vehicle_ids[$cust_vehicle->getID()] = true; + } + + // cleanup + // delete all vehicles not in list + $cvs = $cust->getVehicles(); + foreach ($cvs as $cv) + { + if (!isset($vehicle_ids[$cv->getID()])) + { + $cust->removeVehicle($cv); + $em->remove($cv); + } + } + } + + // 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'])) { + $query->join('q.vehicles', 'cv') + ->where('q.first_name LIKE :filter') + ->orWhere('q.last_name LIKE :filter') + ->orWhere('q.customer_classification LIKE :filter') + ->orWhere('cv.plate_number LIKE :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + } +} diff --git a/src/Service/CustomerHandler/ResqCustomerHandler.php b/src/Service/CustomerHandler/ResqCustomerHandler.php new file mode 100644 index 00000000..5f7e00e2 --- /dev/null +++ b/src/Service/CustomerHandler/ResqCustomerHandler.php @@ -0,0 +1,700 @@ +em = $em; + $this->validator = $validator; + $this->country_code = $country_code; + + $this->loadTemplates(); + } + + // initialize form to display customer list + public function initializeCustomerIndexForm() + { + $params['template'] = $this->getTwigTemplate('cust_list'); + + return $params; + } + + // get customers + public function getCustomers(Request $req) + { + // build query + $tqb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + $qb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $tqb->select('COUNT(q)'); + + // add filters 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.first_name', '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['title'] = $orow->getTitle(); + $row['first_name'] = $orow->getFirstName(); + $row['last_name'] = $orow->getLastName(); + $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); + $row['flag_mobile_app'] = $orow->hasMobileApp(); + $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; + $row['flag_active'] = $orow->isActive(); + $row['flag_csat'] = $orow->isCSAT(); + + // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events + // prepend the country code before each mobile number + $mobile_number_list = []; + $mobile_numbers = $orow->getMobileNumberList(); + foreach ($mobile_numbers as $mobile_number) + { + $mobile_number_list[] = $this->country_code . $mobile_number; + } + $row['mobile_numbers'] = implode("
", $mobile_number_list); + $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + + return $params; + } + + // initialize add customer form + public function initializeAddCustomerForm() + { + $params['obj'] = new Customer(); + $params['mode'] = 'create'; + + // get dropdown parameters + $this->fillDropdownParameters($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('cust_add_form'); + + // return params + return $params; + } + + + // add new customer and customer vehicle, if any + public function addCustomer(Request $req) + { + // create new row + $em = $this->em; + $row = new Customer(); + + $this->setObject($row, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + + if (!empty($vehicles)) + { + foreach ($vehicles as $vehicle) + { + // check if vehicle exists + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + + if (empty($vobj)) + { + $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; + } + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor($vehicle->color) + ->setStatusCondition($vehicle->status_condition) + ->setFuelType($vehicle->fuel_type) + ->setActive($vehicle->flag_active) + ->setCustomer($row); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + + if (empty($bobj)) + { + $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; + } + else + { + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + $verrors = $this->validator->validate($cust_vehicle); + + // add errors to list + foreach ($verrors as $error) + { + if (!isset($verror_array[$vehicle->index])) + $verror_array[$vehicle->index] = []; + + $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); + } + + // add to entity + if (!isset($verror_array[$vehicle->index])) + { + $row->addVehicle($cust_vehicle); + } + } + } + } + + // validate + $errors = $this->validator->validate($row); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + $result = []; + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + } + else + { + // validated! save the entity + $em->persist($row); + $em->flush(); + + $result = [ + 'id' => $row->getID(), + ]; + } + + return $result; + } + + // initialize update customer form + public function initializeUpdateCustomerForm($id) + { + $params['mode'] = 'update'; + + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // get dropdown parameters + $this->fillDropdownParameters($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('cust_update_form'); + + $params['obj'] = $row; + + return $params; + } + + // update customer and customer vehicle + public function updateCustomer(Request $req, $id) + { + // get row data + $em = $this->em; + $cust = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($cust)) + throw $this->createNotFoundException('The item does not exist'); + + $this->setObject($cust, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // TODO: validate mobile numbers + // TODO: validate vehicles + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + $this->updateVehicles($em, $cust, $vehicles); + + // validate + $errors = $this->validator->validate($cust); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + $result = []; + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + + } + else + { + // validated! save the entity. do a persist anyway to save child entities + $em->persist($cust); + $em->flush(); + + $result = [ + 'id' => $cust->getID(), + ]; + } + + return $result; + } + + // delete customer + public function deleteCustomer(int $id) + { + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // delete this row + $em->remove($row); + $em->flush(); + } + + // get customer vehicles + public function getCustomerVehicles(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em + ->getRepository(CustomerVehicle::class) + ->createQueryBuilder('q'); + + /* + // build expression now since we're reusing it + $vehicle_label = $qb->expr()->concat( + 'q.plate_number', + $qb->expr()->literal(' - '), + 'c.first_name', + $qb->expr()->literal(' '), + 'c.last_name', + $qb->expr()->literal(' (+63'), + 'c.phone_mobile', + $qb->expr()->literal(')') + ); + */ + + // count total records + $tquery = $qb->select('COUNT(q)'); + + // add filters to count query + if (!empty($term)) { + $tquery->where('q.plate_number like :search') + ->setParameter('search', $term . '%'); + /* + $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') + ->setParameter('search', $term . '*'); + */ + /* + $tquery->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q'); + /* + ->addSelect($vehicle_label . ' as vehicle_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name'); + */ + + // add filters if needed + if (!empty($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 . '*'); + */ + /* + $query->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + // get rows + $query_obj = $query->orderBy('q.plate_number', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + // error_log($query_obj->getSql()); + + $obj_rows = $query_obj->getResult(); + + // build vehicles array + $vehicles = []; + + foreach ($obj_rows as $cv) { + $cust = $cv->getCustomer(); + $vehicles[] = [ + 'id' => $cv->getID(), + 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' ('. $this->country_code . $cust->getPhoneMobile() . ')', + ]; + } + + $results = [ + 'vehicles' => $vehicles, + 'has_more_pages' => $has_more_pages, + ]; + + return $results; + } + + // get customer vehicle info + public function getCustomerVehicleInfo(Request $req) + { + // get id + $id = $req->query->get('id'); + + // get row data + $em = $this->em; + $obj = $em->getRepository(CustomerVehicle::class)->find($id); + + // make sure this row exists + if (empty($obj)) { + return null; + } + + $customer = $obj->getCustomer(); + $vehicle = $obj->getVehicle(); + $battery = $obj->getCurrentBattery(); + + // build response + $row = [ + 'customer' => [ + 'id' => $customer->getID(), + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'customer_notes' => $customer->getCustomerNotes(), + 'phone_mobile' => $customer->getPhoneMobile(), + 'phone_landline' => $customer->getPhoneLandline(), + 'phone_office' => $customer->getPhoneOffice(), + 'phone_fax' => $customer->getPhoneFax(), + ], + 'vehicle' => [ + 'id' => $vehicle->getID(), + 'mfg_name' => $vehicle->getManufacturer()->getName(), + 'make' => $vehicle->getMake(), + 'model_year_from' => $vehicle->getModelYearFrom(), + 'model_year_to' => $vehicle->getModelYearTo(), + 'model_year' => $obj->getModelYear(), + 'color' => $obj->getColor(), + 'plate_number' => $obj->getPlateNumber(), + 'fuel_type' => $obj->getFuelType(), + 'status_condition' => $obj->getStatusCondition(), + ] + ]; + + if (!empty($battery)) { + $row['battery'] = [ + 'id' => $battery->getID(), + 'mfg_name' => $battery->getManufacturer()->getName(), + 'model_name' => $battery->getModel()->getName(), + 'size_name' => $battery->getSize()->getName(), + 'prod_code' => $battery->getProductCode(), + 'warranty_code' => $obj->getWarrantyCode(), + 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", + 'has_motolite_battery' => $obj->hasMotoliteBattery(), + 'is_active' => $obj->isActive() + ]; + } + + return $row; + } + + protected function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + protected function setObject($obj, $req) + { + // set and save values + $obj->setTitle($req->request->get('title')) + ->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setCustomerClassification($req->request->get('customer_classification')) + ->setCustomerNotes($req->request->get('customer_notes')) + ->setEmail($req->request->get('email')) + ->setIsCSAT($req->request->get('flag_csat') ? true : false) + ->setActive($req->request->get('flag_active') ? true : false); + + // phone numbers + $obj->setPhoneMobile($req->request->get('phone_mobile')) + ->setPhoneLandline($req->request->get('phone_landline')) + ->setPhoneOffice($req->request->get('phone_office')) + ->setPhoneFax($req->request->get('phone_fax')); + } + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + + $params['classifications'] = CustomerClassification::getCollection(); + $params['fuel_types'] = FuelType::getCollection(); + $params['status_conditions'] = VehicleStatusCondition::getCollection(); + + $params['years'] = $this->generateYearOptions(); + $params['batteries'] = $em->getRepository(Battery::class)->findAll(); + } + + protected function generateYearOptions() + { + $start_year = 1950; + return range($start_year, date("Y") + 1); + } + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for customer to hash + $this->template_hash['cust_add_form'] = 'customer/form.html.twig'; + $this->template_hash['cust_update_form'] = 'customer/form.html.twig'; + $this->template_hash['cust_list'] = 'customer/list.html.twig'; + } + + protected function updateVehicles($em, Customer $cust, $vehicles) + { + $vehicle_ids = []; + + foreach ($vehicles as $vehicle) + { + // check if customer vehicle exists + if (!empty($vehicle->id)) + { + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); + if ($cust_vehicle == null) + throw new CrudException("Could not find customer vehicle."); + + } + // this is a new vehicle + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setCustomer($cust); + $cust->addVehicle($cust_vehicle); + $em->persist($cust_vehicle); + } + + // vehicle, because they could have changed vehicle type + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + if ($vobj == null) + throw new CrudException("Could not find vehicle."); + + // TODO: validate details + + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor($vehicle->color) + ->setStatusCondition($vehicle->status_condition) + ->setFuelType($vehicle->fuel_type) + ->setActive($vehicle->flag_active); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + if ($bobj == null) + throw new CrudException("Could not find battery."); + + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + // add to list of vehicles to keep + $vehicle_ids[$cust_vehicle->getID()] = true; + } + + // cleanup + // delete all vehicles not in list + $cvs = $cust->getVehicles(); + foreach ($cvs as $cv) + { + if (!isset($vehicle_ids[$cv->getID()])) + { + $cust->removeVehicle($cv); + $em->remove($cv); + } + } + } + + // 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'])) { + $query->join('q.vehicles', 'cv') + ->where('q.first_name LIKE :filter') + ->orWhere('q.last_name LIKE :filter') + ->orWhere('q.customer_classification LIKE :filter') + ->orWhere('cv.plate_number LIKE :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + } + +} diff --git a/src/Service/CustomerHandlerInterface.php b/src/Service/CustomerHandlerInterface.php new file mode 100644 index 00000000..41256cb8 --- /dev/null +++ b/src/Service/CustomerHandlerInterface.php @@ -0,0 +1,35 @@ +security = $security; + $this->em = $em; + $this->validator = $validator; + } + + public function generateInvoice(InvoiceCriteria $criteria) + { + // initialize + $invoice = new Invoice(); + $total = [ + 'sell_price' => 0.0, + 'vat' => 0.0, + 'vat_ex_price' => 0.0, + 'ti_rate' => 0.0, + 'total_price' => 0.0, + 'discount' => 0.0, + ]; + + $stype = $criteria->getServiceType(); + $cv = $criteria->getCustomerVehicle(); + $has_coolant = $criteria->hasCoolant(); + switch ($stype) + { + case CMBServiceType::JUMPSTART: + $this->processJumpstart($total, $invoice); + break; + //case ServiceType::JUMPSTART_WARRANTY: + // $this->processJumpstartWarranty($total, $invoice); + + case CMBServiceType::BATTERY_REPLACEMENT_NEW: + $this->processEntries($total, $criteria, $invoice); + /* + $this->processBatteries($total, $criteria, $invoice); + $this->processTradeIns($total, $criteria, $invoice); + */ + $this->processDiscount($total, $criteria, $invoice); + break; + + case CMBServiceType::BATTERY_REPLACEMENT_WARRANTY: + $this->processWarranty($total, $criteria, $invoice); + break; + //case ServiceType::POST_RECHARGED: + // $this->processRecharge($total, $invoice); + // break; + //case ServiceType::POST_REPLACEMENT: + // $this->processReplacement($total, $invoice); + // break; + //case ServiceType::TIRE_REPAIR: + // $this->processTireRepair($total, $invoice, $cv); + // $this->processOtherServices($total, $invoice, $stype); + // break; + //case ServiceType::OVERHEAT_ASSISTANCE: + // $this->processOverheat($total, $invoice, $cv, $has_coolant); + // break; + //case ServiceType::EMERGENCY_REFUEL: + // error_log('processing refuel'); + // $ftype = $criteria->getCustomerVehicle()->getFuelType(); + // $this->processRefuel($total, $invoice, $cv); + // break; + } + + // TODO: check if any promo is applied + // apply discounts + $promos = $criteria->getPromos(); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $invoice->setCreatedBy($user); + } + + $invoice->setTotalPrice($total['total_price']) + ->setVATExclusivePrice($total['vat_ex_price']) + ->setVAT($total['vat']) + ->setDiscount($total['discount']) + ->setTradeIn($total['ti_rate']) + ->setStatus(InvoiceStatus::DRAFT); + + // dump + //Debug::dump($invoice, 1); + + return $invoice; + } + + // generate invoice criteria + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array) + { + $em = $this->em; + + // instantiate the invoice criteria + $criteria = new InvoiceCriteria(); + $criteria->setServiceType($jo->getServiceType()) + ->setCustomerVehicle($jo->getCustomerVehicle()); + + $ierror = $this->invoicePromo($criteria, $promo_id); + + if (!$ierror && !empty($invoice_items)) + { + // check for trade-in so we can mark it for mobile app + foreach ($invoice_items as $item) + { + // get first trade-in + if (!empty($item['trade_in'])) + { + $jo->getTradeInType($item['trade_in']); + break; + } + } + + $ierror = $this->invoiceBatteries($criteria, $invoice_items); + } + + if ($ierror) + { + $error_array['invoice'] = $ierror; + } + + else + { + // generate the invoice + $iobj = $this->generateInvoice($criteria); + + // validate + $ierrors = $this->validator->validate($iobj); + + // add errors to list + foreach ($ierrors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if invoice already exists for JO + $old_invoice = $jo->getInvoice(); + if ($old_invoice != null) + { + // remove old invoice + $em->remove($old_invoice); + $em->flush(); + } + + // add invoice to JO + $jo->setInvoice($iobj); + + $em->persist($iobj); + + } + } + + protected function getTaxAmount($price) + { + $vat_ex_price = $this->getTaxExclusivePrice($price); + return $price - $vat_ex_price; + // return round($vat_ex_price * self::TAX_RATE, 2); + } + + protected function getTaxExclusivePrice($price) + { + return round($price / (1 + self::TAX_RATE), 2); + } + + protected function getTradeInRate($ti) + { + $size = $ti['size']; + $trade_in = $ti['trade_in']; + + if ($trade_in == null) + return 0; + + switch ($trade_in) + { + // TODO: for now, tradein uses getTIPriceMotolite. + // Might need to modify later + case CMBTradeInType::YES: + return $size->getTIPriceMotolite(); + } + + return 0; + } + + public function invoicePromo(InvoiceCriteria $criteria, $promo_id) + { + // 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)) + { + return false; + } + + // check if this is a valid promo + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + if (empty($promo)) + return 'Invalid promo specified.'; + + $criteria->addPromo($promo); + return false; + } + + public function invoiceBatteries(InvoiceCriteria $criteria, $items) + { + // check service type + $stype = $criteria->getServiceType(); + if ($stype != CMBServiceType::BATTERY_REPLACEMENT_NEW && $stype != CMBServiceType::BATTERY_REPLACEMENT_WARRANTY) + return null; + + // return error if there's a problem, false otherwise + if (!empty($items)) + { + foreach ($items as $item) + { + // check if this is a valid battery + $battery = $this->em->getRepository(Battery::class)->find($item['battery']); + + if (empty($battery)) + { + $error = 'Invalid battery specified.'; + return $error; + } + + // quantity + $qty = $item['quantity']; + if ($qty < 1) + continue; + + /* + // add to criteria + $criteria->addBattery($battery, $qty); + */ + + // if this is a trade in, add trade in + if (!empty($item['trade_in']) && CMBTradeInType::validate($item['trade_in'])) + $trade_in = $item['trade_in']; + else + $trade_in = null; + + $criteria->addEntry($battery, $trade_in, $qty); + } + } + + return null; + } + + + protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice) + { + // error_log('processing entries...'); + $entries = $criteria->getEntries(); + + $con_batts = []; + $con_tis = []; + foreach ($entries as $entry) + { + $batt = $entry['battery']; + $qty = $entry['qty']; + $trade_in = $entry['trade_in']; + $size = $batt->getSize(); + + // consolidate batteries + $batt_id = $batt->getID(); + if (!isset($con_batts[$batt_id])) + $con_batts[$batt->getID()] = [ + 'batt' => $batt, + 'qty' => 0 + ]; + $con_batts[$batt_id]['qty']++; + + + // no trade-in + if ($trade_in == null) + continue; + + // consolidate trade-ins + $ti_key = $size->getID() . '|' . $trade_in; + if (!isset($con_tis[$ti_key])) + $con_tis[$ti_key] = [ + 'size' => $size, + 'trade_in' => $trade_in, + 'qty' => 0 + ]; + $con_tis[$ti_key]['qty']++; + } + + $this->processBatteries($total, $con_batts, $invoice); + $this->processTradeIns($total, $con_tis, $invoice); + } + + protected function processBatteries(&$total, $con_batts, Invoice $invoice) + { + // process batteries + foreach ($con_batts as $con_data) + { + $batt = $con_data['batt']; + $qty = $con_data['qty']; + + $sell_price = $batt->getSellingPrice(); + $vat = $this->getTaxAmount($sell_price); + // $vat_ex_price = $this->getTaxExclusivePrice($sell_price); + + $total['sell_price'] += $sell_price * $qty; + $total['vat'] += $vat * $qty; + $total['vat_ex_price'] += ($sell_price - $vat) * $qty; + + $total['total_price'] += $sell_price * $qty; + + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName()) + ->setQuantity($qty) + ->setPrice($sell_price) + ->setBattery($batt); + + $invoice->addItem($item); + } + } + + protected function processTradeIns(&$total, $con_tis, Invoice $invoice) + { + foreach ($con_tis as $ti) + { + $qty = $ti['qty']; + $ti_rate = $this->getTradeInRate($ti); + + $total['ti_rate'] += $ti_rate * $qty; + $total['total_price'] -= $ti_rate * $qty; + + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Trade-in ' . CMBTradeInType::getName($ti['trade_in']) . ' ' . $ti['size']->getName() . ' battery') + ->setQuantity($qty) + ->setPrice($ti_rate * -1); + + $invoice->addItem($item); + } + } + + 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; + } + + // if discount is higher than 0, display in invoice + if ($discount > 0) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Promo discount') + ->setQuantity(1) + ->setPrice(-1 * $discount); + $invoice->addItem($item); + } + + $total['discount'] = $discount; + $total['total_price'] -= $discount; + + // process + $invoice->setPromo($promo); + } + + protected function processJumpstart(&$total, $invoice) + { + // add troubleshooting fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Troubleshooting fee') + ->setQuantity(1) + ->setPrice(self::TROUBLESHOOTING_FEE); + $invoice->addItem($item); + + $total['sell_price'] = self::TROUBLESHOOTING_FEE; + $total['vat_ex_price'] = self::TROUBLESHOOTING_FEE; + $total['total_price'] = self::TROUBLESHOOTING_FEE; + } + + protected function processJumpstartWarranty(&$total, $invoice) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Troubleshooting fee') + ->setQuantity(1) + ->setPrice(0.00); + $invoice->addItem($item); + } + + protected function processRecharge(&$total, $invoice) + { + // add recharge fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Recharge fee') + ->setQuantity(1) + ->setPrice(self::RECHARGE_FEE); + $invoice->addItem($item); + + $total['sell_price'] = self::RECHARGE_FEE; + $total['vat_ex_price'] = self::RECHARGE_FEE; + $total['total_price'] = self::RECHARGE_FEE; + } + + protected function processReplacement(&$total, $invoice) + { + // add recharge fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Battery replacement') + ->setQuantity(1) + ->setPrice(self::BATT_REPLACEMENT_FEE); + $invoice->addItem($item); + } + + protected function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) + { + // error_log('processing warranty'); + $entries = $criteria->getEntries(); + foreach ($entries as $entry) + { + $batt = $entry['battery']; + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit') + ->setQuantity(1) + ->setPrice(self::WARRANTY_FEE) + ->setBattery($batt); + $invoice->addItem($item); + } + } + + protected function processOtherServices(&$total, $invoice, $stype) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - ' . CMBServiceType::getName($stype)) + ->setQuantity(1) + ->setPrice(self::OTHER_SERVICES_FEE); + $invoice->addItem($item); + + $total['total_price'] = 200.00; + } + + protected function processOverheat(&$total, $invoice, $cv, $has_coolant) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - Overheat Assistance') + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + + $total_price = $fee; + + if ($has_coolant) + { + $coolant = new InvoiceItem(); + $coolant->setInvoice($invoice) + ->setTitle('4L Coolant') + ->setQuantity(1) + ->setPrice(self::COOLANT_FEE); + $invoice->addItem($coolant); + + $total_price += self::COOLANT_FEE; + //$total_price += 1600; + } + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + protected function processTireRepair(&$total, $invoice, $cv) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - Flat Tire') + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + $total_price = $fee; + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + protected function processRefuel(&$total, $invoice, $cv) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $ftype = $cv->getFuelType(); + $item = new InvoiceItem(); + + // service charge + $item->setInvoice($invoice) + ->setTitle('Service - ' . CMBServiceType::getName(CMBServiceType::EMERGENCY_REFUEL)) + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + $total_price = $fee; + // $total['total_price'] = 200.00; + + $gas_price = self::REFUEL_FEE_GAS; + $diesel_price = self::REFUEL_FEE_DIESEL; + + $fuel = new InvoiceItem(); + error_log('fuel type - ' . $ftype); + switch ($ftype) + { + case FuelType::GAS: + $fuel->setInvoice($invoice) + ->setTitle('4L Fuel - Gas') + ->setQuantity(1) + ->setPrice($gas_price); + $invoice->addItem($fuel); + $total_price += $gas_price; + break; + case FuelType::DIESEL: + $fuel->setInvoice($invoice) + ->setTitle('4L Fuel - Diesel') + ->setQuantity(1) + ->setPrice($diesel_price); + $total_price += $diesel_price; + $invoice->addItem($fuel); + break; + default: + // NOTE: should never get to this point + $fuel->setInvoice($invoice) + ->setTitle('Fuel - Unknown') + ->setQuantity(1) + ->setPrice(0); + $total_price += 0.00; + $invoice->addItem($fuel); + break; + } + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + +} diff --git a/src/Service/InvoiceCreator.php b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php similarity index 63% rename from src/Service/InvoiceCreator.php rename to src/Service/InvoiceGenerator/ResqInvoiceGenerator.php index 1c6a74a7..72e196fe 100644 --- a/src/Service/InvoiceCreator.php +++ b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php @@ -1,8 +1,14 @@ security = $security; + $this->em = $em; + $this->validator = $validator; } - public function getVATAmount($price) + public function generateInvoice(InvoiceCriteria $criteria) { - $vat_ex_price = $this->getVATExclusivePrice($price); + // initialize + $invoice = new Invoice(); + $total = [ + 'sell_price' => 0.0, + 'vat' => 0.0, + 'vat_ex_price' => 0.0, + 'ti_rate' => 0.0, + 'total_price' => 0.0, + 'discount' => 0.0, + ]; + + $stype = $criteria->getServiceType(); + $cv = $criteria->getCustomerVehicle(); + $has_coolant = $criteria->hasCoolant(); + // error_log($stype); + switch ($stype) + { + case ServiceType::JUMPSTART_TROUBLESHOOT: + $this->processJumpstart($total, $invoice); + break; + case ServiceType::JUMPSTART_WARRANTY: + $this->processJumpstartWarranty($total, $invoice); + + case ServiceType::BATTERY_REPLACEMENT_NEW: + $this->processEntries($total, $criteria, $invoice); + /* + $this->processBatteries($total, $criteria, $invoice); + $this->processTradeIns($total, $criteria, $invoice); + */ + $this->processDiscount($total, $criteria, $invoice); + break; + + case ServiceType::BATTERY_REPLACEMENT_WARRANTY: + $this->processWarranty($total, $criteria, $invoice); + break; + case ServiceType::POST_RECHARGED: + $this->processRecharge($total, $invoice); + break; + case ServiceType::POST_REPLACEMENT: + $this->processReplacement($total, $invoice); + break; + case ServiceType::TIRE_REPAIR: + $this->processTireRepair($total, $invoice, $cv); + // $this->processOtherServices($total, $invoice, $stype); + break; + case ServiceType::OVERHEAT_ASSISTANCE: + $this->processOverheat($total, $invoice, $cv, $has_coolant); + break; + case ServiceType::EMERGENCY_REFUEL: + error_log('processing refuel'); + $ftype = $criteria->getCustomerVehicle()->getFuelType(); + $this->processRefuel($total, $invoice, $cv); + break; + } + + // TODO: check if any promo is applied + // apply discounts + $promos = $criteria->getPromos(); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $invoice->setCreatedBy($user); + } + + $invoice->setTotalPrice($total['total_price']) + ->setVATExclusivePrice($total['vat_ex_price']) + ->setVAT($total['vat']) + ->setDiscount($total['discount']) + ->setTradeIn($total['ti_rate']) + ->setStatus(InvoiceStatus::DRAFT); + + // dump + //Debug::dump($invoice, 1); + + return $invoice; + } + + // generate invoice criteria + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array) + { + $em = $this->em; + + // instantiate the invoice criteria + $criteria = new InvoiceCriteria(); + $criteria->setServiceType($jo->getServiceType()) + ->setCustomerVehicle($jo->getCustomerVehicle()); + + $ierror = $this->invoicePromo($criteria, $promo_id); + + if (!$ierror && !empty($invoice_items)) + { + // check for trade-in so we can mark it for mobile app + foreach ($invoice_items as $item) + { + // get first trade-in + if (!empty($item['trade_in'])) + { + $jo->getTradeInType($item['trade_in']); + break; + } + } + + $ierror = $this->invoiceBatteries($criteria, $invoice_items); + } + + if ($ierror) + { + $error_array['invoice'] = $ierror; + } + + else + { + // generate the invoice + $iobj = $this->generateInvoice($criteria); + + // validate + $ierrors = $this->validator->validate($iobj); + + // add errors to list + foreach ($ierrors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if invoice already exists for JO + $old_invoice = $jo->getInvoice(); + if ($old_invoice != null) + { + // remove old invoice + $em->remove($old_invoice); + $em->flush(); + } + + // add invoice to JO + $jo->setInvoice($iobj); + + $em->persist($iobj); + + } + } + + protected function getTaxAmount($price) + { + $vat_ex_price = $this->getTaxExclusivePrice($price); return $price - $vat_ex_price; - // return round($vat_ex_price * self::VAT_RATE, 2); + // return round($vat_ex_price * self::TAX_RATE, 2); } - public function getVATExclusivePrice($price) + protected function getTaxExclusivePrice($price) { - return round($price / (1 + self::VAT_RATE), 2); + return round($price / (1 + self::TAX_RATE), 2); } - public function getTradeInRate($ti) + protected function getTradeInRate($ti) { $size = $ti['size']; $trade_in = $ti['trade_in']; @@ -57,6 +226,75 @@ class InvoiceCreator return 0; } + public function invoicePromo(InvoiceCriteria $criteria, $promo_id) + { + // return error if there's a problem, false otherwise + // check service type + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW) + return null; + + + if (empty($promo_id)) + { + return false; + } + + // check if this is a valid promo + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + if (empty($promo)) + return 'Invalid promo specified.'; + + $criteria->addPromo($promo); + return false; + } + + public function invoiceBatteries(InvoiceCriteria $criteria, $items) + { + // check service type + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) + return null; + + // return error if there's a problem, false otherwise + if (!empty($items)) + { + foreach ($items as $item) + { + // check if this is a valid battery + $battery = $this->em->getRepository(Battery::class)->find($item['battery']); + + if (empty($battery)) + { + $error = 'Invalid battery specified.'; + return $error; + } + + // quantity + $qty = $item['quantity']; + if ($qty < 1) + continue; + + /* + // add to criteria + $criteria->addBattery($battery, $qty); + */ + + // if this is a trade in, add trade in + if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in'])) + $trade_in = $item['trade_in']; + else + $trade_in = null; + + $criteria->addEntry($battery, $trade_in, $qty); + } + } + + return null; + } + + protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice) { // error_log('processing entries...'); @@ -109,8 +347,8 @@ class InvoiceCreator $qty = $con_data['qty']; $sell_price = $batt->getSellingPrice(); - $vat = $this->getVATAmount($sell_price); - // $vat_ex_price = $this->getVATExclusivePrice($sell_price); + $vat = $this->getTaxAmount($sell_price); + // $vat_ex_price = $this->getTaxExclusivePrice($sell_price); $total['sell_price'] += $sell_price * $qty; $total['vat'] += $vat * $qty; @@ -192,22 +430,22 @@ class InvoiceCreator $invoice->setPromo($promo); } - public function processJumpstart(&$total, $invoice) + protected function processJumpstart(&$total, $invoice) { // add troubleshooting fee $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Troubleshooting fee') ->setQuantity(1) - ->setPrice(150.00); + ->setPrice(self::TROUBLESHOOTING_FEE); $invoice->addItem($item); - $total['sell_price'] = 150.00; - $total['vat_ex_price'] = 150.00; - $total['total_price'] = 150.00; + $total['sell_price'] = self::TROUBLESHOOTING_FEE; + $total['vat_ex_price'] = self::TROUBLESHOOTING_FEE; + $total['total_price'] = self::TROUBLESHOOTING_FEE; } - public function processJumpstartWarranty(&$total, $invoice) + protected function processJumpstartWarranty(&$total, $invoice) { $item = new InvoiceItem(); $item->setInvoice($invoice) @@ -217,33 +455,33 @@ class InvoiceCreator $invoice->addItem($item); } - public function processRecharge(&$total, $invoice) + protected function processRecharge(&$total, $invoice) { // add recharge fee $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Recharge fee') ->setQuantity(1) - ->setPrice(300.00); + ->setPrice(self::RECHARGE_FEE); $invoice->addItem($item); - $total['sell_price'] = 300.00; - $total['vat_ex_price'] = 300.00; - $total['total_price'] = 300.00; + $total['sell_price'] = self::RECHARGE_FEE; + $total['vat_ex_price'] = self::RECHARGE_FEE; + $total['total_price'] = self::RECHARGE_FEE; } - public function processReplacement(&$total, $invoice) + protected function processReplacement(&$total, $invoice) { // add recharge fee $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Battery replacement') ->setQuantity(1) - ->setPrice(0.00); + ->setPrice(self::BATT_REPLACEMENT_FEE); $invoice->addItem($item); } - public function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) + protected function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) { // error_log('processing warranty'); $entries = $criteria->getEntries(); @@ -254,25 +492,25 @@ class InvoiceCreator $item->setInvoice($invoice) ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit') ->setQuantity(1) - ->setPrice(0.00) + ->setPrice(self::WARRANTY_FEE) ->setBattery($batt); $invoice->addItem($item); } } - public function processOtherServices(&$total, $invoice, $stype) + protected function processOtherServices(&$total, $invoice, $stype) { $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Service - ' . ServiceType::getName($stype)) ->setQuantity(1) - ->setPrice(200.00); + ->setPrice(self::OTHER_SERVICES_FEE); $invoice->addItem($item); $total['total_price'] = 200.00; } - public function processOverheat(&$total, $invoice, $cv, $has_coolant) + protected function processOverheat(&$total, $invoice, $cv, $has_coolant) { // free if they have a motolite battery if ($cv->hasMotoliteBattery()) @@ -295,20 +533,21 @@ class InvoiceCreator $coolant->setInvoice($invoice) ->setTitle('4L Coolant') ->setQuantity(1) - ->setPrice(1600); + ->setPrice(self::COOLANT_FEE); $invoice->addItem($coolant); - $total_price += 1600; + $total_price += self::COOLANT_FEE; + //$total_price += 1600; } - $vat_ex_price = $this->getVATExclusivePrice($total_price); + $vat_ex_price = $this->getTaxExclusivePrice($total_price); $vat = $total_price - $vat_ex_price; $total['total_price'] = $total_price; $total['vat_ex_price'] = $vat_ex_price; $total['vat'] = $vat; } - public function processTireRepair(&$total, $invoice, $cv) + protected function processTireRepair(&$total, $invoice, $cv) { // free if they have a motolite battery if ($cv->hasMotoliteBattery()) @@ -324,14 +563,14 @@ class InvoiceCreator $invoice->addItem($item); $total_price = $fee; - $vat_ex_price = $this->getVATExclusivePrice($total_price); + $vat_ex_price = $this->getTaxExclusivePrice($total_price); $vat = $total_price - $vat_ex_price; $total['total_price'] = $total_price; $total['vat_ex_price'] = $vat_ex_price; $total['vat'] = $vat; } - public function processRefuel(&$total, $invoice, $cv) + protected function processRefuel(&$total, $invoice, $cv) { // free if they have a motolite battery if ($cv->hasMotoliteBattery()) @@ -351,8 +590,8 @@ class InvoiceCreator $total_price = $fee; // $total['total_price'] = 200.00; - $gas_price = 260; - $diesel_price = 220; + $gas_price = self::REFUEL_FEE_GAS; + $diesel_price = self::REFUEL_FEE_DIESEL; $fuel = new InvoiceItem(); error_log('fuel type - ' . $ftype); @@ -385,84 +624,11 @@ class InvoiceCreator break; } - $vat_ex_price = $this->getVATExclusivePrice($total_price); + $vat_ex_price = $this->getTaxExclusivePrice($total_price); $vat = $total_price - $vat_ex_price; $total['total_price'] = $total_price; $total['vat_ex_price'] = $vat_ex_price; $total['vat'] = $vat; } - public function processCriteria(InvoiceCriteria $criteria) - { - // initialize - $invoice = new Invoice(); - $total = [ - 'sell_price' => 0.0, - 'vat' => 0.0, - 'vat_ex_price' => 0.0, - 'ti_rate' => 0.0, - 'total_price' => 0.0, - 'discount' => 0.0, - ]; - - $stype = $criteria->getServiceType(); - $cv = $criteria->getCustomerVehicle(); - $has_coolant = $criteria->hasCoolant(); - // error_log($stype); - switch ($stype) - { - case ServiceType::JUMPSTART_TROUBLESHOOT: - $this->processJumpstart($total, $invoice); - break; - case ServiceType::JUMPSTART_WARRANTY: - $this->processJumpstartWarranty($total, $invoice); - - case ServiceType::BATTERY_REPLACEMENT_NEW: - $this->processEntries($total, $criteria, $invoice); - /* - $this->processBatteries($total, $criteria, $invoice); - $this->processTradeIns($total, $criteria, $invoice); - */ - $this->processDiscount($total, $criteria, $invoice); - break; - - case ServiceType::BATTERY_REPLACEMENT_WARRANTY: - $this->processWarranty($total, $criteria, $invoice); - break; - case ServiceType::POST_RECHARGED: - $this->processRecharge($total, $invoice); - break; - case ServiceType::POST_REPLACEMENT: - $this->processReplacement($total, $invoice); - break; - case ServiceType::TIRE_REPAIR: - $this->processTireRepair($total, $invoice, $cv); - // $this->processOtherServices($total, $invoice, $stype); - break; - case ServiceType::OVERHEAT_ASSISTANCE: - $this->processOverheat($total, $invoice, $cv, $has_coolant); - break; - case ServiceType::EMERGENCY_REFUEL: - error_log('processing refuel'); - $ftype = $criteria->getCustomerVehicle()->getFuelType(); - $this->processRefuel($total, $invoice, $cv); - break; - } - - // TODO: check if any promo is applied - // apply discounts - $promos = $criteria->getPromos(); - - $invoice->setTotalPrice($total['total_price']) - ->setVATExclusivePrice($total['vat_ex_price']) - ->setVAT($total['vat']) - ->setDiscount($total['discount']) - ->setTradeIn($total['ti_rate']); - - - // dump - //Debug::dump($invoice, 1); - - return $invoice; - } } diff --git a/src/Service/InvoiceGeneratorInterface.php b/src/Service/InvoiceGeneratorInterface.php new file mode 100644 index 00000000..85407c30 --- /dev/null +++ b/src/Service/InvoiceGeneratorInterface.php @@ -0,0 +1,18 @@ +em = $em; + $this->ic = $ic; + $this->security = $security; + $this->validator = $validator; + $this->translator = $translator; + $this->rah = $rah; + $this->country_code = $country_code; + $this->wh = $wh; + + $this->loadTemplates(); + } + + // get job order rows + public function getRows(Request $req, $tier) + { + // check which job order tier is being called for and confirm access + $tier_params = $this->checkTier($tier); + + // get current user + $user = $this->security->getUser(); + if ($user == null) + throw new AccessDeniedHttpException('No access.'); + + $hubs = $user->getHubs(); + + // get query builder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + + $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); + + $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 + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + $query = $qb->select('q'); + + $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); + + // 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.date_schedule', 'asc'); + } + + // get rows for this page + $query_obj = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + + // error_log($query_obj->getSQL()); + + $obj_rows = $query_obj->getResult(); + + $statuses = JOStatus::getCollection(); + $service_types = CMBServiceType::getCollection(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); + $row['delivery_address'] = $orow->getDeliveryAddress(); + $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); + $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; + $row['service_type'] = $service_types[$orow->getServiceType()]; + $row['status'] = $statuses[$orow->getStatus()]; + $row['flag_advance'] = $orow->isAdvanceOrder(); + $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); + $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; + + $processor = $orow->getProcessedBy(); + if ($processor == null) + $row['processor'] = ''; + else + $row['processor'] = $orow->getProcessedBy()->getFullName(); + + $assignor = $orow->getAssignedBy(); + if ($assignor == null) + $row['assignor'] = ''; + else + $row['assignor'] = $orow->getAssignedBy()->getFullName(); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + $params['tier_params'] = $tier_params; + + return $params; + + } + + // get job orders + public function getJobOrders(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // build expression now since we're reusing it + $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); + + // count total records + $tquery = $qb->select('COUNT(q)') + ->join('q.customer', 'c') + ->join('q.cus_vehicle', 'v'); + + // add filters to count query + if (!empty($term)) { + $tquery->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q') + ->addSelect($jo_label . ' as jo_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name') + ->addSelect('v.plate_number as vehicle_plate_number'); + + // add filters if needed + if (!empty($term)) { + $query->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + // get rows + $obj_rows = $query->orderBy('q.id', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // build job order array + $job_orders = []; + + foreach ($obj_rows as $jo) { + $service_type = CMBServiceType::getName($jo[0]->getServiceType()); + + $job_orders[] = [ + 'id' => $jo[0]->getID(), + 'text' => $jo['jo_label'] . ' - ' . $service_type + ]; + } + + $params['job_orders'] = $job_orders; + $params['has_more_pages'] = $has_more_pages; + + return $params; + } + + // creates/updates job order + public function generateJobOrder(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 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 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.'; + } + } + + if (empty($error_array)) { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $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'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::PENDING) + ->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')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')); + + // 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', []); + $promo_id = $req->request->get('invoice_promo'); + $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); + } + + // 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); + $em->flush(); + } + } + + return $error_array; + } + + public function generateOneStepJobOrder(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 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 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.'; + } + } + + // check if hub AND rider is selected + if ((empty($req->request->get('hub_id'))) && + (empty($req->request->get('rider_id')))) { + $error_array['hub'] = 'No hub selected.'; + } else { + if (empty($req->request->get('rider_id'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub_id')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider_id')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + } + } + + if (empty($error_array)) + { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $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'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->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')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')) + ->setHub($hub) + ->setRider($rider); + + // 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', []); + $promo_id = $req->request->get('invoice_promo'); + $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); + } + + // 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); + $em->flush(); + } + } + + return $error_array; + } + + // dispatch job order + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $processor = $obj->getProcessedBy(); + $user = $this->security->getUser();; + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if cancelled already + if (!$obj->canDispatch()) + { + throw new NotFoundHttpException('Could not dispatch. Job Order is not pending.'); + // TODO: have this handled better, so UI shows the error + // $error_array['dispatch'] = 'Could not dispatch. Job Order is not pending.'; + } + + // 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 hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check facilitated type + $fac_type = $req->request->get('facilitated_type'); + if (!empty($fac_type)) + { + if (!FacilitatedType::validate($fac_type)) + $fac_type = null; + } + else + $fac_type = null; + + // check facilitated by + $fac_by_id = $req->request->get('facilitated_by'); + $fac_by = null; + if (!empty($fac_by_id)) + { + $fac_by = $em->getRepository(Hub::class)->find($fac_by_id); + if (empty($fac_by)) + $fac_by = null; + } + + if (empty($error_array)) + { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setFacilitatedType($fac_type) + ->setFacilitatedBy($fac_by) + ->setHub($hub); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // assign job order + public function assignJobOrder(Request $req, $id, MQTTCLient $mclient, APNSClient $aclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if we can assign + if (!$obj->canAssign()) + throw new NotFoundHttpException('Cannot assign rider to this job order.'); + + // 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 rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + // get current user + $user = $this->security->getUser(); + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->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')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // call rider assignment handler's assignJobOrder + $this->rah->assignJobOrder($obj, $rider); + } + + return $error_array; + } + + // fulfill job order + public function fulfillJobOrder(Request $req, $id, MQTTClient $mclient) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // 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.'; + } + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + $obj->fulfill(); + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + $em->persist($event); + + // save to customer vehicle battery record + $this->updateVehicleBattery($obj); + + // validated! save the entity + $em->flush(); + + // get rider + $rider = $obj->getRider(); + + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + // call rider assignment handler's fulfillJobOrder + $this->rah->fulfillJobOrder($obj, $image_url, $rider); + + // create the warranty if new battery only + if ($obj->getServiceType () == CMBServiceType::BATTERY_REPLACEMENT_NEW) + { + $serial = null; + $warranty_class = $obj->getWarrantyClass(); + $first_name = $obj->getCustomer()->getFirstName(); + $last_name = $obj->getCustomer()->getLastName(); + $mobile_number = $obj->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($obj->getDateFulfill() == null) + $date_purchase = $obj->getDateCreate(); + else + $date_purchase = $obj->getDateFulfill(); + + $plate_number = $this->wh->cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); + + $batt_list = array(); + $invoice = $obj->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); + } + } + } + + // cancel job order + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + $cancel_reason = $req->request->get('cancel_reason'); + $obj->cancel($cancel_reason); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CANCEL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + + $em->persist($event); + + // save + $em->flush(); + + // send mobile app event + $payload = [ + 'event' => 'cancelled', + 'reason' => $cancel_reason, + 'jo_id' => $obj->getID(), + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + // set hub for job order + public function setHub($req, $id, $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // 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 hub is set + if (empty($req->request->get('hub'))) { + $error_array['hub'] = 'No hub selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + if (empty($error_array)) + { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setHub($hub) + ->clearRider(); + + if ($user != null) + { + $obj->setProcessedBy($user); + } + + $em->persist($obj); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) { + + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // reject hub for job order + public function rejectHub($req, $id) + { + // get object data + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + $processor = $jo->getProcessedBy(); + $user = $this->security->getUser(); + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($user != null) + { + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + } + + // initialize error list + $error_array = []; + + // make sure job order exists + if (empty($jo)) + throw new NotFoundHttpException('The item does not exist'); + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check if this hub has already been rejected on this job order + $robj = $em->getRepository(JORejection::class)->findOneBy([ + 'job_order' => $jo, + 'hub' => $hub + ]); + + if (!empty($robj)) + $error_array['hub'] = 'This hub has already been rejected for the current job order.'; + + // check if reason is set + if (empty($req->request->get('reason'))) + $error_array['reason'] = 'No reason selected.'; + else if (!JORejectionReason::validate($req->request->get('reason'))) + $error_array['reason'] = 'Invalid reason specified.'; + + if (empty($error_array)) + { + // coordinates + $obj = new JORejection(); + + // set and save values + $obj->setDateCreate(new DateTime()) + ->setHub($hub) + ->setJobOrder($jo) + ->setReason($req->request->get('reason')) + ->setRemarks($req->request->get('remarks')) + ->setContactPerson($req->request->get('contact_person')); + + if ($user != null) + { + $obj->setUser($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // validated! save the entity + $em->persist($obj); + $em->flush(); + } + + return $error_array; + + } + + // set rider for job order + public function setRider($req, $id, $mclient) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // 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 rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + if (empty($error_array)) { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->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')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + $em->persist($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) + { + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'driver_assigned' + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + return $error_array; + } + + // unlock processor + public function unlockProcessor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setProcessedBy(null); + + $em->flush(); + } + } + + // unlock assignor + public function unlockAssignor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setAssignedBy(null); + + $em->flush(); + } + + } + + + // initialize incoming job order form + public function initializeIncomingForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'create'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_form'); + + // return params + return $params; + } + + public function initializeOneStepForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'onestep'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep'); + + // return params + return $params; + } + + public function initializeOneStepEditForm($id, $map_tools) + { + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $obj; + $params['mode'] = 'onestep-edit'; + $params['cvid'] = $obj->getCustomerVehicle()->getID(); + $params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get the hubs + // TODO: move this snippet to a function + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + $params['hubs'][] = $hub; + } + + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep_edit_form'); + + return $params; + } + + // initialize open edit job order form + public function initializeOpenEditForm($id) + { + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $jo; + $params['mode'] = 'open_edit'; + $params['cvid'] = $jo->getCustomerVehicle()->getID(); + $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_edit_form'); + + return $params; + } + + // initialize incoming vehicle form + public function initializeIncomingVehicleForm(int $cvid) + { + $params['mode'] = 'create_vehicle'; + $params['cvid'] = $cvid; + + $em = $this->em; + + // get customer vehicle + $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); + $params['vid'] = $cv->getVehicle()->getID(); + + // make sure this customer vehicle exists + if (empty($cv)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + $jo = new JobOrder(); + $jo->setCustomerVehicle($cv) + ->setCustomer($cv->getCustomer()); + + $params['obj'] = $jo; + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_vehicle_form'); + + return $params; + } + + // initialize all job orders form for a specific job order id + public function initializeAllForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-all'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_all_form'); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + // timeline stuff (descending by time) + $params['timeline'] = [ + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 4", + 'color' => "#f4516c" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 3", + 'color' => "#34bfa3" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 2", + 'color' => "#716aca" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 1", + 'color' => "#ffb822" + ], + ]; + + return $params; + } + + // initialize dispatch/processing job order form + public function initializeProcessingForm($id, $map_tools) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + try + { + // lock and get data + $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); + + // make sure this job order exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have a pending status'); + } + + // check if we are the processor + $processor = $obj->getProcessedBy(); + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + // TODO: go back to list page and display alert / flash that says they cannot access it because they + // are not the processor + if ($processor != null && $processor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the processor'); + } + + // make this user be the processor + $obj->setProcessedBy($user); + } + $em->flush(); + + $em->getConnection()->commit(); + } + catch(PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the processor'); + } + + // NOTE: we are able to lock, everything should be fine now + + $params['mode'] = 'update-processing'; + $params['status_cancelled'] = JOStatus::CANCELLED; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_processing_form'); + + return $params; + } + + // initialize assign job order form + public function initializeAssignForm($id) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + $params['mode'] = 'update-assigning'; + + try + { + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigning status'); + } + + // check if super user + $user = $this->security->getUser(); + if ($user != null) + { + if ($user->isSuperAdmin()) + { + // do nothing, just allow page to be accessed + } + else + { + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + // check if we are the assignor + $assignor = $obj->getAssignedBy(); + + if ($assignor != null && $assignor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the assignor'); + } + + // make this user be the assignor + $obj->setAssignedBy($user); + } + } + + $em->flush(); + + $em->getConnection()->commit(); + } + catch (PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the assignor'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_assigning_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize fulflll job order form + public function initializeFulfillmentForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-fulfillment'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) + { + throw new NotFoundHttpException('The job order does not have a fulfillment status'); + } + + // get current user + $user = $this->security->getUser(); + + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_fulfillment_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize hub form + public function initializeHubForm($id, $map_tools) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-hub'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['status_cancelled'] = JOStatus::CANCELLED; + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_hub_form'); + + return $params; + } + + // initialize rider form + public function initializeRiderForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-rider'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() == JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigned hub'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_rider_form'); + + return $params; + } + + // generate pdf form for job order + public function generatePDFForm($req, $id, $proj_path) + { + $em = $this->em; + $translator = $this->translator; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + // set output filename + $filename = 'job-order-' . $obj->getID() . '.pdf'; + + // translate the title and the logo for the pdf + $translated_title = $translator->trans('jo_title_pdf'); + $translated_logo = $translator->trans('image_jo_pdf'); + $translated_delivery_instructions_label = $translator->trans('delivery_instructions_label'); + + // generate the pdf + $pdf = new FPDF('P', 'mm', 'letter'); + $pdf->AddPage(); + $pdf->setTitle($translated_title . ' #' . $obj->getID()); + $pdf->SetFillColor(211, 211, 211); + + // style defaults + $margin = 10; + $page_width = $pdf->GetPageWidth() - ($margin * 2); + $table_col_width = $page_width / 12; + $line_height = 5; + $jo_line_height = 10; + $table_line_height = 7; + $font_face = 'Arial'; + $body_font_size = 9; + $header_font_size = 9; + $jo_font_size = 16; + $col1_x = $margin; + $col2_x = 120; + $label_width = 40; + $val_width = 60; + + // insert the logo + $image_path = $proj_path . $translated_logo; + $pdf->Image($image_path, $col1_x, 10); + + // insert JO number + $pdf->SetFont($font_face, 'B', $jo_font_size); + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); + $pdf->SetTextColor(9, 65, 150); + $pdf->Cell(0, $jo_line_height, $obj->getID()); + + // insert customer info + $customer = $obj->getCustomer(); + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->SetTextColor(0, 0, 0); + + $pdf->Ln($line_height * 7); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Customer Name:'); + $pdf->MultiCell($val_width, $line_height, $customer ? $customer->getFirstName() . ' ' . $customer->getLastName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneMobile() ? $this->country_code . $customer->getPhoneMobile() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Delivery Date:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landline:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneLandline() ? $this->country_code . $customer->getPhoneLandline() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Office Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneOffice() ? $this->country_code . $customer->getPhoneOffice() : '', 0, 'L'); + + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $line_height, 'Fax:'); + $pdf->MultiCell($val_width, $line_height, $customer && $customer->getPhoneFax() ? $this->country_code . $customer->getPhoneFax() : '', 0, 'L'); + + // insert vehicle info + $cv = $obj->getCustomerVehicle(); + $vehicle = $cv->getVehicle(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Vehicle Details'); + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetFont($font_face, '', $body_font_size); + $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(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Brand:'); + $pdf->MultiCell($val_width, $line_height, $vehicle && $vehicle->getManufacturer() ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Model / Year:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Make:'); + $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); + + // insert battery info + $battery = $cv->getCurrentBattery(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Battery Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Current Battery:'); + $pdf->MultiCell($val_width, $line_height, $battery && $battery->getManufacturer() && $battery->getModel() && $battery->getSize() ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Serial Number:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); + $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); + + // insert transaction details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Transaction Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Warranty Class:'); + $pdf->MultiCell($val_width, $line_height, CMBWarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); + $pdf->MultiCell(0, $line_height, CMBModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->Cell($label_width, $line_height, 'Delivery Address:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landmark:'); + $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("g:i A") : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatched By:'); + $pdf->MultiCell(0, $line_height, $obj->getProcessedBy() ? $obj->getProcessedBy()->getFullName() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + // insert delivery instructions + $pdf->SetY($y); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell(0, $line_height, $translated_delivery_instructions_label); + $pdf->Ln(); + + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); + + // insert invoice details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Invoice Details'); + $pdf->Ln(); + + // invoice table headers + $invoice = $obj->getInvoice(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); + $pdf->SetFont($font_face, '', $body_font_size); + + // build invoice items table + if ($invoice && !empty($invoice->getItems())) + { + foreach ($invoice->getItems() as $item) + { + $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); + } + } + else + { + $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); + } + + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + // insert invoice footer details + $pdf->Cell($label_width, $line_height, 'Transaction Type:'); + $pdf->MultiCell($val_width, $line_height, CMBServiceType::getName($obj->getServiceType()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVATExclusivePrice(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'OR Name:'); + $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'TAX:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVAT(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); + $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getDiscount(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Discount Type:'); + $pdf->MultiCell($val_width, $line_height, $invoice && $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getTotalPrice(), 2) : '', 0, 'R'); + $pdf->SetFont($font_face, ''); + + $params['obj'] = $pdf; + $params['filename'] = $filename; + + return $params; + } + + public function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + // db loaded + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['promos'] = $em->getRepository(Promo::class)->findAll(); + + // list of hubs + $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); + $fac_hubs = []; + foreach ($hubs as $hub) + { + $fac_hubs[$hub->getID()] = $hub->getName() . ' - ' . $hub->getBranch(); + } + + // name values + $params['service_types'] = CMBServiceType::getCollection(); + $params['warranty_classes'] = CMBWarrantyClass::getCollection(); + $params['modes_of_payment'] = CMBModeOfPayment::getCollection(); + $params['statuses'] = JOStatus::getCollection(); + $params['discount_apply'] = DiscountApply::getCollection(); + $params['trade_in_types'] = CMBTradeInType::getCollection(); + $params['facilitated_types'] = FacilitatedType::getCollection(); + $params['facilitated_hubs'] = $fac_hubs; + $params['sources'] = TransactionOrigin::getCollection(); + } + + protected function initFormTags(&$params) + { + // default to editing, as we have more forms editing than creating + $params['ftags'] = [ + 'title' => 'Job Order Form', + 'vehicle_dropdown' => false, + 'invoice_edit' => false, + 'set_map_coordinate' => true, + 'preset_vehicle' => false, + 'ticket_table' => true, + 'cancel_button' => true, + ]; + } + + protected function fillFormTags(&$params) + { + $this->initFormTags($params); + + switch ($params['mode']) + { + case 'create': + $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 'create_vehicle': + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'open_edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + case 'onestep': + $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 'onestep-edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + } + } + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for job order to hash + // TODO: put this in an array declaration + // $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_onestep_edit_form'] = 'job-order/cmb.form.onestep.html.twig'; + } + + protected function checkTier($tier) + { + // check specified tier + switch ($tier) { + case 'proc': + $tier_key = 'jo_proc'; + $tier_name = 'Dispatch'; + $rows_route = 'jo_proc_rows'; + $edit_route = 'jo_proc_form'; + $unlock_route = 'jo_proc_unlock'; + $jo_status = JOStatus::PENDING; + break; + case 'assign': + $tier_key = 'jo_assign'; + $tier_name = 'Assigning'; + $rows_route = 'jo_assign_rows'; + $edit_route = 'jo_assign_form'; + $unlock_route = 'jo_assign_unlock'; + $jo_status = JOStatus::RIDER_ASSIGN; + break; + case 'fulfill': + $tier_key = 'jo_fulfill'; + $tier_name = 'Fullfillment'; + $rows_route = 'jo_fulfill_rows'; + $edit_route = 'jo_fulfill_form'; + $unlock_route = ''; + $jo_status = [ + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS + ]; + break; + case 'open': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = ''; + $unlock_route = ''; + $jo_status = [ + JOStatus::PENDING, + JOStatus::RIDER_ASSIGN, + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS, + JOStatus::IN_TRANSIT, + ]; + break; + case 'all': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = 'jo_all_form'; + $unlock_route = ''; + $jo_status = ''; + break; + default: + throw new AccessDeniedHttpException('No access.'); + } + + // check acl + if (!($this->security->isGranted($tier_key . '.list'))) + throw new AccessDeniedHttpException('No access.'); + + // return params if allowed access + return [ + 'key' => $tier_key, + 'name' => $tier_name, + 'rows_route' => $rows_route, + 'edit_route' => $edit_route, + 'unlock_route' => $unlock_route, + 'jo_status' => $jo_status + ]; + } + + protected function updateVehicleBattery(JobOrder $jo) + { + // check if new battery + if ($jo->getServiceType() != CMBServiceType::BATTERY_REPLACEMENT_NEW) + return; + + // customer vehicle + $cv = $jo->getCustomerVehicle(); + if ($cv == null) + return; + + // invoice + $invoice = $jo->getInvoice(); + if ($invoice == null) + return; + + // invoice items + $items = $invoice->getItems(); + if (count($items) <= 0) + return; + + // get first battery from invoice + $battery = null; + foreach ($items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + break; + } + + // no battery in order + if ($battery == null) + return; + + // warranty expiration + // use GetWarrantyPrivate for passenger warranty + $warr = $jo->getWarrantyClass(); + if ($warr == CMBWarrantyClass::WTY_PASSENGER) + $warr_months = $battery->getWarrantyPrivate(); + else if ($warr == CMBWarrantyClass::WTY_COMMERCIAL) + $warr_months = $battery->getWarrantyCommercial(); + + $warr_date = new DateTime(); + $warr_date->add(new DateInterval('P' . $warr_months . 'M')); + + // update customer vehicle battery + $cv->setCurrentBattery($battery) + ->setHasMotoliteBattery(true) + ->setWarrantyExpiration($warr_date); + } + + // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter + // check if datatable filter is present and append to query + protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status) + { + switch ($tier) + { + case 'fulfill': + $query->where('q.status IN (:statuses)') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'assign': + $query->where('q.status = :status') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('status', $status) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'open': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('q.status IN (:statuses)') + ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + else + { + $query->where('q.status IN (:statuses)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); + } + break; + case 'all': + 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'] . '%'); + } + break; + default: + $query->where('q.status = :status') + ->setParameter('status', $status); + } + } +} diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php new file mode 100644 index 00000000..3f6a336c --- /dev/null +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -0,0 +1,2497 @@ +em = $em; + $this->ic = $ic; + $this->security = $security; + $this->validator = $validator; + $this->translator = $translator; + $this->country_code = $country_code; + $this->wh = $wh; + + $this->loadTemplates(); + } + + // get job order rows + public function getRows(Request $req, $tier) + { + // check which job order tier is being called for and confirm access + $tier_params = $this->checkTier($tier); + + // get current user + $user = $this->security->getUser(); + if ($user == null) + throw new AccessDeniedHttpException('No access.'); + + $hubs = $user->getHubs(); + + // get query builder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + + $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); + + $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 + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + $query = $qb->select('q'); + + $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); + + // 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.date_schedule', 'asc'); + } + + // get rows for this page + $query_obj = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + + // error_log($query_obj->getSQL()); + + $obj_rows = $query_obj->getResult(); + + $statuses = JOStatus::getCollection(); + $service_types = ServiceType::getCollection(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); + $row['delivery_address'] = $orow->getDeliveryAddress(); + $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); + $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; + $row['service_type'] = $service_types[$orow->getServiceType()]; + $row['status'] = $statuses[$orow->getStatus()]; + $row['flag_advance'] = $orow->isAdvanceOrder(); + $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); + $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; + + $processor = $orow->getProcessedBy(); + if ($processor == null) + $row['processor'] = ''; + else + $row['processor'] = $orow->getProcessedBy()->getFullName(); + + $assignor = $orow->getAssignedBy(); + if ($assignor == null) + $row['assignor'] = ''; + else + $row['assignor'] = $orow->getAssignedBy()->getFullName(); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + $params['tier_params'] = $tier_params; + + return $params; + + } + + // get job orders + public function getJobOrders(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // build expression now since we're reusing it + $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); + + // count total records + $tquery = $qb->select('COUNT(q)') + ->join('q.customer', 'c') + ->join('q.cus_vehicle', 'v'); + + // add filters to count query + if (!empty($term)) { + $tquery->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q') + ->addSelect($jo_label . ' as jo_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name') + ->addSelect('v.plate_number as vehicle_plate_number'); + + // add filters if needed + if (!empty($term)) { + $query->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + // get rows + $obj_rows = $query->orderBy('q.id', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // build job order array + $job_orders = []; + + foreach ($obj_rows as $jo) { + $service_type = ServiceType::getName($jo[0]->getServiceType()); + + $job_orders[] = [ + 'id' => $jo[0]->getID(), + 'text' => $jo['jo_label'] . ' - ' . $service_type + ]; + } + + $params['job_orders'] = $job_orders; + $params['has_more_pages'] = $has_more_pages; + + return $params; + } + + // creates/updates job order + public function generateJobOrder(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 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 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.'; + } + } + + if (empty($error_array)) { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $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'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::PENDING) + ->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')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')); + + // 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', []); + $promo_id = $req->request->get('invoice_promo'); + $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); + } + + // 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); + $em->flush(); + } + } + + return $error_array; + } + + // dispatch job order + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $processor = $obj->getProcessedBy(); + $user = $this->security->getUser();; + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if cancelled already + if (!$obj->canDispatch()) + { + throw new NotFoundHttpException('Could not dispatch. Job Order is not pending.'); + // TODO: have this handled better, so UI shows the error + // $error_array['dispatch'] = 'Could not dispatch. Job Order is not pending.'; + } + + // 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 hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check facilitated type + $fac_type = $req->request->get('facilitated_type'); + if (!empty($fac_type)) + { + if (!FacilitatedType::validate($fac_type)) + $fac_type = null; + } + else + $fac_type = null; + + // check facilitated by + $fac_by_id = $req->request->get('facilitated_by'); + $fac_by = null; + if (!empty($fac_by_id)) + { + $fac_by = $em->getRepository(Hub::class)->find($fac_by_id); + if (empty($fac_by)) + $fac_by = null; + } + + if (empty($error_array)) + { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setFacilitatedType($fac_type) + ->setFacilitatedBy($fac_by) + ->setHub($hub); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // assign job order + public function assignJobOrder(Request $req, $id, MQTTCLient $mclient, APNSClient $aclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if we can assign + if (!$obj->canAssign()) + throw new NotFoundHttpException('Cannot assign rider to this job order.'); + + // 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 rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + // get current user + $user = $this->security->getUser(); + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->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')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // set rider unavailable + $rider->setAvailable(false); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'driver_assigned' + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + + // sned push notification + $aclient->sendPush($obj, "A RESQ rider is on his way to you."); + } + + return $error_array; + } + + // fulfill job order + public function fulfillJobOrder(Request $req, $id, MQTTClient $mclient) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // 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.'; + } + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + $obj->fulfill(); + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + $em->persist($event); + + // save to customer vehicle battery record + $this->updateVehicleBattery($obj); + + // validated! save the entity + $em->flush(); + + // get rider + $rider = $obj->getRider(); + + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + // send to mqtt + $payload = [ + 'event' => 'fulfilled', + 'jo_id' => $obj->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + + // create the warranty if new battery only + if ($obj->getServiceType () == ServiceType::BATTERY_REPLACEMENT_NEW) + { + $serial = null; + $warranty_class = $obj->getWarrantyClass(); + $first_name = $obj->getCustomer()->getFirstName(); + $last_name = $obj->getCustomer()->getLastName(); + $mobile_number = $obj->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($obj->getDateFulfill() == null) + $date_purchase = $obj->getDateCreate(); + else + $date_purchase = $obj->getDateFulfill(); + + $plate_number = $this->wh->cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); + + $batt_list = array(); + $invoice = $obj->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); + } + + } + } + + // cancel job order + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + $cancel_reason = $req->request->get('cancel_reason'); + $obj->cancel($cancel_reason); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CANCEL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + + $em->persist($event); + + // save + $em->flush(); + + // send mobile app event + $payload = [ + 'event' => 'cancelled', + 'reason' => $cancel_reason, + 'jo_id' => $obj->getID(), + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + // set hub for job order + public function setHub($req, $id, $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // 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 hub is set + if (empty($req->request->get('hub'))) { + $error_array['hub'] = 'No hub selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + if (empty($error_array)) + { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setHub($hub) + ->clearRider(); + + if ($user != null) + { + $obj->setProcessedBy($user); + } + + $em->persist($obj); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) { + + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // reject hub for job order + public function rejectHub($req, $id) + { + // get object data + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + $processor = $jo->getProcessedBy(); + $user = $this->security->getUser(); + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($user != null) + { + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + } + + // initialize error list + $error_array = []; + + // make sure job order exists + if (empty($jo)) + throw new NotFoundHttpException('The item does not exist'); + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check if this hub has already been rejected on this job order + $robj = $em->getRepository(JORejection::class)->findOneBy([ + 'job_order' => $jo, + 'hub' => $hub + ]); + + if (!empty($robj)) + $error_array['hub'] = 'This hub has already been rejected for the current job order.'; + + // check if reason is set + if (empty($req->request->get('reason'))) + $error_array['reason'] = 'No reason selected.'; + else if (!JORejectionReason::validate($req->request->get('reason'))) + $error_array['reason'] = 'Invalid reason specified.'; + + if (empty($error_array)) + { + // coordinates + $obj = new JORejection(); + + // set and save values + $obj->setDateCreate(new DateTime()) + ->setHub($hub) + ->setJobOrder($jo) + ->setReason($req->request->get('reason')) + ->setRemarks($req->request->get('remarks')) + ->setContactPerson($req->request->get('contact_person')); + + if ($user != null) + { + $obj->setUser($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // validated! save the entity + $em->persist($obj); + $em->flush(); + } + + return $error_array; + + } + + // set rider for job order + public function setRider($req, $id, $mclient) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // 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 rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + if (empty($error_array)) { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->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')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + $em->persist($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) + { + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'driver_assigned' + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + return $error_array; + } + + // unlock processor + public function unlockProcessor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setProcessedBy(null); + + $em->flush(); + } + } + + // unlock assignor + public function unlockAssignor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setAssignedBy(null); + + $em->flush(); + } + + } + + public function generateOneStepJobOrder(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 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 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.'; + } + } + + // check if hub AND rider is selected + if ((empty($req->request->get('hub_id'))) && + (empty($req->request->get('rider_id')))) { + $error_array['hub'] = 'No hub selected.'; + } else { + if (empty($req->request->get('rider_id'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub_id')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider_id')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + } + } + + if (empty($error_array)) + { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $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'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->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')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')) + ->setHub($hub) + ->setRider($rider); + + // 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', []); + $promo_id = $req->request->get('invoice_promo'); + $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); + } + + // 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); + $em->flush(); + } + } + + return $error_array; + + } + + + // initialize incoming job order form + public function initializeIncomingForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'create'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_form'); + + // return params + return $params; + } + + public function initializeOneStepForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'onestep'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep'); + + // return params + return $params; + } + + public function initializeOneStepEditForm($id, $map_tools) + { + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $obj; + $params['mode'] = 'onestep-edit'; + $params['cvid'] = $obj->getCustomerVehicle()->getID(); + $params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get the hubs + // TODO: move this snippet to a function + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + $params['hubs'][] = $hub; + } + + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep_edit_form'); + + return $params; + } + + // initialize open edit job order form + public function initializeOpenEditForm($id) + { + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $jo; + $params['mode'] = 'open_edit'; + $params['cvid'] = $jo->getCustomerVehicle()->getID(); + $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_edit_form'); + + return $params; + } + + // initialize incoming vehicle form + public function initializeIncomingVehicleForm(int $cvid) + { + $params['mode'] = 'create_vehicle'; + $params['cvid'] = $cvid; + + $em = $this->em; + + // get customer vehicle + $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); + $params['vid'] = $cv->getVehicle()->getID(); + + // make sure this customer vehicle exists + if (empty($cv)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + $jo = new JobOrder(); + $jo->setCustomerVehicle($cv) + ->setCustomer($cv->getCustomer()); + + $params['obj'] = $jo; + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_vehicle_form'); + + return $params; + } + + // initialize all job orders form for a specific job order id + public function initializeAllForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-all'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_all_form'); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + // timeline stuff (descending by time) + $params['timeline'] = [ + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 4", + 'color' => "#f4516c" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 3", + 'color' => "#34bfa3" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 2", + 'color' => "#716aca" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 1", + 'color' => "#ffb822" + ], + ]; + + return $params; + } + + // initialize dispatch/processing job order form + public function initializeProcessingForm($id, $map_tools) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + try + { + // lock and get data + $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); + + // make sure this job order exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have a pending status'); + } + + // check if we are the processor + $processor = $obj->getProcessedBy(); + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + // TODO: go back to list page and display alert / flash that says they cannot access it because they + // are not the processor + if ($processor != null && $processor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the processor'); + } + + // make this user be the processor + $obj->setProcessedBy($user); + } + $em->flush(); + + $em->getConnection()->commit(); + } + catch(PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the processor'); + } + + // NOTE: we are able to lock, everything should be fine now + + $params['mode'] = 'update-processing'; + $params['status_cancelled'] = JOStatus::CANCELLED; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_processing_form'); + + return $params; + } + + // initialize assign job order form + public function initializeAssignForm($id) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + $params['mode'] = 'update-assigning'; + + try + { + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigning status'); + } + + // check if super user + $user = $this->security->getUser(); + if ($user != null) + { + if ($user->isSuperAdmin()) + { + // do nothing, just allow page to be accessed + } + else + { + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + // check if we are the assignor + $assignor = $obj->getAssignedBy(); + + if ($assignor != null && $assignor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the assignor'); + } + + // make this user be the assignor + $obj->setAssignedBy($user); + } + } + + $em->flush(); + + $em->getConnection()->commit(); + } + catch (PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the assignor'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_assigning_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize fulflll job order form + public function initializeFulfillmentForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-fulfillment'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) + { + throw new NotFoundHttpException('The job order does not have a fulfillment status'); + } + + // get current user + $user = $this->security->getUser(); + + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_fulfillment_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize hub form + public function initializeHubForm($id, $map_tools) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-hub'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['status_cancelled'] = JOStatus::CANCELLED; + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_hub_form'); + + return $params; + } + + // initialize rider form + public function initializeRiderForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-rider'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() == JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigned hub'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_rider_form'); + + return $params; + } + + // generate pdf form for job order + public function generatePDFForm($req, $id, $proj_path) + { + $em = $this->em; + $translator = $this->translator; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + // set output filename + $filename = 'job-order-' . $obj->getID() . '.pdf'; + + // translate the title and the logo for the pdf + $translated_title = $translator->trans('jo_title_pdf'); + $translated_logo = $translator->trans('image_jo_pdf'); + + // generate the pdf + $pdf = new FPDF('P', 'mm', 'letter'); + $pdf->AddPage(); + $pdf->setTitle($translated_title . ' #' . $obj->getID()); + $pdf->SetFillColor(211, 211, 211); + + // style defaults + $margin = 10; + $page_width = $pdf->GetPageWidth() - ($margin * 2); + $table_col_width = $page_width / 12; + $line_height = 5; + $jo_line_height = 10; + $table_line_height = 7; + $font_face = 'Arial'; + $body_font_size = 9; + $header_font_size = 9; + $jo_font_size = 16; + $col1_x = $margin; + $col2_x = 120; + $label_width = 40; + $val_width = 60; + + // insert the logo + $image_path = $proj_path . $translated_logo; + $pdf->Image($image_path, $col1_x, 10); + + // insert JO number + $pdf->SetFont($font_face, 'B', $jo_font_size); + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); + $pdf->SetTextColor(9, 65, 150); + $pdf->Cell(0, $jo_line_height, $obj->getID()); + + // insert customer info + $customer = $obj->getCustomer(); + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->SetTextColor(0, 0, 0); + + $pdf->Ln($line_height * 7); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Customer Name:'); + $pdf->MultiCell($val_width, $line_height, $customer ? $customer->getFirstName() . ' ' . $customer->getLastName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneMobile() ? $this->country_code . $customer->getPhoneMobile() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Delivery Date:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landline:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneLandline() ? $this->country_code . $customer->getPhoneLandline() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Office Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneOffice() ? $this->country_code . $customer->getPhoneOffice() : '', 0, 'L'); + + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $line_height, 'Fax:'); + $pdf->MultiCell($val_width, $line_height, $customer && $customer->getPhoneFax() ? $this->country_code . $customer->getPhoneFax() : '', 0, 'L'); + + // insert vehicle info + $cv = $obj->getCustomerVehicle(); + $vehicle = $cv->getVehicle(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Vehicle Details'); + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetFont($font_face, '', $body_font_size); + $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(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Brand:'); + $pdf->MultiCell($val_width, $line_height, $vehicle && $vehicle->getManufacturer() ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Model / Year:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Make:'); + $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); + + // insert battery info + $battery = $cv->getCurrentBattery(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Battery Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Current Battery:'); + $pdf->MultiCell($val_width, $line_height, $battery && $battery->getManufacturer() && $battery->getModel() && $battery->getSize() ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Serial Number:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); + $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); + + // insert transaction details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Transaction Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Warranty Class:'); + $pdf->MultiCell($val_width, $line_height, WarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); + $pdf->MultiCell(0, $line_height, ModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->Cell($label_width, $line_height, 'Delivery Address:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landmark:'); + $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("g:i A") : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatched By:'); + $pdf->MultiCell(0, $line_height, $obj->getProcessedBy() ? $obj->getProcessedBy()->getFullName() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + // insert delivery instructions + $pdf->SetY($y); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell(0, $line_height, 'Delivery Instructions'); + $pdf->Ln(); + + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); + + // insert invoice details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Invoice Details'); + $pdf->Ln(); + + // invoice table headers + $invoice = $obj->getInvoice(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); + $pdf->SetFont($font_face, '', $body_font_size); + + // build invoice items table + if ($invoice && !empty($invoice->getItems())) + { + foreach ($invoice->getItems() as $item) + { + $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); + } + } + else + { + $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); + } + + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + // insert invoice footer details + $pdf->Cell($label_width, $line_height, 'Transaction Type:'); + $pdf->MultiCell($val_width, $line_height, ServiceType::getName($obj->getServiceType()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVATExclusivePrice(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'OR Name:'); + $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'TAX:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVAT(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); + $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getDiscount(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Discount Type:'); + $pdf->MultiCell($val_width, $line_height, $invoice && $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getTotalPrice(), 2) : '', 0, 'R'); + $pdf->SetFont($font_face, ''); + + $params['obj'] = $pdf; + $params['filename'] = $filename; + + return $params; + } + + public function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + // db loaded + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['promos'] = $em->getRepository(Promo::class)->findAll(); + + // list of hubs + $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); + $fac_hubs = []; + foreach ($hubs as $hub) + { + $fac_hubs[$hub->getID()] = $hub->getName() . ' - ' . $hub->getBranch(); + } + + // name values + $params['service_types'] = ServiceType::getCollection(); + $params['warranty_classes'] = WarrantyClass::getCollection(); + $params['modes_of_payment'] = ModeOfPayment::getCollection(); + $params['statuses'] = JOStatus::getCollection(); + $params['discount_apply'] = DiscountApply::getCollection(); + $params['trade_in_types'] = TradeInType::getCollection(); + $params['facilitated_types'] = FacilitatedType::getCollection(); + $params['facilitated_hubs'] = $fac_hubs; + $params['sources'] = TransactionOrigin::getCollection(); + } + + protected function initFormTags(&$params) + { + // default to editing, as we have more forms editing than creating + $params['ftags'] = [ + 'title' => 'Job Order Form', + 'vehicle_dropdown' => false, + 'invoice_edit' => false, + 'set_map_coordinate' => true, + 'preset_vehicle' => false, + 'ticket_table' => true, + 'cancel_button' => true, + ]; + } + + protected function fillFormTags(&$params) + { + $this->initFormTags($params); + + switch ($params['mode']) + { + case 'create': + $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 'create_vehicle': + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'open_edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + case 'onestep': + $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 'onestep-edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + } + } + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for job order to hash + // TODO: put this in an array declaration + // $this->template_hash = [ + // 'blah' => 'blah', + // ]; + $this->template_hash['jo_incoming_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_open_edit_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_incoming_vehicle_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_processing_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_assigning_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_fulfillment_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_open_hub_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_open_rider_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_all_form'] = 'job-order/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/form.onestep.html.twig'; + $this->template_hash['jo_onestep_edit_form'] = 'job-order/form.onestep.html.twig'; + } + + protected function checkTier($tier) + { + // check specified tier + switch ($tier) { + case 'proc': + $tier_key = 'jo_proc'; + $tier_name = 'Dispatch'; + $rows_route = 'jo_proc_rows'; + $edit_route = 'jo_proc_form'; + $unlock_route = 'jo_proc_unlock'; + $jo_status = JOStatus::PENDING; + break; + case 'assign': + $tier_key = 'jo_assign'; + $tier_name = 'Assigning'; + $rows_route = 'jo_assign_rows'; + $edit_route = 'jo_assign_form'; + $unlock_route = 'jo_assign_unlock'; + $jo_status = JOStatus::RIDER_ASSIGN; + break; + case 'fulfill': + $tier_key = 'jo_fulfill'; + $tier_name = 'Fullfillment'; + $rows_route = 'jo_fulfill_rows'; + $edit_route = 'jo_fulfill_form'; + $unlock_route = ''; + $jo_status = [ + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS + ]; + break; + case 'open': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = ''; + $unlock_route = ''; + $jo_status = [ + JOStatus::PENDING, + JOStatus::RIDER_ASSIGN, + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS, + JOStatus::IN_TRANSIT, + ]; + break; + case 'all': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = 'jo_all_form'; + $unlock_route = ''; + $jo_status = ''; + break; + default: + throw new AccessDeniedHttpException('No access.'); + } + + // check acl + if (!($this->security->isGranted($tier_key . '.list'))) + throw new AccessDeniedHttpException('No access.'); + + // return params if allowed access + return [ + 'key' => $tier_key, + 'name' => $tier_name, + 'rows_route' => $rows_route, + 'edit_route' => $edit_route, + 'unlock_route' => $unlock_route, + 'jo_status' => $jo_status + ]; + } + + protected function updateVehicleBattery(JobOrder $jo) + { + // check if new battery + if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) + return; + + // customer vehicle + $cv = $jo->getCustomerVehicle(); + if ($cv == null) + return; + + // invoice + $invoice = $jo->getInvoice(); + if ($invoice == null) + return; + + // invoice items + $items = $invoice->getItems(); + if (count($items) <= 0) + return; + + // get first battery from invoice + $battery = null; + foreach ($items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + break; + } + + // no battery in order + if ($battery == null) + return; + + // warranty expiration + $warr = $jo->getWarrantyClass(); + if ($warr == WarrantyClass::WTY_PRIVATE) + $warr_months = $battery->getWarrantyPrivate(); + else if ($warr == WarrantyClass::WTY_COMMERCIAL) + $warr_months = $battery->getWarrantyCommercial(); + else if ($warr == WarrantyClass::WTY_TNV) + $warr_months = 12; + + $warr_date = new DateTime(); + $warr_date->add(new DateInterval('P' . $warr_months . 'M')); + + // update customer vehicle battery + $cv->setCurrentBattery($battery) + ->setHasMotoliteBattery(true) + ->setWarrantyExpiration($warr_date); + } + + // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter + // check if datatable filter is present and append to query + protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status) + { + switch ($tier) + { + case 'fulfill': + $query->where('q.status IN (:statuses)') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'assign': + $query->where('q.status = :status') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('status', $status) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'open': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('q.status IN (:statuses)') + ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + else + { + $query->where('q.status IN (:statuses)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); + } + break; + case 'all': + 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'] . '%'); + } + break; + default: + $query->where('q.status = :status') + ->setParameter('status', $status); + } + } +} diff --git a/src/Service/JobOrderHandlerInterface.php b/src/Service/JobOrderHandlerInterface.php new file mode 100644 index 00000000..0e38359d --- /dev/null +++ b/src/Service/JobOrderHandlerInterface.php @@ -0,0 +1,93 @@ +em = $em; + $this->mclient = $mclient; + $this->aclient = $aclient; + } + + // assign job order to rider + public function assignJobOrder(JobOrder $obj, Rider $rider) + { + // create the payload + $payload = [ + 'event' => 'driver_assigned' + ]; + + // send event + $this->mclient->sendEvent($obj, $payload); + + // check if rider is available + if ($rider->isAvailable()) + { + error_log('set rider availability to false'); + // set rider to unavailable + $rider->setAvailable(false); + + // send event to rider + $this->mclient->sendRiderEvent($obj, $payload); + + // send push notification + $this->aclient->sendPush($obj, "A RESQ rider is on his way to you."); + } + } + + // complete job order + public function fulfillJobOrder(JobOrder $obj, string $image_url, Rider $rider) + { + // send to mqtt + $payload = [ + 'event' => 'fulfilled', + 'jo_id' => $obj->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($obj, $payload); + + // send fulfill/complete event to rider + $this->mclient->sendRiderEvent($obj, $payload); + + // search for the JO assigned to rider with JOStatus::ASSIGNED and sort by assign date + $jo_results = $this->em->getRepository(JobOrder::class)->findBy(['status' => JOStatus::ASSIGNED, 'rider' => $rider->getID()], + ['date_assign' => 'ASC']); + + // check if jo_results is empty + if (!empty($jo_results)) + { + error_log('rider has another JO in queue'); + // get first entry + $jo = current($jo_results); + + // form the payload for the next job order + $jo_payload = [ + 'event' => 'driver_assigned' + ]; + + // set rider to unavailable + $rider->setAvailable(false); + + // send event to rider + $this->mclient->sendRiderEvent($jo, $jo_payload); + + // send push notification + $this->aclient->sendPush($obj, "A RESQ rider is on his way to you."); + } + } +} diff --git a/src/Service/RiderAssignmentHandlerInterface.php b/src/Service/RiderAssignmentHandlerInterface.php new file mode 100644 index 00000000..08e0e816 --- /dev/null +++ b/src/Service/RiderAssignmentHandlerInterface.php @@ -0,0 +1,15 @@ +redis->hget($key, 'longitude'); $lat = $this->redis->hget($key, 'latitude'); - $coordinates = new Point($long, $lat); - } - else - { - $rider = $this->em->getRepository(Rider::class)->find($rider_id); - $coordinates = $rider->getHub()->getCoordinates(); + return new Point($long, $lat); } - return $coordinates; + // not in cache, get hub + $rider = $this->em->getRepository(Rider::class)->find($rider_id); + $hub = $rider->getHub(); + // no hub + // TODO: return valid coordinate + if ($hub == null) + return new Point(0, 0); + + return $hub->getCoordinates(); } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 251d8f55..ec13aa91 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -35,12 +35,16 @@ - {% block stylesheets %}{% endblock %} + {% block stylesheets %} + + {% endblock %} - +
@@ -695,30 +699,6 @@
- diff --git a/templates/customer/cmb.form.html.twig b/templates/customer/cmb.form.html.twig new file mode 100644 index 00000000..e417f3bb --- /dev/null +++ b/templates/customer/cmb.form.html.twig @@ -0,0 +1,1127 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Customers

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Customer + {{ obj.getFirstName() ~ ' ' ~ obj.getLastName() }} + {% else %} + New Customer + {% endif %} +

+
+
+
+
+
+
+
+

+ Customer Info +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + +
+
+
+
+ + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+ +
+ +
+
+

+ Contact Numbers +

+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ + {# +
+
+
+
+
+
+
+ + + +
+
+ +
+
+ #} +
+ +
+ +
+
+

+ Vehicles +

+
+
+
+
+
+
+
+
+ +
+
+
+ + {% if mode == 'update' %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+

+ Job Orders +

+
+
+
+
+
+
+
+
+ {% endif %} +
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/customer/form.html.twig b/templates/customer/form.html.twig index af85fd94..f502e778 100644 --- a/templates/customer/form.html.twig +++ b/templates/customer/form.html.twig @@ -1,1189 +1,1190 @@ -{% extends 'base.html.twig' %} - -{% block body %} - -
-
-
-

Customers

-
-
-
- -
- -
-
-
-
-
-
- - - -

- {% if mode == 'update' %} - Edit Customer - {{ obj.getFirstName() ~ ' ' ~ obj.getLastName() }} - {% else %} - New Customer - {% endif %} -

-
-
-
-
-
-
-
-

- Customer Info -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - -
-
-
-
- - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
- -
- -
-
-

- Contact Numbers -

-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- - {# -
-
-
-
-
-
-
- - - -
-
- -
-
- #} -
- -
- -
-
-

- Vehicles -

-
-
-
-
-
-
-
-
- -
-
-
- - {% if mode == 'update' %} -
- -
-
-

- Tickets -

-
-
-
-
-
-
-
- -
-
- -
-
- -
-
-

- Job Orders -

-
-
-
-
-
-
-
-
- {% endif %} -
-
-
-
-
- - Back -
-
-
-
-
-
-
-
-
- -{% endblock %} - -{% block scripts %} - -{% endblock %} +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Customers

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Customer + {{ obj.getFirstName() ~ ' ' ~ obj.getLastName() }} + {% else %} + New Customer + {% endif %} +

+
+
+
+
+
+
+
+

+ Customer Info +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + +
+
+
+
+ + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+ +
+ +
+
+

+ Contact Numbers +

+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ + {# +
+
+
+
+
+
+
+ + + +
+
+ +
+
+ #} +
+ +
+ +
+
+

+ Vehicles +

+
+
+
+
+
+
+
+
+ +
+
+
+ + {% if mode == 'update' %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+

+ Job Orders +

+
+
+
+
+
+
+
+
+ {% endif %} +
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} + diff --git a/templates/geofence/list.html.twig b/templates/geofence/list.html.twig index 1b9598bc..f8f59a16 100644 --- a/templates/geofence/list.html.twig +++ b/templates/geofence/list.html.twig @@ -96,7 +96,10 @@ initMap(); function initMap() { var map = new google.maps.Map(document.getElementById('m_gmap'), { - center: {lat: 14.6091, lng: 121.0223}, + center: { + lat: {% trans %}default_lat{% endtrans %}, + lng: {% trans %}default_long{% endtrans %}, + }, mapTypeId: 'roadmap', zoom: 13 }); diff --git a/templates/home.html.twig b/templates/home.html.twig index 2907f228..f55fe863 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -1,32 +1,123 @@ {% extends 'base.html.twig' %} -{% block body %} - -
-
-
-

- Dashboard -

-
-
- - - - - - - - - -
-
-
- -
- - - - -
+{% block stylesheets %} + +{% endblock %} + +{% block body %} +
+ + +{% endblock %} + + +{% block scripts %} + + + +{{ include('map/' ~ map_js_file) }} + + + {% endblock %} diff --git a/templates/hub/form.html.twig b/templates/hub/form.html.twig index bb943166..df8a7190 100644 --- a/templates/hub/form.html.twig +++ b/templates/hub/form.html.twig @@ -164,12 +164,34 @@ {% block scripts %} - + + + + +{% endblock %} diff --git a/templates/job-order/cmb.form.onestep.html.twig b/templates/job-order/cmb.form.onestep.html.twig new file mode 100644 index 00000000..7bdcc8bb --- /dev/null +++ b/templates/job-order/cmb.form.onestep.html.twig @@ -0,0 +1,1431 @@ +{% extends 'base.html.twig' %} + +{% block body %} + + + +
+ +
+
+
+
+
+
+ + + +

+ One-step Job Order +

+
+
+
+
+ +
+ {%if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ +63 + + +
+
+
+ +
+ +63 + + +
+
+
+
+
+ +
+ +63 + + +
+
+
+ +
+ +63 + + +
+
+
+
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+ +
+
+

+ Nearest Hubs +

+
+
+
+ + + +
+ + + + + + + + + + + + +
HubBranchContact NumbersAction
+
+
+
+
+
+
+

+ Rider Assignment +

+
+
+
+ + + +
+ + + + + + + + + + + + +
First NameLast NameContact No.Plate NumberAction
+
+
+
+
+ +
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+
+
+
+
+ + Back +
+
+
+
+ +
+
+
+
+{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index 1914dfe9..05b36628 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -1,1674 +1,1710 @@ -{% extends 'base.html.twig' %} - -{% block body %} - -
-
-
-

Job Order

-
-
-
- -
- -
-
-
-
-
-
- - - -

- {% if mode == 'update-processing' %} - Dispatch - {{ obj.getID() }} - {% elseif mode == 'update-assigning' %} - Rider Assignment - {{ obj.getID() }} - {% elseif mode == 'update-reassign-hub' %} - Re-assign Hub - {{ obj.getID() }} - {% elseif mode == 'update-reassign-rider' %} - Re-assign Rider - {{ obj.getID() }} - {% elseif mode == 'update-all' %} - Viewing - {{ obj.getID() }} - {% else %} - Incoming - {% endif %} -

-
-
-
-
- -
- - {% if ftags.vehicle_dropdown %} -
-
-
- - - -
- -
-
-
-
-
- - - -
-
-
- {% else %} - - {% endif %} - {% if obj.getReferenceJO %} -
-
-
- - - -
-
-
- {% endif %} - -
-
-

- Customer Details -

-
-
-
- - - -
-
- - - -
-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
-
-
- - - -
-
-
-
-
-

- Vehicle Details -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
-
-
-

- Battery Details -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
-
-
-

- Transaction Details -

- - - -
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- -
- - - - -
- -
-
- -
- - - - -
- -
-
-
-
- - - -
-
- - - -
-
-
-
-
- - - -
-
-
- - -
-
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- -
-
-
-
-

- Location -

-
-
-
- - - -
-
- - - -
-
-
-
- - - - -
- - - - -
-
-
-
-
-
-
-
-

- Invoice -

-
-
-
- - - -
-
- - - -
-
-
-
- - {% if ftags.invoice_edit %} - - - {% else %} - - {% endif %} -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - - - - - - - - - - - {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} - - - - {% else %} - {% for item in obj.getInvoice.getItems %} - - - - - - - {% endfor %} - {% endif %} - -
ItemQuantityUnit PriceAmount
- No items to display. -
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
-
-
- {% if ftags.invoice_edit %} -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - -
-
- {% endif %} -
- - {% if mode in ['update-processing', 'update-reassign-hub'] %} -
-
-
-

- Nearest Hubs -

-
-
-
- - -
- - - - - - - - - - - - - - - - - - {% for hub in hubs %} - - - - - - - - - - {% endfor %} - -
HubBranchAvailable RidersJobs For AssignmentContact NumbersAction
- No items to display. -
{{ hub.hub.getName }}{{ hub.hub.getBranch }}{{ hub.rider_count }}{{ hub.jo_count }}{{ hub.hub.getContactNumbers|replace({"\n": ', '}) }} - {% if hub.flag_rejected %} - - {% else %} - - {% endif %} -
-
-
-
-
-
-
-
- - -
-
- - -
-
-
-
-
-
-
-
-
-
- {% endif %} - - {% if mode in ['update-assigning', 'update-fulfillment', 'update-reassign-rider', 'update-all'] %} -
- {% if obj.getHub %} -
-
-

- Hub Details -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
- {% endif %} - -
- - {% if mode in ['update-assigning', 'update-reassign-rider'] %} -
-
-

- Rider Assignment -

-
-
-
- - -
- - - - - - - - - - - - - {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} - - - - - {% if obj.getHub %} - {% for rider in avail_riders %} - - - - - - - - - {% endfor %} - {% endif %} - -
First NameLast NameContact No.Plate NumberStatus
- No riders available. -
-
-
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
-
-
-
-
- {% endif %} - - {% if mode in ['update-fulfillment', 'update-all'] %} - {% if obj.getRider %} -
-
-

- Rider Details -

-
-
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
-
- -
-
-
-
- {% endif %} - {% endif %} - {% endif %} - - {% if mode == 'update-all' %} -
- -
-
-

- Timeline -

-
-
-
-
-
- {% for event in obj.getEvents %} -
- - {{ event.getDateHappen|date("M j, Y") }} -
{{ event.getDateHappen|date("h:i:s a") }}
-
-
- -
-
- {{ event.getTypeName }} by {{ event.getUser.getFullName|default('Application') }} {% if event.getRider %} - Rider - {{ event.getRider.getFullName }}{% endif %} -
-
- {% endfor %} -
-
-
-
-
- {% endif %} - - {% if ftags.ticket_table %} -
- -
-
-

- Tickets -

-
-
-
-
-
-
-
- -
-
- {% endif %} - -
-
-
-
-
- {% if mode != 'update-all' %} - - {% endif %} - {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} - Cancel Job Order - {% endif %} - {% if mode != 'create' %} - Back - {% endif %} -
-
-
-
-
-
-
-
-
- - {% if mode in ['update-processing', 'update-reassign-hub'] %} - - - {% endif %} -{% endblock %} - -{% block scripts %} - - - - - -{% endblock %} +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Job Order

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update-processing' %} + Dispatch + {{ obj.getID() }} + {% elseif mode == 'update-assigning' %} + Rider Assignment + {{ obj.getID() }} + {% elseif mode == 'update-reassign-hub' %} + Re-assign Hub + {{ obj.getID() }} + {% elseif mode == 'update-reassign-rider' %} + Re-assign Rider + {{ obj.getID() }} + {% elseif mode == 'update-all' %} + Viewing + {{ obj.getID() }} + {% else %} + Incoming + {% endif %} +

+
+
+
+
+ +
+ + {% if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ +63 + + +
+
+
+ +
+ +63 + + +
+
+
+
+
+ +
+ +63 + + +
+
+
+ +
+ +63 + + +
+
+
+
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ +
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+ {% if ftags.invoice_edit %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ {% endif %} +
+ + {% if mode in ['update-processing', 'update-reassign-hub'] %} +
+
+
+

+ Nearest Hubs +

+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + {% for hub in hubs %} + + + + + + + + + + {% endfor %} + +
HubBranchAvailable RidersJobs For AssignmentContact NumbersAction
+ No items to display. +
{{ hub.hub.getName }}{{ hub.hub.getBranch }}{{ hub.rider_count }}{{ hub.jo_count }}{{ hub.hub.getContactNumbers|replace({"\n": ', '}) }} + {% if hub.flag_rejected %} + + {% else %} + + {% endif %} +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ {% endif %} + + {% if mode in ['update-assigning', 'update-fulfillment', 'update-reassign-rider', 'update-all'] %} +
+ {% if obj.getHub %} +
+
+

+ Hub Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+ {% endif %} + +
+ + {% if mode in ['update-assigning', 'update-reassign-rider'] %} +
+
+

+ Rider Assignment +

+
+
+
+ + +
+ + + + + + + + + + + + + {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} + + + + + {% if obj.getHub %} + {% for rider in avail_riders %} + + + + + + + + + {% endfor %} + {% endif %} + +
First NameLast NameContact No.Plate NumberStatus
+ No riders available. +
+
+
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
+
+
+
+
+ {% endif %} + + {% if mode in ['update-fulfillment', 'update-all'] %} + {% if obj.getRider %} +
+
+

+ Rider Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% endif %} + + {% if mode == 'update-all' %} +
+ +
+
+

+ Timeline +

+
+
+
+
+
+ {% for event in obj.getEvents %} +
+ + {{ event.getDateHappen|date("M j, Y") }} +
{{ event.getDateHappen|date("h:i:s a") }}
+
+
+ +
+
+ {{ event.getTypeName }} by {{ event.getUser.getFullName|default('Application') }} {% if event.getRider %} - Rider - {{ event.getRider.getFullName }}{% endif %} +
+
+ {% endfor %} +
+
+
+
+
+ {% endif %} + + {% if ftags.ticket_table %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ {% endif %} + +
+
+
+
+
+ {% if mode != 'update-all' %} + + {% endif %} + {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} + Cancel Job Order + {% endif %} + {% if mode != 'create' %} + Back + {% endif %} +
+
+
+
+
+
+
+
+
+ + {% if mode in ['update-processing', 'update-reassign-hub'] %} + + + {% endif %} +{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + diff --git a/templates/job-order/form.onestep.html.twig b/templates/job-order/form.onestep.html.twig new file mode 100644 index 00000000..c9e14254 --- /dev/null +++ b/templates/job-order/form.onestep.html.twig @@ -0,0 +1,1421 @@ +{% extends 'base.html.twig' %} + +{% block body %} + + + +
+ +
+
+
+
+
+
+ + + +

+ One-step Job Order +

+
+
+
+
+ +
+ {%if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ +63 + + +
+
+
+ +
+ +63 + + +
+
+
+
+
+ +
+ +63 + + +
+
+
+ +
+ +63 + + +
+
+
+
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+ +
+
+

+ Nearest Hubs +

+
+
+
+ + + +
+ + + + + + + + + + + + +
HubBranchContact NumbersAction
+
+
+
+
+
+
+

+ Rider Assignment +

+
+
+
+ + + +
+ + + + + + + + + + + + +
First NameLast NameContact No.Plate NumberAction
+
+
+
+
+ +
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+
+
+
+
+ + Back +
+
+
+
+ +
+
+
+
+{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + diff --git a/templates/job-order/form.pdf.html.twig b/templates/job-order/form.pdf.html.twig index 5499f1fb..4602b6e6 100644 --- a/templates/job-order/form.pdf.html.twig +++ b/templates/job-order/form.pdf.html.twig @@ -221,7 +221,7 @@
- +
@@ -651,8 +651,8 @@ $(function() { var map = new GMaps({ div: '#m_gmap', - lat: 14.6091, - lng: 121.0223, + lat: {% trans %}default_lat{% endtrans %}, + lng: {% trans %}default_long{% endtrans %}, click: function(e) { // handle click in map selectPoint(map, e.latLng); diff --git a/templates/job-order/list.fulfillment.html.twig b/templates/job-order/list.fulfillment.html.twig index f38bd4a5..46210a9d 100644 --- a/templates/job-order/list.fulfillment.html.twig +++ b/templates/job-order/list.fulfillment.html.twig @@ -108,7 +108,7 @@ sortable: false, overflow: 'visible', template: function (row, index, datatable) { - var actions = ''; + var actions = '' + ''; return actions; }, diff --git a/templates/job-order/list.open.html.twig b/templates/job-order/list.open.html.twig index cea8df51..62af1e3a 100644 --- a/templates/job-order/list.open.html.twig +++ b/templates/job-order/list.open.html.twig @@ -122,6 +122,9 @@ {% if is_granted('jo_open.edit') %} actions += ''; {% endif %} + {% if is_granted('jo_onestep.edit') %} + actions += ''; + {% endif %} return actions; }, diff --git a/templates/job-order/popup.html.twig b/templates/job-order/popup.html.twig new file mode 100644 index 00000000..509c7296 --- /dev/null +++ b/templates/job-order/popup.html.twig @@ -0,0 +1,8 @@ +{% set cust = jo.getCustomer %} +{% set cv = jo.getCustomerVehicle %} +{{ cust.getNameDisplay }}
+{{ cv.getPlateNumber }}
+Job Order #{{ jo.getID }}
+{{ jo.getServiceTypeName }}
+{{ jo.getStatusText }} + diff --git a/templates/map/initBingMap.js b/templates/map/initBingMap.js new file mode 100644 index 00000000..0183fe54 --- /dev/null +++ b/templates/map/initBingMap.js @@ -0,0 +1,36 @@ + + + + diff --git a/templates/map/initGoogleMap.js b/templates/map/initGoogleMap.js new file mode 100644 index 00000000..51349ae2 --- /dev/null +++ b/templates/map/initGoogleMap.js @@ -0,0 +1,22 @@ + + + diff --git a/templates/map/initOpenStreetMap.js b/templates/map/initOpenStreetMap.js new file mode 100644 index 00000000..358b91d4 --- /dev/null +++ b/templates/map/initOpenStreetMap.js @@ -0,0 +1,143 @@ + + + diff --git a/templates/map/joOpenStreetMap.js b/templates/map/joOpenStreetMap.js new file mode 100644 index 00000000..3d92c330 --- /dev/null +++ b/templates/map/joOpenStreetMap.js @@ -0,0 +1,26 @@ + + + diff --git a/templates/outlet/form.html.twig b/templates/outlet/form.html.twig index b5abfd4b..89f94857 100644 --- a/templates/outlet/form.html.twig +++ b/templates/outlet/form.html.twig @@ -143,12 +143,34 @@ {% block scripts %} - + +