Merge branch '270-final-cmb-fixes' of gitlab.com:jankstudio/resq into 299-cmb-realtime-map

Conflicts:
	config/services.yaml
This commit is contained in:
Kendrick Chan 2020-01-18 22:40:25 +08:00
commit bbaaf250c6
79 changed files with 18468 additions and 5819 deletions

View file

@ -242,6 +242,10 @@ access_keys:
label: Edit label: Edit
- id: joborder.cancel - id: joborder.cancel
label: Cancel label: Cancel
- id: jo_onestep.form
label: One-step Process
- id: jo_onestep.edit
label: One-step Process Edit
- id: support - id: support
label: Customer Support Access label: Customer Support Access

View file

@ -98,6 +98,10 @@ main_menu:
acl: joborder.menu acl: joborder.menu
label: Job Order label: Job Order
icon: flaticon-calendar-3 icon: flaticon-calendar-3
- id: jo_onestep_form
acl: jo_onestep.form
label: One-step Process
parent: joborder
- id: jo_in - id: jo_in
acl: jo_in.list acl: jo_in.list
label: Incoming label: Incoming

View file

@ -3,7 +3,3 @@
# controller: App\Controller\DefaultController::index # controller: App\Controller\DefaultController::index
# #
# #
home:
path: /
controller: App\Controller\HomeController::index

8
config/routes/home.yaml Normal file
View file

@ -0,0 +1,8 @@
home:
path: /
controller: App\Controller\HomeController::index
rider_locations:
path: /rider_locations
controller: App\Controller\HomeController::getRiderLocations

View file

@ -34,3 +34,12 @@ hub_delete:
controller: App\Controller\HubController::destroy controller: App\Controller\HubController::destroy
methods: [DELETE] 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]

View file

@ -175,3 +175,23 @@ jo_reject_hub:
path: /job-order/{id}/reject-hub path: /job-order/{id}/reject-hub
controller: App\Controller\JobOrderController::rejectHubSubmit controller: App\Controller\JobOrderController::rejectHubSubmit
methods: [POST] 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]

View file

@ -102,6 +102,11 @@ services:
$cvu_mfg_id: "%env(CVU_MFG_ID)%" $cvu_mfg_id: "%env(CVU_MFG_ID)%"
$cvu_brand_id: "%env(CVU_BRAND_ID)%" $cvu_brand_id: "%env(CVU_BRAND_ID)%"
# rider tracker service
App\Service\RiderTracker:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
Catalyst\APIBundle\Security\APIKeyUserProvider: Catalyst\APIBundle\Security\APIKeyUserProvider:
arguments: arguments:
$em: "@doctrine.orm.entity_manager" $em: "@doctrine.orm.entity_manager"
@ -145,3 +150,40 @@ services:
$menu_name: "main_menu" $menu_name: "main_menu"
tags: tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - { 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\CMBJobOrderHandler: ~
#job order generator interface
#App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler"
App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\ResqJobOrderHandler"
# customer generator
App\Service\CustomerHandler\CMBCustomerHandler: ~
# 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"

View file

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

View file

@ -295,3 +295,40 @@ span.has-danger,
.btn-icon { .btn-icon {
margin-right: .5em; 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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,315 @@
<?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\Common\Persistence\ObjectManager;
use App\Entity\Battery;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
class ImportCMBBatteryDataCommand extends Command
{
const F_BATT_CODE = 1;
const F_BATT_DESC = 2;
const F_BATT_PRICE = 3;
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
public function __construct(ObjectManager $om)
{
$this->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;
}
}

View file

@ -0,0 +1,113 @@
<?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\Common\Persistence\ObjectManager;
use App\Entity\BatterySize;
class ImportCMBBatteryTradeInPriceCommand extends Command
{
const F_SIZE_DESC = 2;
const F_TRADEIN_PRICE = 3;
protected $em;
protected $bsize_hash;
public function __construct(ObjectManager $om)
{
$this->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;
}
}
}

View file

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

View file

@ -0,0 +1,59 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\Common\Persistence\ObjectManager;
use App\Service\RedisClientProvider;
use App\Entity\RiderSession;
class SeedRiderSessionsCommand extends Command
{
protected $em;
protected $redis;
public function __construct(ObjectManager $om, RedisClientProvider $redis)
{
$this->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);
}
}
}
}

View file

@ -24,7 +24,7 @@ use App\Ramcar\TransactionOrigin;
use App\Ramcar\TradeInType; use App\Ramcar\TradeInType;
use App\Ramcar\JOEventType; use App\Ramcar\JOEventType;
use App\Service\InvoiceCreator; use App\Service\InvoiceGeneratorInterface;
use App\Service\RisingTideGateway; use App\Service\RisingTideGateway;
use App\Service\MQTTClient; use App\Service\MQTTClient;
use App\Service\GeofenceTracker; use App\Service\GeofenceTracker;
@ -817,7 +817,7 @@ class APIController extends Controller
return $res->getReturnResponse(); 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 // check required parameters and api key
$required_params = [ $required_params = [
@ -979,7 +979,7 @@ class APIController extends Controller
$icrit->addEntry($batt, $trade_in, 1); $icrit->addEntry($batt, $trade_in, 1);
// send to invoice generator // send to invoice generator
$invoice = $ic->processCriteria($icrit); $invoice = $ic->generateInvoice($icrit);
$jo->setInvoice($invoice); $jo->setInvoice($invoice);
$em->persist($jo); $em->persist($jo);
@ -1026,7 +1026,7 @@ class APIController extends Controller
return $res->getReturnResponse(); return $res->getReturnResponse();
} }
public function getEstimate(Request $req, InvoiceCreator $ic) public function getEstimate(Request $req, InvoiceGeneratorInterface $ic)
{ {
// $this->debugRequest($req); // $this->debugRequest($req);
@ -1126,7 +1126,7 @@ class APIController extends Controller
$icrit->addEntry($batt, $trade_in, 1); $icrit->addEntry($batt, $trade_in, 1);
// send to invoice generator // send to invoice generator
$invoice = $ic->processCriteria($icrit); $invoice = $ic->generateInvoice($icrit);
// make invoice json data // make invoice json data
$data = [ $data = [

View file

@ -2,134 +2,51 @@
namespace App\Controller; namespace App\Controller;
use App\Ramcar\CustomerClassification;
use App\Ramcar\FuelType;
use App\Ramcar\VehicleStatusCondition;
use App\Ramcar\CrudException; use App\Ramcar\CrudException;
use App\Entity\Customer; use App\Service\CustomerHandlerInterface;
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 Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Catalyst\MenuBundle\Annotation\Menu; use Catalyst\MenuBundle\Annotation\Menu;
use DateTime;
class CustomerController extends Controller class CustomerController extends Controller
{ {
/** /**
* @Menu(selected="customer_list") * @Menu(selected="customer_list")
*/ */
public function index() public function index(CustomerHandlerInterface $cust_handler)
{ {
$this->denyAccessUnlessGranted('customer.list', null, 'No access.'); $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.'); $this->denyAccessUnlessGranted('customer.list', null, 'No access.');
// build query $params = $cust_handler->getCustomers($req);
$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();
$meta = $params['meta'];
$rows = $params['rows'];
// process rows // process rows
$rows = []; foreach ($rows as $key => $data) {
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("<br>", $orow->getMobileNumberList());
$row['plate_numbers'] = implode("<br>", $orow->getPlateNumberList());
// add row metadata
$row['meta'] = [
'update_url' => '',
'delete_url' => ''
];
// add crud urls // add crud urls
if ($this->isGranted('customer.update')) $cust_id = $rows[$key]['id'];
$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']]);
$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 // response
@ -139,347 +56,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") * @Menu(selected="customer_list")
*/ */
public function addForm() public function addForm(CustomerHandlerInterface $cust_handler)
{ {
$this->denyAccessUnlessGranted('customer.add', null, 'No access.'); $this->denyAccessUnlessGranted('customer.add', null, 'No access.');
$params['obj'] = new Customer(); $params = $cust_handler->initializeAddCustomerForm();
$params['mode'] = 'create';
// get dropdown parameters $template = $params['template'];
$this->fillDropdownParameters($params);
// response // response
return $this->render('customer/form.html.twig', $params); return $this->render($template, $params);
} }
protected function setObject($obj, $req) public function addSubmit(Request $req, CustomerHandlerInterface $cust_handler)
{
// 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)
{ {
$this->denyAccessUnlessGranted('customer.add', null, 'No access.'); $this->denyAccessUnlessGranted('customer.add', null, 'No access.');
// create new row $result = $cust_handler->addCustomer($req);
$em = $this->getDoctrine()->getManager();
$row = new Customer();
$this->setObject($row, $req); if (isset($result['id']))
{
// initialize error lists $id = $result['id'];
$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();
// return successful response // return successful response
return $this->json([ return $this->json([
'success' => 'Changes have been saved!', '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 else
{ {
// validated! save the entity. do a persist anyway to save child entities $error_array = $result['error_array'];
$em->persist($cust); $nerror_array = $result['nerror_array'];
$em->flush(); $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 successful response
return $this->json([ return $this->json([
'success' => 'Changes have been saved!', '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.'); $this->denyAccessUnlessGranted('customer.delete', null, 'No access.');
// get row data try
$em = $this->getDoctrine()->getManager(); {
$row = $em->getRepository(Customer::class)->find($id); $cust_handler->deleteCustomer($id);
}
if (empty($row)) catch (NotFoundHttpException $e)
throw $this->createNotFoundException('The item does not exist'); {
throw $this->createNotFoundException($e->getMessage());
// delete this row }
$em->remove($row);
$em->flush();
// response // response
$response = new Response(); $response = new Response();
@ -487,110 +176,17 @@ class CustomerController extends Controller
$response->send(); $response->send();
} }
protected function generateYearOptions() public function getCustomerVehicles(Request $req, CustomerHandlerInterface $cust_handler)
{
$start_year = 1950;
return range($start_year, date("Y") + 1);
}
public function getCustomerVehicles(Request $req)
{ {
if (!$this->isGranted('jo_in.list')) { if (!$this->isGranted('jo_in.list')) {
$exception = $this->createAccessDeniedException('No access.'); $exception = $this->createAccessDeniedException('No access.');
throw $exception; throw $exception;
} }
// get search term $results = $cust_handler->getCustomerVehicles($req);
$term = $req->query->get('search');
// get querybuilder $vehicles = $results['vehicles'];
$qb = $this->getDoctrine() $has_more_pages = $results['has_more_pages'];
->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() . ' (+63' . $cust->getPhoneMobile() . ')',
];
}
// response // response
return $this->json([ return $this->json([
@ -602,85 +198,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.'); $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.');
// get id $result = $cust_handler->getCustomerVehicleInfo($req);
$id = $req->query->get('id');
// get row data if ($result == null)
$em = $this->getDoctrine()->getManager(); {
$obj = $em->getRepository(CustomerVehicle::class)->find($id);
// make sure this row exists
if (empty($obj)) {
return $this->json([ return $this->json([
'success' => false, 'success' => false,
'error' => 'The item does not exist' 'error' => 'The item does not exist'
]); ]);
} }
else
$customer = $obj->getCustomer(); {
$vehicle = $obj->getVehicle(); // response
$battery = $obj->getCurrentBattery(); return $this->json([
'success' => true,
// build response 'data' => $result
$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'] . '%');
} }
} }
} }

View file

@ -5,13 +5,84 @@ namespace App\Controller;
use Catalyst\MenuBundle\Annotation\Menu; use Catalyst\MenuBundle\Annotation\Menu;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; 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 class HomeController extends Controller
{ {
/** /**
* @Menu(selected="home") * @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)
{
// 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,
]);
} }
} }

View file

@ -17,6 +17,9 @@ use DateTime;
use Catalyst\MenuBundle\Annotation\Menu; use Catalyst\MenuBundle\Annotation\Menu;
use App\Service\MapTools;
use App\Service\RiderTracker;
class HubController extends Controller class HubController extends Controller
{ {
/** /**
@ -287,4 +290,86 @@ class HubController extends Controller
$response->setStatusCode(Response::HTTP_OK); $response->setStatusCode(Response::HTTP_OK);
$response->send(); $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,
]);
}
} }

File diff suppressed because it is too large Load diff

View file

@ -16,17 +16,18 @@ use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Ramcar\APIResult; use App\Ramcar\APIResult;
use App\Ramcar\JOStatus; use App\Ramcar\JOStatus;
use App\Ramcar\InvoiceCriteria; use App\Ramcar\InvoiceCriteria;
use App\Ramcar\ServiceType; use App\Ramcar\CMBServiceType;
use App\Ramcar\WarrantyClass; use App\Ramcar\WarrantyClass;
use App\Ramcar\APIRiderStatus; use App\Ramcar\APIRiderStatus;
use App\Ramcar\TransactionOrigin; use App\Ramcar\TransactionOrigin;
use App\Ramcar\TradeInType; use App\Ramcar\CMBTradeInType;
use App\Ramcar\InvoiceStatus; use App\Ramcar\InvoiceStatus;
use App\Ramcar\ModeOfPayment; use App\Ramcar\ModeOfPayment;
use App\Ramcar\JOEventType; use App\Ramcar\JOEventType;
use App\Service\InvoiceCreator; use App\Service\InvoiceGeneratorInterface;
use App\Service\MQTTClient; use App\Service\MQTTClient;
use App\Service\RedisClientProvider;
use App\Entity\RiderSession; use App\Entity\RiderSession;
use App\Entity\Customer; use App\Entity\Customer;
@ -42,9 +43,10 @@ use App\Entity\RiderRating;
use App\Entity\Rider; use App\Entity\Rider;
use App\Entity\User; use App\Entity\User;
use App\Entity\JOEvent; use App\Entity\JOEvent;
use App\Entity\Warranty;
use DateTime; use DateTime;
use DateInterval;
// Rider API controller // Rider API controller
class RAPIController extends Controller class RAPIController extends Controller
@ -135,7 +137,7 @@ class RAPIController extends Controller
return $res; return $res;
} }
public function register(Request $req) public function register(Request $req, RedisClientProvider $redis)
{ {
$res = new APIResult(); $res = new APIResult();
@ -178,6 +180,12 @@ class RAPIController extends Controller
// save // save
$em->persist($sess); $em->persist($sess);
$em->flush(); $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) catch (DBALException $e)
{ {
@ -201,7 +209,7 @@ class RAPIController extends Controller
return $res->getReturnResponse(); return $res->getReturnResponse();
} }
public function login(Request $req, EncoderFactoryInterface $ef) public function login(Request $req, EncoderFactoryInterface $ef, RedisClientProvider $redis)
{ {
$required_params = [ $required_params = [
'user', 'user',
@ -247,6 +255,13 @@ class RAPIController extends Controller
$em->flush(); $em->flush();
// update redis rider.id.<session 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(); $hub = $rider->getHub();
if ($hub == null) if ($hub == null)
$hub_data = null; $hub_data = null;
@ -634,6 +649,12 @@ class RAPIController extends Controller
]; ];
$mclient->sendEvent($jo, $payload); $mclient->sendEvent($jo, $payload);
// create the warranty if new battery only
if ($jo->getServiceType () == CMBServiceType::BATTERY_REPLACEMENT_NEW)
{
$this->createWarranty($jo);
}
return $res->getReturnResponse(); return $res->getReturnResponse();
} }
@ -739,7 +760,7 @@ class RAPIController extends Controller
error_log(print_r($all, true)); error_log(print_r($all, true));
} }
public function changeService(Request $req, InvoiceCreator $ic) public function changeService(Request $req, InvoiceGeneratorInterface $ic)
{ {
$this->debugRequest($req); $this->debugRequest($req);
@ -752,7 +773,7 @@ class RAPIController extends Controller
// check service type // check service type
$stype_id = $req->request->get('stype_id'); $stype_id = $req->request->get('stype_id');
if (!ServiceType::validate($stype_id)) if (!CMBServiceType::validate($stype_id))
{ {
$res->setError(true) $res->setError(true)
->setErrorMessage('Invalid service type - ' . $stype_id); ->setErrorMessage('Invalid service type - ' . $stype_id);
@ -814,7 +835,7 @@ class RAPIController extends Controller
// check trade in // check trade in
$trade_in = $req->request->get('trade_in'); $trade_in = $req->request->get('trade_in');
if (!TradeInType::validate($trade_in)) if (!CMBTradeInType::validate($trade_in))
$trade_in = null; $trade_in = null;
// check mode of payment // check mode of payment
@ -839,8 +860,7 @@ class RAPIController extends Controller
error_log('adding entry for battery - ' . $battery->getID()); error_log('adding entry for battery - ' . $battery->getID());
} }
$invoice = $ic->processCriteria($crit); $invoice = $ic->generateInvoice($crit);
$invoice->setStatus(InvoiceStatus::DRAFT);
// remove previous invoice // remove previous invoice
$old_invoice = $jo->getInvoice(); $old_invoice = $jo->getInvoice();
@ -868,4 +888,80 @@ class RAPIController extends Controller
return $res->getReturnResponse(); return $res->getReturnResponse();
} }
protected function createWarranty($jo)
{
$em = $this->getDoctrine()->getManager();
$warranty = new Warranty();
$warranty_class = $jo->getWarrantyClass();
$first_name = $jo->getCustomer()->getFirstName();
$last_name = $jo->getCustomer()->getLastName();
$mobile_number = $jo->getCustomer()->getPhoneMobile();
// check if date fulfilled is null
if ($jo->getDateFulfill() == null)
$date_create = $jo->getDateCreate();
else
$date_create = $jo->getDateFulfill();
// normalize the plate number
$plate_number = $this->normalizePlateNumber($jo->getCustomerVehicle()->getPlateNumber());
// get battery and its warranty periods
$warranty_period = 0;
$invoice_items = $jo->getInvoice()->getItems();
foreach ($invoice_items as $item)
{
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();
}
}
// compute expiry date
$expiry_date = $this->computeDateExpire($date_create, $warranty_period);
$warranty->setWarrantyClass($warranty_class)
->setFirstName($first_name)
->setLastName($last_name)
->setMobileNumber($mobile_number)
->setDatePurchase($date_create)
->setDateExpire($expiry_date)
->setPlateNumber($plate_number);
$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;
}
} }

View file

@ -60,14 +60,13 @@ class Battery
// product code // product code
/** /**
* @ORM\Column(type="string", length=80) * @ORM\Column(type="string", length=80, nullable=true)
* @Assert\NotBlank()
*/ */
protected $prod_code; protected $prod_code;
// sap code // sap code
/** /**
* @ORM\Column(type="string", length=80) * @ORM\Column(type="string", length=80, nullable=true)
*/ */
protected $sap_code; protected $sap_code;

View file

@ -14,6 +14,9 @@ use App\Ramcar\CustomerClassification;
*/ */
class Customer class Customer
{ {
// TODO: make this a setting somewhere
const COUNTRY_CODE_PREFIX = '+60';
// unique id // unique id
/** /**
* @ORM\Id * @ORM\Id
@ -231,6 +234,11 @@ class Customer
return $this->last_name; return $this->last_name;
} }
public function getNameDisplay()
{
return $this->first_name . ' ' . $this->last_name;
}
public function setCustomerClassification($customer_classification) public function setCustomerClassification($customer_classification)
{ {
$this->customer_classification = $customer_classification; $this->customer_classification = $customer_classification;
@ -258,16 +266,16 @@ class Customer
$phones = []; $phones = [];
if (!empty($this->phone_mobile)) if (!empty($this->phone_mobile))
$phones[] = '+63' . $this->phone_mobile; $phones[] = self::COUNTRY_CODE_PREFIX . $this->phone_mobile;
if (!empty($this->phone_landline)) if (!empty($this->phone_landline))
$phones[] = '+63' . $this->phone_landline; $phones[] = self::COUNTRY_CODE_PREFIX . $this->phone_landline;
if (!empty($this->phone_office)) if (!empty($this->phone_office))
$phones[] = '+63' . $this->phone_office; $phones[] = self::COUNTRY_CODE_PREFIX . $this->phone_office;
if (!empty($this->phone_fax)) if (!empty($this->phone_fax))
$phones[] = '+63' . $this->phone_fax; $phones[] = self::COUNTRY_CODE_PREFIX . $this->phone_fax;
return $phones; return $phones;
} }

View file

@ -67,14 +67,12 @@ class CustomerVehicle
// vehicle status (new / second-hand) // vehicle status (new / second-hand)
/** /**
* @ORM\Column(type="string", length=15) * @ORM\Column(type="string", length=15)
* @Assert\NotBlank()
*/ */
protected $status_condition; protected $status_condition;
// fuel type - diesel, gas // fuel type - diesel, gas
/** /**
* @ORM\Column(type="string", length=15) * @ORM\Column(type="string", length=15)
* @Assert\NotBlank()
*/ */
protected $fuel_type; protected $fuel_type;

View file

@ -319,4 +319,10 @@ class Rider
{ {
return $this->sessions; return $this->sessions;
} }
public function getMapLabel()
{
$map_label = $this->first_name .' ' . $this->last_name;
return $map_label;
}
} }

View file

@ -0,0 +1,16 @@
<?php
namespace App\Ramcar;
class CMBModeOfPayment extends NameValue
{
const CASH = 'cash';
const CREDIT_CARD = 'credit_card';
const BANK_TRANSFER = 'bank_transfer';
const COLLECTION = [
'cash' => 'Cash',
'credit_card' => 'Credit Card',
'bank_transfer' => 'Bank Transfer',
];
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Ramcar;
class CMBServiceType extends NameValue
{
const BATTERY_REPLACEMENT_NEW = 'battery_new';
const BATTERY_REPLACEMENT_WARRANTY = 'battery_warranty';
const JUMPSTART = 'jumpstart';
const COLLECTION = [
'battery_new' => 'Battery Sales',
'battery_warranty' => 'Under Warranty',
'jumpstart' => 'Jumpstart',
];
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Ramcar;
class CMBTradeInType extends NameValue
{
const YES = 'yes';
const COLLECTION = [
'yes' => 'Yes',
];
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Ramcar;
class CMBWarrantyClass extends NameValue
{
const WTY_PASSENGER = 'passenger';
const WTY_COMMERCIAL = 'commercial';
const COLLECTION = [
'passenger' => 'Passenger',
'commercial' => 'Commercial',
];
}

View file

@ -0,0 +1,691 @@
<?php
namespace App\Service\CustomerHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use App\Ramcar\CustomerClassification;
use App\Ramcar\CrudException;
use App\Service\CustomerHandlerInterface;
use App\Entity\Customer;
use App\Entity\CustomerVehicle;
use App\Entity\Vehicle;
use App\Entity\Battery;
use App\Entity\VehicleManufacturer;
use App\Entity\BatteryManufacturer;
use DateTime;
class CMBCustomerHandler implements CustomerHandlerInterface
{
const COUNTRY_CODE_PREFIX = '+60';
protected $em;
protected $validator;
protected $template_hash;
public function __construct(EntityManagerInterface $em, ValidatorInterface $validator)
{
$this->em = $em;
$this->validator = $validator;
$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
$row['mobile_numbers'] = implode("<br>", $orow->getMobileNumberList());
$row['plate_numbers'] = implode("<br>", $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() . ' ('. self::COUNTRY_CODE_PREFIX . $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'] . '%');
}
}
}

View file

@ -0,0 +1,693 @@
<?php
namespace App\Service\CustomerHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use App\Service\CustomerHandlerInterface;
use App\Ramcar\CustomerClassification;
use App\Ramcar\FuelType;
use App\Ramcar\VehicleStatusCondition;
use App\Ramcar\CrudException;
use App\Entity\Customer;
use App\Entity\CustomerVehicle;
use App\Entity\Vehicle;
use App\Entity\Battery;
use App\Entity\VehicleManufacturer;
use App\Entity\BatteryManufacturer;
use DateTime;
class ResqCustomerHandler implements CustomerHandlerInterface
{
const COUNTRY_CODE_PREFIX = '+63';
protected $em;
protected $validator;
protected $template_hash;
public function __construct(EntityManagerInterface $em, ValidatorInterface $validator)
{
$this->em = $em;
$this->validator = $validator;
$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
$row['mobile_numbers'] = implode("<br>", $orow->getMobileNumberList());
$row['plate_numbers'] = implode("<br>", $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() . ' ('. self::COUNTRY_CODE_PREFIX . $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'] . '%');
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\Request;
interface CustomerHandlerInterface
{
// initialize form to display customer list
public function initializeCustomerIndexForm();
// get customers
public function getCustomers(Request $req);
// initialize add customer form
public function initializeAddCustomerForm();
// add new customer and customer vehicle, if any
public function addCustomer(Request $req);
// initialize update customer form
public function initializeUpdateCustomerForm(int $id);
// update customer and customer vehicle
public function updateCustomer(Request $req, int $id);
// delete customer
public function deleteCustomer(int $id);
// get customer vehicles
public function getCustomerVehicles(Request $req);
// get customer vehicle info
public function getCustomerVehicleInfo(Request $req);
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Service\GISManager;
use App\Service\GISManagerInterface;
class Bing implements GISManagerInterface
{
const JS_INIT_FILE = 'initBingMap.js';
public function getJSInitFile()
{
// return the bing map js file: initBingMap.js
return self::JS_INIT_FILE;
}
public function getJSJOFile()
{
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Service\GISManager;
use App\Service\GISManagerInterface;
class Google implements GISManagerInterface
{
const JS_INIT_FILE = 'initGoogleMap.js';
public function getJSInitFile()
{
// return the google map js file: initGoogleMap.js
return self::JS_INIT_FILE;
}
public function getJSJOFile()
{
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Service\GISManager;
use App\Service\GISManagerInterface;
class OpenStreet implements GISManagerInterface
{
const JS_INIT_FILE = 'initOpenStreetMap.js';
const JS_JO_FILE = 'joOpenStreetMap.js';
public function getJSInitFile()
{
// return the openstreet map js file: initOpenStreetMap.js
return self::JS_INIT_FILE;
}
public function getJSJOFile()
{
return self::JS_JO_FILE;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Service;
interface GISManagerInterface
{
// returns the actual JS file
public function getJSInitFile();
// return the JS file for JO map
public function getJSJOFile();
}

View file

@ -0,0 +1,632 @@
<?php
namespace App\Service\InvoiceGenerator;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\InvoiceStatus;
use App\Ramcar\CMBTradeInType;
use App\Ramcar\DiscountApply;
use App\Ramcar\CMBServiceType;
use App\Ramcar\FuelType;
use App\Entity\Invoice;
use App\Entity\InvoiceItem;
use App\Entity\Battery;
use App\Entity\Promo;
use App\Entity\User;
use App\Service\InvoiceGeneratorInterface;
use Doctrine\Common\Util\Debug;
class CMBInvoiceGenerator implements InvoiceGeneratorInterface
{
const TAX_RATE = 0.00;
const SERVICE_FEE = 300;
const RECHARGE_FEE = 300;
const TROUBLESHOOTING_FEE = 150;
const BATT_REPLACEMENT_FEE = 0;
const WARRANTY_FEE = 0;
const OTHER_SERVICES_FEE = 200;
const COOLANT_FEE = 1600;
const REFUEL_FEE_GAS = 260;
const REFUEL_FEE_DIESEL = 220;
private $security;
protected $em;
protected $validator;
// creates invoice based on the criteria sent
public function __construct(Security $security, EntityManagerInterface $em,
ValidatorInterface $validator)
{
$this->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;
}
}

View file

@ -1,8 +1,14 @@
<?php <?php
namespace App\Service; namespace App\Service\InvoiceGenerator;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Ramcar\InvoiceCriteria; use App\Ramcar\InvoiceCriteria;
use App\Ramcar\InvoiceStatus;
use App\Ramcar\TradeInType; use App\Ramcar\TradeInType;
use App\Ramcar\DiscountApply; use App\Ramcar\DiscountApply;
use App\Ramcar\ServiceType; use App\Ramcar\ServiceType;
@ -11,32 +17,195 @@ use App\Ramcar\FuelType;
use App\Entity\Invoice; use App\Entity\Invoice;
use App\Entity\InvoiceItem; use App\Entity\InvoiceItem;
use App\Entity\User; use App\Entity\User;
use App\Entity\Battery;
use App\Service\InvoiceGeneratorInterface;
use Doctrine\Common\Util\Debug; use Doctrine\Common\Util\Debug;
class InvoiceCreator class ResqInvoiceGenerator implements InvoiceGeneratorInterface
{ {
const VAT_RATE = 0.12; const TAX_RATE = 0.12;
const SERVICE_FEE = 300; const SERVICE_FEE = 300;
const RECHARGE_FEE = 300;
const TROUBLESHOOTING_FEE = 150;
const BATT_REPLACEMENT_FEE = 0;
const WARRANTY_FEE = 0;
const OTHER_SERVICES_FEE = 200;
const COOLANT_FEE = 1600;
const REFUEL_FEE_GAS = 260;
const REFUEL_FEE_DIESEL = 220;
private $security;
protected $em;
protected $validator;
// creates invoice based on the criteria sent // creates invoice based on the criteria sent
public function __construct() public function __construct(Security $security, EntityManagerInterface $em,
ValidatorInterface $validator)
{ {
$this->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 $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']; $size = $ti['size'];
$trade_in = $ti['trade_in']; $trade_in = $ti['trade_in'];
@ -57,6 +226,75 @@ class InvoiceCreator
return 0; 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) protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice)
{ {
// error_log('processing entries...'); // error_log('processing entries...');
@ -109,8 +347,8 @@ class InvoiceCreator
$qty = $con_data['qty']; $qty = $con_data['qty'];
$sell_price = $batt->getSellingPrice(); $sell_price = $batt->getSellingPrice();
$vat = $this->getVATAmount($sell_price); $vat = $this->getTaxAmount($sell_price);
// $vat_ex_price = $this->getVATExclusivePrice($sell_price); // $vat_ex_price = $this->getTaxExclusivePrice($sell_price);
$total['sell_price'] += $sell_price * $qty; $total['sell_price'] += $sell_price * $qty;
$total['vat'] += $vat * $qty; $total['vat'] += $vat * $qty;
@ -192,22 +430,22 @@ class InvoiceCreator
$invoice->setPromo($promo); $invoice->setPromo($promo);
} }
public function processJumpstart(&$total, $invoice) protected function processJumpstart(&$total, $invoice)
{ {
// add troubleshooting fee // add troubleshooting fee
$item = new InvoiceItem(); $item = new InvoiceItem();
$item->setInvoice($invoice) $item->setInvoice($invoice)
->setTitle('Troubleshooting fee') ->setTitle('Troubleshooting fee')
->setQuantity(1) ->setQuantity(1)
->setPrice(150.00); ->setPrice(self::TROUBLESHOOTING_FEE);
$invoice->addItem($item); $invoice->addItem($item);
$total['sell_price'] = 150.00; $total['sell_price'] = self::TROUBLESHOOTING_FEE;
$total['vat_ex_price'] = 150.00; $total['vat_ex_price'] = self::TROUBLESHOOTING_FEE;
$total['total_price'] = 150.00; $total['total_price'] = self::TROUBLESHOOTING_FEE;
} }
public function processJumpstartWarranty(&$total, $invoice) protected function processJumpstartWarranty(&$total, $invoice)
{ {
$item = new InvoiceItem(); $item = new InvoiceItem();
$item->setInvoice($invoice) $item->setInvoice($invoice)
@ -217,33 +455,33 @@ class InvoiceCreator
$invoice->addItem($item); $invoice->addItem($item);
} }
public function processRecharge(&$total, $invoice) protected function processRecharge(&$total, $invoice)
{ {
// add recharge fee // add recharge fee
$item = new InvoiceItem(); $item = new InvoiceItem();
$item->setInvoice($invoice) $item->setInvoice($invoice)
->setTitle('Recharge fee') ->setTitle('Recharge fee')
->setQuantity(1) ->setQuantity(1)
->setPrice(300.00); ->setPrice(self::RECHARGE_FEE);
$invoice->addItem($item); $invoice->addItem($item);
$total['sell_price'] = 300.00; $total['sell_price'] = self::RECHARGE_FEE;
$total['vat_ex_price'] = 300.00; $total['vat_ex_price'] = self::RECHARGE_FEE;
$total['total_price'] = 300.00; $total['total_price'] = self::RECHARGE_FEE;
} }
public function processReplacement(&$total, $invoice) protected function processReplacement(&$total, $invoice)
{ {
// add recharge fee // add recharge fee
$item = new InvoiceItem(); $item = new InvoiceItem();
$item->setInvoice($invoice) $item->setInvoice($invoice)
->setTitle('Battery replacement') ->setTitle('Battery replacement')
->setQuantity(1) ->setQuantity(1)
->setPrice(0.00); ->setPrice(self::BATT_REPLACEMENT_FEE);
$invoice->addItem($item); $invoice->addItem($item);
} }
public function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) protected function processWarranty(&$total, InvoiceCriteria $criteria, $invoice)
{ {
// error_log('processing warranty'); // error_log('processing warranty');
$entries = $criteria->getEntries(); $entries = $criteria->getEntries();
@ -254,25 +492,25 @@ class InvoiceCreator
$item->setInvoice($invoice) $item->setInvoice($invoice)
->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit') ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit')
->setQuantity(1) ->setQuantity(1)
->setPrice(0.00) ->setPrice(self::WARRANTY_FEE)
->setBattery($batt); ->setBattery($batt);
$invoice->addItem($item); $invoice->addItem($item);
} }
} }
public function processOtherServices(&$total, $invoice, $stype) protected function processOtherServices(&$total, $invoice, $stype)
{ {
$item = new InvoiceItem(); $item = new InvoiceItem();
$item->setInvoice($invoice) $item->setInvoice($invoice)
->setTitle('Service - ' . ServiceType::getName($stype)) ->setTitle('Service - ' . ServiceType::getName($stype))
->setQuantity(1) ->setQuantity(1)
->setPrice(200.00); ->setPrice(self::OTHER_SERVICES_FEE);
$invoice->addItem($item); $invoice->addItem($item);
$total['total_price'] = 200.00; $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 // free if they have a motolite battery
if ($cv->hasMotoliteBattery()) if ($cv->hasMotoliteBattery())
@ -295,20 +533,21 @@ class InvoiceCreator
$coolant->setInvoice($invoice) $coolant->setInvoice($invoice)
->setTitle('4L Coolant') ->setTitle('4L Coolant')
->setQuantity(1) ->setQuantity(1)
->setPrice(1600); ->setPrice(self::COOLANT_FEE);
$invoice->addItem($coolant); $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; $vat = $total_price - $vat_ex_price;
$total['total_price'] = $total_price; $total['total_price'] = $total_price;
$total['vat_ex_price'] = $vat_ex_price; $total['vat_ex_price'] = $vat_ex_price;
$total['vat'] = $vat; $total['vat'] = $vat;
} }
public function processTireRepair(&$total, $invoice, $cv) protected function processTireRepair(&$total, $invoice, $cv)
{ {
// free if they have a motolite battery // free if they have a motolite battery
if ($cv->hasMotoliteBattery()) if ($cv->hasMotoliteBattery())
@ -324,14 +563,14 @@ class InvoiceCreator
$invoice->addItem($item); $invoice->addItem($item);
$total_price = $fee; $total_price = $fee;
$vat_ex_price = $this->getVATExclusivePrice($total_price); $vat_ex_price = $this->getTaxExclusivePrice($total_price);
$vat = $total_price - $vat_ex_price; $vat = $total_price - $vat_ex_price;
$total['total_price'] = $total_price; $total['total_price'] = $total_price;
$total['vat_ex_price'] = $vat_ex_price; $total['vat_ex_price'] = $vat_ex_price;
$total['vat'] = $vat; $total['vat'] = $vat;
} }
public function processRefuel(&$total, $invoice, $cv) protected function processRefuel(&$total, $invoice, $cv)
{ {
// free if they have a motolite battery // free if they have a motolite battery
if ($cv->hasMotoliteBattery()) if ($cv->hasMotoliteBattery())
@ -351,8 +590,8 @@ class InvoiceCreator
$total_price = $fee; $total_price = $fee;
// $total['total_price'] = 200.00; // $total['total_price'] = 200.00;
$gas_price = 260; $gas_price = self::REFUEL_FEE_GAS;
$diesel_price = 220; $diesel_price = self::REFUEL_FEE_DIESEL;
$fuel = new InvoiceItem(); $fuel = new InvoiceItem();
error_log('fuel type - ' . $ftype); error_log('fuel type - ' . $ftype);
@ -385,84 +624,11 @@ class InvoiceCreator
break; break;
} }
$vat_ex_price = $this->getVATExclusivePrice($total_price); $vat_ex_price = $this->getTaxExclusivePrice($total_price);
$vat = $total_price - $vat_ex_price; $vat = $total_price - $vat_ex_price;
$total['total_price'] = $total_price; $total['total_price'] = $total_price;
$total['vat_ex_price'] = $vat_ex_price; $total['vat_ex_price'] = $vat_ex_price;
$total['vat'] = $vat; $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;
}
} }

View file

@ -0,0 +1,18 @@
<?php
namespace App\Service;
use App\Entity\Invoice;
use App\Entity\JobOrder;
use App\Ramcar\InvoiceCriteria;
interface InvoiceGeneratorInterface
{
// generate invoice using a criteria
public function generateInvoice(InvoiceCriteria $criteria);
// generate invoice criteria
public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, array &$error_array);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,93 @@
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\Request;
use App\Service\MQTTClient;
use App\Service\APNSClient;
use App\Service\MapTools;
interface JobOrderHandlerInterface
{
// TODO: event sending has been moved to rider assignment handler for cmb. Might need
// to consider resq implementation for event sending for the other methods.
// get job order rows
public function getRows(Request $req, string $tier);
// get job orders
public function getJobOrders(Request $req);
// generate job order
public function generateJobOrder(Request $req, int $id);
// generate one step job order
public function generateOneStepJobOrder(Request $req, int $id);
// dispatch job order
public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient);
// assign job order
public function assignJobOrder(Request $req, int $id, MQTTCLient $mclient, APNSClient $aclient);
// fulfill job order
public function fulfillJobOrder(Request $req, int $id, MQTTClient $mclient);
// cancel job order
public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient);
// set hub for job order
public function setHub(Request $req, int $id, MQTTClient $mclient);
// reject hub for job order
public function rejectHub(Request $req, int $id);
// set rider for job order
public function setRider(Request $req, int $id, MQTTClient $mclient);
// unlock processor
public function unlockProcessor(int $id);
// unlock assignor
public function unlockAssignor(int $id);
// initialize incoming job order form
public function initializeIncomingForm();
// initialize open edit job order form
public function initializeOpenEditForm(int $id);
// initialize incoming vehicle form
public function initializeIncomingVehicleForm(int $cvid);
// initialize all job orders form for a specific job order id
public function initializeAllForm(int $id);
// initialize dispatch/processing job order form
public function initializeProcessingForm(int $id, MapTools $map_tools);
// initialize assign job order form
public function initializeAssignForm(int $id);
// initialize fulflll job order form
public function initializeFulfillmentForm(int $id);
// initialize hub form
public function initializeHubForm(int $id, MapTools $map_tools);
// initialize rider form
public function initializeRiderForm(int $id);
// initialize one step form
public function initializeOneStepForm();
// initialize one step edit form
public function initializeOneStepEditForm(int $id, MapTools $map_tools);
// generate pdf form for job order
public function generatePDFForm(Request $req, int $id, string $proj_path);
// get template to display
public function getTwigTemplate(string $id);
}

View file

@ -0,0 +1,98 @@
<?php
namespace App\Service\RiderAssignmentHandler;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RiderAssignmentHandlerInterface;
use App\Service\MQTTClient;
use App\Service\APNSClient;
use App\Entity\JobOrder;
use App\Entity\Rider;
use App\Ramcar\JOStatus;
class CMBRiderAssignmentHandler implements RiderAssignmentHandlerInterface
{
protected $em;
protected $aclient;
protected $mclient;
public function __construct(EntityManagerInterface $em, MQTTClient $mclient,
APNSClient $aclient)
{
$this->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.");
}
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Service;
use App\Entity\JobOrder;
use App\Entity\Rider;
interface RiderAssignmentHandlerInterface
{
// assign job order to rider
public function assignJobOrder(JobOrder $obj, Rider $rider);
// complete job order
public function fulfillJobOrder(JobOrder $obj, string $image_url, Rider $rider);
}

View file

@ -38,15 +38,18 @@ class RiderTracker
$long = $this->redis->hget($key, 'longitude'); $long = $this->redis->hget($key, 'longitude');
$lat = $this->redis->hget($key, 'latitude'); $lat = $this->redis->hget($key, 'latitude');
$coordinates = new Point($long, $lat); return new Point($long, $lat);
}
else
{
$rider = $this->em->getRepository(Rider::class)->find($rider_id);
$coordinates = $rider->getHub()->getCoordinates();
} }
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();
} }
} }

View file

@ -35,12 +35,16 @@
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<!--begin::Extra Styles --> <!--begin::Extra Styles -->
{% block stylesheets %}{% endblock %} {% block stylesheets %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
{% endblock %}
<!--end::Extra Styles --> <!--end::Extra Styles -->
</head> </head>
<!-- end::Head --> <!-- end::Head -->
<!-- end::Body --> <!-- end::Body -->
<body class="m-page--fluid m--skin- m-content--skin-light2 m-header--fixed m-header--fixed-mobile m-aside-left--enabled m-aside-left--skin-dark m-aside-left--offcanvas m-footer--push m-aside--offcanvas-default"> <body class="m-page--fluid m--skin- m-content--skin-light2 m-header--fixed m-header--fixed-mobile m-aside-left--enabled m-aside-left--skin-dark m-aside-left--offcanvas m-aside--offcanvas-default">
<!-- begin:: Page --> <!-- begin:: Page -->
<div class="m-grid m-grid--hor m-grid--root m-page"> <div class="m-grid m-grid--hor m-grid--root m-page">
<!-- BEGIN: Header --> <!-- BEGIN: Header -->
@ -695,30 +699,6 @@
</div> </div>
<!-- end:: Body --> <!-- end:: Body -->
<!-- begin::Footer --> <!-- begin::Footer -->
<footer class="m-grid__item m-footer">
<div class="m-container m-container--fluid m-container--full-height m-page__container">
<div class="m-stack m-stack--flex-tablet-and-mobile m-stack--ver m-stack--desktop">
<div class="m-stack__item m-stack__item--left m-stack__item--middle m-stack__item--last">
<span class="m-footer__copyright">
{{ "now"|date("Y") }} &copy; {% trans %}copyright{% endtrans %}
</span>
</div>
<div class="m-stack__item m-stack__item--right m-stack__item--middle m-stack__item--first">
<ul class="m-footer__nav m-nav m-nav--inline m--pull-right">
<!--
<li class="m-nav__item">
<a href="#" class="m-nav__link">
<span class="m-nav__link-text">
About
</span>
</a>
</li>
-->
</ul>
</div>
</div>
</div>
</footer>
<!-- end::Footer --> <!-- end::Footer -->
</div> </div>
<!-- end:: Page --> <!-- end:: Page -->

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -96,7 +96,10 @@ initMap();
function initMap() { function initMap() {
var map = new google.maps.Map(document.getElementById('m_gmap'), 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', mapTypeId: 'roadmap',
zoom: 13 zoom: 13
}); });

View file

@ -1,32 +1,30 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block body %} {% block stylesheets %}
<!-- BEGIN: Subheader --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Dashboard
</h3>
</div>
<div>
<span class="m-subheader__daterange" id="m_dashboard_daterangepicker">
<span class="m-subheader__daterange-label">
<span class="m-subheader__daterange-title"></span>
<span class="m-subheader__daterange-date m--font-brand"></span>
</span>
<a href="#" class="btn btn-sm btn-brand m-btn m-btn--icon m-btn--icon-only m-btn--custom m-btn--pill">
<i class="la la-angle-down"></i>
</a>
</span>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<!--End::Section-->
<!--Begin::Section-->
<!--End::Section-->
</div>
{% endblock %} {% endblock %}
{% block body %}
<div id="dashboard_map" style="height:100%;"></div>
<!-- BEGIN: Subheader -->
<!-- END: Subheader -->
{% endblock %}
{% block scripts %}
{{ include('map/' ~ map_js_file) }}
<script>
{% if 'OpenStreet' in map_js_file %}
initMap();
{% endif %}
function initMap() {
var default_lat = {% trans %}default_lat{% endtrans %};
var default_lng = {% trans %}default_long{% endtrans %};
var map = mapCreate('dashboard_map', default_lat, default_lng, 'road', 13);
}
</script>
{% endblock %}

View file

@ -164,12 +164,34 @@
{% block scripts %} {% block scripts %}
<script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key }}" type="text/javascript"></script> <script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key }}&libraries=places" type="text/javascript"></script>
<script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script> <script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script>
<script> <script>
// BEGIN google maps stuff // BEGIN google maps stuff
// location search autocomplete
var input = document.getElementById('m_gmap_address');
var autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.setComponentRestrictions({'country': ['{% trans %}default_region{% endtrans %}']});
autocomplete.addListener('place_changed', function() {
var place = autocomplete.getPlace();
if (!place.geometry) {
return;
}
var message = {
'action': 'map.search',
'params': {
'lat': place.geometry.location.lat(),
'lng': place.geometry.location.lng()
}
};
console.log(message);
});
function selectPoint(map, latlng) { function selectPoint(map, latlng) {
var lat = latlng.lat(); var lat = latlng.lat();
var lng = latlng.lng(); var lng = latlng.lng();
@ -189,8 +211,8 @@ function selectPoint(map, latlng) {
var map = new GMaps({ var map = new GMaps({
div: '#m_gmap', div: '#m_gmap',
lat: 14.6091, lat: {% trans %}default_lat{% endtrans %},
lng: 121.0223, lng: {% trans %}default_long{% endtrans %},
click: function(e) { click: function(e) {
// handle click in map // handle click in map
selectPoint(map, e.latLng); selectPoint(map, e.latLng);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -89,7 +89,7 @@
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_phone_mobile">Mobile Phone</label> <label data-field="customer_phone_mobile">Mobile Phone</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_mobile" id="customer-phone-mobile" class="form-control m-input" value="{{ obj.getCustomer.getPhoneMobile|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_phone_mobile" id="customer-phone-mobile" class="form-control m-input" value="{{ obj.getCustomer.getPhoneMobile|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_mobile"></div> <div class="form-control-feedback hide" data-field="customer_phone_mobile"></div>
</div> </div>
@ -97,7 +97,7 @@
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_phone_landline">Landline</label> <label data-field="customer_phone_landline">Landline</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_landline" id="customer-phone-landline" class="form-control m-input" value="{{ obj.getCustomer.getPhoneLandline|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_phone_landline" id="customer-phone-landline" class="form-control m-input" value="{{ obj.getCustomer.getPhoneLandline|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_landline"></div> <div class="form-control-feedback hide" data-field="customer_phone_landline"></div>
</div> </div>
@ -221,7 +221,7 @@
<div class="form-group m-form__group row"> <div class="form-group m-form__group row">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="col-lg-12 form-group-inner"> <div class="col-lg-12 form-group-inner">
<label data-field="delivery_instructions">Delivery Instructions</label> <label data-field="delivery_instructions">{% trans %}delivery_instructions_label{% endtrans %}</label>
<textarea name="delivery_instructions" class="form-control m-input" rows="4">{{ obj.getDeliveryInstructions }}</textarea> <textarea name="delivery_instructions" class="form-control m-input" rows="4">{{ obj.getDeliveryInstructions }}</textarea>
</div> </div>
</div> </div>
@ -651,8 +651,8 @@ $(function() {
var map = new GMaps({ var map = new GMaps({
div: '#m_gmap', div: '#m_gmap',
lat: 14.6091, lat: {% trans %}default_lat{% endtrans %},
lng: 121.0223, lng: {% trans %}default_long{% endtrans %},
click: function(e) { click: function(e) {
// handle click in map // handle click in map
selectPoint(map, e.latLng); selectPoint(map, e.latLng);

View file

@ -108,7 +108,7 @@
sortable: false, sortable: false,
overflow: 'visible', overflow: 'visible',
template: function (row, index, datatable) { template: function (row, index, datatable) {
var actions = '<a href="' + row.meta.update_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="View / Edit"><i class="la la-edit"></i></a>'; var actions = '<a href="' + row.meta.update_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="View / Edit"><i class="la la-edit"></i></a>' + '<a href="' + row.meta.onestep_edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" title="One Step Edit"><i class="la la-edit"></i></a>';
return actions; return actions;
}, },

View file

@ -122,6 +122,9 @@
{% if is_granted('jo_open.edit') %} {% if is_granted('jo_open.edit') %}
actions += '<a href="' + row.meta.edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="Edit"><i class="fa fa-file"></i></a>'; actions += '<a href="' + row.meta.edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="Edit"><i class="fa fa-file"></i></a>';
{% endif %} {% endif %}
{% if is_granted('jo_onestep.edit') %}
actions += '<a href="' + row.meta.onestep_edit_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-reassign-hub" title="One Step Edit"><i class="fa fa-file"></i></a>';
{% endif %}
return actions; return actions;
}, },

View file

@ -0,0 +1,36 @@
<script type='text/javascript' src='https://www.bing.com/api/maps/mapcontrol?callback=initMap&key={{ bingmaps_api_key|raw }}' async defer></script>
<script>
function mapCreate(div_id, center_lat, center_lng, map_type, zoom) {
var map_type_id = Microsoft.Maps.MapTypeId.road;
switch (map_type) {
case 'road':
map_type_id = Microsoft.Maps.MapTypeId.road;
break;
case 'aerial':
map_type_id = Microsoft.Maps.MapTypeId.aerial;
break;
case 'dark':
map_type_id = Microsoft.Maps.MapTypeId.canvasDark;
break;
case 'light':
map_type_id = Microsoft.Maps.MapTypeId.canvasLight;
break;
case 'grayscale':
map_type_id = Microsoft.Maps.MapTypeId.grayscale;
break;
default:
map_type_id = Microsoft.Maps.MapTypeId.road;
break;
}
var map = new Microsoft.Maps.Map('#' + div_id, {
center: new Microsoft.Maps.Location(center_lat, center_lng),
mapTypeId: map_type_id,
zoom: zoom
});
return map;
}
</script>

View file

@ -0,0 +1,22 @@
<script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key|raw }}&callback=initMap" type="text/javascript" async defer></script>
<script>
function mapCreate(div_id, center_lat, center_lng, map_type, zoom) {
var map_type_id = 'roadmap';
switch (map_type) {
case 'road':
map_type_id = 'roadmap';
break;
default:
map_type_id = 'roadmap';
break;
}
var map = new google.maps.Map(document.getElementById(div_id),
{
center: {lat: center_lat, lng: center_lng},
mapTypeId: map_type_id,
zoom: zoom
});
}
</script>

View file

@ -0,0 +1,123 @@
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin="">
</script>
<script>
function mapCreate(div_id, center_lat, center_lng, map_type, zoom) {
var map = L.map(div_id).setView(
[center_lat, center_lng],
zoom
);
// add tile layer
// TODO: put access token in .env
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
accessToken: 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw'
}).addTo(map);
// layer groups
// .addTo(map) --> this will display your riders by default
var lg_avail_rider = L.layerGroup().addTo(map);
var lg_jo_rider = L.layerGroup().addTo(map);
var lg_cust = L.layerGroup().addTo(map);
// this little snippet will not display your riders by default.
// Instead, a toggle button will display in the map, with a checkbox with text Riders.
// Check that to display the riders
//var ridersLayerGroup = L.layerGroup();
// create icons
var icon_rider_active_jo = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
var icon_rider_available = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
var icon_customer = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
$.ajax({
url: '{{ path('rider_locations') }}',
}).done(function(response) {
// clear all markers
lg_avail_rider.clearLayers();
lg_jo_rider.clearLayers();
lg_cust.clearLayers();
// get riders and mark
var riders = response.riders;
$.each(riders, function(rider_id, rider_data) {
// rider location
var point = rider_data['loc'];
var lat = point[0];
var long = point[1];
// customer location
var cloc = rider_data['cust_loc'];
var clat = cloc[0];
var clong = cloc[1];
// rider popup content
rider_popup = '<strong>' + rider_data['label'] + '</strong>';
// create rider markers
if (rider_data['has_jo']) {
var jo_data = rider_data['jo'];
rider_popup += '<br>';
rider_popup += '<a href="' + jo_data['url'] + '">Job Order #' + jo_data['id'] + '</a><br>';
rider_popup += jo_data['stype'] + '<br>';
rider_popup += jo_data['status'] + '<br><br>';
rider_popup += jo_data['cname'] + '<br>';
rider_popup += jo_data['plate'];
var cust_popup = '<strong>' + jo_data['cname'] + '</strong><br>';
cust_popup += jo_data['plate'] + '<br>';
cust_popup += '<a href="' + jo_data['url'] + '">Job Order #' + jo_data['id'] + '</a><br>';
cust_popup += jo_data['stype'] + '<br>';
cust_popup += jo_data['status'];
var rider_marker = L.marker([lat, long], { icon: icon_rider_active_jo }).bindPopup(rider_popup);
var cust_marker = L.marker([clat, clong], { icon: icon_customer }).bindPopup(cust_popup);
lg_cust.addLayer(cust_marker);
lg_jo_rider.addLayer(rider_marker);
} else {
var rider_marker = L.marker([lat, long], { icon: icon_rider_available }).bindPopup(rider_popup);
lg_avail_rider.addLayer(rider_marker);
}
});
});
// base layer
var baseMaps = {
'Streets': streets
};
// overlay layer
var overlayMaps = {
'Available Riders' : lg_avail_rider,
'JO Riders' : lg_jo_rider,
'Customers' : lg_cust
}
L.control.layers(baseMaps, overlayMaps).addTo(map);
return map;
}
</script>

View file

@ -0,0 +1,26 @@
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin="">
</script>
<script>
function mapCreate(div_id, center_lat, center_lng, map_type, zoom) {
var map = L.map(div_id).setView(
[center_lat, center_lng],
zoom
);
//TODO: put access token in .env
// add tile layer
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
accessToken: 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw'
}).addTo(map);
return map;
}
</script>

View file

@ -143,12 +143,34 @@
{% block scripts %} {% block scripts %}
<script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key }}" type="text/javascript"></script> <script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key }}&libraries=places" type="text/javascript"></script>
<script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script> <script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script>
<script> <script>
// BEGIN google maps stuff // BEGIN google maps stuff
// location search autocomplete
var input = document.getElementById('m_gmap_address');
var autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.setComponentRestrictions({'country': ['{% trans %}default_region{% endtrans %}']});
autocomplete.addListener('place_changed', function() {
var place = autocomplete.getPlace();
if (!place.geometry) {
return;
}
var message = {
'action': 'map.search',
'params': {
'lat': place.geometry.location.lat(),
'lng': place.geometry.location.lng()
}
};
console.log(message);
});
function selectPoint(map, latlng) { function selectPoint(map, latlng) {
var lat = latlng.lat(); var lat = latlng.lat();
var lng = latlng.lng(); var lng = latlng.lng();
@ -168,8 +190,8 @@ function selectPoint(map, latlng) {
var map = new GMaps({ var map = new GMaps({
div: '#m_gmap', div: '#m_gmap',
lat: 14.6091, lat: {% trans %}default_lat{% endtrans %},
lng: 121.0223, lng: {% trans %}default_long{% endtrans %},
click: function(e) { click: function(e) {
// handle click in map // handle click in map
selectPoint(map, e.latLng); selectPoint(map, e.latLng);

View file

@ -174,12 +174,34 @@
{% block scripts %} {% block scripts %}
<script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key }}" type="text/javascript"></script> <script src="//maps.google.com/maps/api/js?key={{ gmaps_api_key }}&libraries=places" type="text/javascript"></script>
<script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script> <script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script>
<script> <script>
// BEGIN google maps stuff // BEGIN google maps stuff
// location search autocomplete
var input = document.getElementById('m_gmap_address');
var autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.setComponentRestrictions({'country': ['{% trans %}default_region{% endtrans %}']});
autocomplete.addListener('place_changed', function() {
var place = autocomplete.getPlace();
if (!place.geometry) {
return;
}
var message = {
'action': 'map.search',
'params': {
'lat': place.geometry.location.lat(),
'lng': place.geometry.location.lng()
}
};
console.log(message);
});
function selectPoint(map, latlng) { function selectPoint(map, latlng) {
var lat = latlng.lat(); var lat = latlng.lat();
var lng = latlng.lng(); var lng = latlng.lng();
@ -199,8 +221,8 @@ function selectPoint(map, latlng) {
var map = new GMaps({ var map = new GMaps({
div: '#m_gmap', div: '#m_gmap',
lat: 14.6091, lat: {% trans %}default_lat{% endtrans %},
lng: 121.0223, lng: {% trans %}default_long{% endtrans %},
click: function(e) { click: function(e) {
// handle click in map // handle click in map
selectPoint(map, e.latLng); selectPoint(map, e.latLng);

View file

@ -53,7 +53,7 @@
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_phone_mobile">Mobile Phone</label> <label data-field="customer_phone_mobile">Mobile Phone</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_mobile" id="customer-phone-mobile" class="form-control m-input" value="{{ data.getCustMobile|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_phone_mobile" id="customer-phone-mobile" class="form-control m-input" value="{{ data.getCustMobile|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_mobile"></div> <div class="form-control-feedback hide" data-field="customer_phone_mobile"></div>
</div> </div>
@ -61,7 +61,7 @@
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_phone_landline">Landline</label> <label data-field="customer_phone_landline">Landline</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_landline" id="customer-phone-landline" class="form-control m-input" value="{{ data.getCustLandline|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_phone_landline" id="customer-phone-landline" class="form-control m-input" value="{{ data.getCustLandline|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_phone_landline"></div> <div class="form-control-feedback hide" data-field="customer_phone_landline"></div>
</div> </div>
@ -71,7 +71,7 @@
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_contact">Contact</label> <label data-field="customer_contact">Contact</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_contact" id="customer-contact" class="form-control m-input" value="{{ data.getCustContact|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_contact" id="customer-contact" class="form-control m-input" value="{{ data.getCustContact|default('') }}" data-vehicle-field="1" disabled>
<div class="form-control-feedback hide" data-field="customer_contact"></div> <div class="form-control-feedback hide" data-field="customer_contact"></div>
</div> </div>

View file

@ -71,7 +71,7 @@
<div class="col-lg-4"> <div class="col-lg-4">
<label data-field="contact_num">Contact Number</label> <label data-field="contact_num">Contact Number</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="contact_num" class="form-control m-input" value="{{ customer and mode == 'create' and customer.getPhoneMobile is not empty ? customer.getPhoneMobile : obj.getContactNumber }}"{{ customer ? ' disabled' }}> <input type="text" name="contact_num" class="form-control m-input" value="{{ customer and mode == 'create' and customer.getPhoneMobile is not empty ? customer.getPhoneMobile : obj.getContactNumber }}"{{ customer ? ' disabled' }}>
<div class="form-control-feedback hide" data-field="contact_num"></div> <div class="form-control-feedback hide" data-field="contact_num"></div>
</div> </div>

View file

@ -86,7 +86,7 @@
Mobile Phone Mobile Phone
</label> </label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">+63</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="mobile_number" class="form-control m-input" value="{{ obj.getMobileNumber|default('') }}" data-name="mobile_number"> <input type="text" name="mobile_number" class="form-control m-input" value="{{ obj.getMobileNumber|default('') }}" data-name="mobile_number">
<div class="form-control-feedback hide" data-field="mobile_number"></div> <div class="form-control-feedback hide" data-field="mobile_number"></div>
</div> </div>

View file

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

View file

@ -10,11 +10,18 @@ battery_size_tradein_premium: Trade-in Premium
battery_size_tradein_other: Trade-in Other battery_size_tradein_other: Trade-in Other
add_cust_vehicle_battery_info: This vehicle is using a Motolite battery add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Motolite Res-Q Job Order jo_title_pdf: Motolite Res-Q Job Order
country_code_prefix: '+63'
delivery_instructions_label: Delivery Instructions
# # images # images
image_logo_login: /assets/images/logo-resq.png image_logo_login: /assets/images/logo-resq.png
icon_login: /assets/demo/default/media/img/logo/favicon.ico icon_login: /assets/demo/default/media/img/logo/favicon.ico
icon_base_32x32: /assets/images/favicon/favicon-32x32.png icon_base_32x32: /assets/images/favicon/favicon-32x32.png
icon_base_16x16: /assets/images/favicon/favicon-16x16.png icon_base_16x16: /assets/images/favicon/favicon-16x16.png
image_dashboard: /assets/images/logo-motolite.png image_dashboard: /assets/images/logo-motolite.png
image_jo_pdf: /public/assets/images/logo-resq.png image_jo_pdf: /public/assets/images/logo-resq.png
# default point for maps
default_lat: 14.6091
default_long: 121.0223
default_region: ph

View file

@ -10,6 +10,8 @@ battery_size_tradein_premium: Trade-in Premium
battery_size_tradein_other: Trade-in Other battery_size_tradein_other: Trade-in Other
add_cust_vehicle_battery_info: This vehicle is using a Motolite battery add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Motolite Res-Q Job Order jo_title_pdf: Motolite Res-Q Job Order
country_code_prefix: '+63'
delivery_instructions_label: Delivery Instructions
# images # images
image_logo_login: /assets/images/logo-resq.png image_logo_login: /assets/images/logo-resq.png
@ -18,3 +20,8 @@ icon_base_32x32: /assets/images/favicon/favicon-32x32.png
icon_base_16x16: /assets/images/favicon/favicon-16x16.png icon_base_16x16: /assets/images/favicon/favicon-16x16.png
image_dashboard: /assets/images/logo-motolite.png image_dashboard: /assets/images/logo-motolite.png
image_jo_pdf: /public/assets/images/logo-resq.png image_jo_pdf: /public/assets/images/logo-resq.png
# default point for maps
default_lat: 14.6091
default_long: 121.0223
default_region: ph