Merge branch '744-new-invoice-service' into '746-resq-2-0-final'
Resolve "New invoice service" See merge request jankstudio/resq!860
This commit is contained in:
commit
f26d6284f3
28 changed files with 3651 additions and 6 deletions
|
|
@ -115,7 +115,10 @@ services:
|
|||
App\Service\InvoiceGenerator\ResqInvoiceGenerator: ~
|
||||
|
||||
# invoice generator interface
|
||||
App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator"
|
||||
App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceManager"
|
||||
|
||||
# invoice manager
|
||||
App\Service\InvoiceManager: ~
|
||||
|
||||
# job order generator
|
||||
App\Service\JobOrderHandler\ResqJobOrderHandler:
|
||||
|
|
|
|||
2106
src/Command/TestInvoiceManagerCommand.php
Normal file
2106
src/Command/TestInvoiceManagerCommand.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2865,6 +2865,9 @@ class APIController extends Controller implements LoggedController
|
|||
$icrit = new InvoiceCriteria();
|
||||
$icrit->setServiceType($stype);
|
||||
|
||||
// set taxable
|
||||
$icrit->setIsTaxable(true);
|
||||
|
||||
// check promo
|
||||
$promo_id = $req->request->get('promo_id');
|
||||
if (!empty($promo_id))
|
||||
|
|
|
|||
|
|
@ -1201,6 +1201,7 @@ class RiderAppController extends APIController
|
|||
$crit->setServiceType($stype_id);
|
||||
$crit->setCustomerVehicle($cv);
|
||||
$crit->setHasCoolant($jo->hasCoolant());
|
||||
$crit->setIsTaxable();
|
||||
|
||||
if ($promo != null)
|
||||
$crit->addPromo($promo);
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ class InvoiceController extends ApiController
|
|||
|
||||
$icrit->addEntry($batt, $trade_in, 1);
|
||||
|
||||
// set if taxable
|
||||
$icrit->setIsTaxable();
|
||||
|
||||
// send to invoice generator
|
||||
$invoice = $ic->generateInvoice($icrit);
|
||||
|
||||
|
|
|
|||
|
|
@ -658,6 +658,9 @@ class JobOrderController extends ApiController
|
|||
$icrit->addBattery($batt);
|
||||
*/
|
||||
|
||||
// NOTE: trade in is currently not supported. Would it be better
|
||||
// if we remove trade-in as a required parameter? Or just leave it be
|
||||
// and simply not process it?
|
||||
// check trade-in
|
||||
// only allow motolite, other, none
|
||||
switch ($trade_in) {
|
||||
|
|
@ -672,6 +675,9 @@ class JobOrderController extends ApiController
|
|||
|
||||
$icrit->addEntry($batt, $trade_in, 1);
|
||||
|
||||
// set taxable
|
||||
$icrit->setIsTaxable();
|
||||
|
||||
// send to invoice generator
|
||||
$invoice = $ic->generateInvoice($icrit);
|
||||
$jo->setInvoice($invoice);
|
||||
|
|
@ -1074,6 +1080,9 @@ class JobOrderController extends ApiController
|
|||
break;
|
||||
}
|
||||
|
||||
// set taxable
|
||||
$icrit->setIsTaxable();
|
||||
|
||||
$icrit->addEntry($batt, $trade_in, 1);
|
||||
|
||||
// send to invoice generator
|
||||
|
|
|
|||
|
|
@ -737,7 +737,8 @@ class JobOrderController extends Controller
|
|||
// instantiate invoice criteria
|
||||
$criteria = new InvoiceCriteria();
|
||||
$criteria->setServiceType($stype)
|
||||
->setCustomerVehicle($cv);
|
||||
->setCustomerVehicle($cv)
|
||||
->setIsTaxable();
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -157,6 +157,9 @@ class JobOrderController extends APIController
|
|||
|
||||
$icrit->setCustomerVehicle($data['customer_vehicle']);
|
||||
|
||||
// set taxable
|
||||
$icrit->setIsTaxable();
|
||||
|
||||
$icrit->addEntry($data['batt'], $data['trade_in_type'], 1);
|
||||
|
||||
// send to invoice generator
|
||||
|
|
|
|||
120
src/InvoiceRule/BatteryReplacementWarranty.php
Normal file
120
src/InvoiceRule/BatteryReplacementWarranty.php
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\Ramcar\ServiceType;
|
||||
use App\Ramcar\TradeInType;
|
||||
|
||||
use App\Entity\Battery;
|
||||
|
||||
class BatteryReplacementWarranty implements InvoiceRuleInterface
|
||||
{
|
||||
protected $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function getID()
|
||||
{
|
||||
return 'battery_warranty';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
// get the entries
|
||||
$entries = $criteria->getEntries();
|
||||
|
||||
foreach($entries as $entry)
|
||||
{
|
||||
$batt = $entry['battery'];
|
||||
$qty = 1;
|
||||
$price = $this->getServiceTypeFee();
|
||||
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'battery' => $batt,
|
||||
'qty' => $qty,
|
||||
'title' => $this->getTitle($batt),
|
||||
'price' => $price,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($price, $qty, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
// check service type. Only battery sales and battery warranty should have invoice items. Since this is the
|
||||
// battery replacement warranty rule, we only check for battery replacement warranty.
|
||||
$stype = $criteria->getServiceType();
|
||||
if ($stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY)
|
||||
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)
|
||||
{
|
||||
$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;
|
||||
|
||||
// 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 getTitle($battery)
|
||||
{
|
||||
$title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' - Service Unit';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
123
src/InvoiceRule/BatterySales.php
Normal file
123
src/InvoiceRule/BatterySales.php
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use App\Ramcar\TradeInType;
|
||||
use App\Ramcar\ServiceType;
|
||||
|
||||
use App\Entity\Battery;
|
||||
|
||||
class BatterySales implements InvoiceRuleInterface
|
||||
{
|
||||
protected $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function getID()
|
||||
{
|
||||
return 'battery_new';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
// get the entries
|
||||
$entries = $criteria->getEntries();
|
||||
foreach($entries as $entry)
|
||||
{
|
||||
$batt = $entry['battery'];
|
||||
$qty = $entry['qty'];
|
||||
$trade_in = null;
|
||||
|
||||
if (isset($entry['trade_in']))
|
||||
$trade_in = $entry['trade_in'];
|
||||
|
||||
$size = $batt->getSize();
|
||||
|
||||
if ($trade_in == null)
|
||||
{
|
||||
// battery purchase
|
||||
$price = $batt->getSellingPrice();
|
||||
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'battery' => $batt,
|
||||
'qty' => $qty,
|
||||
'title' => $this->getTitle($batt),
|
||||
'price' => $price,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($price, $qty, 2);
|
||||
|
||||
$total['sell_price'] = bcadd($total['sell_price'], $qty_price, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
// check service type. Only battery sales and battery warranty should have invoice items. Since this is the battery sales
|
||||
// rule, we only check for battery sales.
|
||||
$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)
|
||||
{
|
||||
$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;
|
||||
|
||||
// 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 getTitle($battery)
|
||||
{
|
||||
$title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName();
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
116
src/InvoiceRule/DiscountType.php
Normal file
116
src/InvoiceRule/DiscountType.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use App\Ramcar\ServiceType;
|
||||
use App\Ramcar\DiscountApply;
|
||||
|
||||
use App\Entity\Promo;
|
||||
|
||||
class DiscountType implements InvoiceRuleInterface
|
||||
{
|
||||
protected $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function getID()
|
||||
{
|
||||
return 'discount';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
$promos = $criteria->getPromos();
|
||||
|
||||
if (empty($promos))
|
||||
return $items;
|
||||
|
||||
// NOTE: only get first promo because only one is applicable anyway
|
||||
$promo = $promos[0];
|
||||
|
||||
$rate = $promo->getDiscountRate();
|
||||
$apply_to = $promo->getDiscountApply();
|
||||
|
||||
switch ($apply_to)
|
||||
{
|
||||
case DiscountApply::SRP:
|
||||
$discount = bcmul($total['sell_price'], $rate, 2);
|
||||
break;
|
||||
case DiscountApply::OPL:
|
||||
// $discount = round($total['sell_price'] * 0.6 / 0.7 * $rate, 2);
|
||||
// $discount = round($total['sell_price'] * (1 - 1.5 / 0.7 * $rate), 2);
|
||||
$num1 = bcdiv(1.5, 0.7, 9);
|
||||
$num1_rate = bcmul($num1, $rate, 9);
|
||||
$multiplier = bcsub(1, $num1_rate, 9);
|
||||
|
||||
$discount = bcmul($total['sell_price'], $multiplier, 2);
|
||||
break;
|
||||
}
|
||||
|
||||
// if discount is higher than 0, add to items
|
||||
if ($discount > 0)
|
||||
{
|
||||
$qty = 1;
|
||||
$price = bcmul(-1, $discount, 2);
|
||||
|
||||
$items[] = [
|
||||
'promo' => $promo,
|
||||
'title' => $this->getTitle(),
|
||||
'qty' => $qty,
|
||||
'price' => $price,
|
||||
];
|
||||
}
|
||||
|
||||
$total['discount'] = $discount;
|
||||
$total['total_price'] = bcsub($total['total_price'], $discount, 2);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
// return error if there's a problem, false otherwise
|
||||
// check service type
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
// discount/promo only applies for battery sales
|
||||
if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW)
|
||||
return null;
|
||||
|
||||
if (empty($promo_id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if this is a valid promo
|
||||
$promo = $this->em->getRepository(Promo::class)->find($promo_id);
|
||||
|
||||
if (empty($promo))
|
||||
return 'Invalid promo specified.';
|
||||
|
||||
$criteria->addPromo($promo);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getTitle()
|
||||
{
|
||||
$title = 'Promo discount';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
133
src/InvoiceRule/Fuel.php
Normal file
133
src/InvoiceRule/Fuel.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use App\Ramcar\FuelType;
|
||||
use App\Ramcar\ServiceType;
|
||||
|
||||
class Fuel implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'fuel';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
// check if customer vehicle has a motolite battery
|
||||
$cv = $criteria->getCustomerVehicle();
|
||||
if ($cv->hasMotoliteBattery())
|
||||
$fee = 0;
|
||||
else
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
$ftype = $cv->getFuelType();
|
||||
|
||||
// add the service fee to items
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle($ftype),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_fee = bcmul($qty, $fee, 2);
|
||||
$total_price = $qty_fee;
|
||||
|
||||
switch ($ftype)
|
||||
{
|
||||
case FuelType::GAS:
|
||||
case FuelType::DIESEL:
|
||||
$qty = 1;
|
||||
$price = $this->getFuelFee($ftype);
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getTitle($ftype),
|
||||
'price' => $price,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($price, $qty, 2);
|
||||
$total_price = bcadd($total_price, $qty_price, 2);
|
||||
|
||||
break;
|
||||
default:
|
||||
$qty = 1;
|
||||
$price = 0;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getTitle('Unknown'),
|
||||
'price' => $price,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($price, $qty, 2);
|
||||
$total_price = bcadd($total_price, $qty_price, 2);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$total['total_price'] = bcadd($total['total_price'], $total_price, 2);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any changes are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function getFuelFee($fuel_type)
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any changes are to be made, we just edit the file
|
||||
// instead of the code
|
||||
if ($fuel_type == FuelType::GAS)
|
||||
{
|
||||
// gas fuel fee
|
||||
return 320;
|
||||
}
|
||||
else
|
||||
{
|
||||
// diesel fuel fee
|
||||
return 340;
|
||||
}
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getTitle($fuel_type)
|
||||
{
|
||||
$title = '4L - ' . ucfirst($fuel_type);
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
protected function getServiceTitle($fuel_type)
|
||||
{
|
||||
$title = 'Service - ' . ServiceType::getName(ServiceType::EMERGENCY_REFUEL);
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
64
src/InvoiceRule/Jumpstart.php
Normal file
64
src/InvoiceRule/Jumpstart.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
class Jumpstart implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'jumpstart_troubleshoot';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
// add the service fee to items
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle(),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($fee, $qty, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 150;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getServiceTitle()
|
||||
{
|
||||
$title = 'Service - Troubleshooting fee';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
64
src/InvoiceRule/JumpstartWarranty.php
Normal file
64
src/InvoiceRule/JumpstartWarranty.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
class JumpstartWarranty implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'jumpstart_warranty';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
// add the service fee to items
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle(),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($fee, $qty, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getServiceTitle()
|
||||
{
|
||||
$title = 'Service - Troubleshooting fee';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
103
src/InvoiceRule/Overheat.php
Normal file
103
src/InvoiceRule/Overheat.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use App\Ramcar\ServiceType;
|
||||
|
||||
class Overheat implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'overheat';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
$has_coolant = $criteria->hasCoolant();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
// check if customer vehicle has a motolite battery
|
||||
$cv = $criteria->getCustomerVehicle();
|
||||
if ($cv->hasMotoliteBattery())
|
||||
$fee = 0;
|
||||
else
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
// add the service fee to items
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle(),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_fee = bcmul($qty, $fee, 2);
|
||||
$total_price = $qty_fee;
|
||||
|
||||
if ($has_coolant)
|
||||
{
|
||||
$coolant_fee = $this->getCoolantFee();
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceCoolantTitle(),
|
||||
'price' => $coolant_fee,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($coolant_fee, $qty, 2);
|
||||
$total_price = bcadd($total_price, $qty_price, 2);
|
||||
}
|
||||
|
||||
$total['total_price'] = bcadd($total['total_price'], $total_price, 2);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function getCoolantFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 1600;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getServiceTitle()
|
||||
{
|
||||
$title = 'Service - ' . ServiceType::getName(ServiceType::OVERHEAT_ASSISTANCE);
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
protected function getServiceCoolantTitle()
|
||||
{
|
||||
$title = '4L Coolant';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
64
src/InvoiceRule/PostRecharged.php
Normal file
64
src/InvoiceRule/PostRecharged.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
class PostRecharged implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'post_recharged';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle(),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($fee, $qty, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getServiceTitle()
|
||||
{
|
||||
$title = 'Recharge fee';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
63
src/InvoiceRule/PostReplacement.php
Normal file
63
src/InvoiceRule/PostReplacement.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
class PostReplacement implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'post_replacement';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle(),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($fee, $qty, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getServiceTitle()
|
||||
{
|
||||
$title = 'Battery replacement';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
110
src/InvoiceRule/Tax.php
Normal file
110
src/InvoiceRule/Tax.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use App\Ramcar\ServiceType;
|
||||
|
||||
class Tax implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'tax';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
// check if taxable
|
||||
if (!$criteria->isTaxable())
|
||||
{
|
||||
// nothing to compute
|
||||
return [];
|
||||
}
|
||||
|
||||
$tax_rate = $this->getTaxRate();
|
||||
|
||||
$is_battery_sales = false;
|
||||
$total_price = 0;
|
||||
|
||||
// compute tax per item if service type is battery sales
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
if ($stype == ServiceType::BATTERY_REPLACEMENT_NEW)
|
||||
{
|
||||
// get the entries
|
||||
$entries = $criteria->getEntries();
|
||||
|
||||
foreach($entries as $entry)
|
||||
{
|
||||
// check if entry is trade-in
|
||||
if (isset($entry['trade_in']))
|
||||
{
|
||||
// continue to next entry
|
||||
continue;
|
||||
}
|
||||
|
||||
$battery = $entry['battery'];
|
||||
$qty = $entry['qty'];
|
||||
|
||||
$price = $battery->getSellingPrice();
|
||||
|
||||
$vat = $this->getTaxAmount($price, $tax_rate);
|
||||
|
||||
$qty_price = bcmul($price, $qty, 2);
|
||||
$qty_vat = bcmul($vat, $qty, 2);
|
||||
$price_minus_vat = bcsub($price, $vat, 2);
|
||||
$qty_price_minus_vat = bcmul($price_minus_vat, $qty, 2);
|
||||
|
||||
$total['vat'] = bcadd($total['vat'], $qty_vat, 2);
|
||||
$total['vat_ex_price'] = bcadd($total['vat_ex_price'], $qty_price_minus_vat, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute VAT after adding all item costs, if service type is not battery sales
|
||||
$total_price = $total['total_price'];
|
||||
|
||||
$vat_ex_price = $this->getTaxExclusivePrice($total_price, $tax_rate);
|
||||
$vat = bcsub($total_price, $vat_ex_price, 2);
|
||||
|
||||
$total['vat_ex_price'] = $vat_ex_price;
|
||||
$total['vat'] = $vat;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getTaxAmount($price, $tax_rate)
|
||||
{
|
||||
$vat_ex_price = $this->getTaxExclusivePrice($price, $tax_rate);
|
||||
$tax_amount = bcsub($price, $vat_ex_price, 2);
|
||||
|
||||
return $tax_amount;
|
||||
}
|
||||
|
||||
protected function getTaxExclusivePrice($price, $tax_rate)
|
||||
{
|
||||
$tax_ex_price = bcdiv($price, bcadd(1, $tax_rate, 2), 2);
|
||||
return $tax_ex_price;
|
||||
}
|
||||
|
||||
protected function getTaxRate()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 0.12;
|
||||
}
|
||||
}
|
||||
|
||||
69
src/InvoiceRule/TireRepair.php
Normal file
69
src/InvoiceRule/TireRepair.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
class TireRepair implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'tire';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$stype = $criteria->getServiceType();
|
||||
|
||||
$items = [];
|
||||
|
||||
if ($stype == $this->getID())
|
||||
{
|
||||
// check if customer vehicle has a motolite battery
|
||||
$cv = $criteria->getCustomerVehicle();
|
||||
if ($cv->hasMotoliteBattery())
|
||||
$fee = 0;
|
||||
else
|
||||
$fee = $this->getServiceTypeFee();
|
||||
|
||||
// add the service fee to items
|
||||
$qty = 1;
|
||||
$items[] = [
|
||||
'service_type' => $this->getID(),
|
||||
'qty' => $qty,
|
||||
'title' => $this->getServiceTitle(),
|
||||
'price' => $fee,
|
||||
];
|
||||
|
||||
$qty_price = bcmul($fee, $qty, 2);
|
||||
$total['total_price'] = bcadd($total['total_price'], $qty_price, 2);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function getServiceTypeFee()
|
||||
{
|
||||
// TODO: we need to to put this somewhere like in .env
|
||||
// so that if any chanages are to be made, we just edit the file
|
||||
// instead of the code
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getServiceTitle()
|
||||
{
|
||||
$title = 'Service - Flat Tire';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
87
src/InvoiceRule/TradeIn.php
Normal file
87
src/InvoiceRule/TradeIn.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace App\InvoiceRule;
|
||||
|
||||
use App\InvoiceRuleInterface;
|
||||
|
||||
use App\Ramcar\TradeInType;
|
||||
|
||||
class TradeIn implements InvoiceRuleInterface
|
||||
{
|
||||
public function getID()
|
||||
{
|
||||
return 'trade-in';
|
||||
}
|
||||
|
||||
public function compute($criteria, &$total)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
// get the entries
|
||||
$entries = $criteria->getEntries();
|
||||
foreach($entries as $entry)
|
||||
{
|
||||
$batt = $entry['battery'];
|
||||
$qty = $entry['qty'];
|
||||
$trade_in_type = null;
|
||||
|
||||
if (isset($entry['trade_in']))
|
||||
$trade_in_type = $entry['trade_in'];
|
||||
|
||||
if ($trade_in_type != null)
|
||||
{
|
||||
$ti_rate = $this->getTradeInRate($batt, $trade_in_type);
|
||||
|
||||
$qty_ti = bcmul($ti_rate, $qty, 2);
|
||||
|
||||
$total['ti_rate'] = bcadd($total['ti_rate'], $qty_ti, 2);
|
||||
$total['total_price'] = bcsub($total['total_price'], $qty_ti, 2);
|
||||
|
||||
$price = bcmul($ti_rate, -1, 2);
|
||||
|
||||
$items[] = [
|
||||
'qty' => $qty,
|
||||
'title' => $this->getTitle($batt, $trade_in_type),
|
||||
'price' => $price,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function validatePromo($criteria, $promo_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateInvoiceItems($criteria, $invoice_items)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getTradeInRate($battery, $trade_in_type)
|
||||
{
|
||||
$size = $battery->getSize();
|
||||
|
||||
switch ($trade_in_type)
|
||||
{
|
||||
case TradeInType::MOTOLITE:
|
||||
return $size->getTIPriceMotolite();
|
||||
case TradeInType::PREMIUM:
|
||||
return $size->getTIPricePremium();
|
||||
case TradeInType::OTHER:
|
||||
return $size->getTIPriceOther();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getTitle($battery, $trade_in_type)
|
||||
{
|
||||
$title = 'Trade-in ' . TradeInType::getName($trade_in_type) . ' ' . $battery->getSize()->getName() . ' battery';
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
16
src/InvoiceRuleInterface.php
Normal file
16
src/InvoiceRuleInterface.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
interface InvoiceRuleInterface
|
||||
{
|
||||
// validate promo
|
||||
public function validatePromo($criteria, $promo_id);
|
||||
|
||||
// validate invoice items
|
||||
public function validateInvoiceItems($criteria, $invoice_items);
|
||||
|
||||
// compute
|
||||
// this returns an array of items or empty array
|
||||
public function compute($criteria, &$total);
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ class InvoiceCriteria
|
|||
protected $flag_coolant;
|
||||
protected $discount;
|
||||
protected $service_charges;
|
||||
protected $flag_taxable;
|
||||
|
||||
// entries are battery and trade-in combos
|
||||
protected $entries;
|
||||
|
|
@ -28,6 +29,7 @@ class InvoiceCriteria
|
|||
$this->flag_coolant = false;
|
||||
$this->discount = 0;
|
||||
$this->service_charges = [];
|
||||
$this->flag_taxable = false;
|
||||
}
|
||||
|
||||
public function setServiceType($stype)
|
||||
|
|
@ -153,4 +155,15 @@ class InvoiceCriteria
|
|||
return $this->service_charges;
|
||||
}
|
||||
|
||||
public function setIsTaxable($flag = true)
|
||||
{
|
||||
$this->flag_taxable = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isTaxable()
|
||||
{
|
||||
return $this->flag_taxable;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
285
src/Service/InvoiceManager.php
Normal file
285
src/Service/InvoiceManager.php
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\InvoiceRule;
|
||||
|
||||
use App\Service\InvoiceGeneratorInterface;
|
||||
|
||||
use App\Ramcar\InvoiceCriteria;
|
||||
use App\Ramcar\InvoiceStatus;
|
||||
use App\Ramcar\ServiceType;
|
||||
use App\Ramcar\TradeInType;
|
||||
|
||||
use App\Entity\Invoice;
|
||||
use App\Entity\InvoiceItem;
|
||||
use App\Entity\User;
|
||||
use App\Entity\Battery;
|
||||
use App\Entity\Promo;
|
||||
|
||||
class InvoiceManager implements InvoiceGeneratorInterface
|
||||
{
|
||||
private $security;
|
||||
protected $em;
|
||||
protected $validator;
|
||||
protected $available_rules;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, Security $security, ValidatorInterface $validator)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->security = $security;
|
||||
$this->validator = $validator;
|
||||
|
||||
$this->available_rules = $this->getAvailableRules();
|
||||
}
|
||||
|
||||
public function getAvailableRules()
|
||||
{
|
||||
// TODO: get list of invoice rules from .env or a json file?
|
||||
return [
|
||||
new InvoiceRule\BatterySales($this->em),
|
||||
new InvoiceRule\BatteryReplacementWarranty($this->em),
|
||||
new InvoiceRule\Jumpstart(),
|
||||
new InvoiceRule\JumpstartWarranty(),
|
||||
new InvoiceRule\PostRecharged(),
|
||||
new InvoiceRule\PostReplacement(),
|
||||
new InvoiceRule\Overheat(),
|
||||
new InvoiceRule\Fuel(),
|
||||
new InvoiceRule\TireRepair(),
|
||||
new InvoiceRule\DiscountType($this->em),
|
||||
new InvoiceRule\TradeIn(),
|
||||
new InvoiceRule\Tax(),
|
||||
];
|
||||
}
|
||||
|
||||
// this is called when JO is submitted
|
||||
public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array)
|
||||
{
|
||||
// instantiate the invoice criteria
|
||||
$criteria = new InvoiceCriteria();
|
||||
$criteria->setServiceType($jo->getServiceType())
|
||||
->setCustomerVehicle($jo->getCustomerVehicle());
|
||||
|
||||
// set if taxable
|
||||
// NOTE: ideally, this should be a parameter when calling generateInvoiceCriteria. But that
|
||||
// would mean adding it as a parameter to the call, impacting all calls
|
||||
$criteria->setIsTaxable();
|
||||
|
||||
foreach ($this->available_rules as $avail_rule)
|
||||
{
|
||||
$ierror = $avail_rule->validatePromo($criteria, $promo_id);
|
||||
|
||||
// break out of loop when error found
|
||||
if ($ierror)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$ierror && !empty($invoice_items))
|
||||
{
|
||||
// validate the invoice items (batteries and trade ins)
|
||||
foreach ($this->available_rules as $avail_rule)
|
||||
{
|
||||
$ierror = $avail_rule->validateInvoiceItems($criteria, $invoice_items);
|
||||
|
||||
// break out of loop when error found
|
||||
if ($ierror)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ierror)
|
||||
{
|
||||
$error_array['invoice'] = $ierror;
|
||||
}
|
||||
else
|
||||
{
|
||||
// generate the invoice
|
||||
$invoice = $this->generateInvoice($criteria);
|
||||
|
||||
// validate
|
||||
$ierrors = $this->validator->validate($invoice);
|
||||
|
||||
// add errors to list
|
||||
foreach ($ierrors as $error) {
|
||||
$error_array[$error->getPropertyPath()] = $error->getMessage();
|
||||
}
|
||||
|
||||
// check if invoice already exists for JO
|
||||
$old_invoice = $jo->getInvoice();
|
||||
if ($old_invoice != null)
|
||||
{
|
||||
// remove old invoice
|
||||
$this->em->remove($old_invoice);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
// add invoice to JO
|
||||
$jo->setInvoice($invoice);
|
||||
|
||||
$this->em->persist($invoice);
|
||||
}
|
||||
}
|
||||
|
||||
// this is called by JobOrderController when JS script generateInvoice is called
|
||||
public function generateDraftInvoice($criteria, $promo_id, $service_charges, $items)
|
||||
{
|
||||
foreach ($this->available_rules as $avail_rule)
|
||||
{
|
||||
$ierror = $avail_rule->validatePromo($criteria, $promo_id);
|
||||
|
||||
// break out of loop when error found
|
||||
if ($ierror)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$ierror && !empty($items))
|
||||
{
|
||||
// validate the invoice items (batteries and trade ins)
|
||||
foreach ($this->available_rules as $avail_rule)
|
||||
{
|
||||
$ierror = $avail_rule->validateInvoiceItems($criteria, $items);
|
||||
|
||||
// break out of loop when error found
|
||||
if ($ierror)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $ierror;
|
||||
}
|
||||
|
||||
// called by the following:
|
||||
// (1) JobOrderController when JS script generateInvoice is called
|
||||
// (2) APIController from newRequestJobOrder
|
||||
// (3) generateInvoiceCriteria
|
||||
// (4) RiderAPIHandler's changeService
|
||||
// (5) TAPI's JobOrderController
|
||||
public function generateInvoice($criteria)
|
||||
{
|
||||
// no need to validate since generateDraftInvoice was called before this was called
|
||||
// generate the invoice and from APIController, the fields were validated
|
||||
$invoice_data = $this->compute($criteria);
|
||||
|
||||
$invoice = $this->createInvoice($invoice_data);
|
||||
|
||||
$invoice_items = $invoice->getItems();
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
public function compute($criteria)
|
||||
{
|
||||
// initialize
|
||||
$total = [
|
||||
'sell_price' => 0.0,
|
||||
'vat' => 0.0,
|
||||
'vat_ex_price' => 0.0,
|
||||
'ti_rate' => 0.0,
|
||||
'total_price' => 0.0,
|
||||
'discount' => 0.0,
|
||||
];
|
||||
|
||||
// get what is in criteria
|
||||
$stype = $criteria->getServiceType();
|
||||
$entries = $criteria->getEntries();
|
||||
$promos = $criteria->getPromos();
|
||||
$is_taxable = $criteria->isTaxable();
|
||||
|
||||
$invoice_items = [];
|
||||
$data = [];
|
||||
foreach ($this->available_rules as $rule)
|
||||
{
|
||||
$items = $rule->compute($criteria, $total);
|
||||
|
||||
$promo = null;
|
||||
|
||||
if (count($items) > 0)
|
||||
{
|
||||
foreach ($items as $item)
|
||||
{
|
||||
$title = $item['title'];
|
||||
$quantity = $item['qty'];
|
||||
$price = $item['price'];
|
||||
|
||||
$battery = null;
|
||||
if (isset($item['battery']))
|
||||
$battery = $item['battery'];
|
||||
|
||||
$promo = null;
|
||||
if (isset($item['promo']))
|
||||
$promo = $item['promo'];
|
||||
|
||||
$invoice_items[] = [
|
||||
'title' => $title,
|
||||
'quantity' => $quantity,
|
||||
'price' => $price,
|
||||
'battery' => $battery,
|
||||
'promo' => $promo,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also need to return the total and the promo
|
||||
$data[] = [
|
||||
'promo' => $promo,
|
||||
'invoice_items' => $invoice_items,
|
||||
'total' => $total,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function createInvoice($invoice_data)
|
||||
{
|
||||
$invoice = new Invoice();
|
||||
|
||||
// get current user
|
||||
$user = $this->security->getUser();
|
||||
// check if user is User or APIUser
|
||||
if ($user instanceof User)
|
||||
{
|
||||
$invoice->setCreatedBy($user);
|
||||
}
|
||||
|
||||
foreach ($invoice_data as $data)
|
||||
{
|
||||
$invoice_items = $data['invoice_items'];
|
||||
$total = $data['total'];
|
||||
|
||||
// check if promo is set
|
||||
if (isset($data['promo']))
|
||||
$promo = $data['promo'];
|
||||
|
||||
foreach ($invoice_items as $item)
|
||||
{
|
||||
$invoice_item = new InvoiceItem();
|
||||
|
||||
$invoice_item->setInvoice($invoice)
|
||||
->setTitle($item['title'])
|
||||
->setQuantity($item['quantity'])
|
||||
->setPrice($item['price']);
|
||||
|
||||
if ($item['battery'] != null)
|
||||
$invoice_item->setBattery($item['battery']);
|
||||
|
||||
$invoice->addItem($invoice_item);
|
||||
}
|
||||
|
||||
$invoice->setTotalPrice($total['total_price'])
|
||||
->setVATExclusivePrice($total['vat_ex_price'])
|
||||
->setVAT($total['vat'])
|
||||
->setDiscount($total['discount'])
|
||||
->setTradeIn($total['ti_rate'])
|
||||
->setStatus(InvoiceStatus::DRAFT);
|
||||
}
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -29,12 +29,13 @@ use App\Entity\CustomerTag;
|
|||
use App\Entity\EmergencyType;
|
||||
use App\Entity\OwnershipType;
|
||||
use App\Entity\CustomerLocation;
|
||||
use App\Entity\Battery;
|
||||
|
||||
use App\Ramcar\InvoiceCriteria;
|
||||
use App\Ramcar\ServiceType;
|
||||
use App\Ramcar\TradeInType;
|
||||
use App\Ramcar\JOEventType;
|
||||
use App\Ramcar\JOStatus;
|
||||
use App\Ramcar\InvoiceCriteria;
|
||||
use App\Ramcar\WarrantyClass;
|
||||
use App\Ramcar\DiscountApply;
|
||||
use App\Ramcar\ModeOfPayment;
|
||||
|
|
@ -3487,8 +3488,19 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
|
||||
// db loaded
|
||||
$params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll();
|
||||
$params['trade_in_bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll();
|
||||
$params['promos'] = $em->getRepository(Promo::class)->findAll();
|
||||
|
||||
// list of batteries for trade-in
|
||||
$ti_batteries = $em->getRepository(Battery::class)->findAll();
|
||||
$trade_in_batteries = [];
|
||||
foreach ($ti_batteries as $ti_battery)
|
||||
{
|
||||
$battery_name = $ti_battery->getModel()->getName() . ' ' . $ti_battery->getSize()->getName();
|
||||
$trade_in_batteries[$ti_battery->getID()] = $battery_name;
|
||||
}
|
||||
$params['trade_in_batteries'] = $trade_in_batteries;
|
||||
|
||||
// list of emergency types
|
||||
$e_types = $em->getRepository(EmergencyType::class)->findBy([], ['name' => 'ASC']);
|
||||
$emergency_types = [];
|
||||
|
|
|
|||
|
|
@ -866,6 +866,9 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
$crit->setCustomerVehicle($cv);
|
||||
$crit->setHasCoolant($jo->hasCoolant());
|
||||
|
||||
// set istaxable
|
||||
$crit->setIsTaxable();
|
||||
|
||||
if ($promo != null)
|
||||
$crit->addPromo($promo);
|
||||
|
||||
|
|
|
|||
46
templates/invoice/trade_in.html.twig
Normal file
46
templates/invoice/trade_in.html.twig
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<div class="form-group m-form__group row">
|
||||
<div class="col-lg-1 hide">
|
||||
<label for="invoice-trade-in-bmfg">Manufacturer</label>
|
||||
<select class="form-control m-input" id="invoice-trade-in-bmfg">
|
||||
{% for manufacturer in trade_in_bmfgs %}
|
||||
<option value="{{ manufacturer.getID() }}">{{ manufacturer.getName() }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<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, battery_name in trade_in_batteries %}
|
||||
<option value="{{ id }}">{{ battery_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label for="invoice-trade-in-type">Trade In</label>
|
||||
<select class="form-control m-input" name="invoice_trade_in_type" id="invoice-trade-in-type">
|
||||
<option value="">None</option>
|
||||
{% for key, type in trade_in_types %}
|
||||
<option value="{{ key }}">{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label data-field="no_trade_in_reason">No Trade In Reason</label>
|
||||
<select class="form-control m-input" id="no-trade-in-reason" name="no_trade_in_reason">
|
||||
<option value="">Select reason</option>
|
||||
{% for key, class in no_trade_in_reasons %}
|
||||
<option value="{{ key }}"{{ obj.getNoTradeInReason == key ? ' selected' }}>{{ class }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-control-feedback hide" data-field="no_trade_in_reason"></div>
|
||||
</div>
|
||||
<div class="col-lg-1">
|
||||
<label for="invoice-trade-in-quantity">Quantity</label>
|
||||
<input type="text" id="invoice-trade-in-quantity" class="form-control m-input text-right" value="1">
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<div><label> </label></div>
|
||||
<button type="button" class="btn btn-primary" id="btn-add-trade-in-to-invoice">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
17
templates/invoice/trade_in.js.twig
Normal file
17
templates/invoice/trade_in.js.twig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// add trade in battery to invoice
|
||||
$('#btn-add-trade-in-to-invoice').click(function() {
|
||||
var bmfg = $("#invoice-trade-in-bmfg").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: battery,
|
||||
quantity: qty,
|
||||
trade_in: tradeIn,
|
||||
});
|
||||
|
||||
// regenerate the invoice
|
||||
generateInvoice();
|
||||
});
|
||||
|
|
@ -715,6 +715,7 @@
|
|||
<option value="">Select a vehicle and manufacturer first</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--
|
||||
<div class="col-lg-2">
|
||||
<label for="invoice-trade-in-type">Trade In</label>
|
||||
<select class="form-control m-input" name="invoice_trade_in_type" id="invoice-trade-in-type">
|
||||
|
|
@ -734,6 +735,7 @@
|
|||
</select>
|
||||
<div class="form-control-feedback hide" data-field="no_trade_in_reason"></div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="col-lg-1">
|
||||
<label for="invoice-quantity">Quantity</label>
|
||||
|
|
@ -748,6 +750,7 @@
|
|||
<button type="button" class="btn btn-danger" id="btn-reset-invoice">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
{% include('invoice/trade_in.html.twig') %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
@ -1190,7 +1193,6 @@
|
|||
<script src="/assets/vendors/custom/gmaps/gmaps.js" type="text/javascript"></script>
|
||||
|
||||
<script>
|
||||
|
||||
// location search autocomplete
|
||||
var input = document.getElementById('m_gmap_address');
|
||||
|
||||
|
|
@ -1686,11 +1688,13 @@ $(function() {
|
|||
|
||||
var invoiceItems = [];
|
||||
|
||||
{% include 'invoice/trade_in.js.twig' %}
|
||||
|
||||
// add to invoice
|
||||
$("#btn-add-to-invoice").click(function() {
|
||||
var bmfg = $("#invoice-bmfg").val();
|
||||
var battery = $("#invoice-battery").val();
|
||||
var tradeIn = $("#invoice-trade-in-type").val();
|
||||
// var tradeIn = $("#invoice-trade-in-type").val();
|
||||
var qty = $("#invoice-quantity").val();
|
||||
|
||||
if (!bmfg || !battery || !qty) {
|
||||
|
|
@ -1714,10 +1718,12 @@ $(function() {
|
|||
}
|
||||
|
||||
// add to invoice array
|
||||
// TODO: need to figure out how to push battery and tradein separately
|
||||
// right now, battery and trade in is one entry. Need to separate that into two entries
|
||||
invoiceItems.push({
|
||||
battery: battery,
|
||||
quantity: qty,
|
||||
trade_in: tradeIn,
|
||||
trade_in: '',
|
||||
});
|
||||
|
||||
// regenerate the invoice
|
||||
|
|
@ -1752,6 +1758,8 @@ $(function() {
|
|||
var stype = $("#service_type").val();
|
||||
var cvid = $("#customer-vehicle").val();
|
||||
|
||||
console.log(JSON.stringify(invoiceItems));
|
||||
|
||||
// generate invoice values
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
|
|
|
|||
Loading…
Reference in a new issue