Merge branch '485-resq-september-10-release' into 'master'

Resolve "Resq - September 10 release"

Closes #485

See merge request jankstudio/resq!603
This commit is contained in:
Kendrick Chan 2020-10-12 14:43:28 +00:00
commit 271b76331b
26 changed files with 1406 additions and 138 deletions

View file

@ -74,3 +74,6 @@ MAPTILER_API_KEY=map_tiler_api_key
# API version
API_VERSION=insert_api_version_here
#SSL_ENABLE for websockets
SSL_ENABLE=set_to_true_or_false

View file

@ -270,6 +270,8 @@ access_keys:
label: Autoassign Test
- id: jo_hub.list
label: Hub View
- id: jo_cancel.fulfill
label: Fulfill Cancelled JO
- id: support
label: Customer Support Access

View file

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

View file

@ -253,3 +253,17 @@ jo_hub_view_form:
controller: App\Controller\JobOrderController::hubViewForm
methods: [GET]
jo_fulfill_cancel_submit:
path: /job-order/fulfillcancel/{id}
controller: App\Controller\JobOrderController::fulfillCancelSubmit
methods: [POST]
jo_cancel_reasons:
path: /ajax/jo_cancel_reasons
controller: App\Controller\JobOrderController::cancelReasons
methods: [GET]
jo_geofence:
path: /ajax/job-order/geofence
controller: App\Controller\JobOrderController::checkGeofence
methods: [GET]

View file

@ -0,0 +1,2 @@
UPDATE customer SET date_create=NOW() WHERE date_create="0000-00-00 00:00:00";

View file

@ -1,7 +1,8 @@
class MapEventHandler {
constructor(options, dashmap) {
constructor(options, dashmap, ssl) {
this.options = options;
this.dashmap = dashmap;
this.ssl = ssl;
}
connect(user_id, host, port) {
@ -11,7 +12,7 @@ class MapEventHandler {
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
// useSSL: true,
useSSL: this.ssl,
timeout: 3,
invocationContext: this,
onSuccess: this.onConnect.bind(this),

View file

@ -0,0 +1,331 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\SAPBattery;
use App\Entity\SAPBatteryBrand;
use App\Entity\SAPBatterySize;
class ImportSAPDeltaCommand extends Command
{
private $em;
private $sap_battery_hash;
private $sap_battery_size_hash;
private $sap_battery_brand_hash;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->initSAPBatteryHash();
$this->initSAPBatterySizeHash();
$this->initSAPBatteryBrandHash();
parent::__construct();
}
protected function configure()
{
$this->setName('sap_battery:delta')
->setDescription('Update SAP battery table with new SAP battery data.')
->setHelp('Creates SAP battery data entries from CSV file.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to the output CSV file.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('file');
$report_file = $input->getArgument('output_file');
// CSV column order:
// 0 - brand
// 1 - sku
// 2 - size
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could not be read.');
}
// get entity manager
$em = $this->em;
// loop through the rows
$row_num = 0;
error_log('Processing sap battery csv file...');
$existing_brands = [];
$existing_sizes = [];
$existing_batteries = [];
$added_brands = [];
$added_sizes = [];
$added_batteries = [];
$total_processed = 0;
while (($fields = fgetcsv($fh)) !== false)
{
// data starts at row 2
if ($row_num < 2)
{
$row_num++;
continue;
}
// check if blank row
if (strlen(trim($fields[0])) == 0)
continue;
// clean up fields
$clean_brand = $this->normalizeName(trim($fields[0]));
$clean_sku = $this->normalizeName(trim($fields[1]));
$clean_size = $this->normalizeName(trim($fields[2]));
// check if sap battery code already exists
if (!isset($this->sap_battery_hash[$clean_sku]))
{
// check if brand in system
$sap_brand = null;
if (!isset($this->sap_battery_brand_hash[$clean_brand]))
{
// add brand
$sap_brand = new SAPBatteryBrand();
$sap_brand->setName($clean_brand);
$em->persist($sap_brand);
// add to list of added brands
$added_brands[$clean_brand] = $clean_brand;
//$output->writeln('Adding brand: ' . $clean_brand);
// add to hash
$this->sap_battery_brand_hash[$clean_brand] = $sap_brand;
}
else
{
// get the brand
$sap_brand = $this->sap_battery_brand_hash[$clean_brand];
// need to check if this is in added_brands because the hash contains
// existing + added. What we need is the list of existing only
if (!isset($added_brands[$clean_brand]))
{
// add to list of existing brands
//$output->writeln('Brand already in db ' . $clean_brand);
$existing_brands[$clean_brand] = $clean_brand;
}
}
// check if size in system
$sap_size = null;
if (!isset($this->sap_battery_size_hash[$clean_size]))
{
// add size
$sap_size = new SAPBatterySize();
$sap_size->setName($clean_size);
$em->persist($sap_size);
// add to list of added sizes
$added_sizes[$clean_size] = $clean_size;
//$output->writeln('Adding size: ' . $clean_size);
// add to hash
$this->sap_battery_size_hash[$clean_size] = $sap_size;
}
else
{
// get the size
$sap_size = $this->sap_battery_size_hash[$clean_size];
// need to check if this is in added_sizes because the hash contains
// existing + added. What we need is the list of existing only
if (!isset($added_sizes[$clean_size]))
{
// add to list of existing sizes
//$output->writeln('Size already in db ' . $clean_size);
$existing_sizes[$clean_size] = $clean_size;
}
}
// add the sap_battery
// create sap battery entry
$sap_battery = new SAPBattery();
$sap_battery->setID($clean_sku)
->setSize($sap_size)
->setBrand($sap_brand);
$em->persist($sap_battery);
// add to list of added batteries
$added_batteries[$clean_sku] = $clean_sku;
//$output->writeln('Adding battery: ' . $clean_sku);
// add to hash
$this->sap_battery_hash[$clean_sku] = $sap_battery;
}
else
{
// TODO: for now, assume that the brand and size in system == brand and size in csv file
// need to check if this is in added_batteries because the hash contains
// existing + added. What we need is the list of existing only
if (!isset($added_batteries[$clean_sku]))
{
// add to list of existing batteries
//$output->writeln('Battery already in db ' . $clean_sku);
$existing_batteries[$clean_sku] = $this->sap_battery_hash[$clean_sku];
if (!isset($existing_brands[$clean_brand]))
$existing_brands[$clean_brand] = $clean_brand;
if (!isset($existing_sizes[$clean_size]))
$existing_sizes[$clean_size] = $clean_size;
}
}
$total_processed++;
$row_num++;
}
$em->flush();
// need to output the list of added and not added data
$this->writeOutputFile($report_file, $added_brands, $added_sizes, $added_batteries,
$existing_brands, $existing_sizes, $existing_batteries, $total_processed);
return 0;
}
protected function initSAPBatteryHash()
{
$this->sap_battery_hash = [];
$batts = $this->em->getRepository(SAPBattery::class)->findAll();
foreach ($batts as $batt)
{
$id = $this->normalizeName($batt->getID());
$this->sap_battery_hash[$id] = $batt;
}
}
protected function initSAPBatterySizeHash()
{
$this->sap_battery_size_hash = [];
$sizes = $this->em->getRepository(SAPBatterySize::class)->findAll();
foreach ($sizes as $size)
{
$name = $this->normalizeName($size->getName());
$this->sap_battery_size_hash[$name] = $size;
}
}
protected function initSAPBatteryBrandHash()
{
$this->sap_battery_brand_hash = [];
$brands = $this->em->getRepository(SAPBatteryBrand::class)->findAll();
foreach ($brands as $brand)
{
$name = $this->normalizeName($brand->getName());
$this->sap_battery_brand_hash[$name] = $brand;
}
}
protected function writeOutputFile($report_file, $added_brands, $added_sizes, $added_batteries,
$existing_brands, $existing_sizes, $existing_batteries, $total_processed)
{
try
{
$fh = fopen($report_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $report_file . '" could be opened.');
}
fputs($fh, 'Total entries processed: ' . $total_processed . "\n");
fputs($fh, 'Total brands added: ' . count($added_brands) . "\n");
fputs($fh, 'Total sizes added: ' . count($added_sizes) . "\n");
fputs($fh, 'Total batteries added: ' . count($added_batteries) . "\n");
fputs($fh, 'Total number of brands in csv file that are in the system: ' . count($existing_brands) . "\n");
fputs($fh, 'Total number of sizes in csv file that are in the system: ' . count($existing_sizes) . "\n");
fputs($fh, 'Total number of batteries in csv file that are in the system: ' . count($existing_batteries) . "\n");
// write the added batteries
// check the existing brands array for the added ones
$not_added_batteries = [];
fputs($fh, 'Added Batteries: ' . "\n");
foreach($added_batteries as $added_battery)
{
fputs($fh, $added_battery, strlen($added_battery));
fputs($fh, "\n");
}
// write the added brands
// check the existing arrays for the added ones
$not_added_brands = [];
fputs($fh, 'Added Brands: ' . "\n");
foreach ($added_brands as $added_brand)
{
fputs($fh, $added_brand, strlen($added_brand));
fputs($fh, "\n");
}
// write the added sizes
// check the existing array for the added ones
$not_added_sizes = [];
fputs($fh, 'Added Sizes: ' . "\n");
foreach ($added_sizes as $added_size)
{
fputs($fh, $added_size, strlen($added_size));
fputs($fh, "\n");
}
// write the not added batteries
fputs($fh, 'Batteries already in system: ' . "\n");
foreach($existing_batteries as $not_added_battery)
{
fputs($fh, $not_added_battery->getID(), strlen($not_added_battery->getID()));
fputs($fh, "\n");
}
// write the not added brands
fputs($fh, 'Brands already in system: ' . "\n");
foreach($existing_brands as $not_added_brand)
{
fputs($fh, $not_added_brand, strlen($not_added_brand));
fputs($fh, "\n");
}
// write the not added sizes
fputs($fh, 'Sizes already in system: ' . "\n");
foreach($existing_sizes as $not_added_size)
{
fputs($fh, $not_added_size, strlen($not_added_size));
fputs($fh, "\n");
}
fclose($fh);
}
protected function normalizeName($name)
{
$normalized_key = trim(strtoupper($name));
return $normalized_key;
}
}

View file

@ -0,0 +1,256 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\SAPBattery;
use App\Entity\SAPBatteryBrand;
use App\Entity\SAPBatterySize;
class ImportSAPMasterCommand extends Command
{
private $em;
private $sap_battery_hash;
private $sap_battery_size_hash;
private $sap_battery_brand_hash;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->initSAPBatteryHash();
$this->initSAPBatterySizeHash();
$this->initSAPBatteryBrandHash();
parent::__construct();
}
protected function configure()
{
$this->setName('sap_battery:master')
->setDescription('Import SAP battery master list.')
->setHelp('Creates SAP battery, SAP battery brands, and SAP battery sizes from CSV file.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to the output CSV file.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('file');
$report_file = $input->getArgument('output_file');
// CSV column order:
// 0 - brand
// 1 - sku
// 2 - size
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could not be read.');
}
// get entity manager
$em = $this->em;
// loop through the rows
$row_num = 0;
error_log('Processing sap battery csv file...');
$added_brands = [];
$added_sizes = [];
$added_batteries = [];
$repeat_brands = [];
$repeat_sizes = [];
$repeat_batteries = [];
$total_processed = 0;
while (($fields = fgetcsv($fh)) !== false)
{
// data starts at row 2
if ($row_num < 2)
{
$row_num++;
continue;
}
// check if blank row
if (strlen(trim($fields[0])) == 0)
continue;
// clean up fields
$clean_brand = $this->normalizeName(trim($fields[0]));
$clean_sku = $this->normalizeName(trim($fields[1]));
$clean_size = $this->normalizeName(trim($fields[2]));
// check if brand in system
$sap_brand = null;
if (!isset($this->sap_battery_brand_hash[$clean_brand]))
{
// add brand
$sap_brand = new SAPBatteryBrand();
$sap_brand->setName($clean_brand);
$em->persist($sap_brand);
// add to list of added brands
$added_brands[$clean_brand] = $clean_brand;
//$output->writeln('Adding brand: ' . $clean_brand);
// add to hash
$this->sap_battery_brand_hash[$clean_brand] = $sap_brand;
}
else
{
// get the brand
$sap_brand = $this->sap_battery_brand_hash[$clean_brand];
// repeat brand so no need to add but still need to count
$repeat_brands[] = $clean_brand;
}
// check if size in system
$sap_size = null;
if (!isset($this->sap_battery_size_hash[$clean_size]))
{
// add size
$sap_size = new SAPBatterySize();
$sap_size->setName($clean_size);
$em->persist($sap_size);
// add to list of added sizes
$added_sizes[$clean_size] = $clean_size;
//$output->writeln('Adding size: ' . $clean_size);
// add to hash
$this->sap_battery_size_hash[$clean_size] = $sap_size;
}
else
{
// get the size
$sap_size = $this->sap_battery_size_hash[$clean_size];
// repeat size so no need to add to system but still need to count
$repeat_sizes[] = $clean_size;
}
// check if sap battery code already exists
if (!isset($this->sap_battery_hash[$clean_sku]))
{
// add the sap_battery
// create sap battery entry
$sap_battery = new SAPBattery();
$sap_battery->setID($clean_sku)
->setSize($sap_size)
->setBrand($sap_brand);
$em->persist($sap_battery);
// add to list of added batteries
$added_batteries[$clean_sku] = $clean_sku;
//$output->writeln('Adding battery: ' . $clean_sku);
// add to hash
$this->sap_battery_hash[$clean_sku] = $sap_battery;
}
else
{
$repeat_batteries[] = $clean_sku;
}
$total_processed++;
$row_num++;
}
$em->flush();
// need to output the list of added and not added data
$this->writeOutputFile($report_file, $added_brands, $added_sizes, $added_batteries, $total_processed,
$repeat_brands, $repeat_sizes, $repeat_batteries);
return 0;
}
protected function initSAPBatteryHash()
{
$this->sap_battery_hash = [];
$batts = $this->em->getRepository(SAPBattery::class)->findAll();
foreach ($batts as $batt)
{
$id = $this->normalizeName($batt->getID());
$this->sap_battery_hash[$id] = $batt;
}
}
protected function initSAPBatterySizeHash()
{
$this->sap_battery_size_hash = [];
$sizes = $this->em->getRepository(SAPBatterySize::class)->findAll();
foreach ($sizes as $size)
{
$name = $this->normalizeName($size->getName());
$this->sap_battery_size_hash[$name] = $size;
}
}
protected function initSAPBatteryBrandHash()
{
$this->sap_battery_brand_hash = [];
$brands = $this->em->getRepository(SAPBatteryBrand::class)->findAll();
foreach ($brands as $brand)
{
$name = $this->normalizeName($brand->getName());
$this->sap_battery_brand_hash[$name] = $brand;
}
}
protected function writeOutputFile($report_file, $added_brands, $added_sizes, $added_batteries, $total_processed,
$repeat_brands, $repeat_sizes, $repeat_batteries)
{
try
{
$fh = fopen($report_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $report_file . '" could be opened.');
}
fputs($fh, 'Total entries processed: ' . $total_processed . "\n");
fputs($fh, 'Total brands added: ' . count($added_brands) . "\n");
fputs($fh, 'Total sizes added: ' . count($added_sizes) . "\n");
fputs($fh, 'Total batteries added: ' . count($added_batteries) . "\n");
fputs($fh, 'Repeat brands: ' . count($repeat_brands) . "\n");
fputs($fh, 'Repeat sizes: ' . count($repeat_sizes) . "\n");
fputs($fh, 'Repeat batteries: ' . count($repeat_batteries) . "\n");
foreach ($repeat_batteries as $repeat_battery)
{
fputs($fh, 'Duplicate SKU: ' . $repeat_battery . "\n");
}
}
protected function normalizeName($name)
{
$normalized_key = trim(strtoupper($name));
return $normalized_key;
}
}

View file

@ -6,6 +6,7 @@ use App\Ramcar\JOStatus;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\CMBServiceType;
use App\Ramcar\ServiceType;
use App\Ramcar\JOCancelReasons;
use App\Entity\CustomerVehicle;
use App\Entity\Promo;
@ -22,6 +23,7 @@ use App\Service\MapTools;
use App\Service\MQTTClient;
use App\Service\APNSClient;
use App\Service\InventoryManager;
use App\Service\GeofenceTracker;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@ -631,7 +633,6 @@ class JobOrderController extends Controller
$params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll();
$params['vmakes'] = $em->getRepository(Vehicle::class)->findAll();
$params['return_url'] = $this->generateUrl('jo_all');
$params['submit_url'] = '';
$params['map_js_file'] = $gis->getJSJOFile();
$template = $params['template'];
@ -1153,6 +1154,51 @@ class JobOrderController extends Controller
return $this->render($template, $params);
}
public function fulfillCancelSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id)
{
$this->denyAccessUnlessGranted('jo_cancel.fulfill', null, 'No access.');
// TODO: make the service function to fulfill the cancelled JO
$error_array = [];
$result = $jo_handler->fulfillCancelledJobOrder($req, $id);
$error_array = $result['error_array'];
// 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!'
]);
}
// ajax call
public function cancelReasons()
{
return $this->json([
'cancel_reasons' => JOCancelReasons::getCollection(),
]);
}
// ajax call
public function checkGeofence(Request $req, GeofenceTracker $geo)
{
$lat = $req->query->get('lat');
$lng = $req->query->get('lng');
$is_covered = $geo->isCovered($lng, $lat);
return $this->json([
'is_covered' => $is_covered
]);
}
/**
* @Menu(selected="jo_autoassign")

View file

@ -386,43 +386,58 @@ class WarrantyController extends Controller
$inv_entries = $this->processWarrantyFile($file, $em, $wh);
$resp = new StreamedResponse();
$resp->setCallback(function() use($inv_entries) {
// csv output
$csv_handle = fopen('php://output', 'w+');
fputcsv($csv_handle, [
'Owner First Name',
'Owner Last Name',
'Owner Email',
'Owner Address',
'Owner Mobile',
'Owner Telephone',
'Vehicle Make',
'Vehicle Model',
'Vehicle Year',
'Vehicle Plate Number',
'Battery Serial Number',
'Battery Sales Invoice',
'Battery Date of Purchase',
'Distributor Name',
'Distributor Address',
'Application Type ID',
'Battery ID',
'Ownership Type',
'Reason Warranty Not Added',
]);
foreach ($inv_entries as $row)
{
fputcsv($csv_handle, $row);
}
fclose($csv_handle);
});
if (count($inv_entries) > 0)
{
$resp->setCallback(function() use($inv_entries) {
// csv output
$csv_handle = fopen('php://output', 'w+');
fputcsv($csv_handle, [
'Owner First Name',
'Owner Last Name',
'Owner Email',
'Owner Address',
'Owner Mobile',
'Owner Telephone',
'Vehicle Make',
'Vehicle Model',
'Vehicle Year',
'Vehicle Plate Number',
'Battery Serial Number',
'Battery Sales Invoice',
'Battery Date of Purchase',
'Distributor Name',
'Distributor Address',
'Application Type ID',
'Battery ID',
'Ownership Type',
'Reason Warranty Not Added',
]);
foreach ($inv_entries as $row)
{
fputcsv($csv_handle, $row);
}
fclose($csv_handle);
});
}
else
{
$resp->setCallback(function() {
// csv output
$csv_handle = fopen('php://output', 'w+');
fputcsv($csv_handle, ['No Invalid Warranties']);
fclose($csv_handle);
});
}
$filename = 'invalid_warranties' . '.csv';
$resp->setStatusCode(200);
$resp->headers->set('Content-Type', 'text/csv; charset=utf-8');
$resp->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
$resp->setStatusCode(200);
return $resp;
}

View file

@ -6,6 +6,8 @@ use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use DateTime;
use App\Ramcar\CustomerClassification;
/**
@ -184,6 +186,12 @@ class Customer
*/
protected $flag_dpa_consent;
// date customer was created
/**
* @ORM\Column(type="datetime")
*/
protected $date_create;
public function __construct()
{
$this->numbers = new ArrayCollection();
@ -214,6 +222,8 @@ class Customer
$this->flag_promo_email = false;
$this->flag_promo_sms = false;
$this->flag_dpa_consent = false;
$this->date_create = new DateTime();
}
public function getID()

View file

@ -12,6 +12,7 @@ use DateTime;
use App\Ramcar\ModeOfPayment;
use App\Ramcar\JOStatus;
use App\Ramcar\ServiceType;
use App\Ramcar\WillingToWaitContent;
/**
* @ORM\Entity
@ -334,6 +335,30 @@ class JobOrder
*/
protected $phone_mobile;
// customer is willing to wait or not
/**
* @ORM\Column(type="string", length=30)
*/
protected $will_wait;
// reason for not willing to wait
/**
* @ORM\Column(type="string", length=80, nullable=true)
*/
protected $reason_not_waiting;
// more notes on why not willing to wait
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $not_waiting_notes;
// reason for not trading in battery
/**
* @ORM\Column(type="string", length=80, nullable=true)
*/
protected $no_trade_in_reason;
public function __construct()
{
$this->date_create = new DateTime();
@ -356,6 +381,8 @@ class JobOrder
$this->meta = [];
$this->phone_mobile = '';
$this->will_wait = WillingToWaitContent::WILLING_TO_WAIT;
}
public function getID()
@ -962,5 +989,47 @@ class JobOrder
return $this->phone_mobile;
}
public function setWillWait($will_wait)
{
$this->will_wait = $will_wait;
return $this;
}
public function getWillWait()
{
return $this->will_wait;
}
public function setReasonNotWait($reason)
{
$this->reason_not_waiting = $reason;
return $this;
}
public function getReasonNotWait()
{
return $this->reason_not_waiting;
}
public function setNotWaitingNotes($notes)
{
$this->not_waiting_notes = $notes;
return $this;
}
public function getNotWaitingNotes()
{
return $this->not_waiting_notes;
}
public function setNoTradeInReason($reason)
{
$this->no_trade_in_reason = $reason;
return $this;
}
public function getNoTradeInReason()
{
return $this->no_trade_in_reason;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Ramcar;
class CustomerNotWaitReason extends NameValue
{
const EMERGENCY = 'emergency';
const USE_VEHICLE_NOW = 'use_vehicle_now';
const WITH_APPOINTMENT = 'with_appointment';
const POST_REPLACEMENT = 'post_replacement';
const POST_RECHARGE = 'post_recharge';
const NORMAL_REQUEST_NOT_URGENT = 'normal_request_not_urgent';
const COLLECTION = [
'emergency' => 'Emergency',
'use_vehicle_now' => 'Need to Use Vehicle Now',
'with_appointment' => 'With Appointment',
'post_replacement' => 'Post Replacement',
'post_recharge' => 'Post Recharge',
'normal_request_not_urgent' => 'Normal Request not Urgent (Rush below TAT)',
];
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Ramcar;
class JOCancelReasons extends NameValue
{
const WRONG_BATTERY = 'wrong_battery';
const CUSTOMER_NO_SHOW = 'customer_no_show';
const RESCHEDULE = 'reschedule';
const LOCATION_CHANGE = 'location_change';
const WORKING_BATTERY = 'battery_working';
const LATE_DELIVERY = 'late_delivery';
const CUSTOMER_BOUGHT_NEW_BATTERY = 'customer_bought_new_battery';
const BATTERY_NO_STOCK = 'battery_no_stock';
const SYSTEM_PROBLEM = 'system_problem';
const WRONG_PRICE_PROVIDED = 'wrong_price_provided';
const WRONG_PAYMENT_MODE = 'wrong_payment_mode';
const COLLECTION = [
'wrong_battery' => 'Wrong Battery',
'customer_no_show' => 'Customer No Show',
'reschedule' => 'Reschedule',
'location_change' => 'Change Location',
'battery_working' => 'Battery is Already Working',
'late_delivery' => 'Late Delivery',
'customer_bought_new_battery' => 'Customer Already Bought New Battery from Nearby Outlet',
'battery_no_stock' => 'No Stock of Battery',
'system_problem' => 'System Problem',
'wrong_price_provided' => 'Wrong Price Provided',
'wrong_payment_mode' => 'Wrong Mode of Payment',
];
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Ramcar;
class NoTradeInReason extends NameValue
{
const NO_EXISTING_BATTERY = 'no_existing_battery';
const SELL_OTHER_SHOP = 'sell_other_shop';
const SPARE_BATTERY = 'used_as_spare_battery';
const RETURN_BATTERY_COMPANY = 'return_battery_company';
const LOW_TRADE_IN_VALUE = 'low_trade_in_value';
const COLLECTION = [
'no_existing_battery' => 'No existing battery',
'sell_other_shop' => 'Sell to other shop',
'used_as_spare_battery' => 'Used as spare battery',
'return_battery_company' => 'Need to return battery to company',
'low_trade_in_value' => 'Trade in value is low',
];
}

View file

@ -10,6 +10,7 @@ class TransactionOrigin extends NameValue
const VIP = 'vip';
const MOBILE_APP = 'mobile_app';
const WALK_IN = 'walk_in';
const LAZADA = 'lazada';
// TODO: for now, resq also gets the walk-in option
const COLLECTION = [
@ -19,5 +20,6 @@ class TransactionOrigin extends NameValue
'vip' => 'VIP',
'mobile_app' => 'Mobile App',
'walk_in' => 'Walk-in',
'lazada' => 'Lazada',
];
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Ramcar;
class WillingToWaitContent extends NameValue
{
const WILLING_TO_WAIT = 'willing_to_wait';
const NOT_WILLING_TO_WAIT = 'not_willing_to_wait';
const COLLECTION = [
'willing_to_wait' => 'Willing to Wait',
'not_willing_to_wait' => 'Not Willing to Wait',
];
}

View file

@ -6,8 +6,8 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Security;
use App\Service\CustomerHandlerInterface;
@ -30,14 +30,16 @@ class ResqCustomerHandler implements CustomerHandlerInterface
protected $em;
protected $validator;
protected $country_code;
protected $security;
protected $template_hash;
public function __construct(EntityManagerInterface $em, ValidatorInterface $validator,
string $country_code)
string $country_code, Security $security)
{
$this->em = $em;
$this->validator = $validator;
$this->country_code = $country_code;
$this->security = $security;
$this->loadTemplates();
}
@ -172,14 +174,6 @@ class ResqCustomerHandler implements CustomerHandlerInterface
$em = $this->em;
$row = new Customer();
// NOTE: commenting the dpa changes for now
// check if dpa consent is unticked
/*
$is_dpa_checked = $req->request->get('flag_dpa_consent');
if (!$is_dpa_checked)
$error_array['flag_dpa_consent'] = 'DPA consent should be checked.';
*/
// check if email marketing promo is checked
$is_email_promo_checked = $req->request->get('flag_promo_email');
if ($is_email_promo_checked)
@ -342,14 +336,6 @@ class ResqCustomerHandler implements CustomerHandlerInterface
$nerror_array = [];
$verror_array = [];
// NOTE: commenting out the dpa changes for now
// check if dpa consent is unticked
/*
$is_dpa_checked = $req->request->get('flag_dpa_consent');
if (!$is_dpa_checked)
$error_array['flag_dpa_consent'] = 'DPA consent should be checked.';
*/
// check if email marketing promo is checked
$is_email_promo_checked = $req->request->get('flag_promo_email');
if ($is_email_promo_checked)
@ -597,6 +583,11 @@ class ResqCustomerHandler implements CustomerHandlerInterface
protected function setObject($obj, $req)
{
// check for dpa access
$is_dpa_checked = true;
if ($this->security->isGranted('customer.dpa'))
$is_dpa_checked = $req->request->get('flag_dpa_consent', false);
// set and save values
$obj->setTitle($req->request->get('title'))
->setFirstName($req->request->get('first_name'))
@ -608,7 +599,7 @@ class ResqCustomerHandler implements CustomerHandlerInterface
->setActive($req->request->get('flag_active') ? true : false)
->setPromoSms($req->request->get('flag_promo_sms', false))
->setPromoEmail($req->request->get('flag_promo_email', false))
->setDpaConsent($req->request->get('flag_dpa_consent', false));
->setDpaConsent($is_dpa_checked);
// phone numbers
$obj->setPhoneMobile($req->request->get('phone_mobile'))

View file

@ -37,6 +37,9 @@ use App\Ramcar\ModeOfPayment;
use App\Ramcar\TransactionOrigin;
use App\Ramcar\FacilitatedType;
use App\Ramcar\JORejectionReason;
use App\Ramcar\CustomerNotWaitReason;
use App\Ramcar\NoTradeInReason;
use App\Ramcar\WillingToWaitContent;
use App\Service\InvoiceGeneratorInterface;
use App\Service\JobOrderHandlerInterface;
@ -45,6 +48,7 @@ use App\Service\WarrantyHandler;
use App\Service\MQTTClient;
use App\Service\APNSClient;
use App\Service\MapTools;
use App\Service\RisingTideGateway;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
@ -65,13 +69,14 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
protected $rah;
protected $country_code;
protected $wh;
protected $rt;
protected $template_hash;
public function __construct(Security $security, EntityManagerInterface $em,
InvoiceGeneratorInterface $ic, ValidatorInterface $validator,
TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah,
string $country_code, WarrantyHandler $wh)
string $country_code, WarrantyHandler $wh, RisingTideGateway $rt)
{
$this->em = $em;
$this->ic = $ic;
@ -81,6 +86,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$this->rah = $rah;
$this->country_code = $country_code;
$this->wh = $wh;
$this->rt = $rt;
$this->loadTemplates();
}
@ -183,6 +189,12 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
else
$row['assignor'] = $orow->getAssignedBy()->getFullName();
$hub_facilitated = $orow->getFacilitatedBy();
if ($hub_facilitated == null)
$row['hub_facilitated'] = '';
else
$row['hub_facilitated'] = $orow->getFacilitatedBy()->getName();
$rows[] = $row;
}
@ -291,10 +303,24 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
else
{
// get email, dpa_consent, promo_sms, and promo_email, if set
// check for dpa access
$is_dpa_checked = true;
if ($this->security->isGranted('customer.dpa'))
$is_dpa_checked = $req->request->get('flag_dpa_consent', false);
// check if email marketing promo is checked
$is_email_promo_checked = $req->request->get('flag_promo_email');
if ($is_email_promo_checked)
{
// check email field
if (empty($req->request->get('customer_email')))
$error_array['customer_email'] = 'Email address required.';
}
$customer->setEmail($req->request->get('customer_email'))
->setPromoSms($req->request->get('flag_promo_sms', false))
->setPromoEmail($req->request->get('flag_promo_email', false))
->setDpaConsent($req->request->get('flag_dpa_consent', false));
->setDpaConsent($is_dpa_checked);
}
// check if lat and lng are provided
@ -314,6 +340,37 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
}
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
// check if service is battery sales
$stype = $req->request->get('service_type');
$no_trade_in_reason = '';
if ($stype == ServiceType::BATTERY_REPLACEMENT_NEW)
{
// check if trade in
$is_trade_in = $req->request->get('invoice_trade_in_type');
if (empty($is_trade_in))
{
$no_trade_in_reason = $req->request->get('no_trade_in_reason');
if (empty($no_trade_in_reason))
$error_array['no_trade_in_reason'] = 'No trade in reason required.';
}
}
// TODO: check status before saving since JO might already
// have a status that needs to be retained
@ -324,8 +381,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// 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)
@ -343,7 +398,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'));
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason)
->setNoTradeInReason($no_trade_in_reason);
// check if user is null, meaning call to create came from API
if ($user != null)
@ -434,6 +493,37 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
// check if service type is battery sales
$stype = $req->request->get('service_type');
$no_trade_in_reason = '';
if ($stype == ServiceType::BATTERY_REPLACEMENT_NEW)
{
// check if trade in
$is_trade_in = $req->request->get('invoice_trade_in_type');
if (empty($is_trade_in))
{
$no_trade_in_reason = $req->request->get('no_trade_in_reason');
if (empty($no_trade_in_reason))
$error_array['no_trade_in_reason'] = 'No trade in reason required.';
}
}
if (empty($error_array))
{
// get current user
@ -442,8 +532,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// 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)
@ -458,7 +546,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'));
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason)
->setNoTradeInReason($no_trade_in_reason);
// did they change invoice?
$invoice_items = $req->request->get('invoice_items', []);
@ -492,6 +584,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setTypeID(JOEventType::OPEN_EDIT)
->setJobOrder($obj);
error_log('open edit?');
if ($user != null)
{
$event->setUser($user);
@ -587,6 +681,21 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$fac_by = null;
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
if (empty($error_array))
{
// coordinates
@ -606,7 +715,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setDeliveryAddress($req->request->get('delivery_address'))
->setFacilitatedType($fac_type)
->setFacilitatedBy($fac_by)
->setHub($hub);
->setHub($hub)
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason);
// validate
$errors = $this->validator->validate($obj);
@ -680,6 +793,21 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
}
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
// get current user
$user = $this->security->getUser();
@ -700,7 +828,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setDateAssign(new DateTime())
->setRider($rider);
->setRider($rider)
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason);
if ($user != null)
{
@ -763,6 +895,21 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
@ -777,7 +924,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'));
->setDeliveryAddress($req->request->get('delivery_address'))
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason);
// validate
$errors = $this->validator->validate($obj);
@ -868,6 +1019,12 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
else
error_log('Invalid plate number for warranty. Plate number = ' . $obj->getCustomerVehicle()->getPlateNumber());
}
// send SMS to customer
// prepend country code to number
$phone_number = $this->country_code . $obj->getCustomer()->getPhoneMobile();
if (!empty($phone_number))
$this->sendSMSToCustomer($phone_number);
}
}
@ -883,6 +1040,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
throw new NotFoundHttpException('The item does not exist');
$cancel_reason = $req->request->get('cancel_reason');
error_log($cancel_reason);
$obj->cancel($cancel_reason);
// the event
@ -947,6 +1105,23 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
}
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
error_log($req->request->get('landmark'));
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
if (empty($error_array))
{
// rider mqtt event
@ -974,6 +1149,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setHub($hub)
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason)
->clearRider();
if ($user != null)
@ -1148,6 +1327,21 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
}
}
// check if landmark is set
if (empty($req->request->get('landmark')))
$error_array['landmark'] = 'Landmark is required.';
// check if customer is not willing to wait
$will_wait = $req->request->get('flag_willing_to_wait');
$reason = '';
$more_reason = '';
if ($will_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
// get the reason and text
$reason = $req->request->get('no_wait_reason');
$more_reason = $req->request->get('not_wait_notes');
}
if (empty($error_array)) {
// rider mqtt event
// NOTE: need to send this before saving because rider will be cleared
@ -1174,7 +1368,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setDateAssign(new DateTime())
->setRider($rider);
->setRider($rider)
->setLandmark($req->request->get('landmark'))
->setWillWait($req->request->get('flag_willing_to_wait'))
->setReasonNotWait($reason)
->setNotWaitingNotes($more_reason);
if ($user != null)
{
@ -1562,6 +1760,18 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
// check JO status to determine the mode and submit_url to return
if ($obj->getStatus() == JOStatus::CANCELLED)
{
$params['mode'] = 'fulfill-cancel';
$params['submit_url'] = 'jo_fulfill_cancel_submit';
}
else
{
$params['mode'] = 'update-all';
$params['submit_url'] = '';
}
// get template to display
$params['template'] = $this->getTwigTemplate('jo_all_form');
@ -2434,6 +2644,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$params['facilitated_types'] = FacilitatedType::getCollection();
$params['facilitated_hubs'] = $fac_hubs;
$params['sources'] = TransactionOrigin::getCollection();
$params['willing_to_wait_content'] = WillingToWaitContent::getCollection();
$params['no_wait_reasons'] = CustomerNotWaitReason::getCollection();
$params['no_trade_in_reasons'] = NoTradeInReason::getCollection();
}
protected function initFormTags(&$params)
@ -2765,20 +2978,35 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->andWhere('h.flag_hub_view = :flag_hub_view')
->setParameter('flag_hub_view', true);
}
if (isset($datatable['query']['schedule_date']))
{
$start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00';
$end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00';
$date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start);
$date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end);
$query->andWhere('q.date_schedule >= :date_start')
->andWhere('q.date_schedule <= :date_end')
->setParameter('date_start', $date_start)
->setParameter('date_end', $date_end);
}
else
{
$c_date = new DateTime();
$start_curr_date = $c_date->format('Y-m-d') . ' ' . '00:00:00';
$end_curr_date = $c_date->format('Y-m-d') . ' ' . '23:59:00';
$c_date = new DateTime();
$start_curr_date = $c_date->format('Y-m-d') . ' ' . '00:00:00';
$end_curr_date = $c_date->format('Y-m-d') . ' ' . '23:59:00';
$start_current_date = DateTime::createFromFormat('Y-m-d H:i:s', $start_curr_date);
$end_current_date = DateTime::createFromFormat('Y-m-d H:i:s', $end_curr_date);
$start_current_date = DateTime::createFromFormat('Y-m-d H:i:s', $start_curr_date);
$end_current_date = DateTime::createFromFormat('Y-m-d H:i:s', $end_curr_date);
$query->andWhere('q.date_schedule >= :start_current_date')
->andWhere('q.date_schedule <= :end_current_date')
->andWhere('q.status IN (:statuses)')
->setParameter('start_current_date', $start_current_date)
->setParameter('end_current_date', $end_current_date)
->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY);
$query->andWhere('q.date_schedule >= :start_current_date')
->andWhere('q.date_schedule <= :end_current_date')
->andWhere('q.status IN (:statuses)')
->setParameter('start_current_date', $start_current_date)
->setParameter('end_current_date', $end_current_date)
->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY);
}
break;
default:
$query->where('q.status = :status')
@ -2954,4 +3182,44 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
return $params;
}
public function fulfillCancelledJobOrder(Request $req, $id)
{
// 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');
$obj->fulfill();
// 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);
$em->flush();
}
public function sendSMSToCustomer($phone_number)
{
// TODO: put this in config file or somewhere
$message = "Your Resq job order has been completed.";
$this->rt->sendSMS($phone_number, 'MOTOLITE', $message);
}
}

View file

@ -21,6 +21,7 @@ use App\Service\MQTTClient;
use App\Service\WarrantyHandler;
use App\Service\JobOrderHandlerInterface;
use App\Service\InvoiceGeneratorInterface;
use App\Service\RisingTideGateway;
use App\Entity\RiderSession;
use App\Entity\Rider;
@ -45,12 +46,13 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
protected $jo_handler;
protected $ic;
protected $session;
protected $rt;
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis,
EncoderFactoryInterface $ef, RiderCache $rcache,
string $country_code, MQTTClient $mclient,
WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler,
InvoiceGeneratorInterface $ic)
InvoiceGeneratorInterface $ic, RisingTideGateway $rt)
{
$this->em = $em;
$this->redis = $redis;
@ -61,6 +63,7 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
$this->wh = $wh;
$this->jo_handler = $jo_handler;
$this->ic = $ic;
$this->rt = $rt;
// one device = one session, since we have control over the devices
// when a rider logs in, we just change the rider assigned to the device
@ -523,6 +526,15 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
// save to customer vehicle battery record
$this->jo_handler->updateVehicleBattery($jo);
// send SMS to customer
$phone_number = $jo->getCustomer()->getPhoneMobile();
if (!empty($phone_number))
{
// TODO: put this in config file or somewhere
$message = "Your Resq job order has been completed.";
$this->rt->sendSMS($phone_number, 'MOTOLITE', $message);
}
$this->em->flush();
// create warranty

View file

@ -123,12 +123,11 @@
</div>
</div>
<div class="col-lg-4">
<!-- commenting out the dpa access -->
{# {% if is_granted('customer.dpa') %} #}
{% if is_granted('customer.dpa') %}
<input type="checkbox" name="flag_dpa_consent" id="flag-dpa_consent" value="1"{{ obj.isDpaConsent ? ' checked' }} >
<label class="switch-label">With DPA Consent</label>
<div class="form-control-feedback hide" data-field="flag_dpa_consent"></div>
{# {% endif %} #}
{% endif %}
</div>
</div>
<div class="form-group m-form__group row">

View file

@ -45,7 +45,7 @@ function initMap(r_markers, c_markers, icons) {
return dashmap;
}
function initEventHandler(dashmap) {
function initEventHandler(dashmap, icons, ssl) {
var options = {
'track_jo': true,
'track_rider': true,
@ -58,7 +58,7 @@ function initEventHandler(dashmap) {
},
};
var event_handler = new MapEventHandler(options, dashmap);
var event_handler = new MapEventHandler(options, dashmap, ssl);
event_handler.connect('{{ app.user.getID }}', '{{ mqtt_host }}', {{ mqtt_port }});
}
@ -94,8 +94,13 @@ var icons = {
var r_markers = {};
var c_markers = {};
var ssl = false;
{% if ssl_enable == 'true' %}
ssl = true;
{% endif %}
var dashmap = initMap(r_markers, c_markers, icons);
initEventHandler(dashmap, icons);
initEventHandler(dashmap, icons, ssl);
{% endif %}
</script>

View file

@ -34,7 +34,7 @@
{% elseif mode == 'update-reassign-rider' %}
Re-assign Rider
<small>{{ obj.getID() }}</small>
{% elseif mode == 'update-all' %}
{% elseif mode in ['update-all', 'fulfill-cancel'] %}
Viewing
<small>{{ obj.getID() }}</small>
{% else %}
@ -146,18 +146,20 @@
</div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_customer_notes">Customer Notes</label>
<textarea name="customer_customer_notes" id="customer-customer-notes" class="form-control m-input" data-vehicle-field="1" rows="4" disabled>{{ obj.getCustomer ? obj.getCustomer.getCustomerNotes }}</textarea>
<div class="form-control-feedback hide" data-field="customer_customer_notes"></div>
</div>
<div class="col-lg-6">
<input type="checkbox" name="flag_dpa_consent" id="flag-dpa-consent" value="1"{{ obj.getCustomer ? obj.getCustomer.isDpaConsent ? ' checked' }} >
<label class="switch-label">With DPA Consent</label>
<div class="form-control-feedback hide" data-field="flag_dpa_consent"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="customer_customer_notes">Customer Notes</label>
<textarea name="customer_customer_notes" id="customer-customer-notes" class="form-control m-input" data-vehicle-field="1" rows="4" disabled>{{ obj.getCustomer ? obj.getCustomer.getCustomerNotes }}</textarea>
<div class="form-control-feedback hide" data-field="customer_customer_notes"></div>
</div>
<div class="col-lg-6">
{% if is_granted('customer.dpa') %}
<input type="checkbox" name="flag_dpa_consent" id="flag-dpa-consent" value="1"{{ obj.getCustomer ? obj.getCustomer.isDpaConsent ? ' checked' }} i>
<label class="switch-label">With DPA Consent</label>
<div class="form-control-feedback hide" data-field="flag_dpa_consent"></div>
{% endif %}
</div>
</div>
</div>
<div class="m-form__section">
<div class="m-form__heading">
@ -292,6 +294,33 @@
<div class="form-control-feedback hide" data-field="date_schedule_time"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-4">
<label data-field="flag_will_wait">Willing to Wait</label>
<select class="form-control m-input" id="flag-willing-to-wait" name="flag_willing_to_wait">
{% for key, class in willing_to_wait_content %}
<option value="{{ key }}"{{ obj.getWillWait == key ? ' selected' }}>{{ class }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="flag_willing_to_wait"></div>
</div>
<div class="col-lg-4">
<label data-field="no_wait_reason">Reason Why Not Willing to Wait</label>
<select class="form-control m-input" id="no-wait-reason" name="no_wait_reason" disabled>
<option value="">None</option>
{% for key, class in no_wait_reasons %}
<option value="{{ key }}"{{ obj.getReasonNotWait == key ? ' selected' }}>{{ class }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="no_wait_reason"></div>
</div>
<div class="col-lg-4">
<label data-field="not_wait_notes">Notes on Not Willing to Wait</label>
<textarea name="not_wait_notes" id="not-wait-notes" class="form-control m-input" rows="2" disabled>{{ obj.getNotWaitingNotes }}</textarea>
<div class="form-control-feedback hide" data-field="not_wait_notes"></div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label data-field="tier1_notes">Tier 1 Notes</label>
@ -439,6 +468,7 @@
<div class="form-control-feedback hide" data-field="promo_detail"></div>
</div>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label>Discount Type</label>
@ -512,7 +542,7 @@
</div>
{% if ftags.invoice_edit %}
<div class="form-group m-form__group row">
<div class="col-lg-2 hide">
<div class="col-lg-1 hide">
<label for="invoice-bmfg">Manufacturer</label>
<select class="form-control m-input" id="invoice-bmfg">
{% for manufacturer in bmfgs %}
@ -528,18 +558,29 @@
</div>
<div class="col-lg-2">
<label for="invoice-trade-in-type">Trade In</label>
<select class="form-control m-input" id="invoice-trade-in-type">
<select class="form-control m-input" name="invoice_trade_in_type" id="invoice-trade-in-type">
<option value="">None</option>
{% for key, type in trade_in_types %}
<option value="{{ key }}">{{ type }}</option>
{% endfor %}
</select>
</div>
<div class="col-lg-2">
<div class="col-lg-2">
<label data-field="no_trade_in_reason">No Trade In Reason</label>
<select class="form-control m-input" id="no-trade-in-reason" name="no_trade_in_reason">
<option value="">Select reason</option>
{% for key, class in no_trade_in_reasons %}
<option value="{{ key }}"{{ obj.getNoTradeInReason == key ? ' selected' }}>{{ class }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="no_trade_in_reason"></div>
</div>
<div class="col-lg-1">
<label for="invoice-quantity">Quantity</label>
<input type="text" id="invoice-quantity" class="form-control m-input text-right" value="1">
</div>
<div class="col-lg-5">
<div class="col-lg-3">
<div><label>&nbsp;</label></div>
<button type="button" class="btn btn-primary" id="btn-add-to-invoice">Add</button>
<!--
@ -786,7 +827,7 @@
{% endif %}
{% endif %}
{% if mode == 'update-all' %}
{% if mode in ['update-all', 'fulfill-cancel'] %}
<div class="m-form__seperator m-form__seperator--dashed"></div>
<div class="m-form__section">
@ -848,7 +889,11 @@
<div class="row">
<div class="col-lg-12">
{% if mode != 'update-all' %}
{% if mode == 'fulfill-cancel' and is_granted('jo_cancel.fulfill') %}
<a href="{{ url('jo_fulfill_cancel_submit', {'id': obj.getID}) }}" class="btn btn-success btn-fulfill-cancel-job-order">Fulfill</a>
{% else %}
<button type="submit" class="btn btn-success">{{ mode == 'update-fulfillment' ? 'Fulfill' : 'Submit' }}</button>
{% endif %}
{% endif %}
{% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %}
<a href="{{ url('jo_cancel', {'id': obj.getID}) }}" class="btn btn-danger btn-cancel-job-order">Cancel Job Order</a>
@ -972,6 +1017,26 @@ $(function() {
function selectPoint(lat, lng)
{
// check if point is in coverage area
$.ajax({
method: "GET",
url: "{{ url('jo_geofence') }}",
data: {
'lat': lat,
'lng': lng
}
}).done(function(response) {
if (response.is_covered === false)
{
console.log('Not in coverage area');
swal({
title: 'Warning!',
text: 'Our service is limited to some areas in Metro Manila, Laguna, and Baguio only. We will update you as soon as we are able to cover your area',
type: 'warning',
});
}
});
// clear markers
markerLayerGroup.clearLayers();
@ -1730,44 +1795,97 @@ $(function() {
// cancel job order
$(".btn-cancel-job-order").click(function(e) {
var url = $(this).prop('href');
var url = $(this).prop('href');
e.preventDefault();
e.preventDefault();
swal({
title: 'Cancel Job Order',
html: 'Please enter the reason for cancellation of this job order:',
input: 'textarea',
inputClass: 'form-control',
type: 'warning',
showCancelButton: true,
width: '40rem',
preConfirm: (reason) => {
if (!reason) {
swal.showValidationError(
'Reason for cancellation is required.'
)
}
}
}).then((reason) => {
$.ajax({
method: "DELETE",
url: url,
data: {
'cancel_reason': reason.value
}
}).done(function(response) {
swal({
title: 'Done!',
text: response.success,
type: 'success',
onClose: function() {
window.location.href = "{{ return_url }}";
}
});
});
});
});
var inputOptionsPromise = new Promise(function(resolve) {
// get your data and pass it to resolve()
$.getJSON("{{ url('jo_cancel_reasons') }}", function(data) {
resolve(data.cancel_reasons)
});
});
swal({
title: 'Cancel Job Order',
html: 'Please select the reason for cancellation of this job order:',
input: 'select',
inputOptions: inputOptionsPromise,
type: 'warning',
showCancelButton: true,
closeOnCancel: true,
width: '40rem',
inputValidator: (value) => {
return new Promise((resolve) => {
resolve()
})
}
}).then((result) => {
if (result.value) {
$.ajax({
method: "DELETE",
url: url,
data: {
'cancel_reason': result.value
}
}).done(function(response) {
swal({
title: 'Done!',
text: response.success,
type: 'success',
onClose: function() {
window.location.href = "{{ return_url }}";
}
});
});
}
});
});
// fulfill cancelled job order
$(".btn-fulfill-cancel-job-order").click(function(e) {
var url = $(this).prop('href');
e.preventDefault();
$.ajax({
method: "POST",
url: url,
}).done(function(response) {
swal({
title: 'Done!',
text: response.success,
type: 'success',
onClose: function() {
window.location.href = "{{ return_url }}";
}
});
});
});
$("#flag-willing-to-wait").change(function() {
var field_value = $(this).val();
var field = $("#flag-willing-to-wait");
console.log(field_value);
if (field_value == 'not_willing_to_wait') {
$("#no-wait-reason").attr("disabled", false);
$("#not-wait-notes").attr("disabled", false);
} else {
$("#no-wait-reason").attr("disabled", true);
$("#not-wait-notes").attr("disabled", true);
// remove selected from option that might have already been selected
$("#no-wait-reason option:selected").prop("selected", false);
// set first option
$("#no-wait-reason option:first").prop("selected", "selected");
// empty the reason text field
$("#not-wait-notes").val('');
}
});
});
</script>
{% endblock %}

View file

@ -116,6 +116,10 @@
field: 'delivery_address',
title: 'Customer Area'
},
{
field: 'hub_facilitated',
title: 'Battery Facilitated By'
},
{
field: 'type',
title: 'Schedule'

View file

@ -42,6 +42,17 @@
</div>
</div>
</div>
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<div class="input-daterange input-group" id="date-range">
<input role="presentation" type="text" class="form-control m-input" id="date_start" name="date_start" placeholder="Start date" />
<div class="input-group-append">
<span class="input-group-text"><i class="la la-ellipsis-h"></i></span>
</div>
<input role="presentation" type="text" class="form-control" id="date_end" name="date_end" placeholder="End date" />
</div>
</div>
</div>
</div>
</div>
</div>
@ -59,6 +70,10 @@
{% block scripts %}
<script>
$(function() {
$("#date-range").datepicker({
orientation: "bottom"
});
var options = {
data: {
type: 'remote',
@ -147,6 +162,22 @@
$("#hub_list").on("change", function() {
table.search($(this).val(), "hub");
});
$("#date_start").on("change", function() {
var date_start = $(this).val();
var date_end = $("[name='date_end']").val();
var date_array = [date_start, date_end];
table.search(date_array, "schedule_date");
});
$("#date_end").on("change", function() {
var date_end = $(this).val();
var date_start = $("[name='date_start']").val();
var date_array = [date_start, date_end];
table.search(date_array, "schedule_date");
});
});
</script>
{% endblock %}