resq/src/Controller/JobOrderController.php
2018-06-18 15:21:09 +08:00

2408 lines
83 KiB
PHP

<?php
namespace App\Controller;
use App\Ramcar\BaseController;
use App\Ramcar\ServiceType;
use App\Ramcar\JOStatus;
use App\Ramcar\WarrantyClass;
use App\Ramcar\DiscountApply;
use App\Ramcar\TradeInType;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\InvoiceStatus;
use App\Ramcar\ModeOfPayment;
use App\Ramcar\TransactionOrigin;
use App\Ramcar\JOEventType;
use App\Entity\JobOrder;
use App\Entity\BatteryManufacturer;
use App\Entity\Customer;
use App\Entity\CustomerVehicle;
//use App\Entity\Outlet;
use App\Entity\Hub;
use App\Entity\Promo;
use App\Entity\Rider;
use App\Entity\Battery;
use App\Entity\JOEvent;
use App\Service\InvoiceCreator;
use App\Service\MapTools;
use App\Service\HubCounter;
use App\Service\MQTTClient;
use Doctrine\ORM\Query;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\PessimisticLockException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use Mosquitto\Client as MosquittoClient;
use DateTime;
use DateInterval;
use FPDF;
class JobOrderController extends BaseController
{
public function getJobOrders(Request $req)
{
$this->denyAccessUnlessGranted('jo_in.list', null, 'No access.');
// get search term
$term = $req->query->get('search');
// get querybuilder
$qb = $this->getDoctrine()
->getRepository(JobOrder::class)
->createQueryBuilder('q');
// build expression now since we're reusing it
$jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')'));
// count total records
$tquery = $qb->select('COUNT(q)')
->join('q.customer', 'c')
->join('q.cus_vehicle', 'v');
// add filters to count query
if (!empty($term)) {
$tquery->where($jo_label . ' LIKE :filter')
->setParameter('filter', '%' . $term . '%');
}
$total = $tquery->getQuery()
->getSingleScalarResult();
// pagination vars
$page = $req->query->get('page') ?? 1;
$perpage = 20;
$offset = ($page - 1) * $perpage;
$pages = ceil($total / $perpage);
$has_more_pages = $page < $pages ? true : false;
// build main query
$query = $qb->select('q')
->addSelect($jo_label . ' as jo_label')
->addSelect('c.first_name as cust_first_name')
->addSelect('c.last_name as cust_last_name')
->addSelect('v.plate_number as vehicle_plate_number');
// add filters if needed
if (!empty($term)) {
$query->where($jo_label . ' LIKE :filter')
->setParameter('filter', '%' . $term . '%');
}
// get rows
$obj_rows = $query->orderBy('q.id', 'asc')
->setFirstResult($offset)
->setMaxResults($perpage)
->getQuery()
->getResult();
// build job order array
$job_orders = [];
foreach ($obj_rows as $jo) {
$service_type = ServiceType::getName($jo[0]->getServiceType());
$job_orders[] = [
'id' => $jo[0]->getID(),
'text' => $jo['jo_label'] . ' - ' . $service_type
];
}
// response
return $this->json([
'success' => true,
'results' => $job_orders,
'pagination' => [
'more' => $has_more_pages
]
]);
}
protected function fillDropdownParameters(&$params)
{
$em = $this->getDoctrine()->getManager();
// db loaded
$params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll();
// $params['customers'] = $em->getRepository(Customer::class)->findAll();
$params['promos'] = $em->getRepository(Promo::class)->findAll();
// name values
$params['service_types'] = ServiceType::getCollection();
$params['warranty_classes'] = WarrantyClass::getCollection();
$params['modes_of_payment'] = ModeOfPayment::getCollection();
$params['statuses'] = JOStatus::getCollection();
$params['discount_apply'] = DiscountApply::getCollection();
$params['trade_in_types'] = TradeInType::getCollection();
$params['sources'] = TransactionOrigin::getCollection();
}
protected function initFormTags(&$params)
{
// default to editing, as we have more forms editing than creating
$params['ftags'] = [
'title' => 'Job Order Form',
'vehicle_dropdown' => false,
'invoice_edit' => false,
'set_map_coordinate' => true,
'preset_vehicle' => false,
'ticket_table' => true,
'cancel_button' => true,
];
}
protected function fillFormTags(&$params)
{
$this->initFormTags($params);
switch ($params['mode'])
{
case 'create':
$params['ftags']['vehicle_dropdown'] = true;
$params['ftags']['set_map_coordinate'] = false;
$params['ftags']['invoice_edit'] = true;
$params['ftags']['ticket_table'] = false;
$params['ftags']['cancel_button'] = false;
break;
case 'create_vehicle':
$params['ftags']['set_map_coordinate'] = false;
$params['ftags']['invoice_edit'] = true;
$params['ftags']['preset_vehicle'] = true;
$params['ftags']['ticket_table'] = false;
$params['ftags']['cancel_button'] = false;
break;
case 'open_edit':
$params['ftags']['invoice_edit'] = true;
$params['ftags']['preset_vehicle'] = true;
break;
}
}
public function incomingForm()
{
$this->denyAccessUnlessGranted('jo_in.list', null, 'No access.');
$params = $this->initParameters('jo_in');
$params['obj'] = new JobOrder();
$params['mode'] = 'create';
$params['submit_url'] = $this->generateUrl('jo_in_submit');
$params['return_url'] = $this->generateUrl('jo_in');
$em = $this->getDoctrine()->getManager();
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
// response
return $this->render('job-order/form.html.twig', $params);
}
public function openEditForm($id)
{
$this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.');
$em = $this->getDoctrine()->getManager();
$jo = $em->getRepository(JobOrder::class)->find($id);
$params = $this->initParameters('jo_in');
$params['obj'] = $jo;
$params['mode'] = 'open_edit';
$params['submit_url'] = $this->generateUrl('jo_open_edit_submit', ['id' => $id]);
$params['return_url'] = $this->generateUrl('jo_open');
$params['cvid'] = $jo->getCustomerVehicle()->getID();
$params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID();
$em = $this->getDoctrine()->getManager();
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
// response
return $this->render('job-order/form.html.twig', $params);
}
public function openEditSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic, $id)
{
$this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.');
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
$user = $this->getUser();
// initialize error list
$error_array = [];
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
$stype = $req->request->get('service_type');
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setServiceType($stype)
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'));
// did they change invoice?
$invoice_items = $req->request->get('invoice_items');
if (!empty($invoice_items))
{
// instantiate invoice criteria
$criteria = new InvoiceCriteria();
$criteria->setServiceType($stype)
->setCustomerVehicle($obj->getCustomerVehicle());
$ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo'));
if (!$ierror)
$ierror = $this->invoiceBatteries($em, $criteria, $invoice_items);
if ($ierror)
{
$error_array['invoice'] = $ierror;
}
else
{
// generate the invoice
$iobj = $ic->processCriteria($criteria);
$iobj->setStatus(InvoiceStatus::DRAFT)
->setCreatedBy($this->getUser());
// validate
$ierrors = $validator->validate($iobj);
// add errors to list
foreach ($ierrors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// remove previous invoice
$old_invoice = $obj->getInvoice();
$em->remove($old_invoice);
$em->flush();
// add invoice to JO
$obj->setInvoice($iobj);
// persist invoice
$em->persist($iobj);
}
}
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::OPEN_EDIT)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
public function incomingVehicleForm($cvid)
{
$this->denyAccessUnlessGranted('jo_in.list', null, 'No access.');
$params = $this->initParameters('jo_in');
$params['mode'] = 'create_vehicle';
$params['submit_url'] = $this->generateUrl('jo_in_submit');
$params['return_url'] = $this->generateUrl('jo_in');
$params['cvid'] = $cvid;
$em = $this->getDoctrine()->getManager();
// get customer vehicle
$cv = $em->getRepository(CustomerVehicle::class)->find($cvid);
$params['vid'] = $cv->getVehicle()->getID();
// make sure this customer vehicle exists
if (empty($cv))
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not exist');
}
$jo = new JobOrder();
$jo->setCustomerVehicle($cv)
->setCustomer($cv->getCustomer());
$params['obj'] = $jo;
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
// response
return $this->render('job-order/form.html.twig', $params);
}
public function incomingSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic)
{
$this->denyAccessUnlessGranted('jo_in.list', null, 'No access.');
// initialize error list
$error_array = [];
// create new row
$em = $this->getDoctrine()->getManager();
$obj = new JobOrder();
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if customer vehicle is set
if (empty($req->request->get('customer_vehicle'))) {
$error_array['customer_vehicle'] = 'No vehicle selected.';
} else {
// get customer vehicle
$cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle'));
if (empty($cust_vehicle)) {
$error_array['customer_vehicle'] = 'Invalid vehicle specified.';
}
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
$stype = $req->request->get('service_type');
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setCreatedBy($this->getUser())
->setServiceType($stype)
->setWarrantyClass($req->request->get('warranty_class'))
->setCustomer($cust_vehicle->getCustomer())
->setCustomerVehicle($cust_vehicle)
->setSource($req->request->get('source'))
->setStatus(JOStatus::PENDING)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setORName($req->request->get('or_name'))
->setPromoDetail($req->request->get('promo_detail'))
->setModeOfPayment($req->request->get('mode_of_payment'))
->setLandmark($req->request->get('landmark'));
// check if reference JO is set and validate
if (!empty($req->request->get('ref_jo'))) {
// get reference JO
$ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo'));
if (empty($ref_jo)) {
$error_array['ref_jo'] = 'Invalid reference job order specified.';
} else {
$obj->setReferenceJO($ref_jo);
}
}
// instantiate invoice criteria
$criteria = new InvoiceCriteria();
$criteria->setServiceType($stype)
->setCustomerVehicle($cust_vehicle);
$ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo'));
$invoice_items = $req->request->get('invoice_items');
if (!$ierror && !empty($invoice_items))
$ierror = $this->invoiceBatteries($em, $criteria, $invoice_items);
if ($ierror)
{
$error_array['invoice'] = $ierror;
}
else
{
// generate the invoice
$iobj = $ic->processCriteria($criteria);
$iobj->setStatus(InvoiceStatus::DRAFT)
->setCreatedBy($this->getUser());
// validate
$ierrors = $validator->validate($iobj);
// add errors to list
foreach ($ierrors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// add invoice to JO
$obj->setInvoice($iobj);
// save
$em->persist($iobj);
}
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// validated! save the entity
$em->persist($obj);
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CREATE)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
protected function checkTier($tier)
{
// check specified tier
switch ($tier) {
case 'proc':
$tier_key = 'jo_proc';
$tier_name = 'Dispatch';
$rows_route = 'jo_proc_rows';
$edit_route = 'jo_proc_form';
$unlock_route = 'jo_proc_unlock';
$jo_status = JOStatus::PENDING;
break;
case 'assign':
$tier_key = 'jo_assign';
$tier_name = 'Assigning';
$rows_route = 'jo_assign_rows';
$edit_route = 'jo_assign_form';
$unlock_route = 'jo_assign_unlock';
$jo_status = JOStatus::RIDER_ASSIGN;
break;
case 'fulfill':
$tier_key = 'jo_fulfill';
$tier_name = 'Fullfillment';
$rows_route = 'jo_fulfill_rows';
$edit_route = 'jo_fulfill_form';
$unlock_route = '';
$jo_status = [
JOStatus::ASSIGNED,
JOStatus::IN_PROGRESS
];
break;
case 'open':
$tier_key = 'jo_open';
$tier_name = 'Open';
$rows_route = 'jo_open_rows';
$edit_route = '';
$unlock_route = '';
$jo_status = [
JOStatus::PENDING,
JOStatus::RIDER_ASSIGN,
JOStatus::ASSIGNED,
JOStatus::IN_PROGRESS
];
break;
case 'all':
$tier_key = 'jo_open';
$tier_name = 'Open';
$rows_route = 'jo_open_rows';
$edit_route = 'jo_all_form';
$unlock_route = '';
$jo_status = '';
break;
default:
$exception = $this->createAccessDeniedException('No access.');
throw $exception;
}
// check acl
$this->denyAccessUnlessGranted($tier_key . '.list', null, 'No access.');
// return params if allowed access
return [
'key' => $tier_key,
'name' => $tier_name,
'rows_route' => $rows_route,
'edit_route' => $edit_route,
'unlock_route' => $unlock_route,
'jo_status' => $jo_status
];
}
public function listProcessing()
{
$this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.');
$params = $this->initParameters('jo_proc');
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
return $this->render('job-order/list.processing.html.twig', $params);
}
public function listAssigning()
{
$this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.');
$params = $this->initParameters('jo_assign');
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
return $this->render('job-order/list.assigning.html.twig', $params);
}
public function listFulfillment()
{
$this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.');
$params = $this->initParameters('jo_fulfill');
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
return $this->render('job-order/list.fulfillment.html.twig', $params);
}
public function listOpen()
{
$this->denyAccessUnlessGranted('jo_open.list', null, 'No access.');
$params = $this->initParameters('jo_open');
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
$params['statuses'] = JOStatus::getCollection();
return $this->render('job-order/list.open.html.twig', $params);
}
public function listAll()
{
$this->denyAccessUnlessGranted('jo_all.list', null, 'No access.');
$params = $this->initParameters('jo_all');
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
return $this->render('job-order/list.all.html.twig', $params);
}
public function listRows($tier)
{
// check which job order tier is being called for and confirm access
$tier_params = $this->checkTier($tier);
$params = $this->initParameters($tier_params['key']);
$params['tier_name'] = $tier_params['name'];
$params['rows_route'] = $tier_params['rows_route'];
$params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval');
// response
return $this->render('job-order/list.html.twig', $params);
}
public function getRows(Request $req, $tier)
{
// check which job order tier is being called for and confirm access
$tier_params = $this->checkTier($tier);
// get current user
$user = $this->getUser();
$hubs = $user->getHubs();
// get query builder
$qb = $this->getDoctrine()
->getRepository(JobOrder::class)
->createQueryBuilder('q');
// get datatable params
$datatable = $req->request->get('datatable');
// count total records
$tquery = $qb->select('COUNT(q)');
$this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']);
$total = $tquery->getQuery()
->getSingleScalarResult();
// get current page number
$page = $datatable['pagination']['page'] ?? 1;
$perpage = $datatable['pagination']['perpage'];
$offset = ($page - 1) * $perpage;
// add metadata
$meta = [
'page' => $page,
'perpage' => $perpage,
'pages' => ceil($total / $perpage),
'total' => $total,
'sort' => 'asc',
'field' => 'id'
];
// build query
$qb = $this->getDoctrine()
->getRepository(JobOrder::class)
->createQueryBuilder('q');
$query = $qb->select('q');
$this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']);
// check if sorting is present, otherwise use default
if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) {
$order = $datatable['sort']['sort'] ?? 'asc';
$query->orderBy('q.' . $datatable['sort']['field'], $order);
} else {
$query->orderBy('q.date_schedule', 'asc');
}
// get rows for this page
$query_obj = $query->setFirstResult($offset)
->setMaxResults($perpage)
->getQuery();
error_log($query_obj->getSQL());
$obj_rows = $query_obj->getResult();
/*
$obj_rows = $query->setFirstResult($offset)
->setMaxResults($perpage)
->getQuery()
->getResult();
*/
$statuses = JOStatus::getCollection();
$service_types = ServiceType::getCollection();
// process rows
$rows = [];
foreach ($obj_rows as $orow) {
// add row data
$row['id'] = $orow->getID();
$row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName();
$row['delivery_address'] = $orow->getDeliveryAddress();
$row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A");
$row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate';
$row['service_type'] = $service_types[$orow->getServiceType()];
$row['status'] = $statuses[$orow->getStatus()];
$row['flag_advance'] = $orow->isAdvanceOrder();
$row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber();
$processor = $orow->getProcessedBy();
if ($processor == null)
$row['processor'] = '';
else
$row['processor'] = $orow->getProcessedBy()->getFullName();
$assignor = $orow->getAssignedBy();
if ($assignor == null)
$row['assignor'] = '';
else
$row['assignor'] = $orow->getAssignedBy()->getFullName();
// add crud urls
if ($tier == 'open')
{
$row['meta']['reassign_hub_url'] = $this->generateUrl('jo_open_hub_form', ['id' => $row['id']]);
$row['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $row['id']]);
$row['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $row['id']]);
}
else
{
$row['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $row['id']]);
$row['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $row['id']]);
}
if ($tier_params['unlock_route'] != '')
$row['meta']['unlock_url'] = $this->generateUrl($tier_params['unlock_route'], ['id' => $row['id']]);
$rows[] = $row;
}
// response
return $this->json([
'meta' => $meta,
'data' => $rows
]);
}
public function processingForm(MapTools $map_tools, $id)
{
$this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
// manual transaction since we're locking
$em->getConnection()->beginTransaction();
try
{
// lock and get data
$obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ);
// make sure this job order exists
if (empty($obj))
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not exist');
}
// check status
if ($obj->getStatus() != JOStatus::PENDING)
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not have a pending status');
}
// check if we are the processor
$processor = $obj->getProcessedBy();
$user = $this->getUser();
// TODO: go back to list page and display alert / flash that says they cannot access it because they
// are not the processor
if ($processor != null && $processor->getID() != $user->getID())
{
$em->getConnection()->rollback();
throw $this->createAccessDeniedException('Not the processor');
}
// make this user be the processor
$obj->setProcessedBy($user);
$em->flush();
$em->getConnection()->commit();
}
catch(PessimisticLockException $e)
{
throw $this->createAccessDeniedException('Not the processor');
}
// NOTE: we are able to lock, everything should be fine now
$params = $this->initParameters('jo_proc');
$params['mode'] = 'update-processing';
$params['status_cancelled'] = JOStatus::CANCELLED;
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
// get closest hubs
$hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 20, date("H:i:s"));
$params['hubs'] = [];
// format duration and distance into friendly time
foreach ($hubs as $hub) {
// duration
$seconds = $hub['duration'];
if (!empty($seconds) && $seconds > 0) {
$hours = floor($seconds / 3600);
$minutes = ceil(($seconds / 60) % 60);
$hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : '');
} else {
$hub['duration'] = false;
}
// distance
$meters = $hub['distance'];
if (!empty($meters) && $meters > 0) {
$hub['distance'] = round($meters / 1000) . " km";
} else {
$hub['distance'] = false;
}
// counters
$hub['rider_count'] = count($hub['hub']->getAvailableRiders());
$hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders());
$params['hubs'][] = $hub;
}
$params['obj'] = $obj;
$params['submit_url'] = $this->generateUrl('jo_proc_submit', ['id' => $obj->getID()]);
$params['return_url'] = $this->generateUrl('jo_proc');
// response
return $this->render('job-order/form.html.twig', $params);
}
public function processingSubmit(Request $req, ValidatorInterface $validator, $id)
{
$this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.');
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
$processor = $obj->getProcessedBy();
$user = $this->getUser();
// check if we're the one processing, return error otherwise
if ($processor == null)
throw $this->createAccessDeniedException('Not the processor');
if ($processor != null && $processor->getID() != $user->getID())
throw $this->createAccessDeniedException('Not the processor');
// initialize error list
$error_array = [];
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if hub is set
if (empty($req->request->get('hub'))) {
$error_array['hub'] = 'No hub selected.';
} else {
// get hub
$hub = $em->getRepository(Hub::class)->find($req->request->get('hub'));
if (empty($hub)) {
$error_array['hub'] = 'Invalid hub specified.';
}
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setServiceType($req->request->get('service_type'))
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setStatus(JOStatus::RIDER_ASSIGN)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setHub($hub);
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::HUB_ASSIGN)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
public function assigningForm(MapTools $map_tools, $id)
{
$this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
// manual transaction since we're locking
$em->getConnection()->beginTransaction();
$params = $this->initParameters('jo_assign');
$params['mode'] = 'update-assigning';
try
{
// get row data
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this row exists
if (empty($obj))
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not exist');
}
// check status
if ($obj->getStatus() != JOStatus::RIDER_ASSIGN)
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not have an assigning status');
}
// check if super user
$user = $this->getUser();
if ($user->isSuperAdmin())
{
// do nothing, just allow page to be accessed
}
else
{
// check if hub is assigned to current user
$user_hubs = $this->getUser()->getHubs();
if (!in_array($obj->getHub()->getID(), $user_hubs))
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order is not on a hub assigned to this user');
}
// check if we are the assignor
$assignor = $obj->getAssignedBy();
if ($assignor != null && $assignor->getID() != $user->getID())
{
$em->getConnection()->rollback();
throw $this->createAccessDeniedException('Not the assignor');
}
// make this user be the assignor
$obj->setAssignedBy($user);
$em->flush();
}
$em->getConnection()->commit();
}
catch (PessimisticLockException $e)
{
throw $this->createAccessDeniedException('Not the assignor');
}
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
$params['obj'] = $obj;
$params['status_cancelled'] = JOStatus::CANCELLED;
$params['submit_url'] = $this->generateUrl('jo_assign_submit', ['id' => $obj->getID()]);
$params['return_url'] = $this->generateUrl('jo_assign');
// response
return $this->render('job-order/form.html.twig', $params);
}
public function assigningSubmit(Request $req, ValidatorInterface $validator, MQTTCLient $mclient, $id)
{
$this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.');
// initialize error list
$error_array = [];
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if rider is set
if (empty($req->request->get('rider'))) {
$error_array['rider'] = 'No rider selected.';
} else {
// get rider
$rider = $em->getRepository(Rider::class)->find($req->request->get('rider'));
if (empty($rider)) {
$error_array['rider'] = 'Invalid rider specified.';
}
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setServiceType($req->request->get('service_type'))
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setStatus(JOStatus::ASSIGNED)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setAssignedBy($this->getUser())
->setDateAssign(new DateTime())
->setRider($rider);
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// set rider unavailable
$rider->setAvailable(false);
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_ASSIGN)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// validated! save the entity
$em->flush();
// send event to mobile app
$payload = [
'event' => 'driver_assigned'
];
$mclient->sendEvent($obj, $payload);
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
public function fulfillmentForm(MapTools $map_tools, $id)
{
$this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
$params = $this->initParameters('jo_fulfill');
$params['mode'] = 'update-fulfillment';
// get row data
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this row exists
if (empty($obj))
{
throw $this->createNotFoundException('The job order does not exist');
}
// check status
if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS]))
{
throw $this->createNotFoundException('The job order does not have a fulfillment status');
}
// check if hub is assigned to current user
$user_hubs = $this->getUser()->getHubs();
if (!in_array($obj->getHub()->getID(), $user_hubs))
{
throw $this->createNotFoundException('The job order is not on a hub assigned to this user');
}
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
$params['obj'] = $obj;
$params['status_cancelled'] = JOStatus::CANCELLED;
$params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $obj->getID()]);
$params['return_url'] = $this->generateUrl('jo_fulfill');
// response
return $this->render('job-order/form.html.twig', $params);
}
protected function updateVehicleBattery(JobOrder $jo)
{
// check if new battery
if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW)
return;
// customer vehicle
$cv = $jo->getCustomerVehicle();
if ($cv == null)
return;
// invoice
$invoice = $jo->getInvoice();
if ($invoice == null)
return;
// invoice items
$items = $invoice->getItems();
if (count($items) <= 0)
return;
// get first battery from invoice
$battery = null;
foreach ($items as $item)
{
$battery = $item->getBattery();
if ($battery != null)
break;
}
// no battery in order
if ($battery == null)
return;
// warranty expiration
$warr = $jo->getWarrantyClass();
if ($warr == WarrantyClass::WTY_PRIVATE)
$warr_months = $battery->getWarrantyPrivate();
else if ($warr == WarrantyClass::WTY_COMMERCIAL)
$warr_months = $battery->getWarrantyCommercial();
else if ($warr == WarrantyClass::WTY_TNV)
$warr_months = 12;
$warr_date = new DateTime();
$warr_date->add(new DateInterval('P' . $warr_months . 'M'));
// update customer vehicle battery
$cv->setCurrentBattery($battery)
->setHasMotoliteBattery(true)
->setWarrantyExpiration($warr_date);
}
public function fulfillmentSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id)
{
$this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.');
// initialize error list
$error_array = [];
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setServiceType($req->request->get('service_type'))
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setStatus(JOStatus::FULFILLED)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setDateFulfill(new DateTime());
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// set rider available
$rider = $obj->getRider();
if ($rider != null)
$rider->setAvailable();
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::FULFILL)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// save to customer vehicle battery record
$this->updateVehicleBattery($obj);
// validated! save the entity
$em->flush();
// get rider
$rider = $obj->getRider();
$image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif';
if ($rider->getImageFile() != null)
$image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile();
// send to mqtt
$payload = [
'event' => 'fulfilled',
'jo_id' => $obj->getID(),
'driver_image' => $image_url,
'driver_name' => $rider->getFullName(),
'driver_id' => $rider->getID(),
];
$mclient->sendEvent($obj, $payload);
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
protected function sendEvent(JobOrder $job_order, $payload)
{
$sessions = $job_order->getCustomer()->getMobileSessions();
if (count($sessions) == 0)
return;
$client = new MosquittoClient();
$client->connect('localhost', 1883);
foreach ($sessions as $sess)
{
$phone_num = $sess->getPhoneNumber();
$channel = 'motolite.control.' . $phone_num;
$client->publish($channel, json_encode($payload));
}
$client->disconnect();
}
public function openHubForm(MapTools $map_tools, $id)
{
$this->denyAccessUnlessGranted('jo_open.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
$params = $this->initParameters('jo_open');
$params['mode'] = 'update-reassign-hub';
// get row data
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this row exists
if (empty($obj))
{
throw $this->createNotFoundException('The job order does not exist');
}
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
// get closest hubs
$hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 10, date("H:i:s"));
$params['status_cancelled'] = JOStatus::CANCELLED;
$params['hubs'] = [];
// format duration and distance into friendly time
foreach ($hubs as $hub) {
// duration
$seconds = $hub['duration'];
if (!empty($seconds) && $seconds > 0) {
$hours = floor($seconds / 3600);
$minutes = ceil(($seconds / 60) % 60);
$hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : '');
} else {
$hub['duration'] = false;
}
// distance
$meters = $hub['distance'];
if (!empty($meters) && $meters > 0) {
$hub['distance'] = round($meters / 1000) . " km";
} else {
$hub['distance'] = false;
}
// counters
$hub['rider_count'] = count($hub['hub']->getAvailableRiders());
$hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders());
$params['hubs'][] = $hub;
}
$params['obj'] = $obj;
$params['submit_url'] = $this->generateUrl('jo_open_hub_submit', ['id' => $obj->getID()]);
$params['return_url'] = $this->generateUrl('jo_open');
// response
return $this->render('job-order/form.html.twig', $params);
}
public function openHubSubmit(Request $req, ValidatorInterface $validator, $id)
{
$this->denyAccessUnlessGranted('jo_open.list', null, 'No access.');
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
$user = $this->getUser();
// initialize error list
$error_array = [];
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if hub is set
if (empty($req->request->get('hub'))) {
$error_array['hub'] = 'No hub selected.';
} else {
// get hub
$hub = $em->getRepository(Hub::class)->find($req->request->get('hub'));
if (empty($hub)) {
$error_array['hub'] = 'Invalid hub specified.';
}
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setServiceType($req->request->get('service_type'))
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setStatus(JOStatus::RIDER_ASSIGN)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setHub($hub)
->setProcessedBy($this->getUser())
->clearRider();
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// add event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::HUB_ASSIGN)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
public function openRiderForm($id)
{
$this->denyAccessUnlessGranted('jo_open.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
$params = $this->initParameters('jo_open');
$params['mode'] = 'update-reassign-rider';
// get row data
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this row exists
if (empty($obj))
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not exist');
}
// check status
if ($obj->getStatus() == JOStatus::PENDING)
{
$em->getConnection()->rollback();
throw $this->createNotFoundException('The job order does not have an assigned hub');
}
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
$params['obj'] = $obj;
$params['status_cancelled'] = JOStatus::CANCELLED;
$params['submit_url'] = $this->generateUrl('jo_open_rider_submit', ['id' => $obj->getID()]);
$params['return_url'] = $this->generateUrl('jo_open');
// response
return $this->render('job-order/form.html.twig', $params);
}
public function openRiderSubmit(Request $req, ValidatorInterface $validator, $id)
{
$this->denyAccessUnlessGranted('jo_open.list', null, 'No access.');
// initialize error list
$error_array = [];
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// check if lat and lng are provided
if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) {
$error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.';
}
// check if rider is set
if (empty($req->request->get('rider'))) {
$error_array['rider'] = 'No rider selected.';
} else {
// get rider
$rider = $em->getRepository(Rider::class)->find($req->request->get('rider'));
if (empty($rider)) {
$error_array['rider'] = 'Invalid rider specified.';
}
}
if (empty($error_array)) {
// coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
// set and save values
$obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time')))
->setCoordinates($point)
->setAdvanceOrder($req->request->get('flag_advance') ?? false)
->setServiceType($req->request->get('service_type'))
->setWarrantyClass($req->request->get('warranty_class'))
->setSource($req->request->get('source'))
->setStatus(JOStatus::ASSIGNED)
->setDeliveryInstructions($req->request->get('delivery_instructions'))
->setTier1Notes($req->request->get('tier1_notes'))
->setTier2Notes($req->request->get('tier2_notes'))
->setDeliveryAddress($req->request->get('delivery_address'))
->setAssignedBy($this->getUser())
->setDateAssign(new DateTime())
->setAssignedBy($this->getUser())
->setRider($rider);
// validate
$errors = $validator->validate($obj);
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// add event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_ASSIGN)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
public function allForm($id)
{
$this->denyAccessUnlessGranted('jo_all.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
$params = $this->initParameters('jo_all');
$params['mode'] = 'update-all';
// get row data
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this row exists
if (empty($obj))
throw $this->createNotFoundException('The job order does not exist');
$this->fillDropdownParameters($params);
$this->fillFormTags($params);
$params['obj'] = $obj;
$params['status_cancelled'] = JOStatus::CANCELLED;
$params['return_url'] = $this->generateUrl('jo_all');
$params['submit_url'] = '';
// timeline stuff (descending by time)
$params['timeline'] = [
[
'date' => date("M j"),
'time' => date("g:i A"),
'event' => "Event 4",
'color' => "#f4516c"
],
[
'date' => date("M j"),
'time' => date("g:i A"),
'event' => "Event 3",
'color' => "#34bfa3"
],
[
'date' => date("M j"),
'time' => date("g:i A"),
'event' => "Event 2",
'color' => "#716aca"
],
[
'date' => date("M j"),
'time' => date("g:i A"),
'event' => "Event 1",
'color' => "#ffb822"
],
];
// response
return $this->render('job-order/form.html.twig', $params);
}
public function pdfForm(Request $req, $id)
{
$this->denyAccessUnlessGranted('jo_pdf.list', null, 'No access.');
$em = $this->getDoctrine()->getManager();
// get row data
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this row exists
if (empty($obj))
throw $this->createNotFoundException('The job order does not exist');
// set output filename
$filename = 'job-order-' . $obj->getID() . '.pdf';
// generate the pdf
$pdf = new FPDF('P', 'mm', 'letter');
$pdf->AddPage();
$pdf->SetTitle('Motolite Res-Q Job Order #' . $obj->getID());
$pdf->SetFillColor(211, 211, 211);
// style defaults
$margin = 10;
$page_width = $pdf->GetPageWidth() - ($margin * 2);
$table_col_width = $page_width / 12;
$line_height = 5;
$jo_line_height = 10;
$table_line_height = 7;
$font_face = 'Arial';
$body_font_size = 9;
$header_font_size = 9;
$jo_font_size = 16;
$col1_x = $margin;
$col2_x = 120;
$label_width = 40;
$val_width = 60;
// insert the logo
$image_path = $this->get('kernel')->getProjectDir() . '/public/assets/images/logo-resq.png';
$pdf->Image($image_path, $col1_x, 10);
// insert JO number
$pdf->SetFont($font_face, 'B', $jo_font_size);
$pdf->SetX($col2_x);
$pdf->Cell($label_width, $jo_line_height, 'JO Number:');
$pdf->SetTextColor(9, 65, 150);
$pdf->Cell(0, $jo_line_height, $obj->getID());
// insert customer info
$customer = $obj->getCustomer();
$pdf->SetFont($font_face, '', $body_font_size);
$pdf->SetTextColor(0, 0, 0);
$pdf->Ln($line_height * 7);
// get current Y
$y = $pdf->GetY();
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Customer Name:');
$pdf->MultiCell($val_width, $line_height, $customer->getFirstName() . ' ' . $customer->getLastName(), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Mobile Phone:');
$pdf->MultiCell(0, $line_height, $customer->getPhoneMobile() ? '+63' . $customer->getPhoneMobile() : '', 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Delivery Date:');
$pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Landline:');
$pdf->MultiCell(0, $line_height, $customer->getPhoneLandline() ? '+63' . $customer->getPhoneLandline() : '', 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Office Phone:');
$pdf->MultiCell(0, $line_height, $customer->getPhoneOffice() ? '+63' . $customer->getPhoneOffice() : '', 0, 'L');
$pdf->SetX($col2_x);
$pdf->Cell($label_width, $line_height, 'Fax:');
$pdf->MultiCell($val_width, $line_height, $customer->getPhoneFax() ? '+63' . $customer->getPhoneFax() : '', 0, 'L');
// insert vehicle info
$cv = $obj->getCustomerVehicle();
$vehicle = $cv->getVehicle();
$pdf->Ln();
$pdf->SetFont($font_face, 'B', $header_font_size);
$pdf->Cell($label_width, $line_height, 'Vehicle Details');
$pdf->Ln($line_height * 2);
// get current Y
$y = $pdf->GetY();
$pdf->SetFont($font_face, '', $body_font_size);
$pdf->Cell($label_width, $line_height, 'Plate Number:');
$pdf->MultiCell($val_width, $line_height, $cv ? $cv->getPlateNumber() : '', 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Vehicle Color:');
$pdf->MultiCell(0, $line_height, $cv ? $cv->getColor() : '', 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Brand:');
$pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getManufacturer()->getName() : '', 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Model / Year:');
$pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Make:');
$pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L');
// insert battery info
$battery = $cv->getCurrentBattery();
$pdf->Ln();
$pdf->SetFont($font_face, 'B', $header_font_size);
$pdf->Cell($label_width, $line_height, 'Battery Details');
$pdf->Ln($line_height * 2);
$pdf->SetFont($font_face, '', $body_font_size);
// get current Y
$y = $pdf->GetY();
$pdf->Cell($label_width, $line_height, 'Current Battery:');
$pdf->MultiCell($val_width, $line_height, $battery ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Serial Number:');
$pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:');
$pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L');
// insert transaction details
$pdf->Ln();
$pdf->SetFont($font_face, 'B', $header_font_size);
$pdf->Cell($label_width, $line_height, 'Transaction Details');
$pdf->Ln($line_height * 2);
$pdf->SetFont($font_face, '', $body_font_size);
// get current Y
$y = $pdf->GetY();
$pdf->Cell($label_width, $line_height, 'Warranty Class:');
$pdf->MultiCell($val_width, $line_height, WarrantyClass::getName($obj->getWarrantyClass()), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Mode of Payment:');
$pdf->MultiCell(0, $line_height, ModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->Cell($label_width, $line_height, 'Delivery Address:');
$pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Landmark:');
$pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Dispatch Time:');
$pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule()->format("g:i A"), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->Cell($label_width, $line_height, 'Dispatched By:');
$pdf->MultiCell(0, $line_height, $obj->getAssignedBy()->getFullName(), 0, 'L');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
// insert delivery instructions
$pdf->SetY($y);
$pdf->Ln();
$pdf->SetFont($font_face, 'B', $header_font_size);
$pdf->Cell(0, $line_height, 'Delivery Instructions');
$pdf->Ln();
$pdf->SetFont($font_face, '', $body_font_size);
$pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L');
// insert invoice details
$pdf->Ln();
$pdf->SetFont($font_face, 'B', $header_font_size);
$pdf->Cell($label_width, $line_height, 'Invoice Details');
$pdf->Ln();
// invoice table headers
$invoice = $obj->getInvoice();
$pdf->SetFont($font_face, 'B', $header_font_size);
$pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1);
$pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1);
$pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1);
$pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1);
$pdf->SetFont($font_face, '', $body_font_size);
// build invoice items table
if ($invoice && !empty($invoice->getItems()))
{
foreach ($invoice->getItems() as $item)
{
$pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1);
$pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R');
$pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R');
$pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R');
}
}
else
{
$pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1);
}
$pdf->Ln($line_height * 2);
// get current Y
$y = $pdf->GetY();
// insert invoice footer details
$pdf->Cell($label_width, $line_height, 'Transaction Type:');
$pdf->MultiCell($val_width, $line_height, ServiceType::getName($obj->getServiceType()), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->SetFont($font_face, 'B');
$pdf->Cell($label_width, $line_height, 'SUBTOTAL:');
$pdf->SetFont($font_face, '');
$pdf->MultiCell(0, $line_height, number_format($invoice->getVATExclusivePrice(), 2), 0, 'R');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'OR Name:');
$pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->SetFont($font_face, 'B');
$pdf->Cell($label_width, $line_height, 'TAX:');
$pdf->SetFont($font_face, '');
$pdf->MultiCell(0, $line_height, number_format($invoice->getVAT(), 2), 0, 'R');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:');
$pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L');
// get Y after left cell
$y1 = $pdf->GetY();
$pdf->SetXY($col2_x, $y);
$pdf->SetFont($font_face, 'B');
$pdf->Cell($label_width, $line_height, 'DISCOUNT:');
$pdf->SetFont($font_face, '');
$pdf->MultiCell(0, $line_height, number_format($invoice->getDiscount(), 2), 0, 'R');
// get Y after right cell
$y2 = $pdf->GetY();
// get row height
$y = max($y1, $y2);
$pdf->SetXY($col1_x, $y);
$pdf->Cell($label_width, $line_height, 'Discount Type:');
$pdf->MultiCell($val_width, $line_height, $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L');
$pdf->SetXY($col2_x, $y);
$pdf->SetFont($font_face, 'B');
$pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:');
$pdf->MultiCell(0, $line_height, number_format($invoice->getTotalPrice(), 2), 0, 'R');
$pdf->SetFont($font_face, '');
// return response
return new Response($pdf->Output('I', $filename), 200, [
'Content-Type' => 'application/pdf'
]);
}
public function cancelJobOrder(Request $req, MQTTClient $mclient, $id)
{
$this->denyAccessUnlessGranted('joborder.cancel', null, 'No access.');
$cancel_reason = $req->request->get('cancel_reason');
if (empty($cancel_reason))
{
// something happened
return $this->json([
'success' => false,
'error' => 'Reason for cancellation is required.'
], 422);
}
// get object data
$em = $this->getDoctrine()->getManager();
$obj = $em->getRepository(JobOrder::class)->find($id);
// make sure this object exists
if (empty($obj))
throw $this->createNotFoundException('The item does not exist');
// cancel job order
$obj->setStatus(JOStatus::CANCELLED)
->setDateCancel(new DateTime())
->setCancelReason($cancel_reason);
// set rider available
$rider = $obj->getRider();
if ($rider != null)
$rider->setAvailable();
// the event
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CANCEL)
->setUser($this->getUser())
->setJobOrder($obj);
$em->persist($event);
// save
$em->flush();
// send mobile app event
$payload = [
'event' => 'cancelled',
'reason' => $cancel_reason,
'jo_id' => $obj->getID(),
];
$mclient->sendEvent($obj, $payload);
// return successful response
return $this->json([
'success' => 'Job order has been cancelled!'
]);
}
// TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter
// check if datatable filter is present and append to query
protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status)
{
switch ($tier)
{
case 'fulfill':
$query->where('q.status IN (:statuses)')
->andWhere('q.hub IN (:hubs)')
->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY)
->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY);
break;
case 'assign':
$query->where('q.status = :status')
->andWhere('q.hub IN (:hubs)')
->setParameter('status', $status)
->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY);
break;
case 'open':
if (isset($datatable['query']['data-rows-search']))
{
$query->innerJoin('q.cus_vehicle', 'cv')
->innerJoin('q.customer', 'c')
->where('q.status IN (:statuses)')
->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter')
->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY)
->setParameter('filter', $datatable['query']['data-rows-search'] . '%');
}
else
{
$query->where('q.status IN (:statuses)')
->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY);
}
break;
case 'all':
if (isset($datatable['query']['data-rows-search']))
{
$query->innerJoin('q.cus_vehicle', 'cv')
->innerJoin('q.customer', 'c')
->where('cv.plate_number like :filter')
->orWhere('c.phone_mobile like :filter')
->orWhere('c.first_name like :filter or c.last_name like :filter')
->setParameter('filter', $datatable['query']['data-rows-search'] . '%');
}
break;
default:
$query->where('q.status = :status')
->setParameter('status', $status);
}
// get only pending rows
/*
$query->where($qb->expr()->orX(
$qb->expr()where('q.status', 'pending');
));
// apply filters
if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) {
$query->where('q.delivery_address LIKE :filter')
->orWhere($qb->expr()->concat('c.first_name', $qb->expr()->literal(' '), 'c.last_name') . ' LIKE :filter')
->orWhere('cv.plate_number LIKE :filter')
->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%');
}
*/
}
protected function invoicePromo($em, InvoiceCriteria $criteria, $promo_id)
{
// return error if there's a problem, false otherwise
// check service type
$stype = $criteria->getServiceType();
if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW)
return null;
if (empty($promo_id))
return false;
// check if this is a valid promo
$promo = $em->getRepository(Promo::class)->find($promo_id);
if (empty($promo))
return 'Invalid promo specified.';
$criteria->addPromo($promo);
return false;
}
protected function invoiceBatteries($em, InvoiceCriteria $criteria, $items)
{
// check service type
$stype = $criteria->getServiceType();
if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY)
return null;
// return error if there's a problem, false otherwise
if (!empty($items))
{
foreach ($items as $item)
{
// error_log('ITEMS');
// check if this is a valid battery
$battery = $em->getRepository(Battery::class)->find($item['battery']);
if (empty($battery))
{
$error = 'Invalid battery specified.';
return $error;
}
// quantity
$qty = $item['quantity'];
if ($qty < 1)
continue;
/*
// add to criteria
$criteria->addBattery($battery, $qty);
*/
// if this is a trade in, add trade in
if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in']))
$trade_in = $item['trade_in'];
else
$trade_in = null;
$criteria->addEntry($battery, $trade_in, $qty);
}
}
return null;
}
public function generateInvoice(Request $req, InvoiceCreator $ic)
{
// error_log('generating invoice...');
$error = false;
$stype = $req->request->get('stype');
$items = $req->request->get('items');
$promo_id = $req->request->get('promo');
$cvid = $req->request->get('cvid');
$em = $this->getDoctrine()->getManager();
// get customer vehicle
$cv = $em->getRepository(CustomerVehicle::class)->find($cvid);
if ($cv == null)
throw new \Exception('Could not get customer vehicle');
// instantiate invoice criteria
$criteria = new InvoiceCriteria();
$criteria->setServiceType($stype)
->setCustomerVehicle($cv);
/*
// if it's a jumpstart or troubleshoot only, we know what to charge already
if ($stype == ServiceType::JUMPSTART_TROUBLESHOOT)
{
$invoice = [
'price' => 150.00,
'discount' => 0,
'trade_in' => 0,
'vat' => 0,
'total_price' => 150.00,
'items' => []
];
$invoice['items'][] = [
'title' => 'Troubleshooting fee',
'quantity' => 1,
'unit_price' => 150.00,
'amount' => 150.00
];
return $this->json([
'success' => true,
'invoice' => $invoice
]);
}
*/
$error = $this->invoicePromo($em, $criteria, $promo_id);
if (!$error)
$error = $this->invoiceBatteries($em, $criteria, $items);
if ($error)
{
// something happened
return $this->json([
'success' => false,
'error' => $error
], 422);
}
// generate the invoice
$iobj = $ic->processCriteria($criteria);
// use invoice object values in a json friendly array
$invoice = [
'discount' => number_format($iobj->getDiscount(), 2),
'trade_in' => number_format($iobj->getTradeIn(), 2), // TODO: computations not done yet for this on invoice creator
'price' => number_format($iobj->getVATExclusivePrice(), 2),
'vat' => number_format($iobj->getVAT(), 2),
'total_price' => number_format($iobj->getTotalPrice(), 2),
'items' => []
];
foreach ($iobj->getItems() as $item)
{
$invoice['items'][] = [
'title' => $item->getTitle(),
'quantity' => number_format($item->getQuantity()), // TODO: quantities are always 1, hardcoded into InvoiceCreator. no way of accepting quantities on InvoiceCriteria
'unit_price' => number_format($item->getPrice(), 2),
'amount' => number_format($item->getPrice() * $item->getQuantity(), 2) // TODO: should this calculation should be a saved value on InvoiceItem instead?
];
}
// return
return $this->json([
'success' => true,
'invoice' => $invoice
]);
}
public function unlockProcessor($id)
{
$this->denyAccessUnlessGranted('jo_proc.unlock', null, 'No access.');
// clear lock
$em = $this->getDoctrine()->getManager();
$jo = $em->getRepository(JobOrder::class)->find($id);
if ($jo == null)
return $this->redirectToRoute('jo_proc');
$jo->setProcessedBy(null);
$em->flush();
// redirect to list
return $this->redirectToRoute('jo_proc');
}
public function unlockAssignor($id)
{
$this->denyAccessUnlessGranted('jo_assign.unlock', null, 'No access.');
// clear lock
$em = $this->getDoctrine()->getManager();
$jo = $em->getRepository(JobOrder::class)->find($id);
if ($jo == null)
return $this->redirectToRoute('jo_assign');
$jo->setAssignedBy(null);
$em->flush();
// redirect to list
return $this->redirectToRoute('jo_assign');
}
}