312 lines
9.8 KiB
PHP
312 lines
9.8 KiB
PHP
<?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\Service\PriceTierManager;
|
|
|
|
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;
|
|
protected $pt_manager;
|
|
|
|
public function __construct(EntityManagerInterface $em, Security $security, ValidatorInterface $validator, PriceTierManager $pt_manager)
|
|
{
|
|
$this->em = $em;
|
|
$this->security = $security;
|
|
$this->validator = $validator;
|
|
$this->pt_manager = $pt_manager;
|
|
|
|
$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, $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\DiscountType($this->em),
|
|
new InvoiceRule\TradeIn($this->em),
|
|
new InvoiceRule\Tax($this->em, $this->pt_manager),
|
|
];
|
|
}
|
|
|
|
// this is called when JO is submitted
|
|
public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, &$error_array)
|
|
{
|
|
// instantiate the invoice criteria
|
|
$criteria = new InvoiceCriteria();
|
|
$criteria->setServiceType($jo->getServiceType())
|
|
->setCustomerVehicle($jo->getCustomerVehicle())
|
|
->setPriceTier($price_tier);
|
|
|
|
// 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();
|
|
|
|
// set JO source
|
|
$criteria->setSource($source);
|
|
|
|
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
|
|
// (6) CAPI's RiderAppController
|
|
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
|
|
// NOTE: is this snippet still needed? if not, remove
|
|
$stype = $criteria->getServiceType();
|
|
$entries = $criteria->getEntries();
|
|
$promos = $criteria->getPromos();
|
|
$is_taxable = $criteria->isTaxable();
|
|
|
|
$invoice_items = [];
|
|
$data = [];
|
|
$promo = null;
|
|
foreach ($this->available_rules as $rule)
|
|
{
|
|
$items = $rule->compute($criteria, $total);
|
|
|
|
if (count($items) > 0)
|
|
{
|
|
foreach ($items as $item)
|
|
{
|
|
$title = $item['title'];
|
|
$quantity = $item['qty'];
|
|
$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,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// also need to return the total and the promo
|
|
// promo is set per invoice, not per item
|
|
$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'];
|
|
|
|
$invoice->setPromo($promo);
|
|
}
|
|
|
|
foreach ($invoice_items as $item)
|
|
{
|
|
$invoice_item = new InvoiceItem();
|
|
|
|
$invoice_item->setInvoice($invoice)
|
|
->setTitle($item['title'])
|
|
->setQuantity($item['quantity'])
|
|
->setPrice((float)$item['price'])
|
|
->setTradeInType($item['trade_in_type']);
|
|
|
|
if ($item['battery'] != null)
|
|
$invoice_item->setBattery($item['battery']);
|
|
|
|
if ($item['battery_size'] != null)
|
|
$invoice_item->setBatterySize($item['battery_size']);
|
|
|
|
$invoice->addItem($invoice_item);
|
|
}
|
|
|
|
// RULE: TYPECAST these values since bc operations return a string
|
|
// and these fields are going to be placed in a JSON response
|
|
$invoice->setTotalPrice((float)$total['total_price'])
|
|
->setVATExclusivePrice((float)$total['vat_ex_price'])
|
|
->setVAT((float)$total['vat'])
|
|
->setDiscount((float)$total['discount'])
|
|
->setTradeIn((float)$total['ti_rate'])
|
|
->setStatus(InvoiceStatus::DRAFT);
|
|
}
|
|
|
|
return $invoice;
|
|
}
|
|
|
|
}
|