resq/src/Service/InvoiceManager.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;
}
}