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), 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(), 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) { // 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 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; if (isset($item['battery'])) $battery = $item['battery']; if (isset($item['promo'])) $promo = $item['promo']; $invoice_items[] = [ 'title' => $title, 'quantity' => $quantity, 'price' => $price, 'battery' => $battery, ]; } } } // 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']); if ($item['battery'] != null) $invoice_item->setBattery($item['battery']); $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; } }