From 98a6bfc8aecd487ab40ddf03cab1d783ea41f095 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 25 Sep 2019 05:45:40 +0000 Subject: [PATCH] Create invoice and job generator for cmb. Modify services.yaml for cmb. #265 --- config/services.yaml | 8 +- .../InvoiceGenerator/CmbInvoiceGenerator.php | 493 ++++++++++++++++++ .../CmbJobOrderGenerator.php | 209 ++++++++ 3 files changed, 706 insertions(+), 4 deletions(-) create mode 100644 src/Service/InvoiceGenerator/CmbInvoiceGenerator.php create mode 100644 src/Service/JobOrderGenerator/CmbJobOrderGenerator.php diff --git a/config/services.yaml b/config/services.yaml index 466fd4a8..9b9231d3 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -132,13 +132,13 @@ services: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } # invoice generator - App\Service\InvoiceGenerator\ResqInvoiceGenerator: ~ + App\Service\InvoiceGenerator\CmbInvoiceGenerator: ~ # invoice generator interface - App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator" + App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\CmbInvoiceGenerator" # job order generator - App\Service\JobOrderGenerator\ResqJobOrderGenerator: ~ + App\Service\JobOrderGenerator\CmbJobOrderGenerator: ~ #job order generator interface - App\Service\JobOrderGeneratorInterface: "@App\\Service\\JobOrderGenerator\\ResqJobOrderGenerator" + App\Service\JobOrderGeneratorInterface: "@App\\Service\\JobOrderGenerator\\CmbJobOrderGenerator" diff --git a/src/Service/InvoiceGenerator/CmbInvoiceGenerator.php b/src/Service/InvoiceGenerator/CmbInvoiceGenerator.php new file mode 100644 index 00000000..8ea9c74b --- /dev/null +++ b/src/Service/InvoiceGenerator/CmbInvoiceGenerator.php @@ -0,0 +1,493 @@ +security = $security; + } + + public function generateInvoice(InvoiceCriteria $criteria) + { + // initialize + $invoice = new Invoice(); + $total = [ + 'sell_price' => 0.0, + 'vat' => 0.0, + 'vat_ex_price' => 0.0, + 'ti_rate' => 0.0, + 'total_price' => 0.0, + 'discount' => 0.0, + ]; + + $stype = $criteria->getServiceType(); + $cv = $criteria->getCustomerVehicle(); + $has_coolant = $criteria->hasCoolant(); + // error_log($stype); + switch ($stype) + { + case ServiceType::JUMPSTART_TROUBLESHOOT: + $this->processJumpstart($total, $invoice); + break; + case ServiceType::JUMPSTART_WARRANTY: + $this->processJumpstartWarranty($total, $invoice); + + case ServiceType::BATTERY_REPLACEMENT_NEW: + $this->processEntries($total, $criteria, $invoice); + /* + $this->processBatteries($total, $criteria, $invoice); + $this->processTradeIns($total, $criteria, $invoice); + */ + $this->processDiscount($total, $criteria, $invoice); + break; + + case ServiceType::BATTERY_REPLACEMENT_WARRANTY: + $this->processWarranty($total, $criteria, $invoice); + break; + case ServiceType::POST_RECHARGED: + $this->processRecharge($total, $invoice); + break; + case ServiceType::POST_REPLACEMENT: + $this->processReplacement($total, $invoice); + break; + case ServiceType::TIRE_REPAIR: + $this->processTireRepair($total, $invoice, $cv); + // $this->processOtherServices($total, $invoice, $stype); + break; + case ServiceType::OVERHEAT_ASSISTANCE: + $this->processOverheat($total, $invoice, $cv, $has_coolant); + break; + case ServiceType::EMERGENCY_REFUEL: + error_log('processing refuel'); + $ftype = $criteria->getCustomerVehicle()->getFuelType(); + $this->processRefuel($total, $invoice, $cv); + break; + } + + // TODO: check if any promo is applied + // apply discounts + $promos = $criteria->getPromos(); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $invoice->setCreatedBy($user); + } + + $invoice->setTotalPrice($total['total_price']) + ->setVATExclusivePrice($total['vat_ex_price']) + ->setVAT($total['vat']) + ->setDiscount($total['discount']) + ->setTradeIn($total['ti_rate']) + ->setStatus(InvoiceStatus::DRAFT); + + // dump + //Debug::dump($invoice, 1); + + return $invoice; + } + + protected function getTaxAmount($price) + { + $vat_ex_price = $this->getTaxExclusivePrice($price); + return $price - $vat_ex_price; + // return round($vat_ex_price * self::TAX_RATE, 2); + } + + protected function getTaxExclusivePrice($price) + { + return round($price / (1 + self::TAX_RATE), 2); + } + + protected function getTradeInRate($ti) + { + $size = $ti['size']; + $trade_in = $ti['trade_in']; + + if ($trade_in == null) + return 0; + + switch ($trade_in) + { + case TradeInType::MOTOLITE: + return $size->getTIPriceMotolite(); + case TradeInType::PREMIUM: + return $size->getTIPricePremium(); + case TradeInType::OTHER: + return $size->getTIPriceOther(); + } + + return 0; + } + + protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice) + { + // error_log('processing entries...'); + $entries = $criteria->getEntries(); + + $con_batts = []; + $con_tis = []; + foreach ($entries as $entry) + { + $batt = $entry['battery']; + $qty = $entry['qty']; + $trade_in = $entry['trade_in']; + $size = $batt->getSize(); + + // consolidate batteries + $batt_id = $batt->getID(); + if (!isset($con_batts[$batt_id])) + $con_batts[$batt->getID()] = [ + 'batt' => $batt, + 'qty' => 0 + ]; + $con_batts[$batt_id]['qty']++; + + + // no trade-in + if ($trade_in == null) + continue; + + // consolidate trade-ins + $ti_key = $size->getID() . '|' . $trade_in; + if (!isset($con_tis[$ti_key])) + $con_tis[$ti_key] = [ + 'size' => $size, + 'trade_in' => $trade_in, + 'qty' => 0 + ]; + $con_tis[$ti_key]['qty']++; + } + + $this->processBatteries($total, $con_batts, $invoice); + $this->processTradeIns($total, $con_tis, $invoice); + } + + protected function processBatteries(&$total, $con_batts, Invoice $invoice) + { + // process batteries + foreach ($con_batts as $con_data) + { + $batt = $con_data['batt']; + $qty = $con_data['qty']; + + $sell_price = $batt->getSellingPrice(); + $vat = $this->getTaxAmount($sell_price); + // $vat_ex_price = $this->getTaxExclusivePrice($sell_price); + + $total['sell_price'] += $sell_price * $qty; + $total['vat'] += $vat * $qty; + $total['vat_ex_price'] += ($sell_price - $vat) * $qty; + + $total['total_price'] += $sell_price * $qty; + + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName()) + ->setQuantity($qty) + ->setPrice($sell_price) + ->setBattery($batt); + + $invoice->addItem($item); + } + } + + protected function processTradeIns(&$total, $con_tis, Invoice $invoice) + { + foreach ($con_tis as $ti) + { + $qty = $ti['qty']; + $ti_rate = $this->getTradeInRate($ti); + + $total['ti_rate'] += $ti_rate * $qty; + $total['total_price'] -= $ti_rate * $qty; + + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Trade-in ' . TradeInType::getName($ti['trade_in']) . ' ' . $ti['size']->getName() . ' battery') + ->setQuantity($qty) + ->setPrice($ti_rate * -1); + + $invoice->addItem($item); + } + } + + protected function processDiscount(&$total, InvoiceCriteria $criteria, Invoice $invoice) + { + $promos = $criteria->getPromos(); + if (count($promos) < 1) + return; + + // 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 = round($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); + break; + } + + // if discount is higher than 0, display in invoice + if ($discount > 0) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Promo discount') + ->setQuantity(1) + ->setPrice(-1 * $discount); + $invoice->addItem($item); + } + + $total['discount'] = $discount; + $total['total_price'] -= $discount; + + // process + $invoice->setPromo($promo); + } + + protected function processJumpstart(&$total, $invoice) + { + // add troubleshooting fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Troubleshooting fee') + ->setQuantity(1) + ->setPrice(self::TROUBLESHOOTING_FEE); + $invoice->addItem($item); + + $total['sell_price'] = self::TROUBLESHOOTING_FEE; + $total['vat_ex_price'] = self::TROUBLESHOOTING_FEE; + $total['total_price'] = self::TROUBLESHOOTING_FEE; + } + + protected function processJumpstartWarranty(&$total, $invoice) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Troubleshooting fee') + ->setQuantity(1) + ->setPrice(0.00); + $invoice->addItem($item); + } + + protected function processRecharge(&$total, $invoice) + { + // add recharge fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Recharge fee') + ->setQuantity(1) + ->setPrice(self::RECHARGE_FEE); + $invoice->addItem($item); + + $total['sell_price'] = self::RECHARGE_FEE; + $total['vat_ex_price'] = self::RECHARGE_FEE; + $total['total_price'] = self::RECHARGE_FEE; + } + + protected function processReplacement(&$total, $invoice) + { + // add recharge fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Battery replacement') + ->setQuantity(1) + ->setPrice(self::BATT_REPLACEMENT_FEE); + $invoice->addItem($item); + } + + protected function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) + { + // error_log('processing warranty'); + $entries = $criteria->getEntries(); + foreach ($entries as $entry) + { + $batt = $entry['battery']; + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit') + ->setQuantity(1) + ->setPrice(self::WARRANTY_FEE) + ->setBattery($batt); + $invoice->addItem($item); + } + } + + protected function processOtherServices(&$total, $invoice, $stype) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - ' . ServiceType::getName($stype)) + ->setQuantity(1) + ->setPrice(self::OTHER_SERVICES_FEE); + $invoice->addItem($item); + + $total['total_price'] = 200.00; + } + + protected function processOverheat(&$total, $invoice, $cv, $has_coolant) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - Overheat Assistance') + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + + $total_price = $fee; + + if ($has_coolant) + { + $coolant = new InvoiceItem(); + $coolant->setInvoice($invoice) + ->setTitle('4L Coolant') + ->setQuantity(1) + ->setPrice(self::COOLANT_FEE); + $invoice->addItem($coolant); + + $total_price += self::COOLANT_FEE; + //$total_price += 1600; + } + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + protected function processTireRepair(&$total, $invoice, $cv) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - Flat Tire') + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + $total_price = $fee; + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + protected function processRefuel(&$total, $invoice, $cv) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $ftype = $cv->getFuelType(); + $item = new InvoiceItem(); + + // service charge + $item->setInvoice($invoice) + ->setTitle('Service - ' . ServiceType::getName(ServiceType::EMERGENCY_REFUEL)) + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + $total_price = $fee; + // $total['total_price'] = 200.00; + + $gas_price = self::REFUEL_FEE_GAS; + $diesel_price = self::REFUEL_FEE_DIESEL; + + $fuel = new InvoiceItem(); + error_log('fuel type - ' . $ftype); + switch ($ftype) + { + case FuelType::GAS: + $fuel->setInvoice($invoice) + ->setTitle('4L Fuel - Gas') + ->setQuantity(1) + ->setPrice($gas_price); + $invoice->addItem($fuel); + $total_price += $gas_price; + break; + case FuelType::DIESEL: + $fuel->setInvoice($invoice) + ->setTitle('4L Fuel - Diesel') + ->setQuantity(1) + ->setPrice($diesel_price); + $total_price += $diesel_price; + $invoice->addItem($fuel); + break; + default: + // NOTE: should never get to this point + $fuel->setInvoice($invoice) + ->setTitle('Fuel - Unknown') + ->setQuantity(1) + ->setPrice(0); + $total_price += 0.00; + $invoice->addItem($fuel); + break; + } + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + +} diff --git a/src/Service/JobOrderGenerator/CmbJobOrderGenerator.php b/src/Service/JobOrderGenerator/CmbJobOrderGenerator.php new file mode 100644 index 00000000..15c8506d --- /dev/null +++ b/src/Service/JobOrderGenerator/CmbJobOrderGenerator.php @@ -0,0 +1,209 @@ +em = $em; + $this->ic = $ic; + $this->security = $security; + $this->validator = $validator; + } + public function generateJobOrder(JobOrder $jo, $promo_id, $invoice_change, $invoice_items, &$error_array) + { + // TODO: data validation to be moved here + + // check if invoice changed + if ($invoice_change) + { + $this->processInvoice($jo, $promo_id, $invoice_items, $error_array); + } + + // validate + $errors = $this->validator->validate($jo); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if errors are found + if (empty($error_array)) + { + // validated, no error. save the job order + $this->em->persist($jo); + + // get current user + $user = $this->security->getUser(); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + + if ($user != null) + { + $event->setUser($user); + } + + $this->em->persist($event); + + $this->em->flush(); + } + + return $error_array; + } + + protected function processInvoice($jo, $promo_id, $invoice_items, &$error_array) + { + // instantiate the invoice criteria + $criteria = new InvoiceCriteria(); + $criteria->setServiceType($jo->getServiceType()) + ->setCustomerVehicle($jo->getCustomerVehicle()); + + $ierror = $this->invoicePromo($criteria, $promo_id); + + if (!$ierror && !empty($invoice_items)) + { + // check for trade-in so we can mark it for mobile app + foreach ($invoice_items as $item) + { + // get first trade-in + if (!empty($item['trade_in'])) + { + $jo->getTradeInType($item['trade_in']); + break; + } + } + + $ierror = $this->invoiceBatteries($criteria, $invoice_items); + } + + if ($ierror) + { + $error_array['invoice'] = $ierror; + } + else + { + // generate the invoice + $iobj = $this->ic->generateInvoice($criteria); + + // validate + $ierrors = $this->validator->validate($iobj); + + // 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($iobj); + + $this->em->persist($iobj); + + } + } + + protected function invoicePromo(InvoiceCriteria $criteria, $promo_id) + { + // return error if there's a problem, false otherwise + // check service type + $stype = $criteria->getServiceType(); + 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; + } + + protected function invoiceBatteries(InvoiceCriteria $criteria, $items) + { + // check service type + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) + return null; + + // return error if there's a problem, false otherwise + if (!empty($items)) + { + foreach ($items as $item) + { + // check if this is a valid 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; + + /* + // add to criteria + $criteria->addBattery($battery, $qty); + */ + + // 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; + } +}