Compare commits

..

No commits in common. "master" and "792-battery-facilitated-by-issue" have entirely different histories.

93 changed files with 629 additions and 5961 deletions

3
.gitignore vendored
View file

@ -12,6 +12,3 @@
*.swp
/public/warranty_uploads/*
.vscode
*__pycache__
/public/assets/images/insurance-premiums.png

View file

@ -634,45 +634,6 @@ catalyst_auth:
- id: service_offering.delete
label: Delete
- id: price_tier
label: Price Tier
acls:
- id: price_tier.menu
label: Menu
- id: price_tier.list
label: List
- id: price_tier.add
label: Add
- id: price_tier.update
label: Update
- id: price_tier.delete
label: Delete
- id: item_type
label: Item Type
acls:
- id: item_type.menu
label: Menu
- id: item_type.list
label: List
- id: item_type.add
label: Add
- id: item_type.update
label: Update
- id: item_type.delete
label: Delete
- id: item
label: Item
acls:
- id: item.menu
label: Menu
- id: item_pricing
label: Item Pricing
acls:
- id: item_pricing.update
label: Update
api:
user_entity: "App\\Entity\\ApiUser"
acl_data:
@ -751,16 +712,6 @@ catalyst_auth:
acls:
- id: warrantyserial.upload
label: Upload
- id: hub
label: Hub Access
acls:
- id: hub.list
label: List
- id: joborder
label: Job Order Access
acls:
- id: joborder.find
label: Find Job Order
- id: tapi_vmanufacturer
label: Third Party Vehicle Manufacturer Access

View file

@ -177,7 +177,7 @@ catalyst_menu:
acl: support.menu
label: '[menu.support]'
icon: flaticon-support
order: 11
order: 10
- id: customer_list
acl: customer.list
label: '[menu.support.customers]'
@ -223,7 +223,7 @@ catalyst_menu:
acl: service.menu
label: '[menu.service]'
icon: flaticon-squares
order: 12
order: 11
- id: service_list
acl: service.list
label: '[menu.service.services]'
@ -233,7 +233,7 @@ catalyst_menu:
acl: partner.menu
label: '[menu.partner]'
icon: flaticon-network
order: 13
order: 12
- id: partner_list
acl: partner.list
label: '[menu.partner.partners]'
@ -247,7 +247,7 @@ catalyst_menu:
acl: motolite_event.menu
label: '[menu.motolite_event]'
icon: flaticon-event-calendar-symbol
order: 14
order: 13
- id: motolite_event_list
acl: motolite_event.list
label: '[menu.motolite_event.events]'
@ -257,7 +257,7 @@ catalyst_menu:
acl: analytics.menu
label: '[menu.analytics]'
icon: flaticon-graphic
order: 15
order: 14
- id: analytics_forecast_form
acl: analytics.forecast
label: '[menu.analytics.forecasting]'
@ -267,7 +267,7 @@ catalyst_menu:
acl: database.menu
label: '[menu.database]'
icon: fa fa-database
order: 16
order: 15
- id: ticket_type_list
acl: ticket_type.menu
label: '[menu.database.tickettypes]'
@ -288,21 +288,3 @@ catalyst_menu:
acl: service_offering.menu
label: '[menu.database.serviceofferings]'
parent: database
- id: item_type_list
acl: item_type.menu
label: '[menu.database.itemtypes]'
parent: database
- id: item
acl: item.menu
label: Item Management
icon: fa fa-boxes
order: 10
- id: price_tier_list
acl: price_tier.list
label: Price Tiers
parent: item
- id: item_pricing
acl: item_pricing.update
label: Item Pricing
parent: item

View file

@ -303,13 +303,3 @@ apiv2_insurance_application_create:
path: /apiv2/insurance/application
controller: App\Controller\CustomerAppAPI\InsuranceController::createApplication
methods: [POST]
apiv2_insurance_premiums_banner:
path: /apiv2/insurance/premiums_banner
controller: App\Controller\CustomerAppAPI\InsuranceController::getPremiumsBanner
methods: [GET]
apiv2_insurance_body_types:
path: /apiv2/insurance/body_types
controller: App\Controller\CustomerAppAPI\InsuranceController::getBodyTypes
methods: [GET]

View file

@ -194,16 +194,3 @@ capi_warranty_serial_upload:
path: /capi/warranty_serial/upload
controller: App\Controller\CAPI\WarrantySerialController::uploadWarrantySerialFile
methods: [POST]
# pullout form system
# hub
capi_hub_list:
path: /capi/hubs
controller: App\Controller\CAPI\HubController::getAll
methods: [GET]
# job order details
capi_job_order:
path: /capi/job_order/{id}
controller: App\Controller\CAPI\JobOrderController::getJobOrder
methods: [GET]

View file

@ -94,24 +94,3 @@ capi_rider_jo_start:
path: /rider_api/start
controller: App\Controller\CAPI\RiderAppController::startJobOrder
methods: [POST]
# trade-ins
capi_rider_battery_sizes:
path: /rider_api/battery_sizes
controller: App\Controller\CAPI\RiderAppController::getBatterySizes
methods: [GET]
capi_rider_trade_in_types:
path: /rider_api/trade_in_types
controller: App\Controller\CAPI\RiderAppController::getTradeInTypes
methods: [GET]
capi_rider_battery_info:
path: /rider_api/battery/{serial}
controller: App\Controller\CAPI\RiderAppController::getBatteryInfo
methods: [GET]
capi_rider_update_jo:
path: /rider_api/job_order/update
controller: App\Controller\CAPI\RiderAppController::updateJobOrder
methods: [POST]

View file

@ -1,14 +0,0 @@
item_pricing:
path: /item-pricing
controller: App\Controller\ItemPricingController::index
methods: [GET]
item_pricing_update:
path: /item-pricing
controller: App\Controller\ItemPricingController::formSubmit
methods: [POST]
item_pricing_prices:
path: /item-pricing/{pt_id}/{it_id}/prices
controller: App\Controller\ItemPricingController::itemPrices
methods: [GET]

View file

@ -1,34 +0,0 @@
item_type_list:
path: /item-types
controller: App\Controller\ItemTypeController::index
methods: [GET]
item_type_rows:
path: /item-types/rowdata
controller: App\Controller\ItemTypeController::datatableRows
methods: [POST]
item_type_add_form:
path: /item-types/newform
controller: App\Controller\ItemTypeController::addForm
methods: [GET]
item_type_add_submit:
path: /item-types
controller: App\Controller\ItemTypeController::addSubmit
methods: [POST]
item_type_update_form:
path: /item-types/{id}
controller: App\Controller\ItemTypeController::updateForm
methods: [GET]
item_type_update_submit:
path: /item-types/{id}
controller: App\Controller\ItemTypeController::updateSubmit
methods: [POST]
item_type_delete:
path: /item-types/{id}
controller: App\Controller\ItemTypeController::deleteSubmit
methods: [DELETE]

View file

@ -1,34 +0,0 @@
price_tier_list:
path: /price-tiers
controller: App\Controller\PriceTierController::index
methods: [GET]
price_tier_rows:
path: /price-tiers/rows
controller: App\Controller\PriceTierController::datatableRows
methods: [POST]
price_tier_add_form:
path: /price-tiers/newform
controller: App\Controller\PriceTierController::addForm
methods: [GET]
price_tier_add_submit:
path: /price-tiers
controller: App\Controller\PriceTierController::addSubmit
methods: [POST]
price_tier_update_form:
path: /price-tiers/{id}
controller: App\Controller\PriceTierController::updateForm
methods: [GET]
price_tier_update_submit:
path: /price-tiers/{id}
controller: App\Controller\PriceTierController::updateSubmit
methods: [POST]
price_tier_delete:
path: /price-tiers/{id}
controller: App\Controller\PriceTierController::deleteSubmit
methods: [DELETE]

View file

@ -51,7 +51,7 @@ tapi_vehicle_make_list:
tapi_battery_list:
path: /tapi/vehicles/{vid}/compatible_batteries
controller: App\Controller\TAPI\BatteryController::getCompatibleBatteries
methods: [POST]
methods: [GET]
# promos
tapi_promo_list:

View file

@ -15,8 +15,6 @@ parameters:
api_version: "%env(API_VERSION)%"
android_app_version: "%env(ANDROID_APP_VERSION)%"
ios_app_version: "%env(IOS_APP_VERSION)%"
insurance_premiums_banner_url: "%env(INSURANCE_PREMIUMS_BANNER_URL)%"
enabled_hub_filters: "%env(ENABLED_HUB_FILTERS)%"
services:
# default configuration for services in *this* file
@ -110,12 +108,6 @@ services:
arguments:
$callback_url: "%env(WARRANTY_SERIAL_CALLBACK_URL)%"
App\Command\ProcessLatePaymongoTransactionsCommand:
arguments:
$em: "@doctrine.orm.entity_manager"
$paymongo: "@App\\Service\\PayMongoConnector"
$webhook_id: "%env(PAYMONGO_WEBHOOK_ID)%"
# rider tracker service
App\Service\RiderTracker:
arguments:
@ -318,41 +310,3 @@ services:
arguments:
$server_key: "%env(FCM_SERVER_KEY)%"
$sender_id: "%env(FCM_SENDER_ID)%"
# price tier manager
App\Service\PriceTierManager:
arguments:
$em: "@doctrine.orm.entity_manager"
# hub filters
App\Service\HubFilter\BaseHubFilter:
arguments:
$hub_filter_logger: "@App\\Service\\HubFilterLogger"
$em: "@doctrine.orm.entity_manager"
$rt: "@App\\Service\\RisingTideGateway"
$trans: "@Symfony\\Contracts\\Translation\\TranslatorInterface"
App\Service\HubFilter\Filters\DateAndTimeHubFilter:
public: true
App\Service\HubFilter\Filters\JoTypeHubFilter:
public: true
App\Service\HubFilter\Filters\MaxResultsHubFilter:
public: true
App\Service\HubFilter\Filters\PaymentMethodHubFilter:
public: true
App\Service\HubFilter\Filters\RiderAvailabilityHubFilter:
public: true
App\Service\HubFilter\Filters\InventoryHubFilter:
public: true
arguments:
$im: "@App\\Service\\InventoryManager"
App\Service\HubFilter\Filters\RoundRobinHubFilter:
public: true
arguments:
$hub_distributor: "@App\\Service\\HubDistributor"

View file

@ -1,136 +0,0 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Doctrine\ORM\EntityManagerInterface;
use App\Ramcar\TransactionStatus;
use App\Entity\GatewayTransaction;
use App\Service\PayMongoConnector;
use DateTime;
class ProcessLatePaymongoTransactionsCommand extends Command
{
protected $em;
protected $paymongo;
protected $webhook_id;
public function __construct(EntityManagerInterface $em, PayMongoConnector $paymongo, $webhook_id)
{
$this->em = $em;
$this->paymongo = $paymongo;
$this->webhook_id = $webhook_id;
parent::__construct();
}
protected function configure()
{
$this->setName('paymongo:checkpending')
->setDescription('Check for any late PayMongo transactions and process if needed.')
->setHelp('Check for any late PayMongo transactions and process if needed.')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Ignore webhook status and process anyway.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$force = $input->getOption('force');
// if we aren't forcing, check webhook status first
if (!$force) {
$output->writeln('Checking webhook status...');
// check if webhook is disabled
$webhook = $this->paymongo->getWebhook($this->webhook_id);
if ($webhook['success'] && $webhook['response']['data']['attributes']['status'] === 'enabled') {
$output->writeln('<info>Webhook is enabled, no need to do anything.</info>');
return 0;
} else {
$output->writeln('<comment>Webhook is disabled! Logging event and attempting to re-enable...</comment>');
// attempt re-enabling of webhook
$result = $this->paymongo->enableWebhook($this->webhook_id);
if ($result['success'] && $result['response']['data']['attributes']['status'] ?? null === 'enabled') {
$output->writeln('<info>Webhook ' . $this->webhook_id . ' re-enabled!</info>');
// log event
$this->paymongo->log('WEBHOOK RE-ENABLED', "[]", json_encode($result['response'], JSON_PRETTY_PRINT), 'webhook');
} else {
$output->writeln('<comment>Webhook ' . $this->webhook_id . ' could not be re-enabled.</comment>');
// log event
$this->paymongo->log('WEBHOOK FAILURE', "[]", json_encode($result['response'], JSON_PRETTY_PRINT), 'webhook');
}
}
}
$output->writeln('Fetching all late pending transactions...');
// set date threshold to 24 hours ago
$date_threshold = (new DateTime())->modify('-24 hours');
$transactions = $this->em->getRepository(GatewayTransaction::class)
->createQueryBuilder('t')
->select('t')
->where('t.status = :status')
->andWhere('t.date_create <= :date_threshold')
->setParameter('status', TransactionStatus::PENDING)
->setParameter('date_threshold', $date_threshold)
->getQuery()
->getResult();
$output->writeln('Found '. count($transactions) . ' rows matching criteria.');
$x = 0;
foreach ($transactions as $trans) {
// check paymongo status
$checkout = $this->paymongo->getCheckout($trans->getExtTransactionId());
if ($checkout['success']) {
// check if we have any payments made
$payments = $checkout['response']['data']['attributes']['payments'] ?? [];
if (!empty($payments)) {
$amount_paid = 0;
// for good measure, we get all successful payments and add them up
foreach ($payments as $payment) {
if ($payment['attributes']['status'] === TransactionStatus::PAID) {
$amount_paid = bcadd($amount_paid, $payment['attributes']['amount']);
}
}
// this transaction is fully paid, so we mark it as paid
if (bccomp($trans->getAmount(), $amount_paid) <= 0) {
$trans->setStatus(TransactionStatus::PAID);
$trans->setDatePay(new DateTime());
$this->em->flush();
$output->writeln('Marked transaction '. $trans->getID() . ' as paid.');
$x++;
} else {
$output->writeln('<comment>Insufficient payment amount (' . $amount_paid . '/' . $trans->getAmount() . ') for this transaction: ' . $trans->getID() . '</comment>');
}
} else {
$output->writeln('<comment>No payments found for transaction: ' . $trans->getID() . '</comment>');
}
} else {
$output->writeln('<comment>Checkout not found: ' . $checkout['error']['message'] . '</comment>');
}
}
$output->writeln('<info>Done! Processed ' . $x . ' rows.</info>');
return 0;
}
}

View file

@ -50,7 +50,6 @@ use App\Service\HubFilterLogger;
use App\Service\HubFilteringGeoChecker;
use App\Service\HashGenerator;
use App\Service\JobOrderManager;
use App\Service\PriceTierManager;
use App\Entity\MobileSession;
use App\Entity\Customer;
@ -2912,10 +2911,6 @@ class APIController extends Controller implements LoggedController
// old app doesn't have separate jumpstart
$icrit->setSource(TransactionOrigin::CALL);
// set price tier
$pt_id = $this->pt_manager->getPriceTier($jo->getCoordinates());
$icrit->setPriceTier($pt_id);
// check promo
$promo_id = $req->request->get('promo_id');
if (!empty($promo_id))

View file

@ -326,7 +326,7 @@ class APIRoleController extends Controller
protected function padAPIACLHierarchy(&$params)
{
// get acl keys hierarchy
$api_acl_data = $this->api_acl_gen->getACL('api');
$api_acl_data = $this->api_acl_gen->getACL();
$params['api_acl_hierarchy'] = $api_acl_data['hierarchy'];
}

View file

@ -1,53 +0,0 @@
<?php
namespace App\Controller\CAPI;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\Query;
use Doctrine\ORM\EntityManagerInterface;
use Catalyst\ApiBundle\Controller\ApiController;
use Catalyst\ApiBundle\Component\Response as APIResponse;
use App\Entity\Hub;
use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator;
class HubController extends ApiController
{
protected $acl_gen;
public function __construct(ACLGenerator $acl_gen)
{
$this->acl_gen = $acl_gen;
}
public function getAll(EntityManagerInterface $em)
{
// get all hub data order by name
$this->denyAccessUnlessGranted('hub.list', null, 'No access.');
$results = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']);
$hubs = [];
foreach($results as $res)
{
$hub_id = $res->getId();
$hub_name = $res->getName();
$hub_address = $res->getAddress();
$hub_branch_code = $res->getBranchCode();
$hubs[$hub_id] = [
'id' => $hub_id,
'name' => $hub_name,
'address' => $hub_address,
'branch_code' => $hub_branch_code,
];
}
$data = [
'hubs' => $hubs,
];
return new APIResponse(true, 'Hubs loaded.', $data);
}
}

View file

@ -1,139 +0,0 @@
<?php
namespace App\Controller\CAPI;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\Query;
use Doctrine\ORM\EntityManagerInterface;
use Catalyst\ApiBundle\Controller\ApiController;
use Catalyst\ApiBundle\Component\Response as APIResponse;
use App\Entity\JobOrder;
use App\Entity\Warranty;
use App\Ramcar\JOStatus;
use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator;
class JobOrderController extends ApiController
{
protected $acl_gen;
public function __construct(ACLGenerator $acl_gen)
{
$this->acl_gen = $acl_gen;
}
public function getJobOrder($id, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('joborder.find', null, 'No access.');
$jo = $em->getRepository(JobOrder::class)->find($id);
if ($jo == null)
return new APIResponse(false, 'No job order found with that number.', null, 404);
$data = $this->generateJobOrderData($jo, $em);
return new APIResponse(true, 'Job order found.', $data);
}
protected function generateJobOrderData($jo, EntityManagerInterface $em)
{
// customer vehicle
$cv = $jo->getCustomerVehicle();
// customer information
$customer = $jo->getCustomer();
// hub
$hub_name = '';
$hub = $jo->getHub();
if ($hub != null)
$hub_name = $hub->getName();
// check if JO is fulfilled, if not, we leave date_purchase blank
$date_purchase = '';
$serial = '';
$status = $jo->getStatus();
if ($status == JOStatus::FULFILLED)
{
if ($jo->getDateFulfill() != null)
$date_purchase = $jo->getDateFulfill()->format('M d, Y H:i');
// find warranty to get the serial using plate number
$serial = $this->getSerialFromWarranty($cv->getPlateNumber(), $em);
}
$jo_data = [
'id' => $jo->getID(),
'first_name' => $customer->getFirstName(),
'last_name' => $customer->getLastName(),
'mobile_number' => $customer->getPhoneMobile(),
'email' => $customer->getEmail(),
'plate_number' => $cv->getPlateNumber(),
'date_purchase' => $date_purchase,
'address' => $jo->getDeliveryAddress(),
'hub' => $hub_name,
'serial' => $serial,
];
// invoice items
$items = [];
$jo_items = $jo->getInvoice()->getItems();
$non_battery_item_titles = ['Promo discount', 'Trade-in', 'Service'];
foreach ($jo_items as $item)
{
$item_title = $item->getTitle();
// check if title has Promo discount, Trade-in, or Service
$flag_battery = $this->checkIfBatteryInvoiceItem($item_title, $non_battery_item_titles);
if ($flag_battery == true)
{
$items[] = [
'title' => $item->getTitle(),
];
}
}
$jo_data['items'] = $items;
return $jo_data;
}
protected function checkIfBatteryInvoiceItem($item_title, $non_battery_item_titles)
{
foreach ($non_battery_item_titles as $nb_item_title)
{
$pos_result = stripos($item_title, $nb_item_title);
// if found, invoice item is not a battery item
if ($pos_result !== false)
return false;
}
return true;
}
protected function getSerialFromWarranty($plate_number, EntityManagerInterface $em)
{
// NOTE: Modify the search for the latest warranty. This seems hacky.
// get latest warranty using plate number
$warranty_results = $em->getRepository(Warranty::class)->findBy(
['plate_number' => $plate_number],
['date_create' => 'desc']
);
$serial = '';
if (!empty($warranty_results))
{
// get first entry
$warranty = current($warranty_results);
$serial = $warranty->getSerial();
}
return $serial;
}
}

View file

@ -23,9 +23,7 @@ use App\Entity\BatterySize;
use App\Entity\RiderAPISession;
use App\Entity\User;
use App\Entity\ApiUser as APIUser;
use App\Entity\JobOrder;
use App\Entity\SAPBattery;
use App\Entity\WarrantySerial;
use App\Service\RedisClientProvider;
use App\Service\RiderCache;
use App\Service\MQTTClient;
@ -36,7 +34,6 @@ use App\Service\JobOrderHandlerInterface;
use App\Service\InvoiceGeneratorInterface;
use App\Service\RisingTideGateway;
use App\Service\RiderTracker;
use App\Service\PriceTierManager;
use App\Ramcar\ServiceType;
use App\Ramcar\TradeInType;
@ -289,9 +286,8 @@ class RiderAppController extends ApiController
// do we have a job order?
// $jo = $rider->getActiveJobOrder();
// NOTE: we do not include job orders that have been cancelled
$jo = $rider->getCurrentJobOrder();
if ($jo == null || $jo->getStatus() == JOStatus::CANCELLED)
if ($jo == null)
{
$data = [
'job_order' => null
@ -386,7 +382,6 @@ class RiderAppController extends ApiController
'flag_coolant' => $jo->hasCoolant(),
'has_motolite' => $cv->hasMotoliteBattery(),
'delivery_status' => $jo->getDeliveryStatus(),
'flag_sealant' => $jo->hasSealant(),
]
];
}
@ -413,11 +408,6 @@ class RiderAppController extends ApiController
if (!empty($msg))
return new APIResponse(false, $msg);
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// TODO: refactor this into a jo handler class, so we don't have to repeat for control center
// set jo status to in transit
@ -468,11 +458,6 @@ class RiderAppController extends ApiController
// TODO: this is a workaround for requeue, because rider app gets stuck in accept / decline screen
return new APIResponse(true, $msg);
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// requeue it, instead of cancelling it
$jo->requeue();
@ -531,11 +516,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB);
@ -576,11 +556,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_PRE_JO);
@ -621,11 +596,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_PRE_JO);
@ -666,11 +636,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_START);
@ -712,11 +677,6 @@ class RiderAppController extends ApiController
// set jo status to in progress
$jo->setStatus(JOStatus::IN_PROGRESS);
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE);
@ -775,11 +735,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB);
@ -803,54 +758,6 @@ class RiderAppController extends ApiController
return new APIResponse(true, 'Rider arrive at hub.', $data);
}
public function getBatterySizes(Request $req, EntityManagerInterface $em)
{
// get capi user
$capi_user = $this->getUser();
if ($capi_user == null)
return new APIResponse(false, 'User not found.');
// get rider id from capi user metadata
$rider = $this->getRiderFromCAPI($capi_user, $em);
if ($rider == null)
return new APIResponse(false, 'No rider found.');
// get sizes
$qb = $em->getRepository(BatterySize::class)
->createQueryBuilder('bs');
$sizes = $qb->select('bs.id, bs.name')
->orderBy('bs.name', 'asc')
->getQuery()
->getResult();
// response
return new APIResponse(true, '', [
'sizes' => $sizes,
]);
}
public function getTradeInTypes(Request $req, EntityManagerInterface $em)
{
// get capi user
$capi_user = $this->getUser();
if ($capi_user == null)
return new APIResponse(false, 'User not found.');
// get rider id from capi user metadata
$rider = $this->getRiderFromCAPI($capi_user, $em);
if ($rider == null)
return new APIResponse(false, 'No rider found.');
// get trade-in types
$types = TradeInType::getCollection();
// response
return new APIResponse(true, '', [
'types' => $types,
]);
}
public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler,
RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, TranslatorInterface $translator)
{
@ -870,22 +777,6 @@ class RiderAppController extends ApiController
if (!empty($msg))
return new APIResponse(false, $msg);
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// need to check if service type is battery sales
// if so, serial is a required parameter
$serial = $req->request->get('serial', '');
if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW)
{
/*
if (empty($serial))
return new APIResponse(false, 'Missing parameter(s): serial');
*/
}
// set invoice to paid
$jo->getInvoice()->setStatus(InvoiceStatus::PAID);
@ -937,6 +828,7 @@ class RiderAppController extends ApiController
// create warranty
if($jo_handler->checkIfNewBattery($jo))
{
$serial = null;
$warranty_class = $jo->getWarrantyClass();
$first_name = $jo->getCustomer()->getFirstName();
$last_name = $jo->getCustomer()->getLastName();
@ -1020,11 +912,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_POST_JO);
@ -1066,11 +953,6 @@ class RiderAppController extends ApiController
// get rider's current job order
$jo = $rider->getCurrentJobOrder();
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// set delivery status
$jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_POST_JO);
@ -1217,178 +1099,7 @@ class RiderAppController extends ApiController
return new APIResponse(true, 'Batteries found.', $data);
}
public function getBatteryInfo(Request $req, $serial, EntityManagerInterface $em)
{
if (empty($serial))
{
return new APIResponse(false, 'Missing parameter(s): serial');
}
// get capi user
$capi_user = $this->getUser();
if ($capi_user == null)
return new APIResponse(false, 'User not found.');
// get rider id from capi user metadata
$rider = $this->getRiderFromCAPI($capi_user, $em);
if ($rider == null)
return new APIResponse(false, 'No rider found.');
// find battery given serial/sap_code and flag_active is true
$serial = $em->getRepository(WarrantySerial::class)->find($serial);
if (empty($serial)) {
return new APIResponse(false, 'Warranty serial number not found.');
}
$sap_battery = $em->getRepository(SAPBattery::class)->find($serial->getSKU());
if (empty($sap_battery)) {
return new APIResponse(false, 'No battery info found.');
}
$battery = [
'id' => $sap_battery->getID(),
'brand' => $sap_battery->getBrand()->getName(),
'size' => $sap_battery->getSize()->getName(),
'size_id' => $sap_battery->getSize()->getID(),
'trade_in_type' => TradeInType::MOTOLITE,
'container_size' => $sap_battery->getContainerSize()->getName(),
];
return new APIResponse(true, 'Battery info found.', [
'battery' => $battery,
]);
}
public function updateJobOrder(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager)
{
$items = json_decode(file_get_contents('php://input'), true);
// get job order id
if (!isset($items['jo_id']))
return new APIResponse(false, 'Missing parameter(s): jo_id');
// validate jo_id
$jo_id = $items['jo_id'];
if (empty($jo_id) || $jo_id == null)
return new APIResponse(false, 'Missing parameter(s): jo_id');
// get capi user
$capi_user = $this->getUser();
if ($capi_user == null)
return new APIResponse(false, 'User not found.');
// get rider id from capi user metadata
$rider = $this->getRiderFromCAPI($capi_user, $em);
if ($rider == null)
return new APIResponse(false, 'No rider found.');
// get the job order
$jo = $em->getRepository(JobOrder::class)->find($jo_id);
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// check if we have trade in items
$ti_items = [];
if (isset($items['trade_in_items']))
{
// validate the trade in items first
$ti_items = $items['trade_in_items'];
$msg = $this->validateTradeInItems($em, $ti_items);
if (!empty($msg))
return new APIResponse(false, $msg);
}
// get the service type
if (!isset($items['stype_id']))
return new APIResponse(false, 'Missing parameter(s): stype_id');
// validate service type
$stype_id = $items['stype_id'];
if (!ServiceType::validate($stype_id))
return new APIResponse(false, 'Invalid service type - ' . $stype_id);
// save service type
$jo->setServiceType($stype_id);
// validate promo if any. Promo not required
$promo = null;
if (isset($items['promo_id']))
{
$promo_id = $items['promo_id'];
$promo = $em->getRepository(Promo::class)->find($promo_id);
if ($promo == null)
return new APIResponse(false, 'Invalid promo id - ' . $promo_id);
}
// get other parameters, if any: has motolite battery, has warranty doc, with coolant, payment method, with sealant
if (isset($items['flag_motolite_battery']))
{
// get customer vehicle from jo
$cv = $jo->getCustomerVehicle();
$has_motolite = $items['flag_motolite_battery'];
if ($has_motolite == 'true')
$cv->setHasMotoliteBattery(true);
else
$cv->setHasMotoliteBattery(false);
$em->persist($cv);
}
if (isset($items['flag_warranty_doc']))
{
// TODO: what do we do?
}
if (isset($items['flag_coolant']))
{
$has_coolant = $items['flag_coolant'];
if ($has_coolant == 'true')
$jo->setHasCoolant(true);
else
$jo->setHasCoolant(false);
}
if (isset($items['mode_of_payment']))
{
$payment_method = $items['payment_method'];
if (!ModeOfPayment::validate($payment_method))
$payment_method = ModeOfPayment::CASH;
$jo->setModeOfPayment($payment_method);
}
if (isset($items['flag_sealant']))
{
$has_sealant = $items['flag_sealant'];
if ($has_sealant == 'true')
$jo->setHasSealant(true);
else
$jo->setHasSealant(false);
}
// get capi user
$capi_user = $this->getUser();
if ($capi_user == null)
return new APIResponse(false, 'User not found.');
// get rider id from capi user metadata
$rider = $this->getRiderFromCAPI($capi_user, $em);
if ($rider == null)
return new APIResponse(false, 'No rider found.');
// need to get the existing invoice items using jo id and invoice id
$existing_ii = $this->getInvoiceItems($em, $jo);
$this->generateUpdatedInvoice($em, $ic, $jo, $existing_ii, $ti_items, $promo, $pt_manager);
$data = [];
return new APIResponse(true, 'Job order updated.', $data);
}
public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager)
public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic)
{
// $this->debugRequest($req);
@ -1409,11 +1120,6 @@ class RiderAppController extends ApiController
if (!empty($msg))
return new APIResponse(false, $msg);
// check if JO can be modified first
if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) {
return new APIResponse(false, 'Job order can no longer be modified.');
}
// check service type
$stype_id = $req->request->get('stype_id');
if (!ServiceType::validate($stype_id))
@ -1453,13 +1159,6 @@ class RiderAppController extends ApiController
else
$jo->setHasCoolant(false);
// sealant
$flag_sealant = $req->request->get('flag_sealant', 'false');
if ($flag_sealant == 'true')
$jo->setHasSealant(true);
else
$jo->setHasSealant(false);
// has motolite battery
$cv = $jo->getCustomerVehicle();
$has_motolite = $req->request->get('has_motolite', 'false');
@ -1504,10 +1203,6 @@ class RiderAppController extends ApiController
$crit->setHasCoolant($jo->hasCoolant());
$crit->setIsTaxable();
// set price tier
$pt_id = $pt_manager->getPriceTier($jo->getCoordinates());
$crit->setPriceTier($pt_id);
if ($promo != null)
$crit->addPromo($promo);
@ -1546,168 +1241,6 @@ class RiderAppController extends ApiController
return new APIResponse(true, 'Job order service changed.', $data);
}
protected function generateUpdatedInvoice(EntityManagerInterface $em, InvoiceGeneratorInterface $ic, JobOrder $jo, $existing_ii, $trade_in_items, $promo, PriceTierManager $pt_manager)
{
// get the service type
$stype = $jo->getServiceType();
// get the source
$source = $jo->getSource();
// get the customer vehicle
$cv = $jo->getCustomerVehicle();
// get coolant if any
$flag_coolant = $jo->hasCoolant();
// get sealant if any
$flag_sealant = $jo->hasSealant();
// check if new promo is null
if ($promo == null)
{
// promo not updated from app so check existing invoice
// get the promo id from existing invoice item
$promo_id = $existing_ii['promo_id'];
if ($promo_id == null)
$promo = null;
else
$promo = $em->getRepository(Promo::class)->find($promo_id);
}
// populate Invoice Criteria
$icrit = new InvoiceCriteria();
$icrit->setServiceType($stype)
->setCustomerVehicle($cv)
->setSource($source)
->setHasCoolant($flag_coolant)
->setHasSealant($flag_sealant)
->setIsTaxable();
// set price tier
$pt_id = $pt_manager->getPriceTier($jo->getCoordinates());
$icrit->setPriceTier($pt_id);
// at this point, all information should be valid
// assuming JO information is already valid since this
// is in the system already
// add promo if any to criteria
if ($promo != null)
$icrit->addPromo($promo);
// get the battery purchased from existing invoice items
// add the batteries ordered to criteria
$ii_items = $existing_ii['invoice_items'];
foreach ($ii_items as $ii_item)
{
$batt_id = $ii_item['batt_id'];
$qty = $ii_item['qty'];
$battery = $em->getRepository(Battery::class)->find($batt_id);
$icrit->addEntry($battery, null, $qty);
}
// add the trade in items to the criteria
foreach ($trade_in_items as $ti_item)
{
$batt_size_id = $ti_item['battery_size_id'];
$qty = $ti_item['qty'];
$trade_in_type = $ti_item['trade_in_type'];
$batt_size = $em->getRepository(BatterySize::class)->find($batt_size_id);
$icrit->addTradeInEntry($batt_size, $trade_in_type, $qty);
}
// call generateInvoice
$invoice = $ic->generateInvoice($icrit);
// remove previous invoice
$old_invoice = $jo->getInvoice();
$em->remove($old_invoice);
$em->flush();
// save new invoice
$jo->setInvoice($invoice);
$em->persist($invoice);
// log event?
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_EDIT)
->setJobOrder($jo)
->setRider($jo->getRider());
$em->persist($event);
$em->flush();
}
protected function getInvoiceItems(EntityManagerInterface $em, JobOrder $jo)
{
$jo_id = $jo->getID();
$conn = $em->getConnection();
// need to get the ordered battery id and quantity from invoice item
// and the promo from invoice
$query_sql = 'SELECT ii.battery_id AS battery_id, ii.qty AS qty, i.promo_id AS promo_id
FROM invoice_item ii, invoice i
WHERE ii.invoice_id = i.id
AND i.job_order_id = :jo_id
AND ii.battery_id IS NOT NULL';
$query_stmt = $conn->prepare($query_sql);
$query_stmt->bindValue('jo_id', $jo_id);
$results = $query_stmt->executeQuery();
$promo_id = null;
$invoice_items = [];
while ($row = $results->fetchAssociative())
{
$promo_id = $row['promo_id'];
$invoice_items[] = [
'batt_id' => $row['battery_id'],
'qty' => $row['qty'],
'trade_in' => ''
];
}
$data = [
'promo_id' => $promo_id,
'invoice_items' => $invoice_items
];
return $data;
}
protected function validateTradeInItems(EntityManagerInterface $em, $ti_items)
{
$msg = '';
foreach ($ti_items as $ti_item)
{
$bs_id = $ti_item['battery_size_id'];
$ti_type = $ti_item['trade_in_type'];
// validate the battery size id
$batt_size = $em->getRepository(BatterySize::class)->find($bs_id);
if ($batt_size == null)
{
$msg = 'Invalid battery size for trade in: ' . $bs_id;
return $msg;
}
// validate the trade in type
if (!TradeInType::validate($ti_type))
{
$msg = 'Invalid trade in type: ' . $ti_type;
return $msg;
}
}
return $msg;
}
protected function getCAPIUser($id, EntityManagerInterface $em)
{
$capi_user = $em->getRepository(APIUser::class)->find($id);
@ -1787,42 +1320,6 @@ class RiderAppController extends ApiController
return $msg;
}
protected function checkJOProgressionAllowed(EntityManagerInterface $em, JobOrder $jo, &$rider)
{
$allowed = true;
error_log("JO delivery status is " . $jo->getDeliveryStatus() . " (not allowed: " . DeliveryStatus::CANCELLED . ")");
error_log("JO status is " . $jo->getStatus() . " (not allowed: " . JOStatus::CANCELLED . ")");
// TODO: add more statuses to block if needed, hence. this is a failsafe in case MQTT is not working.
// check delivery status
switch ($jo->getDeliveryStatus())
{
case DeliveryStatus::CANCELLED:
$allowed = false;
break;
}
// check JO status as well
switch ($jo->getStatus())
{
case JOStatus::CANCELLED:
$allowed = false;
break;
}
// if this is the rider's current JO, set to null
if (!$allowed) {
if ($rider->getCurrentJobOrder() === $jo) {
$rider->setCurrentJobOrder();
$em->persist($rider);
$em->flush();
}
}
return $allowed;
}
protected function debugRequest(Request $req)
{
$all = $req->request->all();

View file

@ -18,7 +18,6 @@ use App\Ramcar\InsuranceApplicationStatus;
use App\Ramcar\InsuranceMVType;
use App\Ramcar\InsuranceClientType;
use App\Ramcar\TransactionStatus;
use App\Ramcar\InsuranceBodyType;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use DateTime;
@ -294,45 +293,6 @@ class InsuranceController extends ApiController
]);
}
public function getPremiumsBanner(Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
return new ApiResponse(true, '', [
'url' => $this->getParameter('insurance_premiums_banner_url'),
]);
}
public function getBodyTypes(Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
$bt_collection = InsuranceBodyType::getCollection();
$body_types = [];
// NOTE: formatting it this way to match how insurance third party API returns their own stuff, so it's all handled one way on the app
foreach ($bt_collection as $bt_key => $bt_name) {
$body_types[] = [
'id' => $bt_key,
'name' => $bt_name,
];
}
return new ApiResponse(true, '', [
'body_types' => $body_types,
]);
}
protected function getLineType($mv_type_id, $vehicle_use_type, $is_public = false)
{
$line = '';

View file

@ -4,23 +4,18 @@ namespace App\Controller\CustomerAppAPI;
use Symfony\Component\HttpFoundation\Request;
use Catalyst\ApiBundle\Component\Response as ApiResponse;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Service\InvoiceGeneratorInterface;
use App\Service\PriceTierManager;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\TradeInType;
use App\Ramcar\TransactionOrigin;
use App\Entity\CustomerVehicle;
use App\Entity\Promo;
use App\Entity\Battery;
use App\Entity\BatterySize;
use App\Entity\Customer;
use App\Entity\CustomerMetadata;
class InvoiceController extends ApiController
{
public function getEstimate(Request $req, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager)
public function getEstimate(Request $req, InvoiceGeneratorInterface $ic)
{
// $this->debugRequest($req);
@ -41,18 +36,6 @@ class InvoiceController extends ApiController
return new ApiResponse(false, 'No customer information found.');
}
// get customer location from customer_metadata using customer id
$lng = $req->request->get('longitude');
$lat = $req->request->get('latitude');
if ((empty($lng)) || (empty($lat)))
{
// use customer metadata location as basis
$coordinates = $this->getCustomerMetadata($cust);
}
else
$coordinates = new Point($lng, $lat);
// make invoice criteria
$icrit = new InvoiceCriteria();
$icrit->setServiceType($req->request->get('service_type'));
@ -120,8 +103,7 @@ class InvoiceController extends ApiController
if (!empty($trade_in_type) && !empty($trade_in_batt)) {
$ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt);
if (!empty($ti_batt_obj)) {
$ti_batt_size_obj = $ti_batt_obj->getSize();
$icrit->addTradeInEntry($ti_batt_size_obj, $trade_in_type, 1);
$icrit->addEntry($ti_batt_obj, $trade_in_type, 1);
}
}
@ -131,18 +113,6 @@ class InvoiceController extends ApiController
// set JO source
$icrit->setSource(TransactionOrigin::MOBILE_APP);
// set price tier
$pt_id = 0;
if ($coordinates != null)
{
error_log('coordinates are not null');
$pt_id = $pt_manager->getPriceTier($coordinates);
}
else
error_log('null?');
$icrit->setPriceTier($pt_id);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
@ -178,28 +148,4 @@ class InvoiceController extends ApiController
// response
return new ApiResponse(true, '', $data);
}
protected function getCustomerMetadata(Customer $cust)
{
$coordinates = null;
// check if customer already has existing metadata
$c_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]);
if ($c_meta != null)
{
$meta_data = $c_meta->getAllMetaInfo();
foreach ($meta_data as $m_info)
{
if ((isset($m_info['longitude'])) && (isset($m_info['latitude'])))
{
$lng = $m_info['longitude'];
$lat = $m_info['latitude'];
$coordinates = new Point($lng, $lat);
}
}
}
return $coordinates;
}
}

View file

@ -21,7 +21,6 @@ use App\Service\HubDistributor;
use App\Service\HubFilterLogger;
use App\Service\HubFilteringGeoChecker;
use App\Service\JobOrderManager;
use App\Service\PriceTierManager;
use App\Ramcar\ServiceType;
use App\Ramcar\APIRiderStatus;
use App\Ramcar\InvoiceCriteria;
@ -35,7 +34,6 @@ use App\Ramcar\WarrantyClass;
use App\Ramcar\HubCriteria;
use App\Ramcar\DeliveryStatus;
use App\Entity\Battery;
use App\Entity\BatterySize;
use App\Entity\Hub;
use App\Entity\Promo;
use App\Entity\JOEvent;
@ -486,11 +484,8 @@ class JobOrderController extends ApiController
HubDistributor $hub_dist,
HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence,
JobOrderManager $jo_manager,
PriceTierManager $pt_manager
JobOrderManager $jo_manager
) {
//error_log("CREATING JOB ORDER WITH PARAMS " . print_r($req->request->all(), true));
// validate params
$validity = $this->validateRequest($req, [
'service_type',
@ -578,8 +573,6 @@ class JobOrderController extends ApiController
$flag_advance_order = true;
// $flag_advance_order = $advance_order ? true : false;
//error_log("RUNNING QUERY NEXT");
$jo = new JobOrder();
$jo->setSource(TransactionOrigin::MOBILE_APP)
->setStatus(JOStatus::PENDING)
@ -648,8 +641,6 @@ class JobOrderController extends ApiController
$icrit->addPromo($promo);
}
//error_log("CONTINUING QUERY BUILDING");
// check customer vehicle
$cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id'));
if ($cv == null) {
@ -697,8 +688,7 @@ class JobOrderController extends ApiController
if (!empty($trade_in_type) && !empty($trade_in_batt)) {
$ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt);
if (!empty($ti_batt_obj)) {
$ti_batt_size_obj = $ti_batt_obj->getSize();
$icrit->addTradeInEntry($ti_batt_size_obj, $trade_in_type, 1);
$icrit->addEntry($ti_batt_obj, $trade_in_type, 1);
}
}
@ -708,38 +698,17 @@ class JobOrderController extends ApiController
// set JO source
$icrit->setSource(TransactionOrigin::MOBILE_APP);
// set price tier
$pt_id = $pt_manager->getPriceTier($jo->getCoordinates());
$icrit->setPriceTier($pt_id);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
$jo->setInvoice($invoice);
//error_log("GENERATED INVOICE");
// save here first so we have a JO ID which is required for the hub selector
$this->em->persist($invoice);
$this->em->persist($jo);
$this->em->flush();
// assign hub and rider
// check if hub is null
if ($hub == null) {
//error_log("NO HUB");
// TODO: need to factor out the setting of HubCriteria fields
$hub_criteria = new HubCriteria();
$hub_criteria->setPoint($jo->getCoordinates());
// set job order info
$hub_criteria->setJobOrderId($jo->getID())
->setJoType($jo->getServiceType())
->setJoOrigin($jo->getSource())
->setCustomerClass($cust->getCustomerClassification())
->setOrderDate($jo->getDateCreate())
->setServiceType($jo->getServiceType());
// get distance limit for mobile from env
// get value of hub_filter_enable from env
$limit_distance = $_ENV['CUST_DISTANCE_LIMIT'];
@ -777,10 +746,6 @@ class JobOrderController extends ApiController
$hub_criteria->setCustomerId($customer_id);
// set filter flags for inventory and available riders
$hub_criteria->setInventoryCheck();
$hub_criteria->setRidersCheck();
// find nearest hubs
$nearest_hubs = $hub_select->find($hub_criteria);
@ -859,8 +824,6 @@ class JobOrderController extends ApiController
}
}
} else {
//error_log("HAS HUB: " . $hub->getID());
$jo->setHub($hub);
$jo->setStatus(JOStatus::RIDER_ASSIGN);
$jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED);
@ -872,10 +835,8 @@ class JobOrderController extends ApiController
$hub_dist->incrementJoCountForHub($hub);
}
//error_log("DONE SELECTING HUB");
// save additional hub related changes
$this->em->persist($jo);
$this->em->persist($invoice);
// add event log for JO
$event = new JOEvent();
@ -986,8 +947,6 @@ class JobOrderController extends ApiController
}
}
//error_log("DONE CREATING JOB ORDER " . $jo->getID());
// response
return new ApiResponse(true, '', [
'jo_id' => $jo->getID(),
@ -1011,8 +970,7 @@ class JobOrderController extends ApiController
HubDistributor $hub_dist,
HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence,
JobOrderManager $jo_manager,
PriceTierManager $pt_manager
JobOrderManager $jo_manager
) {
// validate params
$validity = $this->validateRequest($req, [
@ -1159,8 +1117,7 @@ class JobOrderController extends ApiController
if (!empty($trade_in_type) && !empty($trade_in_batt)) {
$ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt);
if (!empty($ti_batt_obj)) {
$battery_size = $ti_batt_obj->getSize();
$icrit->addTradeInEntry($battery_size, $trade_in_type, 1);
$icrit->addEntry($ti_batt_obj, $trade_in_type, 1);
}
}
@ -1170,10 +1127,6 @@ class JobOrderController extends ApiController
// set JO source
$icrit->setSource(TransactionOrigin::MOBILE_APP);
// set price tier
$pt_id = $pt_manager->getPriceTier($jo->getCoordinates());
$icrit->setPriceTier($pt_id);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
$jo->setInvoice($invoice);

View file

@ -4,19 +4,16 @@ namespace App\Controller\CustomerAppAPI;
use Symfony\Component\HttpFoundation\Request;
use Catalyst\ApiBundle\Component\Response as ApiResponse;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Entity\CustomerVehicle;
use App\Entity\JobOrder;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
use App\Entity\ItemType;
use App\Ramcar\JOStatus;
use App\Ramcar\ServiceType;
use App\Ramcar\TradeInType;
use App\Ramcar\InsuranceApplicationStatus;
use App\Service\PayMongoConnector;
use App\Service\PriceTierManager;
use DateTime;
class VehicleController extends ApiController
@ -240,7 +237,7 @@ class VehicleController extends ApiController
]);
}
public function getCompatibleBatteries(Request $req, $vid, PriceTierManager $pt_manager)
public function getCompatibleBatteries(Request $req, $vid)
{
// validate params
$validity = $this->validateRequest($req);
@ -255,43 +252,11 @@ class VehicleController extends ApiController
return new ApiResponse(false, 'Invalid vehicle.');
}
// get location from request
$lng = $req->query->get('longitude', '');
$lat = $req->query->get('latitude', '');
$batts = $vehicle->getActiveBatteries();
$pt_id = 0;
if ((!(empty($lng))) && (!(empty($lat))))
{
// get the price tier
$coordinates = new Point($lng, $lat);
$pt_id = $pt_manager->getPriceTier($coordinates);
}
// batteries
$batt_list = [];
$batts = $vehicle->getActiveBatteries();
foreach ($batts as $batt) {
// TODO: Add warranty_tnv to battery information
// check if customer location is in a price tier location
if ($pt_id == 0)
$price = $batt->getSellingPrice();
else
{
// get item type for battery
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']);
if ($item_type == null)
$price = $batt->getSellingPrice();
else
{
$item_type_id = $item_type->getID();
$batt_id = $batt->getID();
// find the item price given price tier id and battery id
$price = $pt_manager->getItemPrice($pt_id, $item_type_id, $batt_id);
}
}
$batt_list[] = [
'id' => $batt->getID(),
'mfg_id' => $batt->getManufacturer()->getID(),
@ -300,7 +265,7 @@ class VehicleController extends ApiController
'model_name' => $batt->getModel()->getName(),
'size_id' => $batt->getSize()->getID(),
'size_name' => $batt->getSize()->getName(),
'price' => $price,
'price' => $batt->getSellingPrice(),
'wty_private' => $batt->getWarrantyPrivate(),
'wty_commercial' => $batt->getWarrantyCommercial(),
'image_url' => $this->getBatteryImageURL($req, $batt),

View file

@ -4,7 +4,6 @@ namespace App\Controller;
use App\Ramcar\InsuranceApplicationStatus;
use App\Service\FCMSender;
use App\Service\InsuranceConnector;
use App\Entity\InsuranceApplication;
use Doctrine\ORM\EntityManagerInterface;
@ -16,13 +15,11 @@ use DateTime;
class InsuranceController extends Controller
{
protected $ic;
protected $em;
protected $fcmclient;
public function __construct(InsuranceConnector $ic, EntityManagerInterface $em, FCMSender $fcmclient)
public function __construct(EntityManagerInterface $em, FCMSender $fcmclient)
{
$this->ic = $ic;
$this->em = $em;
$this->fcmclient = $fcmclient;
}
@ -31,8 +28,17 @@ class InsuranceController extends Controller
{
$payload = $req->request->all();
// log this callback
$this->ic->log('CALLBACK', "[]", json_encode($payload), 'callback');
// DEBUG
@file_put_contents(__DIR__ . '/../../var/log/insurance.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND);
error_log(print_r($payload, true));
/*
return $this->json([
'success' => true,
]);
*/
// END DEBUG
// if no transaction code given, silently fail
if (empty($payload['transaction_code'])) {

View file

@ -1,269 +0,0 @@
<?php
namespace App\Controller;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Catalyst\MenuBundle\Annotation\Menu;
use App\Entity\PriceTier;
use App\Entity\Battery;
use App\Entity\ServiceOffering;
use App\Entity\ItemType;
use App\Entity\ItemPrice;
class ItemPricingController extends Controller
{
/**
* @Menu(selected="item_pricing")
* @IsGranted("item_pricing.update")
*/
public function index (EntityManagerInterface $em)
{
// get all the price tiers
$price_tiers = $em->getRepository(PriceTier::class)->findAll();
// get all item types
$item_types = $em->getRepository(ItemType::class)->findBy([], ['name' => 'asc']);
// get all the items/batteries
// load only batteries upon initial loading
$items = $this->getBatteries($em);
// set the default item type to battery
$default_it = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']);
$params = [
'sets' => [
'price_tiers' => $price_tiers,
'item_types' => $item_types,
],
'items' => $items,
'default_item_type_id' => $default_it->getID(),
];
return $this->render('item-pricing/form.html.twig', $params);
}
/**
* @Menu(selected="item_pricing")
* @IsGranted("item_pricing.update")
*/
public function formSubmit(Request $req, EntityManagerInterface $em)
{
$pt_id = $req->request->get('price_tier_id');
$it_id = $req->request->get('item_type_id');
$prices = $req->request->get('price');
// get the item type
$item_type = $em->getRepository(ItemType::class)->find($it_id);
if ($item_type->getCode() == 'battery')
{
// get batteries
$items = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']);
}
else
{
// get service offerings
$items = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']);
}
// on default price tier
if ($pt_id == 0)
{
// default price tier, update battery or service offering, depending on item type
// NOTE: battery and service offering prices or fees are stored as decimal.
foreach ($items as $item)
{
$item_id = $item->getID();
if (isset($prices[$item_id]))
{
// check item type
if ($item_type->getCode() == 'battery')
$item->setSellingPrice($prices[$item_id]);
else
$item->setFee($prices[$item_id]);
}
}
}
else
{
// get the price tier
$price_tier = $em->getRepository(PriceTier::class)->find($pt_id);
$item_prices = $price_tier->getItemPrices();
// clear the tier's item prices for the specific item type
foreach ($item_prices as $ip)
{
if ($ip->getItemType() == $item_type)
$em->remove($ip);
}
// update the tier's item prices
foreach ($items as $item)
{
$item_id = $item->getID();
$item_price = new ItemPrice();
$item_price->setItemType($item_type)
->setPriceTier($price_tier)
->setItemID($item_id);
if (isset($prices[$item_id]))
{
$item_price->setPrice($prices[$item_id] * 100);
}
else
{
$item_price->setPrice($item->getPrice() * 100);
}
// save
$em->persist($item_price);
}
}
$em->flush();
return $this->redirectToRoute('item_pricing');
}
/**
* @IsGranted("item_pricing.update")
*/
public function itemPrices(EntityManagerInterface $em, $pt_id, $it_id)
{
$pt_prices = [];
// get the item type
$it = $em->getRepository(ItemType::class)->find($it_id);
// check if default prices are needed
if ($pt_id != 0)
{
// get the price tier
$pt = $em->getRepository(PriceTier::class)->find($pt_id);
// get the items under the price tier
$pt_items = $pt->getItemPrices();
foreach ($pt_items as $pt_item)
{
// make item price hash
$pt_prices[$pt_item->getItemID()] = $pt_item->getPrice();
}
}
// get the prices from battery or service offering, depending on item type
if ($it->getCode() == 'battery')
{
// get batteries
$items = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']);
}
else
{
// get service offerings
$items = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']);
}
$data_items = [];
foreach ($items as $item)
{
$item_id = $item->getID();
// get default price
if ($it->getCode() == 'battery')
{
$price = $item->getSellingPrice();
$name = $item->getModel()->getName() . ' ' . $item->getSize()->getName();
}
else
{
$price = $item->getFee();
$name = $item->getName();
}
// check if tier has price for item
if (isset($pt_prices[$item_id]))
{
$pt_price = $pt_prices[$item_id];
// actual price
$price = number_format($pt_price / 100, 2, '.', '');
}
$actual_price = $price;
$data_items[] = [
'id' => $item_id,
'name' => $name,
'item_type_id' => $it->getID(),
'item_type' => $it->getName(),
'price' => $actual_price,
];
}
// response
return new JsonResponse([
'items' => $data_items,
]);
}
protected function getBatteries(EntityManagerInterface $em)
{
// get the item type for battery
$batt_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']);
// get all active batteries
$batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']);
foreach ($batts as $batt)
{
$batt_set[$batt->getID()] = [
'name' => $batt->getModel()->getName() . ' ' . $batt->getSize()->getName(),
'item_type_id' => $batt_item_type->getID(),
'item_type' => $batt_item_type->getName(),
'price' => $batt->getSellingPrice(),
];
}
return [
'items' => $batt_set,
];
}
protected function getServiceOfferings(EntityeManagerInterface $em)
{
// get the item type for service offering
$service_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
// get all service offerings
$services = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']);
$service_set = [];
foreach ($services as $service)
{
$service_set[$service->getID()] = [
'name' => $service->getName(),
'item_type_id' => $service_item_type->getID(),
'item_type' => $service_item_type->getName(),
'price' => $service->getFee(),
];
}
return [
'items' => $service_set,
];
}
}

View file

@ -1,251 +0,0 @@
<?php
namespace App\Controller;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use App\Entity\ItemType;
use Catalyst\MenuBundle\Annotation\Menu;
class ItemTypeController extends Controller
{
/**
* @Menu(selected="item_type_list")
* @IsGranted("item_type.list")
*/
public function index ()
{
return $this->render('item-type/list.html.twig');
}
/**
* @IsGranted("item_type.list")
*/
public function datatableRows(Request $req)
{
// get query builder
$qb = $this->getDoctrine()
->getRepository(ItemType::class)
->createQueryBuilder('q');
// get datatable params
$datatable = $req->request->get('datatable');
// count total records
$tquery = $qb->select('COUNT(q)');
$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');
$this->setQueryFilters($datatable, $query);
// check if sorting is present, otherwise use default
if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) {
$order = $datatable['sort']['sort'] ?? 'asc';
$query->orderBy('q.' . $datatable['sort']['field'], $order);
} else {
$query->orderBy('q.id', 'asc');
}
// get rows for this page
$obj_rows = $query->setFirstResult($offset)
->setMaxResults($perpage)
->getQuery()
->getResult();
// process rows
$rows = [];
foreach ($obj_rows as $orow) {
// add row data
$row['id'] = $orow->getID();
$row['name'] = $orow->getName();
// add row metadata
$row['meta'] = [
'update_url' => '',
'delete_url' => ''
];
// add crud urls
if ($this->isGranted('item_type.update'))
$row['meta']['update_url'] = $this->generateUrl('item_type_update_form', ['id' => $row['id']]);
if ($this->isGranted('item_type.delete'))
$row['meta']['delete_url'] = $this->generateUrl('item_type_delete', ['id' => $row['id']]);
$rows[] = $row;
}
// response
return $this->json([
'meta' => $meta,
'data' => $rows
]);
}
/**
* @Menu(selected="item_type.list")
* @IsGranted("item_type.add")
*/
public function addForm()
{
$item_type = new ItemType();
$params = [
'obj' => $item_type,
'mode' => 'create',
];
// response
return $this->render('item-type/form.html.twig', $params);
}
/**
* @IsGranted("item_type.add")
*/
public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator)
{
$item_type = new ItemType();
$this->setObject($item_type, $req);
// validate
$errors = $validator->validate($item_type);
// initialize error list
$error_array = [];
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// validated! save the entity
$em->persist($item_type);
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @Menu(selected="item_type_list")
* @ParamConverter("item_type", class="App\Entity\ItemType")
* @IsGranted("item_type.update")
*/
public function updateForm($id, EntityManagerInterface $em, ItemType $item_type)
{
$params = [];
$params['obj'] = $item_type;
$params['mode'] = 'update';
// response
return $this->render('item-type/form.html.twig', $params);
}
/**
* @ParamConverter("item_type", class="App\Entity\ItemType")
* @IsGranted("item_type.update")
*/
public function updateSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator, ItemType $item_type)
{
$this->setObject($item_type, $req);
// validate
$errors = $validator->validate($item_type);
// initialize error list
$error_array = [];
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @ParamConverter("item_type", class="App\Entity\ItemType")
* @IsGranted("item_type.delete")
*/
public function deleteSubmit(EntityManagerInterface $em, ItemType $item_type)
{
// delete this object
$em->remove($item_type);
$em->flush();
// response
$response = new Response();
$response->setStatusCode(Response::HTTP_OK);
$response->send();
}
protected function setObject(ItemType $obj, Request $req)
{
// set and save values
$obj->setName($req->request->get('name'))
->setCode($req->request->get('code'));
}
protected function setQueryFilters($datatable, QueryBuilder $query)
{
if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) {
$query->where('q.name LIKE :filter')
->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%');
}
}
}

View file

@ -30,7 +30,6 @@ use App\Service\HubSelector;
use App\Service\RiderTracker;
use App\Service\MotivConnector;
use App\Service\PriceTierManager;
use App\Service\GeofenceTracker;
use Symfony\Component\HttpFoundation\Request;
@ -43,8 +42,6 @@ use Doctrine\ORM\EntityManagerInterface;
use Catalyst\MenuBundle\Annotation\Menu;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
class JobOrderController extends Controller
{
public function getJobOrders(Request $req, JobOrderHandlerInterface $jo_handler)
@ -744,7 +741,7 @@ class JobOrderController extends Controller
}
public function generateInvoice(Request $req, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager)
public function generateInvoice(Request $req, InvoiceGeneratorInterface $ic)
{
// error_log('generating invoice...');
$error = false;
@ -754,21 +751,6 @@ class JobOrderController extends Controller
$promo_id = $req->request->get('promo');
$cvid = $req->request->get('cvid');
$service_charges = $req->request->get('service_charges', []);
$flag_coolant = $req->request->get('flag_coolant', false);
$flag_sealant = $req->request->get('flag_sealant', false);
// coordinates
// need to check if lng and lat are set
$lng = $req->request->get('coord_lng', 0);
$lat = $req->request->get('coord_lat', 0);
$price_tier = 0;
if (!empty($lng) && !empty($lat))
{
$coordinates = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
$price_tier = $pt_manager->getPriceTier($coordinates);
}
$em = $this->getDoctrine()->getManager();
@ -785,10 +767,8 @@ class JobOrderController extends Controller
$criteria->setServiceType($stype)
->setCustomerVehicle($cv)
->setIsTaxable()
->setSource(TransactionOrigin::CALL)
->setPriceTier($price_tier)
->setHasCoolant($flag_coolant)
->setHasSealant($flag_sealant);
->setSource(TransactionOrigin::CALL);
/*
// if it's a jumpstart or troubleshoot only, we know what to charge already

View file

@ -4,23 +4,18 @@ namespace App\Controller;
use App\Entity\GatewayTransaction;
use App\Ramcar\TransactionStatus;
use App\Service\PayMongoConnector;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use DateTime;
class PayMongoController extends Controller
{
protected $pm;
protected $em;
public function __construct(PayMongoConnector $pm, EntityManagerInterface $em)
public function __construct(EntityManagerInterface $em)
{
$this->pm = $pm;
$this->em = $em;
}
@ -28,8 +23,16 @@ class PayMongoController extends Controller
{
$payload = json_decode($req->getContent(), true);
// log this callback
$this->pm->log('CALLBACK', "[]", $req->getContent(), 'callback');
// DEBUG
@file_put_contents(__DIR__ . '/../../var/log/paymongo.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND);
/*
return $this->json([
'success' => true,
]);
*/
// END DEBUG
// if no event type given, silently fail
if (empty($payload['data'])) {
@ -47,8 +50,10 @@ class PayMongoController extends Controller
switch ($event_name) {
case "payment.paid":
return $this->handlePaymentPaid($event);
break;
case "payment.failed":
return $this->handlePaymentFailed($event);
return $this->handlePaymentPaid($event);
break;
case "payment.refunded": // TODO: handle refunds
case "payment.refund.updated":
case "checkout_session.payment.paid":
@ -69,7 +74,6 @@ class PayMongoController extends Controller
if (!empty($obj)) {
// mark as paid
$obj->setStatus(TransactionStatus::PAID);
$obj->setDatePay(new DateTime());
$this->em->flush();
}
@ -78,7 +82,7 @@ class PayMongoController extends Controller
]);
}
protected function handlePaymentFailed($event)
protected function handlePaymentFailed(Request $req)
{
// TODO: do something about failed payments?
return $this->json([

View file

@ -1,355 +0,0 @@
<?php
namespace App\Controller;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Catalyst\MenuBundle\Annotation\Menu;
use App\Entity\PriceTier;
use App\Entity\SupportedArea;
class PriceTierController extends Controller
{
/**
* @Menu(selected="price_tier_list")
* @IsGranted("price_tier.list")
*/
public function index()
{
return $this->render('price-tier/list.html.twig');
}
/**
* @IsGranted("price_tier.list")
*/
public function datatableRows(Request $req)
{
// get query builder
$qb = $this->getDoctrine()
->getRepository(PriceTier::class)
->createQueryBuilder('q');
// get datatable params
$datatable = $req->request->get('datatable');
// count total records
$tquery = $qb->select('COUNT(q)');
$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');
$this->setQueryFilters($datatable, $query);
// check if sorting is present, otherwise use default
if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) {
$order = $datatable['sort']['sort'] ?? 'asc';
$query->orderBy('q.' . $datatable['sort']['field'], $order);
} else {
$query->orderBy('q.id', 'asc');
}
// get rows for this page
$obj_rows = $query->setFirstResult($offset)
->setMaxResults($perpage)
->getQuery()
->getResult();
// process rows
$rows = [];
foreach ($obj_rows as $orow) {
// add row data
$row['id'] = $orow->getID();
$row['name'] = $orow->getName();
// add row metadata
$row['meta'] = [
'update_url' => '',
'delete_url' => ''
];
// add crud urls
if ($this->isGranted('price_tier.update'))
$row['meta']['update_url'] = $this->generateUrl('price_tier_update_form', ['id' => $row['id']]);
if ($this->isGranted('service_offering.delete'))
$row['meta']['delete_url'] = $this->generateUrl('price_tier_delete', ['id' => $row['id']]);
$rows[] = $row;
}
// response
return $this->json([
'meta' => $meta,
'data' => $rows
]);
}
/**
* @Menu(selected="price_tier.list")
* @IsGranted("price_tier.add")
*/
public function addForm(EntityManagerInterface $em)
{
$pt = new PriceTier();
// get the supported areas
$sets = $this->generateFormSets($em);
$params = [
'obj' => $pt,
'sets' => $sets,
'mode' => 'create',
];
// response
return $this->render('price-tier/form.html.twig', $params);
}
/**
* @IsGranted("price_tier.add")
*/
public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator)
{
// initialize error list
$error_array = [];
$pt = new PriceTier();
$error_array = $this->validateRequest($em, $req);
$this->setObject($pt, $req);
// validate
$errors = $validator->validate($pt);
// 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($pt);
// set the price tier id for the selected supported areas
$this->updateSupportedAreas($em, $pt, $req);
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @Menu(selected="price_tier_list")
* @ParamConverter("pt", class="App\Entity\PriceTier")
* @IsGranted("price_tier.update")
*/
public function updateForm($id, EntityManagerInterface $em, PriceTier $pt)
{
// get the supported areas
$sets = $this->generateFormSets($em, $pt);
$params = [
'obj' => $pt,
'sets' => $sets,
'mode' => 'update',
];
// response
return $this->render('price-tier/form.html.twig', $params);
}
/**
* @ParamConverter("pt", class="App\Entity\PriceTier")
* @IsGranted("price_tier.update")
*/
public function updateSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator, PriceTier $pt)
{
// initialize error list
$error_array = [];
// clear supported areas of price tier
$this->clearPriceTierSupportedAreas($em, $pt);
$error_array = $this->validateRequest($em, $req);
$this->setObject($pt, $req);
// validate
$errors = $validator->validate($pt);
// 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 the price tier id for the selected supported areas
$this->updateSupportedAreas($em, $pt, $req);
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @ParamConverter("pt", class="App\Entity\PriceTier")
* @IsGranted("price_tier.delete")
*/
public function deleteSubmit(EntityManagerInterface $em, PriceTier $pt)
{
// clear supported areas of price tier
$this->clearPriceTierSupportedAreas($em, $pt);
// delete this object
$em->remove($pt);
$em->flush();
// response
$response = new Response();
$response->setStatusCode(Response::HTTP_OK);
$response->send();
}
protected function validateRequest(EntityManagerInterface $em, Request $req)
{
// get areas
$areas = $req->request->get('areas');
// check if no areas selected aka empty
if (!empty($areas))
{
foreach ($areas as $area_id)
{
$supported_area = $em->getRepository(SupportedArea::class)->find($area_id);
if ($supported_area == null)
return ['areas' => 'Invalid area'];
// check if supported area already belongs to a price tier
if ($supported_area->getPriceTier() != null)
return ['areas' => 'Area already belongs to a price tier.'];
}
}
return null;
}
protected function setObject(PriceTier $obj, Request $req)
{
// clear supported areas first
$obj->clearSupportedAreas();
$obj->setName($req->request->get('name'));
}
protected function clearPriceTierSupportedAreas(EntityManagerInterface $em, PriceTier $obj)
{
// find the supported areas set with the price tier
$areas = $em->getRepository(SupportedArea::class)->findBy(['price_tier' => $obj]);
if (!empty($areas))
{
// set the price tier id for the supported areas to null
foreach ($areas as $area)
{
$area->setPriceTier(null);
}
$em->flush();
}
}
protected function updateSupportedAreas(EntityManagerInterface $em, PriceTier $obj, Request $req)
{
// get the selected areas
$areas = $req->request->get('areas');
// check if no areas selected aka empty
if (!empty($areas))
{
foreach ($areas as $area_id)
{
// get supported area
$supported_area = $em->getRepository(SupportedArea::class)->find($area_id);
if ($supported_area != null)
$supported_area->setPriceTier($obj);
}
}
}
protected function generateFormSets(EntityManagerInterface $em, PriceTier $pt = null)
{
// get the supported areas with no price tier id or price tier id is set to the one that is being updated
$areas = $em->getRepository(SupportedArea::class)->findBy(['price_tier' => array(null, $pt)]);
$areas_set = [];
foreach ($areas as $area)
{
$areas_set[$area->getID()] = $area->getName();
}
return [
'areas' => $areas_set
];
}
protected function setQueryFilters($datatable, QueryBuilder $query)
{
if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) {
$query->where('q.name LIKE :filter')
->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%');
}
}
}

View file

@ -236,10 +236,6 @@ class ReportController extends Controller
$reason = $jor->getReason();
$dispatched_by = 'system';
if ($jor->getUser() != null)
$dispatched_by = $jor->getUser()->getFullName();
$res[] = [
$jo->getID(),
$jo->getDateSchedule()->format('m/d/Y H:i'),
@ -248,7 +244,7 @@ class ReportController extends Controller
JORejectionReason::getName($jor->getReason()),
$jor->getContactPerson(),
$jor->getRemarks(),
!empty($jor->getUser()) ? $jor->getUser()->getFullName() : "",
$jor->getUser()->getFullName(),
ServiceType::getName($jo->getServiceType()),
];
}

View file

@ -23,7 +23,6 @@ use App\Ramcar\JOStatus;
use App\Ramcar\ServiceType;
use App\Ramcar\WillingToWaitContent;
use App\Ramcar\CustomerClassification;
use App\Ramcar\CustomerNotWaitReason;
use DateTime;

View file

@ -13,11 +13,6 @@ use Catalyst\ApiBundle\Component\Response as APIResponse;
use App\Ramcar\APIResult;
use App\Entity\Vehicle;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator;
@ -30,7 +25,7 @@ class BatteryController extends ApiController
$this->acl_gen = $acl_gen;
}
public function getCompatibleBatteries(Request $req, $vid, EntityManagerInterface $em, PriceTierManager $pt_manager)
public function getCompatibleBatteries(Request $req, $vid, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_battery_compatible.list', null, 'No access.');
@ -48,44 +43,13 @@ class BatteryController extends ApiController
return new APIResponse(false, $message);
}
// get location from request
$lng = $req->request->get('longitude', '');
$lat = $req->request->get('latitude', '');
$batts = $vehicle->getActiveBatteries();
$pt_id = 0;
if ((!(empty($lng))) && (!(empty($lat))))
{
// get the price tier
$coordinates = new Point($lng, $lat);
$pt_id = $pt_manager->getPriceTier($coordinates);
}
// batteries
$batt_list = [];
// $batts = $vehicle->getBatteries();
$batts = $vehicle->getActiveBatteries();
foreach ($batts as $batt)
{
// TODO: Add warranty_tnv to battery information
// check if customer location is in a price tier location
if ($pt_id == 0)
$price = $batt->getSellingPrice();
else
{
// get item type for battery
$item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']);
if ($item_type == null)
$price = $batt->getSellingPrice();
else
{
$item_type_id = $item_type->getID();
$batt_id = $batt->getID();
// find the item price given price tier id and battery id
$price = $pt_manager->getItemPrice($pt_id, $item_type_id, $batt_id);
}
}
$batt_list[] = [
'id' => $batt->getID(),
'mfg_id' => $batt->getManufacturer()->getID(),
@ -94,7 +58,7 @@ class BatteryController extends ApiController
'model_name' => $batt->getModel()->getName(),
'size_id' => $batt->getSize()->getID(),
'size_name' => $batt->getSize()->getName(),
'price' => $price,
'price' => $batt->getSellingPrice(),
'wty_private' => $batt->getWarrantyPrivate(),
'wty_commercial' => $batt->getWarrantyCommercial(),
'image_url' => $this->getBatteryImageURL($req, $batt),

View file

@ -46,7 +46,6 @@ use App\Service\RiderTracker;
use App\Service\PromoLogger;
use App\Service\MapTools;
use App\Service\JobOrderManager;
use App\Service\PriceTierManager;
use App\Entity\JobOrder;
use App\Entity\CustomerVehicle;
@ -80,8 +79,7 @@ class JobOrderController extends ApiController
FCMSender $fcmclient,
RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger,
HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em, JobOrderManager $jo_manager,
PriceTierManager $pt_manager)
HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em, JobOrderManager $jo_manager)
{
$this->denyAccessUnlessGranted('tapi_jo.request', null, 'No access.');
@ -167,17 +165,7 @@ class JobOrderController extends ApiController
// set JO source
$icrit->setSource(TransactionOrigin::THIRD_PARTY);
// set price tier
$pt_id = $pt_manager->getPriceTier($jo->getCoordinates());
$icrit->setPriceTier($pt_id);
// add the actual battery item first
$icrit->addEntry($data['batt'], null, 1);
// if we have a trade in, add it as well, assuming trade in battery == battery purchased
if (!empty($data['trade_in_type'])) {
$icrit->addEntry($data['batt'], $data['trade_in_type'], 1);
}
$icrit->addEntry($data['batt'], $data['trade_in_type'], 1);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
@ -239,10 +227,6 @@ class JobOrderController extends ApiController
// find nearest hubs
$nearest_hubs = $hub_select->find($hub_criteria);
// set filter flags for inventory and available riders
$hub_criteria->setInventoryCheck();
$hub_criteria->setRidersCheck();
if (!empty($nearest_hubs))
{
// go through the hub list, find the nearest hub

View file

@ -114,9 +114,7 @@ class Hub
public function getAvailableRiders()
{
$crit = Criteria::create();
$crit->where(Criteria::expr()->eq('flag_available', true))
->where(Criteria::expr()->eq('flag_active', true))
->where(Criteria::expr()->eq('current_job_order', null));
$crit->where(Criteria::expr()->eq('flag_available', true));
return $this->riders->matching($crit);
}

View file

@ -50,24 +50,10 @@ class InvoiceItem
*/
protected $battery;
// battery size for trade in items
/**
* @ORM\ManyToOne(targetEntity="BatterySize")
* @ORM\JoinColumn(name="battery_size_id", referencedColumnName="id")
*/
protected $battery_size;
// trade in type
/**
* @ORM\Column(type="string", length=20)
*/
protected $trade_in_type;
public function __construct()
{
$this->title = '';
$this->price = 0.0;
$this->trade_in_type = '';
}
public function getID()
@ -129,26 +115,4 @@ class InvoiceItem
{
return $this->battery;
}
public function setBatterySize(BatterySize $battery_size)
{
$this->battery_size = $battery_size;
return $this;
}
public function getBatterySize()
{
return $this->battery_size;
}
public function setTradeInType(string $trade_in_type)
{
$this->trade_in_type = $trade_in_type;
return $this;
}
public function getTradeInType()
{
return $this->trade_in_type;
}
}

View file

@ -1,97 +0,0 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="item_price")
*/
class ItemPrice
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="item_prices")
* @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id")
*/
protected $price_tier;
// item type
/**
* @ORM\ManyToOne(targetEntity="ItemType", inversedBy="items")
* @ORM\JoinColumn(name="item_type_id", referencedColumnName="id")
*/
protected $item_type;
// could be battery id or service offering id, loosely coupled
/**
* @ORM\Column(type="integer")
*/
protected $item_id;
// current price
// NOTE: we need to move the decimal point two places to the left to get actual value
// we want to avoid floating point problems
/**
* @ORM\Column(type="integer")
*/
protected $price;
public function getID()
{
return $this->id;
}
public function setPriceTier(PriceTier $price_tier)
{
$this->price_tier = $price_tier;
return $this;
}
public function getPriceTier()
{
return $this->price_tier;
}
public function setItemType(ItemType $item_type)
{
$this->item_type = $item_type;
return $this;
}
public function getItemType()
{
return $this->item_type;
}
public function setItemID($item_id)
{
$this->item_id = $item_id;
return $this;
}
public function getItemID()
{
return $this->item_id;
}
public function setPrice($price)
{
$this->price = $price;
return $this;
}
public function getPrice()
{
return $this->price;
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="item_type", indexes={
* @ORM\Index(name="item_type_idx", columns={"code"})
* })
*/
class ItemType
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $name;
/**
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $code;
// items under an item type
/**
* @ORM\OneToMany(targetEntity="ItemPrice", mappedBy="item_type")
*/
protected $items;
public function __construct()
{
$this->code = '';
}
public function getID()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setCode($code)
{
$this->code = $code;
return $this;
}
public function getCode()
{
return $this->code;
}
public function getItems()
{
return $this->items;
}
}

View file

@ -441,12 +441,6 @@ class JobOrder
*/
protected $flag_cust_new;
// only for tire service, if it requires sealant or not
/**
* @ORM\Column(type="boolean")
*/
protected $flag_sealant;
public function __construct()
{
$this->date_create = new DateTime();
@ -464,7 +458,6 @@ class JobOrder
$this->trade_in_type = null;
$this->flag_rider_rating = false;
$this->flag_coolant = false;
$this->flag_sealant = false;
$this->priority = 0;
$this->meta = [];
@ -1263,15 +1256,4 @@ class JobOrder
return $this->flag_cust_new;
}
public function setHasSealant($flag = true)
{
$this->flag_sealant = $flag;
return $this;
}
public function hasSealant()
{
return $this->flag_sealant;
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity
* @ORM\Table(name="price_tier")
*/
class PriceTier
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// name of price tier
/**
* @ORM\Column(type="string", length=80)
*/
protected $name;
// supported areas under price tier
/**
* @ORM\OneToMany(targetEntity="SupportedArea", mappedBy="price_tier");
*/
protected $supported_areas;
// items under a price tier
/**
* @ORM\OneToMany(targetEntity="ItemPrice", mappedBy="price_tier")
*/
protected $item_prices;
public function __construct()
{
$this->supported_areas = new ArrayCollection();
$this->items = new ArrayCollection();
}
public function getID()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function getSupportedAreaObjects()
{
return $this->supported_areas;
}
public function getSupportedAreas()
{
$str_supported_areas = [];
foreach ($this->supported_areas as $supported_area)
$str_supported_areas[] = $supported_area->getID();
return $str_supported_areas;
}
public function clearSupportedAreas()
{
$this->supported_areas->clear();
return $this;
}
public function getItemPrices()
{
return $this->item_prices;
}
}

View file

@ -6,7 +6,6 @@ use Catalyst\AuthBundle\Entity\Role as BaseRole;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
@ -17,19 +16,6 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
*/
class Role extends BaseRole
{
/**
* @ORM\Id
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $id;
/**
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $name;
/**
* @ORM\ManyToMany(targetEntity="User", mappedBy="roles", fetch="EXTRA_LAZY")
*/

View file

@ -39,24 +39,9 @@ class SupportedArea
*/
protected $coverage_area;
// prevent certain hub filters from being used
/**
* @ORM\Column(type="json", nullable=true)
*/
protected $hub_filter_exceptions;
/**
* @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="supported_areas")
* @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id", nullable=true)
*/
protected $price_tier;
public function __construct()
{
$this->date_create = new DateTime();
$this->price_tier = null;
$this->hub_filter_exceptions = [];
}
public function getID()
@ -72,7 +57,7 @@ class SupportedArea
public function getDateCreate()
{
return $this->date_create;
return $this->date_Create;
}
public function setName($name)
@ -97,27 +82,5 @@ class SupportedArea
{
return $this->coverage_area;
}
public function setPriceTier(PriceTier $price_tier = null)
{
$this->price_tier = $price_tier;
return $this;
}
public function getPriceTier()
{
return $this->price_tier;
}
public function setHubFilterExceptions($exceptions)
{
$this->hub_filter_exceptions = $exceptions;
return $this;
}
public function getHubFilterExceptions()
{
return $this->hub_filter_exceptions;
}
}

View file

@ -58,20 +58,17 @@ class GatewayTransactionListener
'gateway_transaction' => $gt_obj,
]);
// make sure the object exists and has not been processed yet
if (!empty($obj) && $obj->getStatus() === InsuranceApplicationStatus::CREATED) {
if (!empty($obj)) {
// mark as paid
$obj->setDatePay(new DateTime());
$obj->setStatus(InsuranceApplicationStatus::PAID);
$this->em->flush();
}
// flag on api as paid
$result = $this->ic->tagApplicationPaid($obj->getExtTransactionId());
// something went wrong with insurance api
if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') {
error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']);
}
// flag on api as paid
$result = $this->ic->tagApplicationPaid($obj->getID());
if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') {
error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']);
}
}
}

View file

@ -11,19 +11,14 @@ use App\Ramcar\TradeInType;
use App\Entity\Battery;
use App\Entity\ServiceOffering;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class BatteryReplacementWarranty implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -34,7 +29,6 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
@ -46,14 +40,7 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface
{
$batt = $entry['battery'];
$qty = 1;
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id);
if ($pt_price == null)
$price = $this->getServiceTypeFee();
else
$price = $pt_price;
$price = $this->getServiceTypeFee();
$items[] = [
'service_type' => $this->getID(),
@ -116,46 +103,20 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface
$qty = $item['quantity'];
if ($qty < 1)
continue;
if (empty($item['trade_in']))
{
// 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);
}
$criteria->addEntry($battery, $trade_in, $qty);
}
}
return null;
}
protected function getPriceTierItemPrice($pt_id)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
$code = 'battery_replacement_warranty_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getTitle($battery)
{
$title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' - Service Unit';

View file

@ -10,19 +10,14 @@ use App\Ramcar\TradeInType;
use App\Ramcar\ServiceType;
use App\Entity\Battery;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class BatterySales implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -33,7 +28,6 @@ class BatterySales implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$pt = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
@ -42,28 +36,19 @@ class BatterySales implements InvoiceRuleInterface
$entries = $criteria->getEntries();
foreach($entries as $entry)
{
$batt = $entry['battery'];
$qty = $entry['qty'];
$trade_in = null;
// check if entry is for trade in
if (isset($entry['trade_in']))
$trade_in = $entry['trade_in'];
// entry is a battery purchase
$size = $batt->getSize();
if ($trade_in == null)
{
// safe to get entry with battery key since CRM and apps
// will set this for a battery purchase and trade_in will
// will not be set
$batt = $entry['battery'];
// check if price tier has item price for battery
$pt_price = $this->getPriceTierItemPrice($pt, $batt);
if ($pt_price == null)
$price = $batt->getSellingPrice();
else
$price = $pt_price;
// battery purchase
$price = $batt->getSellingPrice();
$items[] = [
'service_type' => $this->getID(),
@ -103,49 +88,32 @@ class BatterySales implements InvoiceRuleInterface
// check if this is a valid battery
foreach ($invoice_items as $item)
{
if (isset($item['battery']))
$battery = $this->em->getRepository(Battery::class)->find($item['battery']);
if (empty($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;
$trade_in = null;
$criteria->addEntry($battery, $trade_in, $qty);
$error = 'Invalid battery specified.';
return $error;
}
// quantity
$qty = $item['quantity'];
if ($qty < 1)
continue;
// 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 getPriceTierItemPrice($pt_id, $batt)
{
// price tier is default
if ($pt_id == 0)
return null;
// find the item type battery
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']);
if ($item_type == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $batt->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getTitle($battery)
{
$title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName();

View file

@ -11,19 +11,14 @@ use App\Ramcar\ServiceType;
use App\Entity\ServiceOffering;
use App\Entity\CustomerVehicle;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class Fuel implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -34,7 +29,6 @@ class Fuel implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$pt_id = $criteria->getPriceTier();
$items = [];
@ -42,13 +36,7 @@ class Fuel implements InvoiceRuleInterface
{
$cv = $criteria->getCustomerVehicle();
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id, $cv);
if ($pt_price == null)
$service_price = $this->getServiceTypeFee($cv);
else
$service_price = $pt_price;
$fee = $this->getServiceTypeFee($cv);
$ftype = $cv->getFuelType();
@ -58,10 +46,10 @@ class Fuel implements InvoiceRuleInterface
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle($ftype),
'price' => $service_price,
'price' => $fee,
];
$qty_fee = bcmul($qty, $service_price, 2);
$qty_fee = bcmul($qty, $fee, 2);
$total_price = $qty_fee;
switch ($ftype)
@ -69,15 +57,7 @@ class Fuel implements InvoiceRuleInterface
case FuelType::GAS:
case FuelType::DIESEL:
$qty = 1;
// check if price tier has item price for fuel type
$pt_price = $this->getPriceTierFuelItemPrice($pt_id, $ftype);
if ($pt_price == null)
$price = $this->getFuelFee($ftype);
else
$price = $pt_price;
$price = $this->getFuelFee($ftype);
$items[] = [
'service_type' => $this->getID(),
'qty' => $qty,
@ -158,70 +138,6 @@ class Fuel implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
// check if customer vehicle has a motolite battery
// if yes, set the code to the motolite user service fee
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_service_fee';
else
$code = 'fuel_service_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getPriceTierFuelItemPrice($pt_id, $fuel_type)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
$code = '';
if ($fuel_type == FuelType::GAS)
$code = 'fuel_gas_fee';
if ($fuel_type == FuelType::DIESEL)
$code = 'fuel_diesel_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getTitle($fuel_type)
{
$title = '4L - ' . ucfirst($fuel_type);

View file

@ -8,21 +8,16 @@ use App\InvoiceRuleInterface;
use App\Entity\ServiceOffering;
use App\Entity\CustomerVehicle;
use App\Entity\ItemType;
use App\Ramcar\TransactionOrigin;
use App\Service\PriceTierManager;
class Jumpstart implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -34,21 +29,13 @@ class Jumpstart implements InvoiceRuleInterface
{
$stype = $criteria->getServiceType();
$source = $criteria->getSource();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
{
$cv = $criteria->getCustomerVehicle();
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id, $source, $cv);
if ($pt_price == null)
$price = $this->getServiceTypeFee($source, $cv);
else
$price = $pt_price;
$fee = $this->getServiceTypeFee($source, $cv);
// add the service fee to items
$qty = 1;
@ -56,10 +43,10 @@ class Jumpstart implements InvoiceRuleInterface
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle(),
'price' => $price,
'price' => $fee,
];
$qty_price = bcmul($price, $qty, 2);
$qty_price = bcmul($fee, $qty, 2);
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
}
@ -68,8 +55,18 @@ class Jumpstart implements InvoiceRuleInterface
public function getServiceTypeFee($source, CustomerVehicle $cv)
{
// get the service fee code, depending on the JO source and if customer vehicle has a motolite battery
$code = $this->getServiceFeeCode($cv, $source);
// check the source of JO
// (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user.
// (2) any other source, jumpstart fees are charged whether motolite user or not
if ($source == TransactionOrigin::MOBILE_APP)
{
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_service_fee';
else
$code = 'jumpstart_fee_mobile_app';
}
else
$code = 'jumpstart_fee';
$fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
@ -89,68 +86,10 @@ class Jumpstart implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id, $source, $cv)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// get the service fee code, depending on the JO source and if customer vehicle has a motolite battery
$code = $this->getServiceFeeCode($cv, $source);
// find the service offering
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getServiceTitle()
{
$title = 'Service - Troubleshooting fee';
return $title;
}
protected function getServiceFeeCode(CustomerVehicle $cv, $source)
{
// check the source of JO
// (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user.
// (2) any other source, jumpstart fees are charged whether motolite user or not. Service fees for non-motolite
// and motolite users are now different (used to be the same)
if ($source == TransactionOrigin::MOBILE_APP)
{
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_service_fee';
else
$code = 'jumpstart_fee_mobile_app';
}
else
{
error_log('hotline');
if ($cv->hasMotoliteBattery())
{
error_log('has motolite battery');
$code = 'motolite_user_jumpstart_fee';
}
else
$code = 'jumpstart_fee';
}
return $code;
}
}

View file

@ -7,20 +7,14 @@ use Doctrine\ORM\EntityManagerInterface;
use App\InvoiceRuleInterface;
use App\Entity\ServiceOffering;
use App\Entity\ItemType;
use App\Entity\CustomerVehicle;
use App\Service\PriceTierManager;
class JumpstartWarranty implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -31,21 +25,12 @@ class JumpstartWarranty implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
{
$cv = $criteria->getCustomerVehicle();
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id, $cv);
if ($pt_price == null)
$price = $this->getServiceTypeFee($cv);
else
$price = $pt_price;
$fee = $this->getServiceTypeFee();
// add the service fee to items
$qty = 1;
@ -53,24 +38,19 @@ class JumpstartWarranty implements InvoiceRuleInterface
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle(),
'price' => $price,
'price' => $fee,
];
$qty_price = bcmul($price, $qty, 2);
$qty_price = bcmul($fee, $qty, 2);
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
}
return $items;
}
public function getServiceTypeFee(CustomerVehicle $cv)
public function getServiceTypeFee()
{
// check if user has motolite battery.
// Motolite users now have a service fee for jumpstart warranty
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_jumpstart_warranty_fee';
else
$code = 'jumpstart_warranty_fee';
$code = 'jumpstart_warranty_fee';
// find the service fee using the code
// if we can't find the fee, return 0
@ -92,39 +72,6 @@ class JumpstartWarranty implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
// check if user has motolite battery.
// Motolite users now have a service fee for jumpstart warranty
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_jumpstart_warranty_fee';
else
$code = 'jumpstart_warranty_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getServiceTitle()
{
$title = 'Service - Troubleshooting fee';

View file

@ -10,19 +10,14 @@ use App\Ramcar\ServiceType;
use App\Entity\ServiceOffering;
use App\Entity\CustomerVehicle;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class Overheat implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -34,22 +29,13 @@ class Overheat implements InvoiceRuleInterface
{
$stype = $criteria->getServiceType();
$has_coolant = $criteria->hasCoolant();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
{
$cv = $criteria->getCustomerVehicle();
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id, $cv);
if ($pt_price == null)
$price = $this->getServiceTypeFee($cv);
else
$price = $pt_price;
$fee = $this->getServiceTypeFee($cv);
// add the service fee to items
$qty = 1;
@ -57,10 +43,10 @@ class Overheat implements InvoiceRuleInterface
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle(),
'price' => $price,
'price' => $fee,
];
$qty_fee = bcmul($qty, $price, 2);
$qty_fee = bcmul($qty, $fee, 2);
$total_price = $qty_fee;
if ($has_coolant)
@ -108,7 +94,7 @@ class Overheat implements InvoiceRuleInterface
// find the service fee using the code
// if we can't find the fee, return 0
$fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
$fee = $this->em->getRepository(ServiceOffering::class)->findOneBy($code);
if ($fee == null)
return 0;
@ -126,39 +112,6 @@ class Overheat implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
$code = 'overheat_fee';
// check if customer vehicle has a motolite battery
// if yes, set the code to the motolite user service fee
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_service_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getServiceTitle()
{
$title = 'Service - ' . ServiceType::getName(ServiceType::OVERHEAT_ASSISTANCE);

View file

@ -7,19 +7,14 @@ use Doctrine\ORM\EntityManagerInterface;
use App\InvoiceRuleInterface;
use App\Entity\ServiceOffering;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class PostRecharged implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -30,29 +25,22 @@ class PostRecharged implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
{
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id);
if ($pt_price == null)
$price = $this->getServiceTypeFee();
else
$price = $pt_price;
$fee = $this->getServiceTypeFee();
$qty = 1;
$items[] = [
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle(),
'price' => $price,
'price' => $fee,
];
$qty_price = bcmul($price, $qty, 2);
$qty_price = bcmul($fee, $qty, 2);
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
}
@ -84,33 +72,6 @@ class PostRecharged implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
$code = 'post_recharged_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getServiceTitle()
{
$title = 'Recharge fee';

View file

@ -7,19 +7,14 @@ use Doctrine\ORM\EntityManagerInterface;
use App\InvoiceRuleInterface;
use App\Entity\ServiceOffering;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class PostReplacement implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -30,29 +25,22 @@ class PostReplacement implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
{
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id);
if ($pt_price == null)
$price = $this->getServiceTypeFee();
else
$price = $pt_price;
$fee = $this->getServiceTypeFee();
$qty = 1;
$items[] = [
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle(),
'price' => $price,
'price' => $fee,
];
$qty_price = bcmul($price, $qty, 2);
$qty_price = bcmul($fee, $qty, 2);
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
}
@ -83,33 +71,6 @@ class PostReplacement implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
$code = 'post_replacement_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getServiceTitle()
{
$title = 'Battery replacement';

View file

@ -9,19 +9,14 @@ use App\InvoiceRuleInterface;
use App\Ramcar\ServiceType;
use App\Entity\ServiceOffering;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class Tax implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -45,7 +40,6 @@ class Tax implements InvoiceRuleInterface
// compute tax per item if service type is battery sales
$stype = $criteria->getServiceType();
$pt = $criteria->getPriceTier();
if ($stype == ServiceType::BATTERY_REPLACEMENT_NEW)
{
@ -64,13 +58,7 @@ class Tax implements InvoiceRuleInterface
$battery = $entry['battery'];
$qty = $entry['qty'];
// check if price tier has item price for battery
$pt_price = $this->getPriceTierItemPrice($pt, $battery);
if ($pt_price == null)
$price = $battery->getSellingPrice();
else
$price = $pt_price;
$price = $battery->getSellingPrice();
$vat = $this->getTaxAmount($price, $tax_rate);
@ -108,25 +96,6 @@ class Tax implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id, $batt)
{
// price tier is default
if ($pt_id == 0)
return null;
// find the item type battery
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']);
if ($item_type == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $batt->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getTaxAmount($price, $tax_rate)
{
$vat_ex_price = $this->getTaxExclusivePrice($price, $tax_rate);

View file

@ -8,19 +8,14 @@ use App\InvoiceRuleInterface;
use App\Entity\ServiceOffering;
use App\Entity\CustomerVehicle;
use App\Entity\ItemType;
use App\Service\PriceTierManager;
class TireRepair implements InvoiceRuleInterface
{
protected $em;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->pt_manager = $pt_manager;
}
public function getID()
@ -31,22 +26,13 @@ class TireRepair implements InvoiceRuleInterface
public function compute($criteria, &$total)
{
$stype = $criteria->getServiceType();
$has_sealant = $criteria->hasSealant();
$pt_id = $criteria->getPriceTier();
$items = [];
if ($stype == $this->getID())
{
$cv = $criteria->getCustomerVehicle();
// check if price tier has item price
$pt_price = $this->getPriceTierItemPrice($pt_id, $cv);
if ($pt_price == null)
$price = $this->getServiceTypeFee($cv);
else
$price = $pt_price;
$fee = $this->getServiceTypeFee($cv);
// add the service fee to items
$qty = 1;
@ -54,31 +40,11 @@ class TireRepair implements InvoiceRuleInterface
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $this->getServiceTitle(),
'price' => $price,
'price' => $fee,
];
$qty_fee = bcmul($qty, $price, 2);
$total_price = $qty_fee;
if ($has_sealant)
{
$sealant_fee_data = $this->getSealantFeeData();
$sealant_fee = $sealant_fee_data['fee'];
$sealant_title = $sealant_fee_data['title'];
$items[] = [
'service_type' => $this->getID(),
'qty' => $qty,
'title' => $sealant_title,
'price' => $sealant_fee,
];
$qty_price = bcmul($sealant_fee, $qty, 2);
$total_price = bcadd($total_price, $qty_price, 2);
}
$total['total_price'] = bcadd($total['total_price'], $total_price, 2);
$qty_price = bcmul($fee, $qty, 2);
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
}
return $items;
@ -113,67 +79,10 @@ class TireRepair implements InvoiceRuleInterface
return null;
}
protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv)
{
// price_tier is default
if ($pt_id == 0)
return null;
// find the item type for service offering
$item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']);
if ($item_type == null)
return null;
// find the service offering
$code = 'tire_repair_fee';
// check if customer vehicle has a motolite battery
// if yes, set the code to the motolite user service fee
if ($cv->hasMotoliteBattery())
$code = 'motolite_user_service_fee';
$service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
// check if service is null. If null, return null
if ($service == null)
return null;
$item_type_id = $item_type->getID();
$item_id = $service->getID();
$price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id);
return $price;
}
protected function getServiceTitle()
{
$title = 'Service - Flat Tire';
return $title;
}
public function getSealantFeeData()
{
$data = [
'fee' => 0.00,
'title' => '',
];
$code = 'tire_sealant_fee';
// find the service fee using the code
// if we can't find the fee, return 0
$fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]);
if ($fee != null)
{
$data = [
'fee' => $fee->getFee(),
'title' => $fee->getName(),
];
}
return $data;
}
}

View file

@ -2,24 +2,12 @@
namespace App\InvoiceRule;
use Doctrine\ORM\EntityManagerInterface;
use App\InvoiceRuleInterface;
use App\Ramcar\TradeInType;
use App\Ramcar\ServiceType;
use App\Entity\BatterySize;
class TradeIn implements InvoiceRuleInterface
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getID()
{
return 'trade-in';
@ -33,6 +21,7 @@ class TradeIn implements InvoiceRuleInterface
$entries = $criteria->getEntries();
foreach($entries as $entry)
{
$batt = $entry['battery'];
$qty = $entry['qty'];
$trade_in_type = null;
@ -41,9 +30,7 @@ class TradeIn implements InvoiceRuleInterface
if ($trade_in_type != null)
{
$batt_size = $entry['battery_size'];
$ti_rate = $this->getTradeInRate($batt_size, $trade_in_type);
$ti_rate = $this->getTradeInRate($batt, $trade_in_type);
$qty_ti = bcmul($ti_rate, $qty, 2);
@ -53,10 +40,8 @@ class TradeIn implements InvoiceRuleInterface
$price = bcmul($ti_rate, -1, 2);
$items[] = [
'battery_size' => $batt_size,
'trade_in_type' => $trade_in_type,
'qty' => $qty,
'title' => $this->getTitle($batt_size, $trade_in_type),
'title' => $this->getTitle($batt, $trade_in_type),
'price' => $price,
];
}
@ -72,47 +57,13 @@ class TradeIn implements InvoiceRuleInterface
public function validateInvoiceItems($criteria, $invoice_items)
{
// check service type. Only battery sales and battery warranty should have invoice items.
$stype = $criteria->getServiceType();
if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW)
return null;
// return error if there's a problem, false otherwise
if (!empty($invoice_items))
{
// check if this is a valid battery
foreach ($invoice_items as $item)
{
if (isset($item['battery_size']))
{
$battery_size = $this->em->getRepository(BatterySize::class)->find($item['battery_size']);
if (empty($battery_size))
{
$error = 'Invalid battery size specified.';
return $error;
}
// quantity
$qty = $item['quantity'];
if ($qty < 1)
continue;
// check if trade in is set and if trade in type if valid
if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in']))
{
$trade_in = $item['trade_in'];
$criteria->addTradeInEntry($battery_size, $trade_in, $qty);
}
}
}
}
return null;
}
protected function getTradeInRate($size, $trade_in_type)
protected function getTradeInRate($battery, $trade_in_type)
{
$size = $battery->getSize();
switch ($trade_in_type)
{
case TradeInType::MOTOLITE:
@ -126,9 +77,9 @@ class TradeIn implements InvoiceRuleInterface
return 0;
}
protected function getTitle($battery_size, $trade_in_type)
protected function getTitle($battery, $trade_in_type)
{
$title = 'Trade-in ' . TradeInType::getName($trade_in_type) . ' ' . $battery_size->getName() . ' battery';
$title = 'Trade-in ' . TradeInType::getName($trade_in_type) . ' ' . $battery->getSize()->getName() . ' battery';
return $title;
}

View file

@ -23,8 +23,8 @@ class DeliveryStatus extends NameValue
const COLLECTION = [
'rider_assign' => 'Assigned Rider',
'requeue' => 'Requeue',
'rider_accept' => 'Rider Accept',
'rider_arrive' => 'Rider Arrive',
'accept' => 'Rider Accept',
'arrive' => 'Rider Arrive',
'rider_edit' => 'Rider Edit',
'rider_depart_hub' => 'Rider Depart Hub',
'rider_arrive_hub_pre_jo' => 'Rider Arrive Hub Pre JO',

View file

@ -12,7 +12,6 @@ class HubCriteria
protected $limit_results; // number of results to return
protected $limit_distance; // distance limit for search in km
protected $flag_inventory_check; // flag if we need to check for inventory
protected $flag_riders_check; // flag if we need to check for riders available
protected $jo_type; // jo service needed
protected $date_time; // date and time to check if hub is open or not
protected $items; // array of items: items[sku] = quantity to check for
@ -21,10 +20,6 @@ class HubCriteria
protected $flag_round_robin; // flag if we use round robin or not
protected $jo_id; // JO id. This is null if called from mobile API
protected $customer_id; // customer id
protected $customer_class; // customer class
protected $order_date; // date JO was created
protected $service_type; // service type of JO
protected $jo_origin; // origin of JO
public function __construct()
{
@ -34,17 +29,12 @@ class HubCriteria
$this->jo_type = '';
$this->date_time = new DateTime();
$this->flag_inventory_check = false;
$this->flag_riders_check = false;
$this->items = [];
$this->payment_method = '';
$this->flag_emergency = false;
$this->flag_round_robin = false;
$this->jo_id = null;
$this->customer_id = null;
$this->customer_class = null;
$this->order_date = new DateTime();
$this->service_type = null;
$this->jo_origin = null;
$flag_emergency = false;
$flag_round_robin = false;
$jo_id = null;
$customer_id = null;
}
public function setPoint(Point $point)
@ -91,17 +81,6 @@ class HubCriteria
return $this->flag_inventory_check;
}
public function setRidersCheck($flag_riders_check = true)
{
$this->flag_riders_check = $flag_riders_check;
return $this;
}
public function hasRidersCheck()
{
return $this->flag_riders_check;
}
public function setJoType($jo_type)
{
// TODO: validate the jo type
@ -192,48 +171,5 @@ class HubCriteria
return $this->customer_id;
}
public function setCustomerClass($customer_class)
{
$this->customer_class = $customer_class;
return $this;
}
public function getCustomerClass()
{
return $this->customer_class;
}
public function setOrderDate($order_date)
{
$this->order_date = $order_date;
return $this;
}
public function getOrderDate()
{
return $this->order_date;
}
public function setServiceType($service_type)
{
$this->service_type = $service_type;
return $this;
}
public function getServiceType()
{
return $this->service_type;
}
public function setJoOrigin($jo_origin)
{
$this->jo_origin = $jo_origin;
return $this;
}
public function getJoOrigin()
{
return $this->jo_origin;
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Ramcar;
class InsuranceBodyType extends NameValue
{
const SEDAN = 'sedan';
const SUV = 'suv';
const TRUCK = 'truck';
const MOTORCYCLE = 'motorcycle';
const COLLECTION = [
'SEDAN' => 'Sedan',
'SUV' => 'SUV',
'TRUCK' => 'Truck',
'MOTORCYCLE' => 'Motorcycle',
];
}

View file

@ -17,8 +17,6 @@ class InvoiceCriteria
protected $service_charges;
protected $flag_taxable;
protected $source; // use Ramcar's TransactionOrigin
protected $price_tier;
protected $flag_sealant;
// entries are battery and trade-in combos
protected $entries;
@ -34,8 +32,6 @@ class InvoiceCriteria
$this->service_charges = [];
$this->flag_taxable = false;
$this->source = '';
$this->price_tier = 0; // set to default
$this->flag_sealant = false;
}
public function setServiceType($stype)
@ -112,17 +108,6 @@ class InvoiceCriteria
$this->entries[] = $entry;
}
public function addTradeInEntry($battery_size, $trade_in, $qty)
{
$entry = [
'battery_size' => $battery_size,
'trade_in' => $trade_in,
'qty' => $qty
];
$this->entries[] = $entry;
}
public function getEntries()
{
return $this->entries;
@ -194,25 +179,4 @@ class InvoiceCriteria
return $this->source;
}
public function setPriceTier($price_tier)
{
$this->price_tier = $price_tier;
return $this;
}
public function getPriceTier()
{
return $this->price_tier;
}
public function setHasSealant($flag = true)
{
$this->flag_sealant = $flag;
return $this;
}
public function hasSealant()
{
return $this->flag_sealant;
}
}

View file

@ -4,24 +4,17 @@ namespace App\Ramcar;
class JORejectionReason extends NameValue
{
const ADMINISTRATIVE = 'administrative';
const NO_STOCK_SALES = 'no_stock_sales';
const NO_STOCK_SERVICE = 'no_stock_service';
const LINE_NO_ANSWER = 'line_no_answer';
const LINE_BUSY = 'line_busy';
const NO_RIDER_AVAILABLE = 'no_rider_available';
const NO_RIDER_IN_TRANSIT = 'no_rider_in_transit';
const REFUSAL = 'refusal';
const STORE_CLOSED = 'store_closed';
const NO_CREDIT_CARD = 'no_credit_card';
const DISCOUNT = 'discount';
const STORE_CLOSED_SCHEDULED = 'store_closed_scheduled';
const STORE_CLOSED_HALF_DAY = 'store_closed_half_day';
const STORE_CLOSED_NO_ADVISE = 'store_closed_no_advise';
const PRIORITY_HUB_WTY_CLAIM = 'priority_hub_wty_claim';
const PRIORITY_HUB_JUMPSTART = 'priority_hub_jumpstart';
const PRIORITY_HUB_RESQ_REQUEST = 'priority_hub_resq_req';
const CUSTOMER_REQUEST = 'customer_request';
const ADMINISTRATIVE = 'administrative';
const NO_STOCK_SALES = 'no_stock_sales';
const NO_STOCK_SERVICE = 'no_stock_service';
const LINE_NO_ANSWER = 'line_no_answer';
const LINE_BUSY = 'line_busy';
const NO_RIDER_AVAILABLE = 'no_rider_available';
const NO_RIDER_IN_TRANSIT = 'no_rider_in_transit';
const REFUSAL = 'refusal';
const STORE_CLOSED = 'store_closed';
const NO_CREDIT_CARD = 'no_credit_card';
const DISCOUNT = 'discount';
const COLLECTION = [
'administrative' => 'ADMINISTRATIVE',
@ -35,16 +28,5 @@ class JORejectionReason extends NameValue
'store_closed' => 'STORE CLOSED',
'no_credit_card' => 'NO CREDIT CARD PAYMENT / NO TERMINAL',
'discount' => 'DISCOUNT',
'store_closed_scheduled' => 'STORE CLOSED (ON SCHEDULE)',
'store_closed_half_day' => 'STORE CLOSED (HALF DAY)',
'store_closed_no_advise' => 'STORE CLOSED (NO ADVISE)',
'priority_hub_wty_claim' => 'PRIORITY HUB OUTLET FOR WARRANTY CLAIM',
'priority_hub_jumpstart' => 'PRIORITY HUB OUTLET FOR JUMPSTART',
'priority_hub_resq_req' => 'PRIORITY HUB OUTLET FOR RESQ-Q REQUEST',
'customer_request' => 'CUSTOMER REQUEST',
];
const BLACKLIST = [
self::ADMINISTRATIVE => true,
];
}

View file

@ -4,21 +4,9 @@ namespace App\Ramcar;
class NameValue
{
const BLACKLIST = [];
static public function getCollection()
{
$result = [];
$blacklist = static::getBlacklist();
// filter from blacklist
foreach(static::COLLECTION as $key => $row) {
if (!isset($blacklist[$key])) {
$result[$key] = $row;
}
}
return $result;
return static::COLLECTION;
}
static public function validate($value)
@ -36,9 +24,4 @@ class NameValue
return 'Unknown';
}
static public function getBlacklist()
{
return static::BLACKLIST ?? [];
}
}

View file

@ -81,7 +81,6 @@ class HubDistributor
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => $hub_jo_count,
'inventory' => $hub_data['inventory'] ?? 0,
];
}
else
@ -92,7 +91,6 @@ class HubDistributor
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
'inventory' => $hub_data['inventory'] ?? 0,
];
}
}

View file

@ -1,144 +0,0 @@
<?php
namespace App\Service\HubFilter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Service\HubFilterLogger;
use App\Entity\Hub;
use App\Entity\JobOrder;
use App\Entity\JORejection;
use App\Ramcar\ServiceType;
use App\Ramcar\JORejectionReason;
use App\Service\RisingTideGateway;
use DateTime;
class BaseHubFilter
{
protected $id;
protected $jo_id;
protected $customer_id;
protected $hub_filter_logger;
protected $em;
protected $rt;
protected $trans;
public function __construct(HubFilterLogger $hub_filter_logger, EntityManagerInterface $em, RisingTideGateway $rt, TranslatorInterface $trans)
{
$this->hub_filter_logger = $hub_filter_logger;
$this->em = $em;
$this->rt = $rt;
$this->trans = $trans;
error_log("-------------------");
error_log("HUB FILTER RUNNING: " . $this->getID());
error_log("-------------------");
}
public function getID(): string
{
return $this->id;
}
public function setJOID(int $jo_id)
{
$this->jo_id = $jo_id;
return $this;
}
public function getJOID(): int
{
return $this->jo_id;
}
public function setCustomerID(int $customer_id)
{
$this->customer_id = $customer_id;
return $this;
}
public function getCustomerID(): int
{
return $this->customer_id;
}
public function log(Hub $hub): void
{
$this->hub_filter_logger->logFilteredHub($hub, $this->getID(), $this->getJOID(), $this->getCustomerID());
// log to file
$filename = '/../../../var/log/hub_rejection.log';
$date = date("Y-m-d H:i:s");
// build log entry
$entry = implode("", [
"[JO: " . $this->getJOID() . "]",
"[" . $date . "]",
"[" . $this->getID() . "]",
" " . $hub->getName() . " (ID: " . $hub->getID() . ")",
"\r\n",
]);
@file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND);
}
protected function createRejectionEntry($hub, $reason, $remarks = ""): JORejection
{
$jo = $this->em->getRepository(JobOrder::class)->find($this->getJOID());
$robj = new JORejection();
$robj->setDateCreate(new DateTime())
->setHub($hub)
->setJobOrder($jo)
->setReason($reason)
->setRemarks(implode(" ", ["Automatically filtered by hub selector.", $remarks]));
$this->em->persist($robj);
$this->em->flush();
return $robj;
}
protected function sendSMSMessage($hub, $order_date, $service_type, $rejection, $reason = "", $remarks = ""): void
{
$jo_id = $this->getJOID();
// check if we already have a rejection record for this hub and JO. this also means an SMS was already sent
$rejection_count = $this->em->createQueryBuilder()
->select('count(r)')
->from(JORejection::class, 'r')
->where('r.job_order = :jo_id')
->andWhere('r.hub = :hub_id')
->andWhere('r.id != :rejection_id')
->setParameter('jo_id', $jo_id)
->setParameter('hub_id', $hub->getID())
->setParameter('rejection_id', $rejection->getID())
->getQuery()
->getSingleScalarResult();
// if we already have a rejection record for this hub and JO, do not send another SMS
if ($rejection_count >= 1) {
error_log("ALREADY SENT REJECTION SMS TO HUB " . $hub->getID() . " FOR JO " . $jo_id);
return;
}
$message = 'Job Order #: ' . $jo_id . "\n" .
'Order Date and Time: ' . $order_date->format('d M Y g:i A') . "\n" .
'Date and Time Rejected: ' . $rejection->getDateCreate()->format('d M Y g:i A') . "\n" .
'Enrollee Name: ' . implode(" - ", [$hub->getName(), $hub->getBranch()]) . "\n" .
'Reason of Rejection: ' . $reason . "\n" .
'Remarks: ' . $remarks . "\n" .
'Type of Service: ' . ServiceType::getName($service_type);
error_log("SENDING SMS MESSAGE:\r\n" . $message);
// send SMS message to hub
$this->rt->sendSMS(
$hub->getNotifNumber(),
$this->trans->trans('message.battery_brand_allcaps'),
$message
);
}
}

View file

@ -1,58 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class DateAndTimeHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'date_and_time';
public function getRequestedParams() : array
{
return [
'date_time',
];
}
public function filter(array $hubs, array $params = []) : array
{
if ($params['date_time'] == null)
return $hubs;
$results = [];
foreach ($hubs as $hub_data)
{
// go through each hub's opening times to check if hub is open
// for the specified time
// get hub opening and closing times
// TODO: maybe in the future, might also have to check if hub
// is open/available on date/day
$hub = $hub_data['hub'];
$time_open = $hub->getTimeOpen()->format("H:i:s");
$time_close = $hub->getTimeClose()->format("H:i:s");
$filter_time = $params['date_time']->format("H:i:s");
if (($filter_time >= $time_open) &&
($filter_time <= $time_close))
{
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
'inventory' => $hub_data['inventory'],
];
}
else
$this->log($hub);
}
return $results;
}
}

View file

@ -1,164 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Ramcar\JORejectionReason;
use App\Ramcar\ServiceType;
use App\Ramcar\CustomerClassification;
use App\Service\InventoryManager;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
use App\Entity\Battery;
use App\Ramcar\TransactionOrigin;
class InventoryHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'no_inventory';
protected $im;
public function __construct(HubFilterLogger $hub_filter_logger, EntityManagerInterface $em, RisingTideGateway $rt, TranslatorInterface $trans, InventoryManager $im)
{
parent::__construct($hub_filter_logger, $em, $rt, $trans);
$this->im = $im;
}
public function getRequestedParams() : array
{
return [
'flag_inventory_check',
'customer_class',
'jo_type',
'jo_origin',
'order_date',
'service_type',
'items',
];
}
public function filter(array $hubs, array $params = []) : array
{
// check if this is enabled
if (!$params['flag_inventory_check']) {
error_log("INVENTORY CHECK " . $this->getJOID() . ": DISABLED");
return $hubs;
}
// check customer class
if ((!empty($params['customer_class']) && $params['customer_class'] == CustomerClassification::VIP) ||
$params['jo_origin'] === TransactionOrigin::VIP) {
error_log("INVENTORY CHECK " . $this->getJOID() . ": VIP CLASS");
return $hubs;
}
// check item list is not empty
if (empty($params['items'])) {
error_log("INVENTORY CHECK " . $this->getJOID() . ": NO ITEMS");
return $hubs;
}
// check this is a battery item related JO
if ($params['jo_type'] != ServiceType::BATTERY_REPLACEMENT_NEW &&
$params['jo_type'] != ServiceType::BATTERY_REPLACEMENT_WARRANTY
) {
error_log("INVENTORY CHECK " . $this->getJOID() . ": INVALID SERVICE TYPE: " . $params['jo_type']);
return $hubs;
}
// get a list of all hubs with branch codes
$branch_codes = [];
foreach ($hubs as $hub_data) {
$branch_code = $hub_data['hub']->getBranchCode();
if (!empty($branch_code)) {
$branch_codes[] = $branch_code;
}
};
$hubs_to_filter = [];
$results = [];
$qtys = [];
// call inventory manager for all hubs for selected SKUs
$skus = array_keys($params['items']);
error_log("CHECKING INVENTORY FOR " . count($skus) . " ITEM(S) ON HUBS " . count($branch_codes) . "...");
$branches = $this->im->getBranchesInventory($branch_codes, $skus);
error_log("REQUEST COMPLETE, RESULT COUNT: " . count($branches));
// check each result to see if sufficient quantity exists to meet request
foreach ($branches as $branch) {
if (isset($branch['BranchCode'])) {
// filter out branch if it does not have sufficient inventory
if (!isset($params['items'][$branch['SapCode']]) || $branch['Quantity'] < $params['items'][$branch['SapCode']] &&
!isset($hubs_to_filter[$branch['BranchCode']])
) {
error_log("FILTERING BRANCH WITH NO INVENTORY: " . $branch['BranchCode']);
$hubs_to_filter[$branch['BranchCode']] = true;
} else {
// save inventory count so we don't have to recheck later
$qtys[$branch['BranchCode']] = $branch['Quantity'];
}
}
}
// get battery models for each requested SKU
$batteries = [];
foreach ($skus as $sku) {
$bobj = $this->em->getRepository(Battery::class)->findOneBy(['sap_code' => $sku]);
$batteries[] = implode(" ", [$bobj->getModel()->getName(), $bobj->getSize()->getName()]);
}
$battery_string = implode(", ", $batteries);
// remove filtered hubs from list
foreach ($hubs as $hub_data) {
$hub = $hub_data['hub'];
$branch_code = $hub_data['hub']->getBranchCode();
// check if we are filtering this hub
if (isset($hubs_to_filter[$branch_code]) || empty($branch_code) || !isset($qtys[$branch_code])) {
// if we have a JO, create rejection record and notify
$jo_id = $this->getJOID();
if (!empty($jo_id)) {
// create rejection report entry
$robj = $this->createRejectionEntry(
$hub,
JORejectionReason::NO_STOCK_SALES,
"SKU(s): " . $battery_string,
);
// build SMS message
$this->sendSMSMessage(
$hub,
$params['order_date'],
$params['service_type'],
$robj,
JORejectionReason::getName(JORejectionReason::NO_STOCK_SALES),
"Requested SKU(s) - " . $battery_string
);
}
// log this filter
$this->log($hub);
error_log("FILTERED HUB " . $hub->getID() . " (no_inventory)");
} else {
// include inventory in hub data
$hub_data['inventory'] = $qtys[$branch_code];
// we only include branches with branch codes and quantities
$results[] = $hub_data;
}
}
// return filtered hubs
return $results;
}
}

View file

@ -1,52 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class JoTypeHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'job_order_type';
public function getRequestedParams() : array
{
return [
'flag_emergency',
'jo_type',
];
}
public function filter(array $hubs, array $params = []) : array
{
if ($params['flag_emergency'])
return $hubs;
if (empty($params['jo_type']))
return $hubs;
$results = [];
foreach ($hubs as $hub_data)
{
$hub = $hub_data['hub'];
// TODO: for now, have this return true
$has_jo_type = true;
// check if hub offers the jo_type
// TODO: add service to hub
if ($has_jo_type)
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
'inventory' => $hub_data['inventory'],
];
else
$this->log($hub);
}
return $results;
}
}

View file

@ -1,35 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class MaxResultsHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'max_results';
public function getRequestedParams() : array
{
return [
'limit_results',
];
}
public function filter(array $hubs, array $params = []) : array
{
if (empty($params['limit_results']))
return $hubs;
$results = [];
for ($i = 0; $i < count($hubs); $i++)
{
if ($i < $params['limit_results'])
$results[] = $hubs[$i];
else
$this->log($hubs[$i]['hub']);
}
return $results;
}
}

View file

@ -1,63 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class PaymentMethodHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'no_payment_method';
public function getRequestedParams() : array
{
return [
'flag_emergency',
'payment_method',
];
}
public function filter(array $hubs, array $params = []) : array
{
if ($params['flag_emergency'])
return $hubs;
if (empty($params['payment_method']))
return $hubs;
$results = [];
foreach ($hubs as $hub_data)
{
$hub = $hub_data['hub'];
// name of payment method is what is saved
$payment_methods = $hub->getPaymentMethods();
if ($payment_methods != null)
{
$flag_found_pmethod = false;
foreach ($payment_methods as $pmethod)
{
if ($pmethod == $params['payment_method'])
{
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
'inventory' => $hub_data['inventory'],
];
}
$flag_found_pmethod = true;
}
if (!$flag_found_pmethod)
$this->log($hub);
}
else
$this->log($hub);
}
return $results;
}
}

View file

@ -1,75 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
use App\Ramcar\JORejectionReason;
use App\Ramcar\CustomerClassification;
class RiderAvailabilityHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'no_available_rider';
public function getRequestedParams() : array
{
return [
'flag_riders_check',
'customer_class',
'order_date',
'service_type',
];
}
public function filter(array $hubs, array $params = []) : array
{
// check if this is enabled
if (!$params['flag_riders_check']) {
return $hubs;
}
// check customer class
if (!empty($params['customer_class']) && $params['customer_class'] == CustomerClassification::VIP) {
error_log("RIDER CHECK " . $this->getJOID() . ": VIP CLASS");
return $hubs;
}
$results = [];
foreach ($hubs as $hub_data) {
$hub = $hub_data['hub'];
$available_riders = count($hub->getAvailableRiders());
// check we have available riders
error_log("TOTAL RIDERS: " . $available_riders);
if ($available_riders === 0) {
// if we have a JO, create rejection record and notify
$jo_id = $this->getJOID();
if (!empty($jo_id)) {
// create rejection report entry
$robj = $this->createRejectionEntry($hub, JORejectionReason::NO_RIDER_AVAILABLE);
// build SMS message
$this->sendSMSMessage(
$hub,
$params['order_date'],
$params['service_type'],
$robj,
JORejectionReason::getName(JORejectionReason::NO_RIDER_AVAILABLE),
);
}
// log this filter
$this->log($hub);
error_log("FILTERED HUB " . $hub->getID() . " (no_available_rider)");
} else {
$results[] = $hub_data;
}
}
return $results;
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Service\HubDistributor;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
class RoundRobinHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'round_robin';
protected $hub_distributor;
public function __construct(HubFilterLogger $hub_filter_logger, EntityManagerInterface $em, RisingTideGateway $rt, TranslatorInterface $trans, HubDistributor $hub_distributor)
{
parent::__construct($hub_filter_logger, $em, $rt, $trans);
$this->hub_distributor = $hub_distributor;
}
public function getRequestedParams() : array
{
return [
'flag_round_robin',
];
}
public function filter(array $hubs, array $params = []) : array
{
if (!$params['flag_round_robin'])
return $hubs;
$results = [];
// call hub distributor service
$arranged_hubs = $this->hub_distributor->arrangeHubs($hubs);
$results = $arranged_hubs;
return $results;
}
}

View file

@ -1,20 +0,0 @@
<?php
namespace App\Service\HubFilter;
interface HubFilterInterface
{
public function getID() : string;
public function filter(array $hubs, array $params = []) : array;
public function setJOID(int $jo_id);
public function getJOID() : int;
public function setCustomerID(int $customer_id);
public function getCustomerID() : int;
public function getRequestedParams() : array;
}

View file

@ -5,20 +5,21 @@ namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Entity\Hub;
use App\Service\HubDistributor;
use App\Service\InventoryManager;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
use App\Ramcar\HubCriteria;
use App\Ramcar\ServiceType;
class HubSelector
{
protected $container;
protected $em;
protected $im;
protected $hub_distributor;
@ -26,11 +27,10 @@ class HubSelector
protected $trans;
protected $rt;
public function __construct(ContainerInterface $container, EntityManagerInterface $em, InventoryManager $im,
public function __construct(EntityManagerInterface $em, InventoryManager $im,
HubDistributor $hub_distributor, HubFilterLogger $hub_filter_logger,
TranslatorInterface $trans, RisingTideGateway $rt)
{
$this->container = $container;
$this->em = $em;
$this->im = $im;
$this->hub_distributor = $hub_distributor;
@ -39,18 +39,6 @@ class HubSelector
$this->rt = $rt;
}
protected function getActiveFilters(): array
{
$fnames = explode(",", $this->container->getParameter('enabled_hub_filters'));
$enabled_filters = [];
foreach ($fnames as $filter) {
$enabled_filters[] = 'App\\Service\\HubFilter\\Filters\\' . $filter;
}
return $enabled_filters;
}
public function find(HubCriteria $criteria)
{
$point = $criteria->getPoint();
@ -58,20 +46,15 @@ class HubSelector
$limit_distance = $criteria->getLimitDistance();
$jo_type = $criteria->getJoType();
$flag_inventory_check = $criteria->hasInventoryCheck();
$flag_riders_check = $criteria->hasRidersCheck();
$items = $criteria->getItems();
$date_time = $criteria->getDateTime();
$payment_method = $criteria->getPaymentMethod();
$flag_emergency = $criteria->isEmergency();
$flag_round_robin = $criteria->isRoundRobin();
$jo_id = $criteria->getJobOrderId();
$jo_origin = $criteria->getJoOrigin();
$customer_id = $criteria->getCustomerId();
$customer_class = $criteria->getCustomerClass();
// needed for JORejection records and SMS notifs
$order_date = $criteria->getOrderDate();
$service_type = $criteria->getServiceType();
$results = [];
// error_log('payment methods ' . $payment_method);
// error_log('distance limit ' . $limit_distance);
@ -80,52 +63,278 @@ class HubSelector
// get all the hubs within distance
$filtered_hubs = $this->getClosestHubs($point, $limit_distance, $jo_id, $customer_id);
// build param list
$params = [
'date_time' => $date_time,
'flag_inventory_check' => $flag_inventory_check,
'customer_class' => $customer_class,
'jo_type' => $jo_type,
'jo_origin' => $jo_origin,
'order_date' => $order_date,
'service_type' => $service_type,
'items' => $items,
'flag_emergency' => $flag_emergency,
'limit_results' => $limit_results,
'payment_method' => $payment_method,
'flag_riders_check' => $flag_riders_check,
'flag_round_robin' => $flag_round_robin,
];
// error_log('closest hubs ' . json_encode($filtered_hubs));
// loop through all enabled filters
foreach ($this->getActiveFilters() as $hub_filter) {
// no hubs left to filter
if (empty($filtered_hubs)) {
break;
}
// filter the first hub results for date and opening times
$hubs_date_time = $this->filterHubsByDateAndTime($filtered_hubs, $date_time, $jo_id, $customer_id);
$filtered_hubs = $hubs_date_time;
$f = $this->container->get($hub_filter);
// error_log('date_time hubs ' . json_encode($filtered_hubs));
// check if supported area is exempted from this filter
if ($this->isExemptedByArea($f->getID(), $point)) {
continue;
}
if (!$flag_emergency)
{
// filter jo types
$hubs_jo_type = $this->filterHubsByJoType($filtered_hubs, $jo_type, $jo_id, $customer_id);
$filtered_hubs = $hubs_jo_type;
$f->setJOID($jo_id);
$f->setCustomerID($customer_id);
//error_log('jo_type hubs ' . json_encode($filtered_hubs));
// get requested params only
$req_params = array_intersect_key($params, array_flip($f->getRequestedParams()));
// filter hubs by payment methods
$hubs_payment_method = $this->filterHubsByPaymentMethod($filtered_hubs, $payment_method, $jo_id, $customer_id);
$filtered_hubs = $hubs_payment_method;
// filter hub list
$filtered_hubs = $f->filter($filtered_hubs, $req_params);
//error_log('payment hubs ' . json_encode($filtered_hubs));
// error_log($f->getID() . ' hubs ' . json_encode($filtered_hubs));
// inventory filter
$hubs_inventory = $this->filterHubsByInventory($filtered_hubs, $flag_inventory_check,
$jo_type, $items, $jo_id, $customer_id);
$filtered_hubs = $hubs_inventory;
//error_log('inventory hubs ' . json_encode($filtered_hubs));
// round robin filter
$hubs_round_robin = $this->filterHubsByRoundRobin($filtered_hubs, $flag_round_robin);
$filtered_hubs = $hubs_round_robin;
// error_log('round robin hubs ' . json_encode($filtered_hubs));
// max results filter
$hubs_max_result = $this->filterHubsByMaxResults($filtered_hubs, $limit_results, $jo_id, $customer_id);
$filtered_hubs = $hubs_max_result;
}
// error_log('final hub list ' . json_encode($filtered_hubs));
$results = $filtered_hubs;
return $filtered_hubs;
// error_log(json_encode($results));
return $results;
}
protected function filterHubsByRoundRobin($hubs, $flag_round_robin)
{
if (empty($hubs))
return $hubs;
if (!$flag_round_robin)
return $hubs;
$results = [];
// call hub distributor service
$arranged_hubs = $this->hub_distributor->arrangeHubs($hubs);
$results = $arranged_hubs;
return $results;
}
protected function filterHubsByMaxResults($hubs, $limit_result, $jo_id, $customer_id)
{
if (empty($hubs))
return $hubs;
if (empty($limit_result))
return $hubs;
$results = [];
for ($i = 0; $i < count($hubs); $i++)
{
if ($i < $limit_result)
$results[] = $hubs[$i];
else
$this->hub_filter_logger->logFilteredHub($hubs[$i]['hub'], 'max_results', $jo_id, $customer_id);
}
return $results;
}
protected function filterHubsByJoType($hubs, $jo_type, $jo_id, $customer_id)
{
if (empty($hubs))
return $hubs;
if (empty($jo_type))
return $hubs;
$results = [];
foreach ($hubs as $hub_data)
{
$hub = $hub_data['hub'];
// TODO: for now, have this return true
$has_jo_type = true;
// check if hub offers the jo_type
// TODO: add service to hub
if ($has_jo_type)
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
];
else
$this->hub_filter_logger->logFilteredHub($hub, 'job_order_type', $jo_id, $customer_id);
}
return $results;
}
protected function filterHubsByPaymentMethod($hubs, $payment_method, $jo_id, $customer_id)
{
if (empty($hubs))
return $hubs;
if (empty($payment_method))
return $hubs;
$results = [];
foreach ($hubs as $hub_data)
{
$hub = $hub_data['hub'];
// name of payment method is what is saved
$payment_methods = $hub->getPaymentMethods();
if ($payment_methods != null)
{
$flag_found_pmethod = false;
foreach ($payment_methods as $pmethod)
{
if ($pmethod == $payment_method)
{
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
];
}
$flag_found_pmethod = true;
}
if (!$flag_found_pmethod)
$this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id);
}
else
$this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id);
}
return $results;
}
protected function filterHubsByDateAndTime($hubs, $date_time, $jo_id, $customer_id)
{
if (empty($hubs))
return $hubs;
if ($date_time == null)
return $hubs;
$results = [];
foreach ($hubs as $hub_data)
{
// go through each hub's opening times to check if hub is open
// for the specified time
// get hub opening and closing times
// TODO: maybe in the future, might also have to check if hub
// is open/available on date/day
$hub = $hub_data['hub'];
$time_open = $hub->getTimeOpen()->format("H:i:s");
$time_close = $hub->getTimeClose()->format("H:i:s");
$filter_time = $date_time->format("H:i:s");
if (($filter_time >= $time_open) &&
($filter_time <= $time_close))
{
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
];
}
else
$this->hub_filter_logger->logFilteredHub($hub, 'date_and_time', $jo_id, $customer_id);
}
return $results;
}
protected function filterHubsByInventory($hubs, $flag_inventory_check, $jo_type, $items, $jo_id, $customer_id)
{
if (empty($hubs))
return $hubs;
if (!$flag_inventory_check)
return $hubs;
$results = [];
if ($flag_inventory_check)
{
foreach ($hubs as $hub_data)
{
$hub = $hub_data['hub'];
if ($jo_type == ServiceType::BATTERY_REPLACEMENT_NEW)
{
// call inventory
$has_items = $this->checkInventory($items, $hub);
if ($has_items)
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
];
else
{
// get the skus for the message
$sku_text = '';
foreach ($items as $key => $value)
{
$sku_text .= ' ' . $key;
}
// send SMS to hub
$message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message'));
// error_log($message);
$this->sendSMSMessage($hub, $items);
$this->hub_filter_logger->logFilteredHub($hub, 'no_inventory', $jo_id, $customer_id);
}
}
if ($jo_type == ServiceType::BATTERY_REPLACEMENT_WARRANTY)
{
// call inventory
$has_items = $this->checkInventory($items, $hub);
if ($has_items)
$results[] = [
'hub' => $hub,
'db_distance' => $hub_data['db_distance'],
'distance' => $hub_data['distance'],
'duration' => $hub_data['duration'],
'jo_count' => 0,
];
else
{
// get the skus for the message
$sku_text = '';
foreach ($items as $key => $value)
{
$sku_text .= ' ' . $key;
}
// send SMS to hub
$message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message'));
// error_log($message);
$this->sendSMSMessage($hub, $items);
$this->hub_filter_logger->logFilteredHub($hub, 'no_inventory', $jo_id, $customer_id);
}
}
}
}
return $results;
}
protected function getClosestHubs(Point $point, $limit_distance, $jo_id, $customer_id)
@ -166,11 +375,7 @@ class HubSelector
'distance' => $dist,
'duration' => 0,
'jo_count' => 0,
'inventory' => 0,
];
// log to file
$this->logClosestHubResult($jo_id, $row[0], $dist, $limit_distance);
}
else
{
@ -181,6 +386,45 @@ class HubSelector
return $hubs_data;
}
protected function checkInventory($items, $hub)
{
// check if hub has all items
$skus = [];
$branch_codes[] = $hub->getBranchCode();
$result = false;
foreach ($items as $key=> $value)
{
// add sap code of item/battery into array since
// getBranchesInventory takes in an array of hubs/branches
// and an array of skus
// $items as format: $items[sku] = quantity
$skus[] = $key;
}
// call InventoryManager's getBranchesInventory to check if hub has all items
$branches_with_items = $this->im->getBranchesInventory($branch_codes, $skus);
if (!empty($branches_with_items))
{
// check if branch has enough quantity for item
foreach ($branches_with_items as $branch)
{
// get quantity from call
$qty_available = $branch['Quantity'];
// get the quantity request
$sku_requested = $branch['SapCode'];
$qty_requested = $items[$sku_requested];
if ($qty_available >= $qty_requested)
$result = true;
}
}
// return true or false
return $result;
}
// convert db distance to kilometers
protected function distance($lat1, $lon1, $lat2, $lon2)
{
@ -196,50 +440,26 @@ class HubSelector
return round(($miles * 1.609344), 1);
}
protected function isExemptedByArea(string $filter_id, Point $coordinates): bool
protected function sendSMSMessage($hub, $items)
{
$long = $coordinates->getLongitude();
$lat = $coordinates->getLatitude();
// get supported area given a set of coordinates
$query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true');
$area = $query->setParameter('long', $long)
->setParameter('lat', $lat)
->setMaxResults(1)
->getOneOrNullResult();
if ($area !== null) {
// get all exceptions
$exceptions = $area->getHubFilterExceptions();
if (isset($exceptions[$filter_id])) {
error_log("FILTER " . $filter_id . " DISABLED FOR AREA: " . $area->getName());
// disable this filter for this area
return true;
}
// compose message
// get the skus for the message
$sku_text = '';
foreach ($items as $key => $value)
{
$sku_text .= ' ' . $key;
}
$message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message'));
// filter is in place
return false;
}
// get hub notification number
$mobile_number = $hub->getNotifNumber();
protected function logClosestHubResult($jo_id, $hub, $distance, $limit_distance)
{
// log to file
$filename = '/../../var/log/closest_hubs_selected.log';
$date = date("Y-m-d H:i:s");
// build log entry
$entry = implode("", [
"[JO: " . $jo_id . "]",
"[" . $date . "]",
"[Distance: " . $distance . " vs " . $limit_distance . "]",
" " . $hub->getName() . " (ID: " . $hub->getID() . ")",
"\r\n",
]);
@file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND);
if (!empty($mobile_number))
{
// send SMS message
// error_log('sending sms to - ' . $mobile_number);
$this->rt->sendSMS($mobile_number, $this->trans->trans('message.battery_brand_allcaps'), $message);
}
}
}

View file

@ -91,7 +91,7 @@ class InsuranceConnector
return base64_encode($this->username . ":" . $this->password);
}
protected function doRequest($url, $method, $request_body = [])
protected function doRequest($url, $method, $body = [])
{
$client = new Client();
$headers = [
@ -102,7 +102,7 @@ class InsuranceConnector
try {
$response = $client->request($method, $this->base_url . '/' . $url, [
'json' => $request_body,
'json' => $body,
'headers' => $headers,
]);
} catch (RequestException $e) {
@ -111,11 +111,6 @@ class InsuranceConnector
error_log("Insurance API Error: " . $error['message']);
error_log(Psr7\Message::toString($e->getRequest()));
error_log($e->getResponse()->getBody()->getContents());
error_log("Insurance Creds: " . $this->username . ", " . $this->password);
error_log("Insurance Hash: " . $this->generateHash());
// log this error
$this->log($url, Psr7\Message::toString($e->getRequest()), Psr7\Message::toString($e->getResponse()), 'error');
if ($e->hasResponse()) {
$error['response'] = Psr7\Message::toString($e->getResponse());
@ -127,32 +122,11 @@ class InsuranceConnector
];
}
$result_body = $response->getBody();
// log response
$this->log($url, json_encode($request_body), $result_body);
error_log(print_r(json_decode($response->getBody(), true), true));
return [
'success' => true,
'response' => json_decode($result_body, true),
'response' => json_decode($response->getBody(), true)
];
}
// TODO: make this more elegant
public function log($title, $request_body = "[]", $result_body = "[]", $type = 'api')
{
$filename = '/../../var/log/insurance_' . $type . '.log';
$date = date("Y-m-d H:i:s");
// build log entry
$entry = implode("\r\n", [
$date,
$title,
"REQUEST:\r\n" . $request_body,
"RESPONSE:\r\n" . $result_body,
"\r\n----------------------------------------\r\n\r\n",
]);
@file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND);
}
}

View file

@ -134,7 +134,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface
}
// generate invoice criteria
public function generateInvoiceCriteria($jo, $discount, $invoice_items, $price_tier = null, $source = null, &$error_array)
public function generateInvoiceCriteria($jo, $discount, $invoice_items, $source = null, &$error_array)
{
$em = $this->em;

View file

@ -144,7 +144,7 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface
}
// generate invoice criteria
public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $price_tier = null, $source = null, &$error_array)
public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source = null, &$error_array)
{
$em = $this->em;

View file

@ -4,7 +4,6 @@ namespace App\Service;
use App\Entity\Invoice;
use App\Entity\JobOrder;
use App\Entity\PriceTier;
use App\Ramcar\InvoiceCriteria;
@ -14,7 +13,7 @@ interface InvoiceGeneratorInterface
public function generateInvoice(InvoiceCriteria $criteria);
// generate invoice criteria
public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, $source, PriceTier $price_tier, array &$error_array);
public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, $source, array &$error_array);
// prepare draft for invoice
public function generateDraftInvoice(InvoiceCriteria $criteria, int $promo_id, array $service_charges, array $items);

View file

@ -10,7 +10,6 @@ use Doctrine\ORM\EntityManagerInterface;
use App\InvoiceRule;
use App\Service\InvoiceGeneratorInterface;
use App\Service\PriceTierManager;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\InvoiceStatus;
@ -29,14 +28,12 @@ class InvoiceManager implements InvoiceGeneratorInterface
protected $em;
protected $validator;
protected $available_rules;
protected $pt_manager;
public function __construct(EntityManagerInterface $em, Security $security, ValidatorInterface $validator, PriceTierManager $pt_manager)
public function __construct(EntityManagerInterface $em, Security $security, ValidatorInterface $validator)
{
$this->em = $em;
$this->security = $security;
$this->validator = $validator;
$this->pt_manager = $pt_manager;
$this->available_rules = $this->getAvailableRules();
}
@ -45,37 +42,28 @@ class InvoiceManager implements InvoiceGeneratorInterface
{
// TODO: get list of invoice rules from .env or a json file?
return [
new InvoiceRule\BatterySales($this->em, $this->pt_manager),
new InvoiceRule\BatteryReplacementWarranty($this->em, $this->pt_manager),
new InvoiceRule\Jumpstart($this->em, $this->pt_manager),
new InvoiceRule\JumpstartWarranty($this->em, $this->pt_manager),
new InvoiceRule\PostRecharged($this->em, $this->pt_manager),
new InvoiceRule\PostReplacement($this->em, $this->pt_manager),
new InvoiceRule\Overheat($this->em, $this->pt_manager),
new InvoiceRule\Fuel($this->em, $this->pt_manager),
new InvoiceRule\TireRepair($this->em, $this->pt_manager),
new InvoiceRule\BatterySales($this->em),
new InvoiceRule\BatteryReplacementWarranty($this->em),
new InvoiceRule\Jumpstart($this->em),
new InvoiceRule\JumpstartWarranty($this->em),
new InvoiceRule\PostRecharged($this->em),
new InvoiceRule\PostReplacement($this->em),
new InvoiceRule\Overheat($this->em),
new InvoiceRule\Fuel($this->em),
new InvoiceRule\TireRepair($this->em),
new InvoiceRule\DiscountType($this->em),
new InvoiceRule\TradeIn($this->em),
new InvoiceRule\Tax($this->em, $this->pt_manager),
new InvoiceRule\TradeIn(),
new InvoiceRule\Tax($this->em),
];
}
// this is called when JO is submitted
public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, &$error_array)
public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, &$error_array)
{
// instantiate the invoice criteria
$criteria = new InvoiceCriteria();
$criteria->setServiceType($jo->getServiceType())
->setCustomerVehicle($jo->getCustomerVehicle())
->setPriceTier($price_tier);
if (($jo->getServiceType() == ServiceType::OVERHEAT_ASSISTANCE) &&
($jo->hasCoolant()))
$criteria->setHasCoolant(true);
if (($jo->getServiceType() == ServiceType::TIRE_REPAIR) &&
($jo->hasSealant()))
$criteria->setHasSealant(true);
->setCustomerVehicle($jo->getCustomerVehicle());
// set if taxable
// NOTE: ideally, this should be a parameter when calling generateInvoiceCriteria. But that
@ -174,7 +162,6 @@ class InvoiceManager implements InvoiceGeneratorInterface
// (3) generateInvoiceCriteria
// (4) RiderAPIHandler's changeService
// (5) TAPI's JobOrderController
// (6) CAPI's RiderAppController
public function generateInvoice($criteria)
{
// no need to validate since generateDraftInvoice was called before this was called
@ -223,27 +210,17 @@ class InvoiceManager implements InvoiceGeneratorInterface
$price = $item['price'];
$battery = null;
$battery_size = null;
$trade_in_type = '';
if (isset($item['battery']))
$battery = $item['battery'];
if (isset($item['promo']))
$promo = $item['promo'];
if (isset($item['battery_size']))
$battery_size = $item['battery_size'];
if (isset($item['trade_in_type']))
$trade_in_type = $item['trade_in_type'];
$invoice_items[] = [
'title' => $title,
'quantity' => $quantity,
'price' => $price,
'battery' => $battery,
'battery_size' => $battery_size,
'trade_in_type' => $trade_in_type,
];
}
}
@ -292,15 +269,11 @@ class InvoiceManager implements InvoiceGeneratorInterface
$invoice_item->setInvoice($invoice)
->setTitle($item['title'])
->setQuantity($item['quantity'])
->setPrice((float)$item['price'])
->setTradeInType($item['trade_in_type']);
->setPrice((float)$item['price']);
if ($item['battery'] != null)
$invoice_item->setBattery($item['battery']);
if ($item['battery_size'] != null)
$invoice_item->setBatterySize($item['battery_size']);
$invoice->addItem($invoice_item);
}

View file

@ -30,7 +30,6 @@ use App\Entity\EmergencyType;
use App\Entity\OwnershipType;
use App\Entity\CustomerLocation;
use App\Entity\Battery;
use App\Entity\BatterySize;
use App\Ramcar\ServiceType;
use App\Ramcar\TradeInType;
@ -70,7 +69,6 @@ use App\Service\HubSelector;
use App\Service\HubDistributor;
use App\Service\HubFilteringGeoChecker;
use App\Service\JobOrderManager;
use App\Service\PriceTierManager;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
@ -96,9 +94,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
protected $hub_dist;
protected $hub_geofence;
protected $cust_distance_limit;
protected $hub_filter_enabled;
protected $hub_filter_enable;
protected $jo_manager;
protected $pt_manager;
protected $template_hash;
@ -107,7 +104,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah,
string $country_code, WarrantyHandler $wh, RisingTideGateway $rt,
PromoLogger $promo_logger, HubDistributor $hub_dist, HubFilteringGeoChecker $hub_geofence,
string $cust_distance_limit, string $hub_filter_enabled, JobOrderManager $jo_manager, PriceTierManager $pt_manager)
string $cust_distance_limit, string $hub_filter_enabled, JobOrderManager $jo_manager)
{
$this->em = $em;
$this->ic = $ic;
@ -124,7 +121,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$this->cust_distance_limit = $cust_distance_limit;
$this->hub_filter_enabled = $hub_filter_enabled;
$this->jo_manager = $jo_manager;
$this->pt_manager = $pt_manager;
$this->loadTemplates();
}
@ -589,9 +585,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
{
$source = $jo->getSource();
// get the price tier according to location.
$price_tier = $this->pt_manager->getPriceTier($jo->getCoordinates());
$this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, $error_array);
$this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $error_array);
}
// validate
@ -823,9 +817,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
{
$source = $obj->getSource();
// get the price tier according to location.
$price_tier = $this->pt_manager->getPriceTier($obj->getCoordinates());
$this->ic->generateInvoiceCriteria($obj, $promo_id, $invoice_items, $source, $price_tier, $error_array);
$this->ic->generateInvoiceCriteria($obj, $promo_id, $invoice_items, $source, $error_array);
}
// validate
@ -2044,7 +2036,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// NOTE: for resq2 app
$mclientv2->sendEvent($obj, $payload);
$mclientv2->sendRiderEvent($obj, $payload);
$fcmclient->sendJoEvent($obj, "jo_fcm_title_driver_assigned", "jo_fcm_body_driver_assigned");
}
@ -2196,9 +2187,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// NOTE: this is CMB code but for compilation purposes we need to add this
$source = $jo->getSource();
// get the price tier according to location.
$price_tier = $this->pt_manager->getPriceTier($jo->getCoordinates());
$this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, $error_array);
$this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $error_array);
}
// validate
@ -2335,13 +2324,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$em = $this->em;
$jo = $em->getRepository(JobOrder::class)->find($id);
// get the job order's invoice items
$invoice = $jo->getInvoice();
$invoice_items = [];
if ($invoice != null)
$invoice_items = $invoice->getItems();
$params['invoice_items'] = $invoice_items;
$params['obj'] = $jo;
$params['mode'] = 'open_edit';
$params['cvid'] = $jo->getCustomerVehicle()->getID();
@ -2581,20 +2563,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setDateTime($obj->getDateSchedule())
->setLimitResults(50);
// NOTE: set JO type regardless, for now
$hub_criteria->setJoType($obj->getServiceType())
->setOrderDate($obj->getDateCreate())
->setServiceType($obj->getServiceType());
// set customer class
$cust = $obj->getCustomer();
$hub_criteria->setCustomerClass($cust->getCustomerClassification());
// check if hub filter is enabled. If not, use default values
// for the rest of the HubCriteria fields
if ($this->hub_filter_enabled == 'true')
{
// TODO: allow this to be disabled via env or CRM. commenting out for now
// error_log('hub filter is enabled');
if ($this->hub_geofence->isCovered($long, $lat))
{
@ -2602,6 +2574,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// error_log('Area is covered by hub filtering');
$hub_criteria->setLimitDistance($this->cust_distance_limit)
->setPaymentMethod($obj->getModeOfPayment())
->setJoType($obj->getServiceType())
->setRoundRobin(true);
}
}
@ -2612,22 +2585,15 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
{
// reset distance limit if emergency
//TODO: move to .env the emergency distance limit
$hub_criteria->setLimitDistance(500)
->setEmergency(true)
->setPaymentMethod(null)
->setRoundRobin(false);
$hub_criteria->setLimitDistance(500);
$hub_criteria->setEmergency(true);
}
// set filter flags for inventory and available riders
$hub_criteria->setInventoryCheck();
$hub_criteria->setRidersCheck();
// get JO and customer id for logging purposes
$jo_id = $obj->getID();
$customer_id = $obj->getCustomer()->getID();
$hub_criteria->setJobOrderId($jo_id)
->setJoOrigin($obj->getSource())
->setCustomerId($customer_id);
$hubs = $hub_selector->find($hub_criteria);
@ -2692,7 +2658,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// handle inventory data
$bcode = $hub['hub']->getBranchCode();
//$hub['inventory'] = 0;
$hub['inventory'] = 0;
if ($bcode != '')
{
$branch_codes[] = $bcode;
@ -2706,65 +2672,43 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$params['hubs'][$hub_id] = $hub;
}
// get all enabled filters
$enabled_filter_str = $_ENV['ENABLED_HUB_FILTERS'];
$enabled_filters = explode(",", $enabled_filter_str);
// if inventory filter is disabled, fetch inventory here
if (!in_array('InventoryHubFilter', $enabled_filters) || $this->skipInventoryCheck($obj->getCoordinates())) {
error_log("NO INVENTORY CHECKS, GETTING INVENTORY FOR JO " . $obj->getID());
// get battery (if any)
$skus = [];
$invoice = $obj->getInvoice();
$inv_items = $invoice->getItems();
foreach ($inv_items as $inv_item)
{
$batt = $inv_item->getBattery();
if ($batt == null)
continue;
$skus[] = $batt->getSapCode();
}
// get inventory
$mres = $motiv->getInventory($branch_codes, $skus);
$x = 0;
error_log("TOTAL RESULTS FROM MOTIV: " . count($mres) . " OUT OF " . count($branch_codes) . " BRANCH CODES AND " . count($skus) . " SKUS");
foreach ($mres as $mres_item)
{
// check if we have a valid response from motiv, ignore otherwise
if (isset($mres_item['BranchCode']))
{
$bcode = $mres_item['BranchCode'];
$inv_count = $mres_item['Quantity'];
if (isset($inv_data[$bcode]))
{
$hub_id = $inv_data[$bcode]['hub_id'];
$params['hubs'][$hub_id]['inventory'] = $inv_count;
error_log("SETTING HUB " . $hub_id . " INVENTORY TO " . $inv_count);
$x++;
} else {
error_log("CANNOT FIND BCODE FOR " . $bcode);
}
} else {
error_log("CANNOT FIND BCODE FOR RESULT: " . print_r($mres_item, true));
}
}
error_log("SET QUANTITY OF " . $x . " HUBS TO NON ZERO");
// error_log(print_r($mres, true));
}
$params['obj'] = $obj;
// get template to display
$params['template'] = $this->getTwigTemplate('jo_processing_form');
// get battery (if any)
$skus = [];
$invoice = $obj->getInvoice();
$inv_items = $invoice->getItems();
foreach ($inv_items as $inv_item)
{
$batt = $inv_item->getBattery();
if ($batt == null)
continue;
$skus[] = $batt->getSapCode();
}
// get inventory
$mres = $motiv->getInventory($branch_codes, $skus);
foreach ($mres as $mres_item)
{
// check if we have a valid response from motiv, ignore otherwise
if (isset($mres_item['BranchCode']))
{
$bcode = $mres_item['BranchCode'];
$inv_count = $mres_item['Quantity'];
if (isset($inv_data[$bcode]))
{
$hub_id = $inv_data[$bcode]['hub_id'];
$params['hubs'][$hub_id]['inventory'] = $inv_count;
}
}
}
// error_log(print_r($mres, true));
return $params;
}
@ -2957,22 +2901,13 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
->setDateTime($obj->getDateSchedule())
->setLimitResults(50);
// NOTE: set JO type regardless, for now
$hub_criteria->setJoType($obj->getServiceType())
->setOrderDate($obj->getDateCreate())
->setServiceType($obj->getServiceType());
// set customer class
$cust = $obj->getCustomer();
$hub_criteria->setCustomerClass($cust->getCustomerClassification());
// TODO: allow this to be disabled via env or CRM. commenting out for now
if ($this->hub_geofence->isCovered($long, $lat))
{
// if true, set other values for HubCriteria
// error_log('Area is covered by hub');
$hub_criteria->setLimitDistance($this->cust_distance_limit)
->setPaymentMethod($obj->getModeOfPayment())
->setJoType($obj->getServiceType())
->setRoundRobin(true);
}
@ -2981,22 +2916,15 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
if ($willing_to_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT)
{
//TODO: move to .env the emergency distance limit
$hub_criteria->setLimitDistance(500)
->setEmergency(true)
->setPaymentMethod(null)
->setRoundRobin(false);
$hub_criteria->setLimitDistance(500);
$hub_criteria->setEmergency(true);
}
// set filter flags for inventory and available riders
$hub_criteria->setInventoryCheck();
$hub_criteria->setRidersCheck();
// get JO and customer id for logging purposes
$jo_id = $obj->getID();
$customer_id = $cust->getID();
$customer_id = $obj->getCustomer()->getID();
$hub_criteria->setJobOrderId($jo_id)
->setJoOrigin($obj->getSource())
->setCustomerId($customer_id);
$hubs = $hub_selector->find($hub_criteria);
@ -3005,8 +2933,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$params['hubs'] = [];
$branch_codes = [];
$inv_data = [];
// format duration and distance into friendly time
foreach ($hubs as $hub) {
// duration
@ -3062,7 +2988,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// handle inventory data
$bcode = $hub['hub']->getBranchCode();
//$hub['inventory'] = 0;
$hub['inventory'] = 0;
if ($bcode != '')
{
$branch_codes[] = $bcode;
@ -3075,62 +3001,40 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$params['hubs'][$hub_id] = $hub;
}
// get all enabled filters
$enabled_filter_str = $_ENV['ENABLED_HUB_FILTERS'];
$enabled_filters = explode(",", $enabled_filter_str);
// if inventory filter is disabled, fetch inventory here
if (!in_array('InventoryHubFilter', $enabled_filters) || $this->skipInventoryCheck($obj->getCoordinates())) {
error_log("NO INVENTORY CHECKS, GETTING INVENTORY FOR JO " . $obj->getID());
// get battery (if any)
$skus = [];
$invoice = $obj->getInvoice();
$inv_items = $invoice->getItems();
foreach ($inv_items as $inv_item)
{
$batt = $inv_item->getBattery();
if ($batt == null)
continue;
// get battery (if any)
$skus = [];
$invoice = $obj->getInvoice();
$inv_items = $invoice->getItems();
foreach ($inv_items as $inv_item)
$skus[] = $batt->getSapCode();
}
// get inventory
$mres = $motiv->getInventory($branch_codes, $skus);
foreach ($mres as $mres_item)
{
// check if we have a valid response from motiv, ignore otherwise
if (isset($mres_item['BranchCode']))
{
$batt = $inv_item->getBattery();
if ($batt == null)
continue;
$skus[] = $batt->getSapCode();
}
// get inventory
$mres = $motiv->getInventory($branch_codes, $skus);
$x = 0;
error_log("TOTAL RESULTS FROM MOTIV: " . count($mres) . " OUT OF " . count($branch_codes) . " BRANCH CODES AND " . count($skus) . " SKUS");
foreach ($mres as $mres_item)
{
// check if we have a valid response from motiv, ignore otherwise
if (isset($mres_item['BranchCode']))
$bcode = $mres_item['BranchCode'];
$inv_count = $mres_item['Quantity'];
if (isset($inv_data[$bcode]))
{
$bcode = $mres_item['BranchCode'];
$inv_count = $mres_item['Quantity'];
if (isset($inv_data[$bcode]))
{
$hub_id = $inv_data[$bcode]['hub_id'];
$hub_id = $inv_data[$bcode]['hub_id'];
$params['hubs'][$hub_id]['inventory'] = $inv_count;
error_log("SETTING HUB " . $hub_id . " INVENTORY TO " . $inv_count);
$x++;
} else {
error_log("CANNOT FIND BCODE FOR " . $bcode);
}
} else {
error_log("CANNOT FIND BCODE FOR RESULT: " . print_r($mres_item, true));
$params['hubs'][$hub_id]['inventory'] = $inv_count;
}
}
error_log("SET QUANTITY OF " . $x . " HUBS TO NON ZERO");
// error_log(print_r($mres, true));
}
// error_log(print_r($mres, true));
$params['obj'] = $obj;
// get template to display
$params['template'] = $this->getTwigTemplate('jo_open_hub_form');
@ -3661,15 +3565,15 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$params['trade_in_bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll();
$params['promos'] = $em->getRepository(Promo::class)->findAll();
// list of battery sizes for trade-in
$ti_batt_sizes = $em->getRepository(BatterySize::class)->findAll();
$trade_in_batt_sizes = [];
foreach ($ti_batt_sizes as $ti_batt_size)
// list of batteries for trade-in
$ti_batteries = $em->getRepository(Battery::class)->findAll();
$trade_in_batteries = [];
foreach ($ti_batteries as $ti_battery)
{
$batt_size_name = $ti_batt_size->getName();
$trade_in_batt_sizes[$ti_batt_size->getID()] = $batt_size_name;
$battery_name = $ti_battery->getModel()->getName() . ' ' . $ti_battery->getSize()->getName();
$trade_in_batteries[$ti_battery->getID()] = $battery_name;
}
$params['trade_in_batt_sizes'] = $trade_in_batt_sizes;
$params['trade_in_batteries'] = $trade_in_batteries;
// list of emergency types
$e_types = $em->getRepository(EmergencyType::class)->findBy([], ['name' => 'ASC']);
@ -4367,10 +4271,12 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$phone_number = $this->country_code . $notif_number;
// check if reason is in the list of rejection reason
$flag_send_sms = $this->checkRejectionReason($rejection->getReason());
// check if reason is administrative
if ($rejection->getReason() == JORejectionReason::ADMINISTRATIVE)
return null;
if (!$flag_send_sms)
// check if reason is discount
if ($rejection->getReason() == JORejectionReason::DISCOUNT)
return null;
// sms content
@ -4404,65 +4310,4 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$this->rt->sendSMS($phone_number, $this->translator->trans('message.battery_brand_allcaps'), $msg);
}
protected function checkRejectionReason($reason): bool
{
// reason is administrative
if ($reason == JORejectionReason::ADMINISTRATIVE)
return false;
// reason is discount
if ($reason == JORejectionReason::DISCOUNT)
return false;
// reason is store closed on schedule
if ($reason == JORejectionReason::STORE_CLOSED_SCHEDULED)
return false;
// store closed half day
if ($reason == JORejectionReason::STORE_CLOSED_HALF_DAY)
return false;
// prio hub for warranty claim
if ($reason == JORejectionReason::PRIORITY_HUB_WTY_CLAIM)
return false;
// prio hub for jumpstart
if ($reason == JORejectionReason::PRIORITY_HUB_JUMPSTART)
return false;
// prio hub for RES-Q request
if ($reason == JORejectionReason::PRIORITY_HUB_RESQ_REQUEST)
return false;
// customer request
if ($reason == JORejectionReason::CUSTOMER_REQUEST)
return false;
return true;
}
protected function skipInventoryCheck(Point $coordinates): bool
{
$long = $coordinates->getLongitude();
$lat = $coordinates->getLatitude();
// get supported area given a set of coordinates
$query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true');
$area = $query->setParameter('long', $long)
->setParameter('lat', $lat)
->setMaxResults(1)
->getOneOrNullResult();
if ($area !== null) {
// get all exceptions
$exceptions = $area->getHubFilterExceptions();
if (isset($exceptions['no_inventory'])) {
return true;
}
}
// filter is in place
return false;
}
}

View file

@ -68,31 +68,4 @@ class MQTTClientApiv2
// error_log('sent to ' . $channel);
}
}
public function sendRiderEvent(JobOrder $job_order, $payload)
{
// check if a rider is available
$rider = $job_order->getRider();
if ($rider == null)
return;
/*
// NOTE: this is for the old rider app
// check if rider has sessions
$sessions = $rider->getSessions();
if (count($sessions) == 0)
return;
// send to every rider session
foreach ($sessions as $sess)
{
$sess_id = $sess->getID();
$channel = self::RIDER_PREFIX . $sess_id;
$this->publish($channel, json_encode($payload));
}
*/
// NOTE: this is for the new rider app
$this->publish('rider/' . $rider->getID() . '/delivery', json_encode($payload));
}
}

View file

@ -74,22 +74,12 @@ class PayMongoConnector
return $this->doRequest('/v1/checkout_sessions/' . $checkout_id, 'GET');
}
public function getWebhook($id)
{
return $this->doRequest('/v1/webhooks/'. $id, 'GET');
}
public function enableWebhook($id)
{
return $this->doRequest('/v1/webhooks/' . $id . '/enable', 'POST');
}
protected function generateHash()
{
return base64_encode($this->secret_key);
}
protected function doRequest($url, $method, $request_body = [])
protected function doRequest($url, $method, $body = [])
{
$client = new Client();
$headers = [
@ -100,14 +90,14 @@ class PayMongoConnector
try {
$response = $client->request($method, $this->base_url . '/' . $url, [
'json' => $request_body,
'json' => $body,
'headers' => $headers,
]);
} catch (RequestException $e) {
$error = ['message' => $e->getMessage()];
ob_start();
//var_dump($request_body);
var_dump($body);
$varres = ob_get_clean();
error_log($varres);
@ -117,9 +107,6 @@ class PayMongoConnector
error_log("PayMongo API Error: " . $error['message']);
error_log(Psr7\Message::toString($e->getRequest()));
// log this error
$this->log($url, Psr7\Message::toString($e->getRequest()), Psr7\Message::toString($e->getResponse()), 'error');
if ($e->hasResponse()) {
$error['response'] = Psr7\Message::toString($e->getResponse());
}
@ -130,32 +117,9 @@ class PayMongoConnector
];
}
$result_body = $response->getBody();
// log response
$this->log($url, json_encode($request_body), $result_body);
return [
'success' => true,
'response' => json_decode($response->getBody(), true)
];
}
// TODO: make this more elegant
public function log($title, $request_body = "[]", $result_body = "[]", $type = 'api')
{
$filename = '/../../var/log/paymongo_' . $type . '.log';
$date = date("Y-m-d H:i:s");
// build log entry
$entry = implode("\r\n", [
$date,
$title,
"REQUEST:\r\n" . $request_body,
"RESPONSE:\r\n" . $result_body,
"\r\n----------------------------------------\r\n\r\n",
]);
@file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND);
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Entity\PriceTier;
class PriceTierManager
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getItemPrice($pt_id, $item_type_id, $item_id)
{
// find the item price, given the price tier, battery id, and item type (battery)
$db_conn = $this->em->getConnection();
$ip_sql = 'SELECT ip.price AS price
FROM item_price ip
WHERE ip.price_tier_id = :pt_id
AND ip.item_type_id = :it_id
AND ip.item_id = :item_id';
$ip_stmt = $db_conn->prepare($ip_sql);
$ip_stmt->bindValue('pt_id', $pt_id);
$ip_stmt->bindValue('it_id', $item_type_id);
$ip_stmt->bindValue('item_id', $item_id);
$ip_result = $ip_stmt->executeQuery();
// results found
$actual_price = null;
// go through rows
while ($row = $ip_result->fetchAssociative())
{
// get the price
$price = $row['price'];
// actual price
$actual_price = number_format($price / 100, 2, '.', '');
}
return $actual_price;
}
public function getPriceTier(Point $coordinates)
{
$price_tier_id = 0;
if ($coordinates != null)
{
$long = $coordinates->getLongitude();
$lat = $coordinates->getLatitude();
// get location's price tier, given a set of coordinates
$query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true');
$area = $query->setParameter('long', $long)
->setParameter('lat', $lat)
->setMaxResults(1)
->getOneOrNullResult();
if ($area != null)
{
$price_tier = $area->getPriceTier();
if ($price_tier != null)
$price_tier_id = $price_tier->getID();
}
}
return $price_tier_id;
}
}

View file

@ -8,11 +8,11 @@
</select>
</div>
<div class="col-lg-3">
<label for="invoice-trade-in-battery-size">Battery Size For Trade In</label>
<select class="form-control m-input" id="invoice-trade-in-battery-size" data-value="">
<label for="invoice-trade-in-battery">Battery For Trade In</label>
<select class="form-control m-input" id="invoice-trade-in-battery" data-value="">
<option value=""></option>
{% for id, batt_size_name in trade_in_batt_sizes %}
<option value="{{ id }}">{{ batt_size_name }}</option>
{% for id, battery_name in trade_in_batteries %}
<option value="{{ id }}">{{ battery_name }}</option>
{% endfor %}
</select>
</div>

View file

@ -1,13 +1,13 @@
// add trade in battery to invoice
$('#btn-add-trade-in-to-invoice').click(function() {
var bmfg = $("#invoice-trade-in-bmfg").val();
var battery_size = $("#invoice-trade-in-battery-size").val();
var battery = $("#invoice-trade-in-battery").val();
var tradeIn = $("#invoice-trade-in-type").val();
var qty = $("#invoice-trade-in-quantity").val();
// add to invoice array
invoiceItems.push({
battery_size: battery_size,
battery: battery,
quantity: qty,
trade_in: tradeIn,
});

View file

@ -1,164 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Item Pricing</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-12">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-2">
<label>Item Prices for </label>
</div>
<div class="col-md-3">
<div class="m-input-icon m-input-icon--left">
<div class="input-group">
<select class="form-control m-input" id="price-tier-select" name="price_tier_list">
<option value="0">Default Price Tier</option>
{% for price_tier in sets.price_tiers %}
<option value="{{ price_tier.getID }}">{{ price_tier.getName }} </option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="col-md-3">
<div class="m-input-icon m-input-icon--left">
<div class="input-group">
<select class="form-control m-input" id="item-type-select" name="item_type_list">
{% for item_type in sets.item_types %}
<option value="{{ item_type.getID }}">{{ item_type.getName }} </option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right" method="post" action="{{ url('item_pricing_update') }}">
<input id="price-tier-id" type="hidden" name="price_tier_id" value="0">
<input id="item-type-id" type="hidden" name="item_type_id" value="{{ default_item_type_id }}">
<div style="padding-left: 25px; padding-right: 25px;">
<table class="table">
<thead>
<tr>
<th style="width: 100px">ID</th>
<th>Name</th>
<th hidden> Item Type ID </th>
<th>Item Type</th>
<th style="width: 180px">Price</th>
</tr>
</thead>
<tbody id="table-body">
{% for id, item in items.items %}
<tr>
<td>{{ id }}</td>
<td>{{ item.name }} </td>
<td hidden> {{ item.item_type_id }} </td>
<td>{{ item.item_type }} </td>
<td class="py-1">
<input name="price[{{ id }}]" class="form-control ca-filter" type="number" value="{{ item.price }}" step="0.01">
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="">
<input type="submit" class="btn btn-primary" value="Update Price">
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
initialize();
function initialize() {
init_price_tier_dropdown();
init_item_type_dropdown();
}
function init_price_tier_dropdown() {
var pt_dropdown = document.getElementById('price-tier-select');
var it_dropdown = document.getElementById('item-type-select');
pt_dropdown.addEventListener('change', function(e) {
var it_type = it_dropdown.value;
load_prices(e.target.value, it_type);
});
}
function init_item_type_dropdown() {
var it_dropdown = document.getElementById('item-type-select');
var pt_dropdown = document.getElementById('price-tier-select');
it_dropdown.addEventListener('change', function(e) {
var pt_type = pt_dropdown.value;
load_prices(pt_type, e.target.value);
});
}
function load_prices(price_tier_id, item_type_id) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
// process response
if (this.readyState == 4 && this.status == 200) {
// update form
update_table(JSON.parse(req.responseText));
var pt_field = document.getElementById('price-tier-id');
pt_field.value = price_tier_id;
var it_field = document.getElementById('item-type-id');
it_field.value = item_type_id;
} else {
// console.log('could not load tier prices');
}
}
var url_pattern = '{{ url('item_pricing_prices', {'pt_id': '--id--', 'it_id': '--it-id--'}) }}';
var url = url_pattern.replace('--id--', price_tier_id).replace('--it-id--', item_type_id);
console.log(url);
req.open('GET', url, true);
req.send();
}
function update_table(data) {
console.log(data);
var item_html = '';
for (var i in data.items) {
var item = data.items[i];
// console.log(item);
item_html += '<tr>';
item_html += '<td>' + item.id + '</td>';
item_html += '<td>' + item.name + '</td>';
item_html += '<td hidden>' + item.item_type_id + '</td>';
item_html += '<td>' + item.item_type + '</td>';
item_html += '<td class="py-1">';
item_html += '<input name="price[' + item.id + ']" class="form-control ca-filter" type="number" value="' + item.price + '" step="0.01">';
item_html += '</td>';
item_html += '</tr>';
}
var table_body = document.getElementById('table-body');
table_body.innerHTML = item_html;
}
</script>
{% endblock %}

View file

@ -1,142 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Item Types</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-6">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<span class="m-portlet__head-icon">
<i class="la la-industry"></i>
</span>
<h3 class="m-portlet__head-text">
{% if mode == 'update' %}
Edit Item Type
<small>{{ obj.getName() }}</small>
{% else %}
New Item Type
{% endif %}
</h3>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right m-form--group-seperator-dashed" method="post" action="{{ mode == 'update' ? url('item_type_update_submit', {'id': obj.getId()}) : url('item_type_add_submit') }}">
<div class="m-portlet__body">
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="name">
Name:
</label>
<div class="col-lg-9">
<input type="text" name="name" class="form-control m-input" value="{{ obj.getName() }}">
<div class="form-control-feedback hide" data-field="name"></div>
</div>
</div>
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="code">
Code:
</label>
<div class="col-lg-9">
<input type="text" name="code" class="form-control m-input" value="{{ obj.getCode() }}">
<div class="form-control-feedback hide" data-field="code"></div>
</div>
</div>
</div>
<div class="m-portlet__foot m-portlet__foot--fit">
<div class="m-form__actions m-form__actions--solid m-form__actions--right">
<div class="row">
<div class="col-lg-12">
<button type="submit" class="btn btn-success">Submit</button>
<a href="{{ url('item_type_list') }}" class="btn btn-secondary">Back</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
$("#row-form").submit(function(e) {
var form = $(this);
e.preventDefault();
$.ajax({
method: "POST",
url: form.prop('action'),
data: form.serialize()
}).done(function(response) {
// remove all error classes
removeErrors();
swal({
title: 'Done!',
text: 'Your changes have been saved!',
type: 'success',
onClose: function() {
window.location.href = "{{ url('item_type_list') }}";
}
});
}).fail(function(response) {
if (response.status == 422) {
var errors = response.responseJSON.errors;
var firstfield = false;
// remove all error classes first
removeErrors();
// display errors contextually
$.each(errors, function(field, msg) {
var formfield = $("[name='" + field + "']");
var label = $("label[data-field='" + field + "']");
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
// add error classes to bad fields
formfield.addClass('form-control-danger');
label.addClass('has-danger');
msgbox.html(msg).addClass('has-danger').removeClass('hide');
// check if this field comes first in DOM
var domfield = formfield.get(0);
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
firstfield = domfield;
}
});
// focus on first bad field
firstfield.focus();
// scroll to above that field to make it visible
$('html, body').animate({
scrollTop: $(firstfield).offset().top - 200
}, 100);
}
});
});
// remove all error classes
function removeErrors() {
$(".form-control-danger").removeClass('form-control-danger');
$("[data-field]").removeClass('has-danger');
$(".form-control-feedback[data-field]").addClass('hide');
}
});
</script>
{% endblock %}

View file

@ -1,146 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Item Types
</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-8 order-2 order-xl-1">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input m-input--solid" placeholder="Search..." id="data-rows-search">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-4 order-1 order-xl-2 m--align-right">
<a href="{{ url('item_type_add_form') }}" class="btn btn-focus m-btn m-btn--custom m-btn--icon m-btn--air m-btn--pill">
<span>
<i class="la la-industry"></i>
<span>New Item Type</span>
</span>
</a>
<div class="m-separator m-separator--dashed d-xl-none"></div>
</div>
</div>
</div>
<!--begin: Datatable -->
<div id="data-rows"></div>
<!--end: Datatable -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
var options = {
data: {
type: 'remote',
source: {
read: {
url: '{{ url("item_type_rows") }}',
method: 'POST'
}
},
saveState: {
cookie: false,
webstorage: false
},
pageSize: 10,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
layout: {
scroll: true
},
columns: [
{
field: 'id',
title: 'ID',
width: 30
},
{
field: 'name',
title: 'Name'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '';
if (row.meta.update_url != '') {
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" data-id="' + row.name + '" title="Edit"><i class="la la-edit"></i></a>';
}
if (row.meta.delete_url != '') {
actions += '<a href="' + row.meta.delete_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-danger m-btn--icon m-btn--icon-only m-btn--pill btn-delete" data-id="' + row.name + '" title="Delete"><i class="la la-trash"></i></a>';
}
return actions;
},
}
],
search: {
onEnter: false,
input: $('#data-rows-search'),
delay: 400
}
};
var table = $("#data-rows").mDatatable(options);
$(document).on('click', '.btn-delete', function(e) {
var url = $(this).prop('href');
var id = $(this).data('id');
var btn = $(this);
e.preventDefault();
swal({
title: 'Confirmation',
html: 'Are you sure you want to delete <strong>' + id + '</strong>?',
type: 'warning',
showCancelButton: true
}).then((result) => {
if (result.value) {
$.ajax({
method: "DELETE",
url: url
}).done(function(response) {
table.row(btn.parents('tr')).remove();
table.reload();
});
}
});
});
});
</script>
{% endblock %}

View file

@ -1,177 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Items</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-6">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<span class="m-portlet__head-icon">
<i class="la la-industry"></i>
</span>
<h3 class="m-portlet__head-text">
{% if mode == 'update' %}
Edit Item
<small>{{ obj.getName() }}</small>
{% else %}
New Item
{% endif %}
</h3>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right m-form--group-seperator-dashed" method="post" action="{{ mode == 'update' ? url('item_type_update_submit', {'id': obj.getId()}) : url('item_add_submit') }}">
<div class="m-portlet__body">
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="item_type">
Item Type:
</label>
<div class="col-lg-9">
<select class="form-control m-input" id="item-type" name="item_type">
{% for id, label in sets.item_types %}
{% if obj.getItemType %}
<option value="{{ id }}"{{ obj.getItemType.getID == id ? ' selected' }}>{{ label }}</option>
{% else %}
<option value="{{ id }}">{{ label }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="item_type"></div>
</div>
</div>
<div class="form-group m-form__group row no-border
{% if obj.getItemType %}
{% if obj.getItemType.getCode is not same as ('battery') %}
hide
{% endif %}
{% else %}
hide
{% endif %}
" id="battery-row">
<label class="col-lg-3 col-form-label" data-field="battery">
Battery:
</label>
<div class="col-lg-9">
<select class="form-control m-input" id="item-type" name="battery">
{% for id, label in sets.batteries %}
<option value="{{ id }}"{{ obj.getItemID == id ? ' selected' }}>{{ label }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="battery"></div>
</div>
</div>
</div>
<div class="m-portlet__foot m-portlet__foot--fit">
<div class="m-form__actions m-form__actions--solid m-form__actions--right">
<div class="row">
<div class="col-lg-12">
<button type="submit" class="btn btn-success">Submit</button>
<a href="{{ url('item_list') }}" class="btn btn-secondary">Back</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
$("#row-form").submit(function(e) {
var form = $(this);
e.preventDefault();
$.ajax({
method: "POST",
url: form.prop('action'),
data: form.serialize()
}).done(function(response) {
// remove all error classes
removeErrors();
swal({
title: 'Done!',
text: 'Your changes have been saved!',
type: 'success',
onClose: function() {
window.location.href = "{{ url('item_list') }}";
}
});
}).fail(function(response) {
if (response.status == 422) {
var errors = response.responseJSON.errors;
var firstfield = false;
// remove all error classes first
removeErrors();
// display errors contextually
$.each(errors, function(field, msg) {
var formfield = $("[name='" + field + "']");
var label = $("label[data-field='" + field + "']");
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
// add error classes to bad fields
formfield.addClass('form-control-danger');
label.addClass('has-danger');
msgbox.html(msg).addClass('has-danger').removeClass('hide');
// check if this field comes first in DOM
var domfield = formfield.get(0);
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
firstfield = domfield;
}
});
// focus on first bad field
firstfield.focus();
// scroll to above that field to make it visible
$('html, body').animate({
scrollTop: $(firstfield).offset().top - 200
}, 100);
}
});
});
// remove all error classes
function removeErrors() {
$(".form-control-danger").removeClass('form-control-danger');
$("[data-field]").removeClass('has-danger');
$(".form-control-feedback[data-field]").addClass('hide');
}
});
$('#item-type').change(function(e) {
console.log('item type change ' + e.target.value);
if (e.target.value === '1') {
// display battery row
$('#battery-row').removeClass("hide");
// hide service offering rows
} else {
// display service offering row
// hide battery row
$('#battery-row').addClass("hide");
}
})
</script>
{% endblock %}

View file

@ -1,146 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Items
</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-8 order-2 order-xl-1">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input m-input--solid" placeholder="Search..." id="data-rows-search">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-4 order-1 order-xl-2 m--align-right">
<a href="{{ url('item_add_form') }}" class="btn btn-focus m-btn m-btn--custom m-btn--icon m-btn--air m-btn--pill">
<span>
<i class="la la-industry"></i>
<span>New Item</span>
</span>
</a>
<div class="m-separator m-separator--dashed d-xl-none"></div>
</div>
</div>
</div>
<!--begin: Datatable -->
<div id="data-rows"></div>
<!--end: Datatable -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
var options = {
data: {
type: 'remote',
source: {
read: {
url: '{{ url("item_rows") }}',
method: 'POST'
}
},
saveState: {
cookie: false,
webstorage: false
},
pageSize: 10,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
layout: {
scroll: true
},
columns: [
{
field: 'id',
title: 'ID',
width: 30
},
{
field: 'name',
title: 'Name'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '';
if (row.meta.update_url != '') {
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" data-id="' + row.name + '" title="Edit"><i class="la la-edit"></i></a>';
}
if (row.meta.delete_url != '') {
actions += '<a href="' + row.meta.delete_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-danger m-btn--icon m-btn--icon-only m-btn--pill btn-delete" data-id="' + row.name + '" title="Delete"><i class="la la-trash"></i></a>';
}
return actions;
},
}
],
search: {
onEnter: false,
input: $('#data-rows-search'),
delay: 400
}
};
var table = $("#data-rows").mDatatable(options);
$(document).on('click', '.btn-delete', function(e) {
var url = $(this).prop('href');
var id = $(this).data('id');
var btn = $(this);
e.preventDefault();
swal({
title: 'Confirmation',
html: 'Are you sure you want to delete <strong>' + id + '</strong>?',
type: 'warning',
showCancelButton: true
}).then((result) => {
if (result.value) {
$.ajax({
method: "DELETE",
url: url
}).done(function(response) {
table.row(btn.parents('tr')).remove();
table.reload();
});
}
});
});
});
</script>
{% endblock %}

View file

@ -630,24 +630,20 @@
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label>Discount Type</label>
{% if ftags.invoice_edit %}
<select class="form-control m-input" id="invoice-promo" name="invoice_promo">
<option value="">None</option>
{% for promo in promos %}
{% if obj.getInvoice and obj.getInvoice.getPromo %}
<option value="{{ promo.getID() }}" {{ obj.getInvoice.getPromo.getID == promo.getID ? ' selected'}}>{{ promo.getName() ~ ' (' ~ promo.getDiscountRate * 100 ~ '% applied to ' ~ discount_apply[promo.getDiscountApply] ~ ')' }}</option>
{% else %}
<option value="{{ promo.getID() }}">{{ promo.getName() ~ ' (' ~ promo.getDiscountRate * 100 ~ '% applied to ' ~ discount_apply[promo.getDiscountApply] ~ ')' }}</option>
{% endif %}
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_promo"></div>
{% else %}
<input type="text" id="invoice-promo" class="form-control m-input" value="{{ obj.getInvoice.getPromo.getName|default('None') }}" disabled>
{% endif %}
</div>
<div class="col-lg-6">
<label>Discount Type</label>
{% if ftags.invoice_edit %}
<select class="form-control m-input" id="invoice-promo" name="invoice_promo">
<option value="">None</option>
{% for promo in promos %}
<option value="{{ promo.getID() }}">{{ promo.getName() ~ ' (' ~ promo.getDiscountRate * 100 ~ '% applied to ' ~ discount_apply[promo.getDiscountApply] ~ ')' }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_promo"></div>
{% else %}
<input type="text" id="invoice-promo" class="form-control m-input" value="{{ obj.getInvoice.getPromo.getName|default('None') }}" disabled>
{% endif %}
</div>
<div class="col-lg-3">
<label>Promo Discount</label>
<input type="text" id="invoice-promo-discount" class="form-control m-input text-right" value="{{ obj.getInvoice ? obj.getInvoice.getDiscount|number_format(2) : '0.00' }}" disabled>
@ -1206,10 +1202,6 @@
<script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script>
<script>
var invoiceItems = [];
var hasCoolant = 0;
var hasSealant = 0;
// location search autocomplete
var input = document.getElementById('m_gmap_address');
@ -1234,7 +1226,6 @@ autocomplete.addListener('place_changed', function() {
$(function() {
var form_in_process = false;
var invoiceItems = [];
// openstreet maps stuff
// TODO: move this to a service
@ -1245,49 +1236,6 @@ $(function() {
var markerLayerGroup = L.layerGroup().addTo(osm_map);
function populateInvoiceItems()
{
{% if invoice_items is defined %}
{% for item in invoice_items %}
var qty = {{ item.getQuantity }};
{% if item.getBattery is not null %}
var battery_id = {{ item.getBattery.getID }};
invoiceItems.push({
battery: battery_id,
quantity: qty,
trade_in: '',
});
{% else %}
{% if item.getBatterySize is not null %}
var battery_size = {{ item.getBatterySize.GetID }};
var trade_in_type = '{{ item.getTradeInType }}';
// add to invoice array
invoiceItems.push({
battery_size: battery_size,
quantity: qty,
trade_in: trade_in_type,
});
{% endif %}
{% endif %}
{% endfor %}
// need to check if jo has coolant or sealant
{% if obj.getServiceType == 'overheat' %}
{% if obj.hasCoolant == 1 %}
hasCoolant = 1;
{% endif %}
{% endif %}
{% if obj.getServiceType == 'tire' %}
{% if obj.hasSealant == 1 %}
hasSealant = 1;
{% endif %}
{% endif %}
{% endif %}
}
function selectPoint(lat, lng)
{
// check if point is in coverage area
@ -1326,9 +1274,6 @@ $(function() {
$('#map_lat').val(lat);
$('#map_lng').val(lng);
// regenerate invoice
generateInvoice();
}
osm_map.on('click', function(e) {
@ -1372,11 +1317,6 @@ $(function() {
// OSM code
var lat = {{ obj.getCoordinates.getLatitude }};
var lng = {{ obj.getCoordinates.getLongitude }};
var promo = $("#invoice-promo").val();
populateInvoiceItems();
selectPoint(lat, lng);
// remove placeholder text
@ -1760,6 +1700,8 @@ $(function() {
placeholder: ""
});
var invoiceItems = [];
{% include 'invoice/trade_in.js.twig' %}
// add to invoice
@ -1829,8 +1771,6 @@ $(function() {
var table = $("#invoice-table tbody");
var stype = $("#service_type").val();
var cvid = $("#customer-vehicle").val();
var lng = $("#map_lng").val();
var lat = $("#map_lat").val();
console.log(JSON.stringify(invoiceItems));
@ -1842,11 +1782,7 @@ $(function() {
'stype': stype,
'items': invoiceItems,
'promo': promo,
'cvid': cvid,
'coord_lng': lng,
'coord_lat': lat,
'flag_coolant': hasCoolant,
'flag_sealant': hasSealant,
'cvid': cvid
}
}).done(function(response) {
// mark as invoice changed

View file

@ -1,154 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Price Tiers</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-6">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<span class="m-portlet__head-icon">
<i class="la la-industry"></i>
</span>
<h3 class="m-portlet__head-text">
{% if mode == 'update' %}
Edit Price Tier
<small>{{ obj.getName() }}</small>
{% else %}
New Price Tier
{% endif %}
</h3>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right m-form--group-seperator-dashed" method="post" action="{{ mode == 'update' ? url('price_tier_update_submit', {'id': obj.getId()}) : url('price_tier_add_submit') }}">
<div class="m-portlet__body">
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="name">
Name:
</label>
<div class="col-lg-9">
<input type="text" name="name" class="form-control m-input" value="{{ obj.getName() }}">
<div class="form-control-feedback hide" data-field="name"></div>
</div>
</div>
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="areas">
Coverage Area:
</label>
<div class="col-lg-9">
{% if sets.areas is empty %}
No available supported areas.
{% else %}
<div class="m-checkbox-list">
{% for id, label in sets.areas %}
<label class="m-checkbox">
<input type="checkbox" name="areas[]" value="{{ id }}"{{ id in obj.getSupportedAreas() ? ' checked' : '' }}>
{{ label }}
<span></span>
</label>
{% endfor %}
</div>
{% endif %}
<div class="form-control-feedback hide" data-field="areas"></div>
</div>
</div>
</div>
<div class="m-portlet__foot m-portlet__foot--fit">
<div class="m-form__actions m-form__actions--solid m-form__actions--right">
<div class="row">
<div class="col-lg-12">
<button type="submit" class="btn btn-success">Submit</button>
<a href="{{ url('price_tier_list') }}" class="btn btn-secondary">Back</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
$("#row-form").submit(function(e) {
var form = $(this);
e.preventDefault();
$.ajax({
method: "POST",
url: form.prop('action'),
data: form.serialize()
}).done(function(response) {
// remove all error classes
removeErrors();
swal({
title: 'Done!',
text: 'Your changes have been saved!',
type: 'success',
onClose: function() {
window.location.href = "{{ url('price_tier_list') }}";
}
});
}).fail(function(response) {
if (response.status == 422) {
var errors = response.responseJSON.errors;
var firstfield = false;
// remove all error classes first
removeErrors();
// display errors contextually
$.each(errors, function(field, msg) {
var formfield = $("[name='" + field + "']");
var label = $("label[data-field='" + field + "']");
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
// add error classes to bad fields
formfield.addClass('form-control-danger');
label.addClass('has-danger');
msgbox.html(msg).addClass('has-danger').removeClass('hide');
// check if this field comes first in DOM
var domfield = formfield.get(0);
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
firstfield = domfield;
}
});
// focus on first bad field
firstfield.focus();
// scroll to above that field to make it visible
$('html, body').animate({
scrollTop: $(firstfield).offset().top - 200
}, 100);
}
});
});
// remove all error classes
function removeErrors() {
$(".form-control-danger").removeClass('form-control-danger');
$("[data-field]").removeClass('has-danger');
$(".form-control-feedback[data-field]").addClass('hide');
}
});
</script>
{% endblock %}

View file

@ -1,146 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Price Tiers
</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-8 order-2 order-xl-1">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input m-input--solid" placeholder="Search..." id="data-rows-search">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-4 order-1 order-xl-2 m--align-right">
<a href="{{ url('price_tier_add_form') }}" class="btn btn-focus m-btn m-btn--custom m-btn--icon m-btn--air m-btn--pill">
<span>
<i class="la la-industry"></i>
<span>New Price Tier</span>
</span>
</a>
<div class="m-separator m-separator--dashed d-xl-none"></div>
</div>
</div>
</div>
<!--begin: Datatable -->
<div id="data-rows"></div>
<!--end: Datatable -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
var options = {
data: {
type: 'remote',
source: {
read: {
url: '{{ url("price_tier_rows") }}',
method: 'POST'
}
},
saveState: {
cookie: false,
webstorage: false
},
pageSize: 10,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
layout: {
scroll: true
},
columns: [
{
field: 'id',
title: 'ID',
width: 30
},
{
field: 'name',
title: 'Name'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '';
if (row.meta.update_url != '') {
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" data-id="' + row.name + '" title="Edit"><i class="la la-edit"></i></a>';
}
if (row.meta.delete_url != '') {
actions += '<a href="' + row.meta.delete_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-danger m-btn--icon m-btn--icon-only m-btn--pill btn-delete" data-id="' + row.name + '" title="Delete"><i class="la la-trash"></i></a>';
}
return actions;
},
}
],
search: {
onEnter: false,
input: $('#data-rows-search'),
delay: 400
}
};
var table = $("#data-rows").mDatatable(options);
$(document).on('click', '.btn-delete', function(e) {
var url = $(this).prop('href');
var id = $(this).data('id');
var btn = $(this);
e.preventDefault();
swal({
title: 'Confirmation',
html: 'Are you sure you want to delete <strong>' + id + '</strong>?',
type: 'warning',
showCancelButton: true
}).then((result) => {
if (result.value) {
$.ajax({
method: "DELETE",
url: url
}).done(function(response) {
table.row(btn.parents('tr')).remove();
table.reload();
});
}
});
});
});
</script>
{% endblock %}

View file

@ -13,8 +13,7 @@ add_cust_vehicle_battery_info: This vehicle is using a Motolite battery
jo_title_pdf: Motolite Res-Q Job Order
country_code_prefix: '+63'
delivery_instructions_label: Delivery Instructions
no_inventory_message: 'A Job Order was created but there is insufficient stock for the following SKU(s) on this branch: [item_display]'
no_riders_message: A Job Order was created but there are no riders available for this branch.
no_inventory_message: No stock for [item_display]
# images
image_logo_login: /assets/images/logo-resq.png
@ -160,7 +159,6 @@ menu.database.subtickettypes: 'Sub Ticket Types'
menu.database.emergencytypes: 'Emergency Types'
menu.database.ownershiptypes: 'Ownership Types'
menu.database.serviceofferings: 'Service Offerings'
menu.database.itemtypes: 'Item Types'
# fcm jo status updates
jo_fcm_title_outlet_assign: 'Looking for riders'

View file

@ -1,2 +0,0 @@
update supported_area set hub_filter_exceptions = '{"no_inventory":true,"no_available_rider":true}' where id = 34;
update supported_area set hub_filter_exceptions = '{"no_inventory":true,"no_available_rider":true}' where id = 35;

View file

@ -1,53 +0,0 @@
-- MySQL dump 10.19 Distrib 10.3.39-MariaDB, for Linux (x86_64)
--
-- Host: localhost Database: resq
-- ------------------------------------------------------
-- Server version 10.3.39-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `item_type`
--
DROP TABLE IF EXISTS `item_type`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `item_type` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(80) NOT NULL,
`code` varchar(80) NOT NULL,
PRIMARY KEY (`id`),
KEY `item_type_idx` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `item_type`
--
LOCK TABLES `item_type` WRITE;
/*!40000 ALTER TABLE `item_type` DISABLE KEYS */;
INSERT INTO `item_type` VALUES (1,'Battery','battery'),(2,'Service Offering','service_offering');
/*!40000 ALTER TABLE `item_type` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-01-28 20:59:44

View file

@ -1 +0,0 @@
INSERT INTO service_offering (name, code, fee) VALUES ('Motolite User Jumpstart Warranty Fee', 'motolite_user_jumpstart_warranty_fee', 200.00);

View file

@ -1 +0,0 @@
INSERT INTO service_offering (name, code, fee) VALUES ('Tire Sealant Fee', 'tire_sealant_fee', '200.00');

View file

@ -1,54 +0,0 @@
-- MySQL dump 10.19 Distrib 10.3.39-MariaDB, for Linux (x86_64)
--
-- Host: localhost Database: resq
-- ------------------------------------------------------
-- Server version 10.3.39-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `service_offering`
--
DROP TABLE IF EXISTS `service_offering`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `service_offering` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(80) NOT NULL,
`code` varchar(80) NOT NULL,
`fee` decimal(9,2) NOT NULL,
PRIMARY KEY (`id`),
KEY `service_offering_idx` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `service_offering`
--
LOCK TABLES `service_offering` WRITE;
/*!40000 ALTER TABLE `service_offering` DISABLE KEYS */;
INSERT INTO `service_offering` VALUES (1,'Tax','tax',0.12),(2,'Motolite User Service Fee','motolite_user_service_fee',0.00),(3,'Battery Replacement Warranty Fee','battery_replacement_warranty_fee',0.00),(4,'Fuel Service Fee','fuel_service_fee',300.00),(5,'Fuel Gas Fee','fuel_gas_fee',340.00),(6,'Fuel Diesel Fee','fuel_diesel_fee',320.00),(7,'Jumpstart Fee','jumpstart_fee',300.00),(8,'Jumpstart Fee Mobile App','jumpstart_fee_mobile_app',300.00),(9,'Jumpstart Warranty Fee','jumpstart_warranty_fee',300.00),(10,'Overheat Fee','overheat_fee',300.00),(11,'Coolant Fee','coolant_fee',1600.00),(12,'Post Recharged Fee','post_recharged_fee',300.00),(13,'Post Replacement Fee','post_replacement_fee',0.00),(14,'Tire Repair Fee','tire_repair_fee',300.00),(17,'Motolite User Jumpstart Warranty Fee','motolite_user_jumpstart_warranty_fee',200.00),(18,'Motolite User Jumpstart Fee','motolite_user_jumpstart_fee',200.00);
/*!40000 ALTER TABLE `service_offering` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-04-02 1:17:40