From 9447f643126c2ee5aba0ca254fa569f0b856931d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 19 Dec 2023 17:30:38 +0800 Subject: [PATCH 01/64] Create item price, price tier, and item type entities for regional pricing. #780 --- src/Entity/ItemPrice.php | 53 +++++++++++++++++++++++++++++ src/Entity/ItemType.php | 69 +++++++++++++++++++++++++++++++++++++ src/Entity/PriceTier.php | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 src/Entity/ItemPrice.php create mode 100644 src/Entity/ItemType.php create mode 100644 src/Entity/PriceTier.php diff --git a/src/Entity/ItemPrice.php b/src/Entity/ItemPrice.php new file mode 100644 index 00000000..d03cadbf --- /dev/null +++ b/src/Entity/ItemPrice.php @@ -0,0 +1,53 @@ +id; + } +} diff --git a/src/Entity/ItemType.php b/src/Entity/ItemType.php new file mode 100644 index 00000000..204fb9b3 --- /dev/null +++ b/src/Entity/ItemType.php @@ -0,0 +1,69 @@ +code = ''; + } + + public function getID() + { + return $this->id; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setCode($code) + { + $this->code = $code; + return $this; + } + + public function getCode() + { + return $this->code; + } +} diff --git a/src/Entity/PriceTier.php b/src/Entity/PriceTier.php new file mode 100644 index 00000000..18b735aa --- /dev/null +++ b/src/Entity/PriceTier.php @@ -0,0 +1,73 @@ +meta_areas = []; + + $this->items = new ArrayCollection(); + } + + public function getID() + { + return $this->id; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function addMetaArea($id, $value) + { + $this->meta_areas[$id] = $value; + } + + public function getAllMetaAreas() + { + return $this->meta_areas; + } +} From fa3cf12be12bb8cc9fd43355a96031997dc70ff1 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 20 Dec 2023 18:15:17 +0800 Subject: [PATCH 02/64] Add controller for price tier. #780 --- config/packages/catalyst_auth.yaml | 42 +++++ config/packages/catalyst_menu.yaml | 26 ++- config/routes/price_tier.yaml | 34 ++++ src/Controller/PriceTierController.php | 244 +++++++++++++++++++++++++ src/Entity/ItemPrice.php | 37 +++- src/Entity/PriceTier.php | 29 ++- src/Entity/SupportedArea.php | 19 ++ templates/price-tier/form.html.twig | 154 ++++++++++++++++ templates/price-tier/list.html.twig | 146 +++++++++++++++ 9 files changed, 713 insertions(+), 18 deletions(-) create mode 100644 config/routes/price_tier.yaml create mode 100644 src/Controller/PriceTierController.php create mode 100644 templates/price-tier/form.html.twig create mode 100644 templates/price-tier/list.html.twig diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index dfaf31e0..9977d263 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -634,6 +634,48 @@ catalyst_auth: - id: service_offering.delete label: Delete + - id: price_tier + label: Price Tier + acls: + - id: price_tier.menu + label: Menu + - id: price_tier.list + label: List + - id: price_tier.add + label: Add + - id: price_tier.update + label: Update + - id: price_tier.delete + label: Delete + + - id: item_type + label: Item Type + acls: + - id: item.type.menu + label: Menu + - id: item.type.list + label: List + - id: item.type.add + label: Add + - id: item.type.update + label: Update + - id: item.type.delete + label: Delete + + - id: item + label: Item + acls: + - id: item.menu + label: Menu + - id: item.list + label: List + - id: item.add + label: Add + - id: item.update + label: Update + - id: item.delete + label: Delete + api: user_entity: "App\\Entity\\ApiUser" acl_data: diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index deb8c43f..a92f2c4e 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -177,7 +177,7 @@ catalyst_menu: acl: support.menu label: '[menu.support]' icon: flaticon-support - order: 10 + order: 11 - id: customer_list acl: customer.list label: '[menu.support.customers]' @@ -223,7 +223,7 @@ catalyst_menu: acl: service.menu label: '[menu.service]' icon: flaticon-squares - order: 11 + order: 12 - id: service_list acl: service.list label: '[menu.service.services]' @@ -233,7 +233,7 @@ catalyst_menu: acl: partner.menu label: '[menu.partner]' icon: flaticon-network - order: 12 + order: 13 - id: partner_list acl: partner.list label: '[menu.partner.partners]' @@ -247,7 +247,7 @@ catalyst_menu: acl: motolite_event.menu label: '[menu.motolite_event]' icon: flaticon-event-calendar-symbol - order: 13 + order: 14 - id: motolite_event_list acl: motolite_event.list label: '[menu.motolite_event.events]' @@ -257,7 +257,7 @@ catalyst_menu: acl: analytics.menu label: '[menu.analytics]' icon: flaticon-graphic - order: 14 + order: 15 - id: analytics_forecast_form acl: analytics.forecast label: '[menu.analytics.forecasting]' @@ -267,7 +267,7 @@ catalyst_menu: acl: database.menu label: '[menu.database]' icon: fa fa-database - order: 15 + order: 16 - id: ticket_type_list acl: ticket_type.menu label: '[menu.database.tickettypes]' @@ -288,3 +288,17 @@ catalyst_menu: acl: service_offering.menu label: '[menu.database.serviceofferings]' parent: database + + - id: item + acl: item.menu + label: Item Management + icon: fa fa-boxes + order: 10 + - id: price_tier_list + acl: price_tier.list + label: Price Tiers + parent: item + - id: item_list + acl: item.list + label: Items + parent: item diff --git a/config/routes/price_tier.yaml b/config/routes/price_tier.yaml new file mode 100644 index 00000000..f0127e12 --- /dev/null +++ b/config/routes/price_tier.yaml @@ -0,0 +1,34 @@ +price_tier_list: + path: /pricetiers + controller: App\Controller\PriceTierController::index + methods: [GET] + +price_tier_rows: + path: /pricetiers/rows + controller: App\Controller\PriceTierController::datatableRows + methods: [POST] + +price_tier_add_form: + path: /pricetiers/newform + controller: App\Controller\PriceTierController::addForm + methods: [GET] + +price_tier_add_submit: + path: /pricetiers + controller: App\Controller\PriceTierController::addSubmit + methods: [POST] + +price_tier_update_form: + path: /pricetiers/{id} + controller: App\Controller\PriceTierController::updateForm + methods: [GET] + +price_tier_update_submit: + path: /pricetiers/{id} + controller: App\Controller\PriceTierController::updateSubmit + methods: [POST] + +price_tier_delete: + path: /pricetiers/{id} + controller: App\Controller\PriceTierController::deleteSubmit + methods: [DELETE] diff --git a/src/Controller/PriceTierController.php b/src/Controller/PriceTierController.php new file mode 100644 index 00000000..cd08f90f --- /dev/null +++ b/src/Controller/PriceTierController.php @@ -0,0 +1,244 @@ +render('price-tier/list.html.twig'); + } + + /** + * @IsGranted("price_tier.list") + */ + public function datatableRows(Request $req) + { + // get query builder + $qb = $this->getDoctrine() + ->getRepository(PriceTier::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + $this->setQueryFilters($datatable, $tquery); + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q'); + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.id', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['name'] = $orow->getName(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('price_tier.update')) + $row['meta']['update_url'] = $this->generateUrl('price_tier_update_form', ['id' => $row['id']]); + if ($this->isGranted('service_offering.delete')) + $row['meta']['delete_url'] = $this->generateUrl('price_tier_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + /** + * @Menu(selected="price_tier.list") + * @IsGranted("price_tier.add") + */ + public function addForm(EntityManagerInterface $em) + { + $pt = new PriceTier(); + + // get the supported areas + $sets = $this->generateFormSets($em); + + $params = [ + 'obj' => $pt, + 'sets' => $sets, + 'mode' => 'create', + ]; + + // response + return $this->render('price-tier/form.html.twig', $params); + } + + /** + * @IsGranted("price_tier.add") + */ + public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator) + { + $pt = new PriceTier(); + + // TODO: add validation for supported area + $this->setObject($pt, $req); + + // validate + $errors = $validator->validate($pt); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // validated! save the entity + $em->persist($pt); + + // set the price tier id for the selected supported areas + $this->updateSupportedAreas($em, $pt, $req); + + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @Menu(selected="price_tier_list") + * @ParamConverter("price_tier", class="App\Entity\PriceTier") + * @IsGranted("price_tier.update") + */ + public function updateForm($id, EntityManagerInterface $em, PriceTier $pt) + { + // get the supported areas + $sets = $this->generateFormSets($em); + + $params = [ + 'obj' => $pt, + 'sets' => $sets, + 'mode' => 'update', + ]; + + // response + return $this->render('price-tier/form.html.twig', $params); + } + + protected function setObject(PriceTier $obj, Request $req) + { + $obj->setName($req->request->get('name')); + } + + public function updateSupportedAreas(EntityManagerInterface $em, PriceTier $obj, Request $req) + { + // get the selected areas + $areas = $req->request->get('areas'); + + foreach ($areas as $area_id) + { + // get supported area + $supported_area = $em->getRepository(SupportedArea::class)->find($area_id); + + if ($supported_area != null) + $supported_area->setPriceTier($obj); + } + } + + protected function generateFormSets(EntityManagerInterface $em) + { + // get the supported areas + // TODO: filter out the supported areas that already have a price tier id? but if we're editing, we need those price tiers + $areas = $em->getRepository(SupportedArea::class)->findAll(); + $areas_set = []; + foreach ($areas as $area) + { + $areas_set[$area->getID()] = $area->getName(); + } + + return [ + 'areas' => $areas_set + ]; + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } +} diff --git a/src/Entity/ItemPrice.php b/src/Entity/ItemPrice.php index d03cadbf..b9f14517 100644 --- a/src/Entity/ItemPrice.php +++ b/src/Entity/ItemPrice.php @@ -20,7 +20,7 @@ class ItemPrice protected $id; /** - * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="items") + * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="item_prices") * @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id") */ protected $price_tier; @@ -44,10 +44,43 @@ class ItemPrice /** * @ORM\Column(type="integer") */ - protected $item_price; + protected $price; public function getID() { return $this->id; } + + public function setPriceTier(PriceTier $price_tier) + { + $this->price_tier = $price_tier; + return $this; + } + + public function getPriceTier() + { + return $this->price_tier; + } + + public function setItemID($item_id) + { + $this->item_id = $item_id; + return $this; + } + + public function getItemID() + { + return $this->item_id; + } + + public function setPrice($price) + { + $this->price = $price; + return $this; + } + + public function getPrice() + { + return $this->price; + } } diff --git a/src/Entity/PriceTier.php b/src/Entity/PriceTier.php index 18b735aa..103b7290 100644 --- a/src/Entity/PriceTier.php +++ b/src/Entity/PriceTier.php @@ -28,21 +28,20 @@ class PriceTier // supported areas under price tier /** - * @ORM\Column(type="json") + * @ORM\OneToMany(targetEntity="SupportedArea", mappedBy="price_tier"); */ - protected $meta_areas; + protected $supported_areas; // items under a price tier /** * @ORM\OneToMany(targetEntity="ItemPrice", mappedBy="price_tier") */ - protected $items; + protected $item_prices; public function __construct() { - $this->meta_areas = []; - - $this->items = new ArrayCollection(); + $this->supported_areas = new ArrayCollection(); + $this->item_prices = new ArrayCollection(); } public function getID() @@ -61,13 +60,23 @@ class PriceTier return $this->name; } - public function addMetaArea($id, $value) + public function getSupportedAreaObjects() { - $this->meta_areas[$id] = $value; + return $this->supported_areas; } - public function getAllMetaAreas() + public function getSupportedAreas() { - return $this->meta_areas; + $str_supported_areas = []; + foreach ($this->supported_areas as $supported_area) + $str_supported_areas[] = $supported_area->getID(); + + return $str_supported_areas; } + + public function getItemPrices() + { + return $this->item_prices; + } + } diff --git a/src/Entity/SupportedArea.php b/src/Entity/SupportedArea.php index 0f70c39e..67d53ed8 100644 --- a/src/Entity/SupportedArea.php +++ b/src/Entity/SupportedArea.php @@ -39,9 +39,17 @@ class SupportedArea */ protected $coverage_area; + /** + * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="supported_areas") + * @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id", nullable=true) + */ + protected $price_tier; + public function __construct() { $this->date_create = new DateTime(); + + $this->price_tier = null; } public function getID() @@ -82,5 +90,16 @@ class SupportedArea { return $this->coverage_area; } + + public function setPriceTier(PriceTier $price_tier) + { + $this->price_tier = $price_tier; + return $this; + } + + public function getPriceTier() + { + return $this->price_tier; + } } diff --git a/templates/price-tier/form.html.twig b/templates/price-tier/form.html.twig new file mode 100644 index 00000000..105d7ec2 --- /dev/null +++ b/templates/price-tier/form.html.twig @@ -0,0 +1,154 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Price Tiers

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Price Tier + {{ obj.getName() }} + {% else %} + New Price Tier + {% endif %} +

+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ {% if sets.areas is empty %} + No supported areas. + {% else %} +
+ {% for id, label in sets.areas %} + + {% endfor %} +
+ {% endif %} + +
+
+
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/price-tier/list.html.twig b/templates/price-tier/list.html.twig new file mode 100644 index 00000000..e97eed40 --- /dev/null +++ b/templates/price-tier/list.html.twig @@ -0,0 +1,146 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

+ Price Tiers +

+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} From 8c810bf27a86c4b27f6f740e8c93762d1787e410 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 21 Dec 2023 16:24:37 +0800 Subject: [PATCH 03/64] Add validation and deletion for price tier. #780 --- src/Controller/PriceTierController.php | 143 ++++++++++++++++++++++--- src/Entity/PriceTier.php | 6 ++ src/Entity/SupportedArea.php | 2 +- 3 files changed, 134 insertions(+), 17 deletions(-) diff --git a/src/Controller/PriceTierController.php b/src/Controller/PriceTierController.php index cd08f90f..b44a6bb8 100644 --- a/src/Controller/PriceTierController.php +++ b/src/Controller/PriceTierController.php @@ -139,17 +139,18 @@ class PriceTierController extends Controller */ public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator) { + // initialize error list + $error_array = []; + $pt = new PriceTier(); - // TODO: add validation for supported area + $error_array = $this->validateRequest($em, $req); + $this->setObject($pt, $req); // validate $errors = $validator->validate($pt); - // initialize error list - $error_array = []; - // add errors to list foreach ($errors as $error) { $error_array[$error->getPropertyPath()] = $error->getMessage(); @@ -180,13 +181,13 @@ class PriceTierController extends Controller /** * @Menu(selected="price_tier_list") - * @ParamConverter("price_tier", class="App\Entity\PriceTier") + * @ParamConverter("pt", class="App\Entity\PriceTier") * @IsGranted("price_tier.update") */ public function updateForm($id, EntityManagerInterface $em, PriceTier $pt) { // get the supported areas - $sets = $this->generateFormSets($em); + $sets = $this->generateFormSets($em, $pt); $params = [ 'obj' => $pt, @@ -198,31 +199,141 @@ class PriceTierController extends Controller return $this->render('price-tier/form.html.twig', $params); } + /** + * @ParamConverter("pt", class="App\Entity\PriceTier") + * @IsGranted("price_tier.update") + */ + public function updateSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator, PriceTier $pt) + { + // initialize error list + $error_array = []; + + // clear supported areas of price tier + $this->clearPriceTierSupportedAreas($em, $pt); + + $error_array = $this->validateRequest($em, $req); + $this->setObject($pt, $req); + + // validate + $errors = $validator->validate($pt); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // set the price tier id for the selected supported areas + $this->updateSupportedAreas($em, $pt, $req); + + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @ParamConverter("pt", class="App\Entity\PriceTier") + * @IsGranted("price_tier.delete") + */ + public function deleteSubmit(EntityManagerInterface $em, PriceTier $pt) + { + // clear supported areas of price tier + $this->clearPriceTierSupportedAreas($em, $pt); + + // delete this object + $em->remove($pt); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } + + protected function validateRequest(EntityManagerInterface $em, Request $req) + { + // get areas + $areas = $req->request->get('areas'); + + // check if no areas selected aka empty + if (!empty($areas)) + { + foreach ($areas as $area_id) + { + $supported_area = $em->getRepository(SupportedArea::class)->find($area_id); + + if ($supported_area == null) + return ['areas' => 'Invalid area']; + + // check if supported area already belongs to a price tier + if ($supported_area->getPriceTier() != null) + return ['areas' => 'Area already belongs to a price tier.']; + } + } + + return null; + } + protected function setObject(PriceTier $obj, Request $req) { + // clear supported areas first + $obj->clearSupportedAreas(); + $obj->setName($req->request->get('name')); } - public function updateSupportedAreas(EntityManagerInterface $em, PriceTier $obj, Request $req) + protected function clearPriceTierSupportedAreas(EntityManagerInterface $em, PriceTier $obj) + { + // find the supported areas set with the price tier + $areas = $em->getRepository(SupportedArea::class)->findBy(['price_tier' => $obj]); + + if (!empty($areas)) + { + // set the price tier id for the supported areas to null + foreach ($areas as $area) + { + $area->setPriceTier(null); + } + + $em->flush(); + } + } + + protected function updateSupportedAreas(EntityManagerInterface $em, PriceTier $obj, Request $req) { // get the selected areas $areas = $req->request->get('areas'); - foreach ($areas as $area_id) + // check if no areas selected aka empty + if (!empty($areas)) { - // get supported area - $supported_area = $em->getRepository(SupportedArea::class)->find($area_id); + foreach ($areas as $area_id) + { + // get supported area + $supported_area = $em->getRepository(SupportedArea::class)->find($area_id); - if ($supported_area != null) - $supported_area->setPriceTier($obj); + if ($supported_area != null) + $supported_area->setPriceTier($obj); + } } } - protected function generateFormSets(EntityManagerInterface $em) + protected function generateFormSets(EntityManagerInterface $em, PriceTier $pt = null) { - // get the supported areas - // TODO: filter out the supported areas that already have a price tier id? but if we're editing, we need those price tiers - $areas = $em->getRepository(SupportedArea::class)->findAll(); + // get the supported areas with no price tier id or price tier id is set to the one that is being updated + $areas = $em->getRepository(SupportedArea::class)->findBy(['price_tier' => array(null, $pt)]); $areas_set = []; foreach ($areas as $area) { diff --git a/src/Entity/PriceTier.php b/src/Entity/PriceTier.php index 103b7290..37c8d8f8 100644 --- a/src/Entity/PriceTier.php +++ b/src/Entity/PriceTier.php @@ -74,6 +74,12 @@ class PriceTier return $str_supported_areas; } + public function clearSupportedAreas() + { + $this->supported_areas->clear(); + return $this; + } + public function getItemPrices() { return $this->item_prices; diff --git a/src/Entity/SupportedArea.php b/src/Entity/SupportedArea.php index 67d53ed8..0e176f6a 100644 --- a/src/Entity/SupportedArea.php +++ b/src/Entity/SupportedArea.php @@ -91,7 +91,7 @@ class SupportedArea return $this->coverage_area; } - public function setPriceTier(PriceTier $price_tier) + public function setPriceTier(PriceTier $price_tier = null) { $this->price_tier = $price_tier; return $this; From 58f46fd5bf90c236e7fdbc4984c68567800f7f12 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 21 Dec 2023 23:23:46 -0500 Subject: [PATCH 04/64] Add route and CRUD for item type. #780 --- config/packages/catalyst_auth.yaml | 10 +- config/packages/catalyst_menu.yaml | 4 + config/routes/item_type.yaml | 34 ++++ config/routes/price_tier.yaml | 14 +- src/Controller/ItemTypeController.php | 251 ++++++++++++++++++++++++++ templates/item-type/form.html.twig | 142 +++++++++++++++ templates/item-type/list.html.twig | 146 +++++++++++++++ translations/messages.en.yaml | 1 + 8 files changed, 590 insertions(+), 12 deletions(-) create mode 100644 config/routes/item_type.yaml create mode 100644 src/Controller/ItemTypeController.php create mode 100644 templates/item-type/form.html.twig create mode 100644 templates/item-type/list.html.twig diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index 9977d263..f63c5bc0 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -651,15 +651,15 @@ catalyst_auth: - id: item_type label: Item Type acls: - - id: item.type.menu + - id: item_type.menu label: Menu - - id: item.type.list + - id: item_type.list label: List - - id: item.type.add + - id: item_type.add label: Add - - id: item.type.update + - id: item_type.update label: Update - - id: item.type.delete + - id: item_type.delete label: Delete - id: item diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index a92f2c4e..1c4f7417 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -288,6 +288,10 @@ catalyst_menu: acl: service_offering.menu label: '[menu.database.serviceofferings]' parent: database + - id: item_type_list + acl: item_type.menu + label: '[menu.database.itemtypes]' + parent: database - id: item acl: item.menu diff --git a/config/routes/item_type.yaml b/config/routes/item_type.yaml new file mode 100644 index 00000000..adaa8dee --- /dev/null +++ b/config/routes/item_type.yaml @@ -0,0 +1,34 @@ +item_type_list: + path: /item-types + controller: App\Controller\ItemTypeController::index + methods: [GET] + +item_type_rows: + path: /item-types/rowdata + controller: App\Controller\ItemTypeController::datatableRows + methods: [POST] + +item_type_add_form: + path: /item-types/newform + controller: App\Controller\ItemTypeController::addForm + methods: [GET] + +item_type_add_submit: + path: /item-types + controller: App\Controller\ItemTypeController::addSubmit + methods: [POST] + +item_type_update_form: + path: /item-types/{id} + controller: App\Controller\ItemTypeController::updateForm + methods: [GET] + +item_type_update_submit: + path: /item-types/{id} + controller: App\Controller\ItemTypeController::updateSubmit + methods: [POST] + +item_type_delete: + path: /item-types/{id} + controller: App\Controller\ItemTypeController::deleteSubmit + methods: [DELETE] diff --git a/config/routes/price_tier.yaml b/config/routes/price_tier.yaml index f0127e12..397858d9 100644 --- a/config/routes/price_tier.yaml +++ b/config/routes/price_tier.yaml @@ -1,34 +1,34 @@ price_tier_list: - path: /pricetiers + path: /price-tiers controller: App\Controller\PriceTierController::index methods: [GET] price_tier_rows: - path: /pricetiers/rows + path: /price-tiers/rows controller: App\Controller\PriceTierController::datatableRows methods: [POST] price_tier_add_form: - path: /pricetiers/newform + path: /price-tiers/newform controller: App\Controller\PriceTierController::addForm methods: [GET] price_tier_add_submit: - path: /pricetiers + path: /price-tiers controller: App\Controller\PriceTierController::addSubmit methods: [POST] price_tier_update_form: - path: /pricetiers/{id} + path: /price-tiers/{id} controller: App\Controller\PriceTierController::updateForm methods: [GET] price_tier_update_submit: - path: /pricetiers/{id} + path: /price-tiers/{id} controller: App\Controller\PriceTierController::updateSubmit methods: [POST] price_tier_delete: - path: /pricetiers/{id} + path: /price-tiers/{id} controller: App\Controller\PriceTierController::deleteSubmit methods: [DELETE] diff --git a/src/Controller/ItemTypeController.php b/src/Controller/ItemTypeController.php new file mode 100644 index 00000000..c7c1b110 --- /dev/null +++ b/src/Controller/ItemTypeController.php @@ -0,0 +1,251 @@ +render('item-type/list.html.twig'); + } + + /** + * @IsGranted("item_type.list") + */ + public function datatableRows(Request $req) + { + // get query builder + $qb = $this->getDoctrine() + ->getRepository(ItemType::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + $this->setQueryFilters($datatable, $tquery); + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q'); + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.id', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['name'] = $orow->getName(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('item_type.update')) + $row['meta']['update_url'] = $this->generateUrl('item_type_update_form', ['id' => $row['id']]); + if ($this->isGranted('item_type.delete')) + $row['meta']['delete_url'] = $this->generateUrl('item_type_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + /** + * @Menu(selected="item_type.list") + * @IsGranted("item_type.add") + */ + public function addForm() + { + $item_type = new ItemType(); + $params = [ + 'obj' => $item_type, + 'mode' => 'create', + ]; + + // response + return $this->render('item-type/form.html.twig', $params); + } + + /** + * @IsGranted("item_type.add") + */ + public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator) + { + $item_type = new ItemType(); + + $this->setObject($item_type, $req); + + // validate + $errors = $validator->validate($item_type); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // validated! save the entity + $em->persist($item_type); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @Menu(selected="item_type_list") + * @ParamConverter("item_type", class="App\Entity\ItemType") + * @IsGranted("item_type.update") + */ + public function updateForm($id, EntityManagerInterface $em, ItemType $item_type) + { + $params = []; + $params['obj'] = $item_type; + $params['mode'] = 'update'; + + // response + return $this->render('item-type/form.html.twig', $params); + } + + /** + * @ParamConverter("item_type", class="App\Entity\ItemType") + * @IsGranted("item_type.update") + */ + public function updateSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator, ItemType $item_type) + { + $this->setObject($item_type, $req); + + // validate + $errors = $validator->validate($item_type); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @ParamConverter("item_type", class="App\Entity\ItemType") + * @IsGranted("item_type.delete") + */ + public function deleteSubmit(EntityManagerInterface $em, ItemType $item_type) + { + // delete this object + $em->remove($item_type); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } + + + protected function setObject(ItemType $obj, Request $req) + { + // set and save values + $obj->setName($req->request->get('name')) + ->setCode($req->request->get('code')); + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } +} diff --git a/templates/item-type/form.html.twig b/templates/item-type/form.html.twig new file mode 100644 index 00000000..97d0d9ec --- /dev/null +++ b/templates/item-type/form.html.twig @@ -0,0 +1,142 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Item Types

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Item Type + {{ obj.getName() }} + {% else %} + New Item Type + {% endif %} +

+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/item-type/list.html.twig b/templates/item-type/list.html.twig new file mode 100644 index 00000000..cc11e81d --- /dev/null +++ b/templates/item-type/list.html.twig @@ -0,0 +1,146 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

+ Item Types +

+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 7278212d..8387663e 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -159,6 +159,7 @@ menu.database.subtickettypes: 'Sub Ticket Types' menu.database.emergencytypes: 'Emergency Types' menu.database.ownershiptypes: 'Ownership Types' menu.database.serviceofferings: 'Service Offerings' +menu.database.itemtypes: 'Item Types' # fcm jo status updates jo_fcm_title_outlet_assign: 'Looking for riders' From e4ffcc0c9dcd199bcb46c0a983fdc40a7fe7c136 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 27 Dec 2023 22:15:44 -0500 Subject: [PATCH 05/64] Add controller for item pricing. #780 --- config/packages/catalyst_auth.yaml | 5 + config/packages/catalyst_menu.yaml | 4 + config/routes/item_pricing.yaml | 14 +++ src/Controller/ItemPricingController.php | 76 +++++++++++++ templates/item-pricing/form.html.twig | 129 +++++++++++++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 config/routes/item_pricing.yaml create mode 100644 src/Controller/ItemPricingController.php create mode 100644 templates/item-pricing/form.html.twig diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index f63c5bc0..7e02d5c0 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -675,6 +675,11 @@ catalyst_auth: label: Update - id: item.delete label: Delete + - id: item_pricing + label: Item Pricing + acls: + - id: item_pricing.update + label: Update api: user_entity: "App\\Entity\\ApiUser" diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index 1c4f7417..c861471d 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -302,6 +302,10 @@ catalyst_menu: acl: price_tier.list label: Price Tiers parent: item + - id: item_pricing + acl: item_pricing.update + label: Item Pricing + parent: item - id: item_list acl: item.list label: Items diff --git a/config/routes/item_pricing.yaml b/config/routes/item_pricing.yaml new file mode 100644 index 00000000..df289902 --- /dev/null +++ b/config/routes/item_pricing.yaml @@ -0,0 +1,14 @@ +item_pricing: + path: /item-pricing + controller: App\Controller\ItemPricingController::index + methods: [GET] + +item_pricing_update: + path: /item-pricing + controller: App\Controller\ItemPricingController::formSubmit + methods: [POST] + +item_pricing_prices: + path: /item-pricing/{id}/prices + controller: App\Controller\ItemPricingController::itemPrices + methods: [GET] diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php new file mode 100644 index 00000000..9aba9f68 --- /dev/null +++ b/src/Controller/ItemPricingController.php @@ -0,0 +1,76 @@ +getRepository(PriceTier::class)->findAll(); + + // get all the items/batteries + $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); + + $params = [ + 'sets' => [ + 'price_tiers' => $price_tiers + ], + 'items' => $items, + ]; + + return $this->render('item-pricing/form.html.twig', $params); + } + + /** + * @Menu(selected="item_pricing") + * @IsGranted("item_pricing.update") + */ + public function formSubmit(Request $req, EntityManagerInterface $em) + { + } + + /** + * @IsGranted("item_pricing.update") + */ + public function (EntityManagerInterface $em, $id) + { + $pt_prices = []; + // check if default prices are needed + if ($id != 0) + { + // get the price tier prices + } + else + { + // get the prices from battery + } + + $data_items = []; + + // response + return new JsonResponse([ + 'items' => $data_items, + ]); + } + diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig new file mode 100644 index 00000000..cde71ae6 --- /dev/null +++ b/templates/item-pricing/form.html.twig @@ -0,0 +1,129 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Item Pricing

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + {% for item in items %} + + + + + + {% endfor %} + +
IDNamePrice
{{ item.getID }}{{ item.getModel.getName ~ ' ' ~ item.getSize.getName}} + +
+
+ +
+
+
+
+
+
+
+
+{% endblock %} + +{% block js_end %} + +{% endblock %} From 01e4baa8c4473775899d78c43155ffad6f155fa6 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 28 Dec 2023 20:41:44 -0500 Subject: [PATCH 06/64] Add item price controller. #780 --- config/packages/catalyst_auth.yaml | 14 +-- config/packages/catalyst_menu.yaml | 4 +- config/routes/item_price.yaml | 34 ++++++ src/Controller/ItemPriceController.php | 126 +++++++++++++++++++++++ src/Controller/ItemPricingController.php | 13 ++- 5 files changed, 179 insertions(+), 12 deletions(-) create mode 100644 config/routes/item_price.yaml create mode 100644 src/Controller/ItemPriceController.php diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index 7e02d5c0..e03cdc32 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -662,18 +662,18 @@ catalyst_auth: - id: item_type.delete label: Delete - - id: item - label: Item + - id: item_price + label: Item Price acls: - - id: item.menu + - id: item_price.menu label: Menu - - id: item.list + - id: item_price.list label: List - - id: item.add + - id: item_price.add label: Add - - id: item.update + - id: item_price.update label: Update - - id: item.delete + - id: item_price.delete label: Delete - id: item_pricing label: Item Pricing diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index c861471d..83f7e852 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -306,7 +306,7 @@ catalyst_menu: acl: item_pricing.update label: Item Pricing parent: item - - id: item_list - acl: item.list + - id: item_price_list + acl: item_price.list label: Items parent: item diff --git a/config/routes/item_price.yaml b/config/routes/item_price.yaml new file mode 100644 index 00000000..607dc3cd --- /dev/null +++ b/config/routes/item_price.yaml @@ -0,0 +1,34 @@ +item_price_list: + path: /items + controller: App\Controller\ItemPriceController::index + methods: [GET] + +item_price_rows: + path: /items/rowdata + controller: App\Controller\ItemPriceController::datatableRows + methods: [POST] + +item_price_add_form: + path: /items/newform + controller: App\Controller\ItemPriceController::addForm + methods: [GET] + +item_price_add_submit: + path: /items + controller: App\Controller\ItemPriceController::addSubmit + methods: [POST] + +item_price_update_form: + path: /items/{id} + controller: App\Controller\ItemPriceController::updateForm + methods: [GET] + +item_price_update_submit: + path: /items/{id} + controller: App\Controller\ItemPriceController::updateSubmit + methods: [POST] + +item_price_delete: + path: /items/{id} + controller: App\Controller\ItemPriceController::deleteSubmit + methods: [DELETE] diff --git a/src/Controller/ItemPriceController.php b/src/Controller/ItemPriceController.php new file mode 100644 index 00000000..0a41994c --- /dev/null +++ b/src/Controller/ItemPriceController.php @@ -0,0 +1,126 @@ +render('item-price/list.html.twig'); + } + + /** + * @IsGranted("item_price.list") + */ + public function datatableRows(Request $req) + { + // get query builder + $qb = $this->getDoctrine() + ->getRepository(ItemPrice::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)') + ->innerJoin('q.battery', 'b', 'WITH', 'b.id = q.item_id') + ->innerJoin('q.service_offering', 'so', 'WITH', 'so.id = q.item_id'); + $this->setQueryFilters($datatable, $tquery); + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q') + ->innerJoin('q.battery', 'b', 'WITH', 'b.id = q.item_id') + ->innerJoin('q.service_offering', 'so', 'WITH', 'so.id = q.item_id'); + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.id', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('item_price.update')) + $row['meta']['update_url'] = $this->generateUrl('item_price_update_form', ['id' => $row['id']]); + if ($this->isGranted('item_price.delete')) + $row['meta']['delete_url'] = $this->generateUrl('item_price_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } +} diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index 9aba9f68..ddfd60bf 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -53,13 +53,20 @@ class ItemPricingController extends Controller /** * @IsGranted("item_pricing.update") */ - public function (EntityManagerInterface $em, $id) + public function itemPrices(EntityManagerInterface $em, $id) { $pt_prices = []; // check if default prices are needed if ($id != 0) { - // get the price tier prices + // get the price tier + $pt = $em->getRepository(PriceTier::class)->find($id); + + // get the item prices under the price tier + $pt_item_prices = $pt->getItemPrices(); + foreach ($pt_item_prices as $pt_item_price) + { + } } else { @@ -73,4 +80,4 @@ class ItemPricingController extends Controller 'items' => $data_items, ]); } - +} From 80b9f903247d4d05ccc80161ecbe9e6f0f2e63b6 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Sat, 30 Dec 2023 23:09:27 -0500 Subject: [PATCH 07/64] Rename ItemPriceController to ItemController. #780 --- config/packages/catalyst_auth.yaml | 14 +- config/packages/catalyst_menu.yaml | 6 +- config/routes/item.yaml | 34 ++++ config/routes/item_price.yaml | 34 ---- ...PriceController.php => ItemController.php} | 16 +- src/Entity/{ItemPrice.php => Item.php} | 19 ++- src/Entity/ItemType.php | 11 ++ src/Entity/PriceTier.php | 10 +- templates/item/form.html.twig | 142 +++++++++++++++++ templates/item/list.html.twig | 146 ++++++++++++++++++ templates/price-tier/form.html.twig | 2 +- 11 files changed, 373 insertions(+), 61 deletions(-) create mode 100644 config/routes/item.yaml delete mode 100644 config/routes/item_price.yaml rename src/Controller/{ItemPriceController.php => ItemController.php} (90%) rename src/Entity/{ItemPrice.php => Item.php} (83%) create mode 100644 templates/item/form.html.twig create mode 100644 templates/item/list.html.twig diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index e03cdc32..7e02d5c0 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -662,18 +662,18 @@ catalyst_auth: - id: item_type.delete label: Delete - - id: item_price - label: Item Price + - id: item + label: Item acls: - - id: item_price.menu + - id: item.menu label: Menu - - id: item_price.list + - id: item.list label: List - - id: item_price.add + - id: item.add label: Add - - id: item_price.update + - id: item.update label: Update - - id: item_price.delete + - id: item.delete label: Delete - id: item_pricing label: Item Pricing diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index 83f7e852..a6bd1583 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -297,7 +297,7 @@ catalyst_menu: acl: item.menu label: Item Management icon: fa fa-boxes - order: 10 + order: 10 - id: price_tier_list acl: price_tier.list label: Price Tiers @@ -306,7 +306,7 @@ catalyst_menu: acl: item_pricing.update label: Item Pricing parent: item - - id: item_price_list - acl: item_price.list + - id: item_list + acl: item.list label: Items parent: item diff --git a/config/routes/item.yaml b/config/routes/item.yaml new file mode 100644 index 00000000..89dde4d2 --- /dev/null +++ b/config/routes/item.yaml @@ -0,0 +1,34 @@ +item_list: + path: /items + controller: App\Controller\ItemController::index + methods: [GET] + +item_rows: + path: /items/rowdata + controller: App\Controller\ItemController::datatableRows + methods: [POST] + +item_add_form: + path: /items/newform + controller: App\Controller\ItemController::addForm + methods: [GET] + +item_add_submit: + path: /items + controller: App\Controller\ItemController::addSubmit + methods: [POST] + +item_update_form: + path: /items/{id} + controller: App\Controller\ItemController::updateForm + methods: [GET] + +item_update_submit: + path: /items/{id} + controller: App\Controller\ItemController::updateSubmit + methods: [POST] + +item_delete: + path: /items/{id} + controller: App\Controller\ItemController::deleteSubmit + methods: [DELETE] diff --git a/config/routes/item_price.yaml b/config/routes/item_price.yaml deleted file mode 100644 index 607dc3cd..00000000 --- a/config/routes/item_price.yaml +++ /dev/null @@ -1,34 +0,0 @@ -item_price_list: - path: /items - controller: App\Controller\ItemPriceController::index - methods: [GET] - -item_price_rows: - path: /items/rowdata - controller: App\Controller\ItemPriceController::datatableRows - methods: [POST] - -item_price_add_form: - path: /items/newform - controller: App\Controller\ItemPriceController::addForm - methods: [GET] - -item_price_add_submit: - path: /items - controller: App\Controller\ItemPriceController::addSubmit - methods: [POST] - -item_price_update_form: - path: /items/{id} - controller: App\Controller\ItemPriceController::updateForm - methods: [GET] - -item_price_update_submit: - path: /items/{id} - controller: App\Controller\ItemPriceController::updateSubmit - methods: [POST] - -item_price_delete: - path: /items/{id} - controller: App\Controller\ItemPriceController::deleteSubmit - methods: [DELETE] diff --git a/src/Controller/ItemPriceController.php b/src/Controller/ItemController.php similarity index 90% rename from src/Controller/ItemPriceController.php rename to src/Controller/ItemController.php index 0a41994c..07e66df1 100644 --- a/src/Controller/ItemPriceController.php +++ b/src/Controller/ItemController.php @@ -14,31 +14,31 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; use App\Entity\ItemType; -use App\Entity\ItemPrice; +use App\Entity\Item; use App\Entity\Battery; use App\Entity\ServiceOffering; use Catalyst\MenuBundle\Annotation\Menu; -class ItemPriceController extends Controller +class ItemController extends Controller { /** - * @Menu(selected="item_price_list") - * @IsGranted("item_price.list") + * @Menu(selected="item_list") + * @IsGranted("item.list") */ public function index () { - return $this->render('item-price/list.html.twig'); + return $this->render('item/list.html.twig'); } /** - * @IsGranted("item_price.list") + * @IsGranted("item.list") */ public function datatableRows(Request $req) { // get query builder $qb = $this->getDoctrine() - ->getRepository(ItemPrice::class) + ->getRepository(Item::class) ->createQueryBuilder('q'); // get datatable params @@ -118,6 +118,8 @@ class ItemPriceController extends Controller protected function setQueryFilters($datatable, QueryBuilder $query) { + // TODO: add filter for item type. + // TODO: fix filter for name since name is with the associated entity if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { $query->where('q.name LIKE :filter') ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); diff --git a/src/Entity/ItemPrice.php b/src/Entity/Item.php similarity index 83% rename from src/Entity/ItemPrice.php rename to src/Entity/Item.php index b9f14517..2e5ed091 100644 --- a/src/Entity/ItemPrice.php +++ b/src/Entity/Item.php @@ -6,10 +6,10 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity - * @ORM\Table(name="item_price") + * @ORM\Table(name="item") */ -class ItemPrice +class Item { // unique id /** @@ -20,14 +20,14 @@ class ItemPrice protected $id; /** - * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="item_prices") + * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="items") * @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id") */ protected $price_tier; // item type /** - * @ORM\ManyToOne(targetEntity="ItemType", inversedBy="item_prices") + * @ORM\ManyToOne(targetEntity="ItemType", inversedBy="items") * @ORM\JoinColumn(name="item_type_id", referencedColumnName="id") */ protected $item_type; @@ -62,6 +62,17 @@ class ItemPrice return $this->price_tier; } + public function setItemType(ItemType $item_type) + { + $this->item_type = $item_type; + return $this; + } + + public function getItemType() + { + return $this->item_type; + } + public function setItemID($item_id) { $this->item_id = $item_id; diff --git a/src/Entity/ItemType.php b/src/Entity/ItemType.php index 204fb9b3..3ef422d8 100644 --- a/src/Entity/ItemType.php +++ b/src/Entity/ItemType.php @@ -35,6 +35,12 @@ class ItemType */ protected $code; + // items under an item type + /** + * @ORM\OneToMany(targetEntity="Item", mappedBy="item_type") + */ + protected $items; + public function __construct() { $this->code = ''; @@ -66,4 +72,9 @@ class ItemType { return $this->code; } + + public function getItems() + { + return $this->items; + } } diff --git a/src/Entity/PriceTier.php b/src/Entity/PriceTier.php index 37c8d8f8..d929fc8f 100644 --- a/src/Entity/PriceTier.php +++ b/src/Entity/PriceTier.php @@ -34,14 +34,14 @@ class PriceTier // items under a price tier /** - * @ORM\OneToMany(targetEntity="ItemPrice", mappedBy="price_tier") + * @ORM\OneToMany(targetEntity="Item", mappedBy="price_tier") */ - protected $item_prices; + protected $items; public function __construct() { $this->supported_areas = new ArrayCollection(); - $this->item_prices = new ArrayCollection(); + $this->items = new ArrayCollection(); } public function getID() @@ -80,9 +80,9 @@ class PriceTier return $this; } - public function getItemPrices() + public function getItems() { - return $this->item_prices; + return $this->items; } } diff --git a/templates/item/form.html.twig b/templates/item/form.html.twig new file mode 100644 index 00000000..97d0d9ec --- /dev/null +++ b/templates/item/form.html.twig @@ -0,0 +1,142 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Item Types

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Item Type + {{ obj.getName() }} + {% else %} + New Item Type + {% endif %} +

+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/item/list.html.twig b/templates/item/list.html.twig new file mode 100644 index 00000000..b3a36a61 --- /dev/null +++ b/templates/item/list.html.twig @@ -0,0 +1,146 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

+ Items +

+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/price-tier/form.html.twig b/templates/price-tier/form.html.twig index 105d7ec2..0056cdc8 100644 --- a/templates/price-tier/form.html.twig +++ b/templates/price-tier/form.html.twig @@ -49,7 +49,7 @@
{% if sets.areas is empty %} - No supported areas. + No available supported areas. {% else %}
{% for id, label in sets.areas %} From c17be92f0a9d24c7e5541345a9f7e6f659d26996 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Sun, 31 Dec 2023 00:46:12 -0500 Subject: [PATCH 08/64] Fix errors for item query. #780 --- src/Controller/ItemController.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Controller/ItemController.php b/src/Controller/ItemController.php index 07e66df1..88cc769c 100644 --- a/src/Controller/ItemController.php +++ b/src/Controller/ItemController.php @@ -5,6 +5,7 @@ namespace App\Controller; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Query\Expr\Join; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -37,7 +38,7 @@ class ItemController extends Controller public function datatableRows(Request $req) { // get query builder - $qb = $this->getDoctrine() + $total_qb = $this->getDoctrine() ->getRepository(Item::class) ->createQueryBuilder('q'); @@ -45,9 +46,9 @@ class ItemController extends Controller $datatable = $req->request->get('datatable'); // count total records - $tquery = $qb->select('COUNT(q)') - ->innerJoin('q.battery', 'b', 'WITH', 'b.id = q.item_id') - ->innerJoin('q.service_offering', 'so', 'WITH', 'so.id = q.item_id'); + $tquery = $total_qb->select('COUNT(q)') + ->leftJoin(Battery::class, 'battery', Join::WITH, 'battery.id = q.item_id') + ->leftJoin(ServiceOffering::class, 'so', Join::WITH, 'so.id = q.item_id'); $this->setQueryFilters($datatable, $tquery); $total = $tquery->getQuery() ->getSingleScalarResult(); @@ -68,10 +69,15 @@ class ItemController extends Controller 'field' => 'id' ]; + // reset query builder + $qb = $this->getDoctrine() + ->getRepository(Item::class) + ->createQueryBuilder('q'); + // build query $query = $qb->select('q') - ->innerJoin('q.battery', 'b', 'WITH', 'b.id = q.item_id') - ->innerJoin('q.service_offering', 'so', 'WITH', 'so.id = q.item_id'); + ->leftJoin(Battery::class, 'battery', Join::WITH, 'battery.id = q.item_id') + ->leftJoin(ServiceOffering::class, 'so', Join::WITH, 'so.id = q.item_id'); $this->setQueryFilters($datatable, $query); // check if sorting is present, otherwise use default From f65ca190107f7bb70052cdce0ae65671257d2e6d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 9 Jan 2024 17:39:29 +0800 Subject: [PATCH 09/64] Load service offering into Item Pricing page. #780 --- config/routes/item_pricing.yaml | 2 +- src/Controller/ItemController.php | 54 ++++++++++++++++++++ src/Controller/ItemPricingController.php | 57 ++++++++++++++++++--- templates/item-pricing/form.html.twig | 37 ++++++++++++-- templates/item/form.html.twig | 65 ++++++++++++++++++------ 5 files changed, 188 insertions(+), 27 deletions(-) diff --git a/config/routes/item_pricing.yaml b/config/routes/item_pricing.yaml index df289902..a557a1ec 100644 --- a/config/routes/item_pricing.yaml +++ b/config/routes/item_pricing.yaml @@ -9,6 +9,6 @@ item_pricing_update: methods: [POST] item_pricing_prices: - path: /item-pricing/{id}/prices + path: /item-pricing/{pt_id}/{it_id}/prices controller: App\Controller\ItemPricingController::itemPrices methods: [GET] diff --git a/src/Controller/ItemController.php b/src/Controller/ItemController.php index 88cc769c..7e9cd277 100644 --- a/src/Controller/ItemController.php +++ b/src/Controller/ItemController.php @@ -122,6 +122,60 @@ class ItemController extends Controller ]); } + /** + * @Menu(selected="item.list") + * @IsGranted("item.add") + */ + public function addForm(EntityManagerInterface $em) + { + $item = new Item(); + + // get the sets for the dropdowns + $sets = $this->generateFormSets($em); + + $params = [ + 'obj' => $item, + 'sets' => $sets, + 'mode' => 'create', + ]; + + // response + return $this->render('item/form.html.twig', $params); + } + + protected function generateFormSets(EntityManagerInterface $em) + { + // item types + $item_types = $em->getRepository(ItemType::class)->findby([], ['name' => 'asc']); + $item_type_set = []; + foreach ($item_types as $it) + { + $item_type_set[$it->getID()] = $it->getName(); + } + + // batteries + $batts = $em->getRepository(Battery::class)->findAll(); + $batt_set = []; + foreach ($batts as $batt) + { + $batt_set[$batt->getID()] = $batt->getModel()->getName() . ' ' . $batt->getSize()->getName(); + } + + // service offerings + $services = $em->getRepository(ServiceOffering::class)->findBy([],['name' => 'asc']); + $service_set = []; + foreach ($services as $service) + { + $service_set[$service->getID()] = $service->getName(); + } + + return [ + 'item_types' => $item_type_set, + 'batteries' => $batt_set, + 'services' => $service_set, + ]; + } + protected function setQueryFilters($datatable, QueryBuilder $query) { // TODO: add filter for item type. diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index ddfd60bf..537ae2a6 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -17,6 +17,8 @@ use Catalyst\MenuBundle\Annotation\Menu; use App\Entity\PriceTier; use App\Entity\Battery; +use App\Entity\ServiceOffering; +use App\Entity\ItemType; class ItemPricingController extends Controller { @@ -29,12 +31,16 @@ class ItemPricingController extends Controller // get all the price tiers $price_tiers = $em->getRepository(PriceTier::class)->findAll(); + // get all item types + $item_types = $em->getRepository(ItemType::class)->findBy([], ['name' => 'asc']); + // get all the items/batteries - $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); + $items = $this->getAllItems($em); $params = [ 'sets' => [ - 'price_tiers' => $price_tiers + 'price_tiers' => $price_tiers, + 'item_types' => $item_types, ], 'items' => $items, ]; @@ -53,7 +59,7 @@ class ItemPricingController extends Controller /** * @IsGranted("item_pricing.update") */ - public function itemPrices(EntityManagerInterface $em, $id) + public function itemPrices(EntityManagerInterface $em, $pt_id, $it_id) { $pt_prices = []; // check if default prices are needed @@ -62,9 +68,9 @@ class ItemPricingController extends Controller // get the price tier $pt = $em->getRepository(PriceTier::class)->find($id); - // get the item prices under the price tier - $pt_item_prices = $pt->getItemPrices(); - foreach ($pt_item_prices as $pt_item_price) + // get the items under the price tier + $pt_items = $pt->getItems(); + foreach ($pt_items as $pt_item) { } } @@ -80,4 +86,43 @@ class ItemPricingController extends Controller 'items' => $data_items, ]); } + + protected function getAllItems(EntityManagerInterface $em) + { + // get the item type for battery + $batt_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); + + // get the item type for service offering + $service_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + + // get all active batteries + $batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); + foreach ($batts as $batt) + { + $batt_set[$batt->getID()] = [ + 'name' => $batt->getModel()->getName() . ' ' . $batt->getSize()->getName(), + 'item_type_id' => $batt_item_type->getID(), + 'item_type' => $batt_item_type->getName(), + 'price' => $batt->getSellingPrice(), + ]; + } + + // get all service offerings + $services = $em->getRepository(ServiceOffering::class)->findBy([], ['name' => 'asc']); + $service_set = []; + foreach ($services as $service) + { + $service_set[$service->getID()] = [ + 'name' => $service->getName(), + 'item_type_id' => $service_item_type->getID(), + 'item_type' => $service_item_type->getName(), + 'price' => $service->getFee(), + ]; + } + + return [ + 'batteries' => $batt_set, + 'services' => $service_set, + ]; + } } diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig index cde71ae6..d9387dac 100644 --- a/templates/item-pricing/form.html.twig +++ b/templates/item-pricing/form.html.twig @@ -34,6 +34,17 @@
+
+
+
+ +
+
+
@@ -46,19 +57,34 @@ ID Name + Item Type ID + Item Type Price - {% for item in items %} + {% for id, item in items.batteries %} - {{ item.getID }} - {{ item.getModel.getName ~ ' ' ~ item.getSize.getName}} + {{ id }} + {{ item.name }} + {{ item.item_type_id }} + {{ item.item_type }} - + - {% endfor %} + {% endfor %} + {% for id, item in items.services %} + + {{ id }} + {{ item.name}} + {{ item.item_type_id }} + {{ item.item_type}} + + + + + {% endfor %}
@@ -116,6 +142,7 @@ function update_table(data) { item_html += ''; item_html += '' + item.id + ''; item_html += '' + item.name + ''; + item_html += '' + item.item_type_id + ''; item_html += ''; item_html += ''; item_html += ''; diff --git a/templates/item/form.html.twig b/templates/item/form.html.twig index 97d0d9ec..f7a76ce1 100644 --- a/templates/item/form.html.twig +++ b/templates/item/form.html.twig @@ -5,7 +5,7 @@
-

Item Types

+

Items

@@ -23,33 +23,53 @@

{% if mode == 'update' %} - Edit Item Type + Edit Item {{ obj.getName() }} {% else %} - New Item Type + New Item {% endif %}

-
+
-
-
-
@@ -58,7 +78,7 @@
- Back + Back
@@ -90,7 +110,7 @@ $(function() { text: 'Your changes have been saved!', type: 'success', onClose: function() { - window.location.href = "{{ url('item_type_list') }}"; + window.location.href = "{{ url('item_list') }}"; } }); }).fail(function(response) { @@ -138,5 +158,20 @@ $(function() { $(".form-control-feedback[data-field]").addClass('hide'); } }); + +$('#item-type').change(function(e) { + console.log('item type change ' + e.target.value); + if (e.target.value === '1') { + // display battery row + $('#battery-row').removeClass("hide"); + + // hide service offering rows + } else { + // display service offering row + + // hide battery row + $('#battery-row').addClass("hide"); + } +}) {% endblock %} From 7f4675a8a23d73eeefc90073cac6dcd7d6b0bb5d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 10 Jan 2024 17:23:15 +0800 Subject: [PATCH 10/64] Add item type dropdown to form. #780 --- src/Controller/ItemPricingController.php | 44 ++++++++++++++----- templates/item-pricing/form.html.twig | 55 +++++++++++++----------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index 537ae2a6..158d3787 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -7,6 +7,7 @@ use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -35,7 +36,8 @@ class ItemPricingController extends Controller $item_types = $em->getRepository(ItemType::class)->findBy([], ['name' => 'asc']); // get all the items/batteries - $items = $this->getAllItems($em); + // load only batteries upon initial loading + $items = $this->getBatteries($em); $params = [ 'sets' => [ @@ -62,21 +64,37 @@ class ItemPricingController extends Controller public function itemPrices(EntityManagerInterface $em, $pt_id, $it_id) { $pt_prices = []; + + // get the item type + $it = $em->getRepository(ItemType::class)->find($it_id); + // check if default prices are needed - if ($id != 0) + if ($pt_id != 0) { // get the price tier - $pt = $em->getRepository(PriceTier::class)->find($id); + $pt = $em->getRepository(PriceTier::class)->find($pt_id); // get the items under the price tier $pt_items = $pt->getItems(); foreach ($pt_items as $pt_item) { + // make item price hash + $pt_prices[$pt_item->->getID()] = $pt_item->getPrice(); } } else { - // get the prices from battery + // get the prices from battery or service offering, depending on item type + if ($it->getCode() == 'battery') + { + // get batteries + $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); + } + else + { + // get service offerings + $items = $em->getRepository(ServiceOffering::class)->findBy([], ['name' => 'asc']); + } } $data_items = []; @@ -87,14 +105,11 @@ class ItemPricingController extends Controller ]); } - protected function getAllItems(EntityManagerInterface $em) + protected function getBatteries(EntityManagerInterface $em) { // get the item type for battery $batt_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); - // get the item type for service offering - $service_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); - // get all active batteries $batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); foreach ($batts as $batt) @@ -107,6 +122,16 @@ class ItemPricingController extends Controller ]; } + return [ + 'items' => $batt_set, + ]; + } + + protected function getServiceOfferings(EntityeManagerInterface $em) + { + // get the item type for service offering + $service_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + // get all service offerings $services = $em->getRepository(ServiceOffering::class)->findBy([], ['name' => 'asc']); $service_set = []; @@ -121,8 +146,7 @@ class ItemPricingController extends Controller } return [ - 'batteries' => $batt_set, - 'services' => $service_set, + 'items' => $service_set, ]; } } diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig index d9387dac..ed41d9e3 100644 --- a/templates/item-pricing/form.html.twig +++ b/templates/item-pricing/form.html.twig @@ -25,8 +25,8 @@
- + {% for price_tier in sets.price_tiers %} {% endfor %} @@ -37,7 +37,7 @@
- {% for item_type in sets.item_types %} {% endfor %} @@ -63,7 +63,7 @@ - {% for id, item in items.batteries %} + {% for id, item in items.items %} {{ id }} {{ item.name }} @@ -74,17 +74,6 @@ {% endfor %} - {% for id, item in items.services %} - - {{ id }} - {{ item.name}} - {{ item.item_type_id }} - {{ item.item_type}} - - - - - {% endfor %}
@@ -99,21 +88,36 @@
{% endblock %} -{% block js_end %} +{% block scripts %} {% endblock %} From b59239055460f7f4db1a29ccb749688f698f5ea7 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 11 Jan 2024 16:48:23 +0800 Subject: [PATCH 11/64] Add response when displaying item prices. #780 --- src/Controller/ItemPricingController.php | 31 +++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index 158d3787..bcecd5ea 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -79,7 +79,7 @@ class ItemPricingController extends Controller foreach ($pt_items as $pt_item) { // make item price hash - $pt_prices[$pt_item->->getID()] = $pt_item->getPrice(); + $pt_prices[$pt_item->getID()] = $pt_item->getPrice(); } } else @@ -98,6 +98,35 @@ class ItemPricingController extends Controller } $data_items = []; + foreach ($items as $item) + { + $item_id = $item->getID(); + + // get default price + if ($it->getCode() == 'battery') + $price = $item->getSellingPrice(); + else + $price = $item->getFee(); + + // check if tier has price for item + if (isset($pt_prices[$item_id])) + { + $price = $pt_prices[$item_id]; + + // actual price + $actual_price = number_format($price / 100, 2, '.', ''); + } + + $actual_price = $price; + + // TODO: recheck this + $data_items[] = [ + 'id' => $item_id, + 'name' => '', + 'item_type' => '', + 'price' = $actual_price, + ]; + } // response return new JsonResponse([ From bfe7a5fbf67c5c1271cc3bcb6c655241a884f5c3 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 15 Jan 2024 15:23:07 +0800 Subject: [PATCH 12/64] Fix display of item prices per tier. #780 --- src/Controller/ItemPricingController.php | 14 ++++++++++---- templates/item-pricing/form.html.twig | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index bcecd5ea..38bc0ce3 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -104,9 +104,15 @@ class ItemPricingController extends Controller // get default price if ($it->getCode() == 'battery') + { $price = $item->getSellingPrice(); + $name = $item->getModel()->getName() . ' ' . $item->getSize()->getName(); + } else + { $price = $item->getFee(); + $name = $item->getName(); + } // check if tier has price for item if (isset($pt_prices[$item_id])) @@ -119,12 +125,12 @@ class ItemPricingController extends Controller $actual_price = $price; - // TODO: recheck this $data_items[] = [ 'id' => $item_id, - 'name' => '', - 'item_type' => '', - 'price' = $actual_price, + 'name' => $name, + 'item_type_id' => $it->getID(), + 'item_type' => $it->getName(), + 'price' => $actual_price, ]; } diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig index ed41d9e3..c27179a3 100644 --- a/templates/item-pricing/form.html.twig +++ b/templates/item-pricing/form.html.twig @@ -74,7 +74,7 @@ {% endfor %} - +
@@ -147,8 +147,9 @@ function update_table(data) { item_html += '' + item.id + ''; item_html += '' + item.name + ''; item_html += '' + item.item_type_id + ''; + item_html += '' + item.item_type + ''; item_html += ''; - item_html += ''; + item_html += ''; item_html += ''; item_html += ''; } From 022336ad8f778f72e2a2474f55054848abbb530d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 15 Jan 2024 17:21:31 +0800 Subject: [PATCH 13/64] Add saving of prices. #780 --- src/Controller/ItemPricingController.php | 66 +++++++++++++++++++----- templates/item-pricing/form.html.twig | 4 +- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index 38bc0ce3..f7fd78ec 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -56,6 +56,48 @@ class ItemPricingController extends Controller */ public function formSubmit(Request $req, EntityManagerInterface $em) { + $pt_id = $req->request->get('price_tier_id'); + $it_id = $req->request->get('item_type_id'); + $prices = $req->request->get('price'); + + // get the item type + $item_type = $em->getRepository(ItemType::class)->find($it_id); + + if ($pt_id == 0) + { + // default price tier, update battery or service offering, depending on item type + // NOTE: battery and service offering prices or fees are stored as decimal. + if ($item_type->getCode() == 'battery') + { + // get batteries + $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']); + } + else + { + // get service offerings + $items = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']); + } + + foreach ($items as $item) + { + $item_id = $item->getID(); + if (isset[$prices[$item_id]]) + { + // check item type + if ($item_type->getCode() == 'battery') + $item->setSellingPrice($prices[$item_id]); + else + $item->setFee($prices[$item_id]); + } + } + } + else + { + // get the price tier + $price_tier = $em->getRepository(PriceTier::class)->find($pt_id); + + // TODO: finish this + } } /** @@ -82,19 +124,17 @@ class ItemPricingController extends Controller $pt_prices[$pt_item->getID()] = $pt_item->getPrice(); } } + + // get the prices from battery or service offering, depending on item type + if ($it->getCode() == 'battery') + { + // get batteries + $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']); + } else { - // get the prices from battery or service offering, depending on item type - if ($it->getCode() == 'battery') - { - // get batteries - $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); - } - else - { - // get service offerings - $items = $em->getRepository(ServiceOffering::class)->findBy([], ['name' => 'asc']); - } + // get service offerings + $items = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']); } $data_items = []; @@ -146,7 +186,7 @@ class ItemPricingController extends Controller $batt_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); // get all active batteries - $batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true]); + $batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']); foreach ($batts as $batt) { $batt_set[$batt->getID()] = [ @@ -168,7 +208,7 @@ class ItemPricingController extends Controller $service_item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); // get all service offerings - $services = $em->getRepository(ServiceOffering::class)->findBy([], ['name' => 'asc']); + $services = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']); $service_set = []; foreach ($services as $service) { diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig index c27179a3..0afd23e3 100644 --- a/templates/item-pricing/form.html.twig +++ b/templates/item-pricing/form.html.twig @@ -51,6 +51,7 @@
+
@@ -94,7 +95,6 @@ initialize(); function initialize() { - console.log('initialize'); init_price_tier_dropdown(); init_item_type_dropdown(); } @@ -126,6 +126,8 @@ function load_prices(price_tier_id, item_type_id) { update_table(JSON.parse(req.responseText)); var pt_field = document.getElementById('price-tier-id'); pt_field.value = price_tier_id; + var it_field = document.getElementById('item-type-id'); + it_field.value = item_type_id; } else { // console.log('could not load tier prices'); } From 6d7c8c5b532809757bab2f89c06daee791724683 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 16 Jan 2024 16:28:26 +0800 Subject: [PATCH 14/64] Add saving of item prices for price tier. #780 --- config/packages/catalyst_auth.yaml | 8 - config/packages/catalyst_menu.yaml | 4 - config/routes/item.yaml | 34 ---- src/Controller/ItemController.php | 188 ----------------------- src/Controller/ItemPricingController.php | 63 ++++++-- src/Entity/{Item.php => ItemPrice.php} | 4 +- src/Entity/ItemType.php | 2 +- src/Entity/PriceTier.php | 8 +- 8 files changed, 56 insertions(+), 255 deletions(-) delete mode 100644 config/routes/item.yaml delete mode 100644 src/Controller/ItemController.php rename src/Entity/{Item.php => ItemPrice.php} (98%) diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index 7e02d5c0..9707fc93 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -667,14 +667,6 @@ catalyst_auth: acls: - id: item.menu label: Menu - - id: item.list - label: List - - id: item.add - label: Add - - id: item.update - label: Update - - id: item.delete - label: Delete - id: item_pricing label: Item Pricing acls: diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index a6bd1583..7972a414 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -306,7 +306,3 @@ catalyst_menu: acl: item_pricing.update label: Item Pricing parent: item - - id: item_list - acl: item.list - label: Items - parent: item diff --git a/config/routes/item.yaml b/config/routes/item.yaml deleted file mode 100644 index 89dde4d2..00000000 --- a/config/routes/item.yaml +++ /dev/null @@ -1,34 +0,0 @@ -item_list: - path: /items - controller: App\Controller\ItemController::index - methods: [GET] - -item_rows: - path: /items/rowdata - controller: App\Controller\ItemController::datatableRows - methods: [POST] - -item_add_form: - path: /items/newform - controller: App\Controller\ItemController::addForm - methods: [GET] - -item_add_submit: - path: /items - controller: App\Controller\ItemController::addSubmit - methods: [POST] - -item_update_form: - path: /items/{id} - controller: App\Controller\ItemController::updateForm - methods: [GET] - -item_update_submit: - path: /items/{id} - controller: App\Controller\ItemController::updateSubmit - methods: [POST] - -item_delete: - path: /items/{id} - controller: App\Controller\ItemController::deleteSubmit - methods: [DELETE] diff --git a/src/Controller/ItemController.php b/src/Controller/ItemController.php deleted file mode 100644 index 7e9cd277..00000000 --- a/src/Controller/ItemController.php +++ /dev/null @@ -1,188 +0,0 @@ -render('item/list.html.twig'); - } - - /** - * @IsGranted("item.list") - */ - public function datatableRows(Request $req) - { - // get query builder - $total_qb = $this->getDoctrine() - ->getRepository(Item::class) - ->createQueryBuilder('q'); - - // get datatable params - $datatable = $req->request->get('datatable'); - - // count total records - $tquery = $total_qb->select('COUNT(q)') - ->leftJoin(Battery::class, 'battery', Join::WITH, 'battery.id = q.item_id') - ->leftJoin(ServiceOffering::class, 'so', Join::WITH, 'so.id = q.item_id'); - $this->setQueryFilters($datatable, $tquery); - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // get current page number - $page = $datatable['pagination']['page'] ?? 1; - - $perpage = $datatable['pagination']['perpage']; - $offset = ($page - 1) * $perpage; - - // add metadata - $meta = [ - 'page' => $page, - 'perpage' => $perpage, - 'pages' => ceil($total / $perpage), - 'total' => $total, - 'sort' => 'asc', - 'field' => 'id' - ]; - - // reset query builder - $qb = $this->getDoctrine() - ->getRepository(Item::class) - ->createQueryBuilder('q'); - - // build query - $query = $qb->select('q') - ->leftJoin(Battery::class, 'battery', Join::WITH, 'battery.id = q.item_id') - ->leftJoin(ServiceOffering::class, 'so', Join::WITH, 'so.id = q.item_id'); - $this->setQueryFilters($datatable, $query); - - // check if sorting is present, otherwise use default - if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { - $order = $datatable['sort']['sort'] ?? 'asc'; - $query->orderBy('q.' . $datatable['sort']['field'], $order); - } else { - $query->orderBy('q.id', 'asc'); - } - - // get rows for this page - $obj_rows = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); - - // process rows - $rows = []; - foreach ($obj_rows as $orow) { - // add row data - $row['id'] = $orow->getID(); - - // add row metadata - $row['meta'] = [ - 'update_url' => '', - 'delete_url' => '' - ]; - - // add crud urls - if ($this->isGranted('item_price.update')) - $row['meta']['update_url'] = $this->generateUrl('item_price_update_form', ['id' => $row['id']]); - if ($this->isGranted('item_price.delete')) - $row['meta']['delete_url'] = $this->generateUrl('item_price_delete', ['id' => $row['id']]); - - $rows[] = $row; - } - - // response - return $this->json([ - 'meta' => $meta, - 'data' => $rows - ]); - } - - /** - * @Menu(selected="item.list") - * @IsGranted("item.add") - */ - public function addForm(EntityManagerInterface $em) - { - $item = new Item(); - - // get the sets for the dropdowns - $sets = $this->generateFormSets($em); - - $params = [ - 'obj' => $item, - 'sets' => $sets, - 'mode' => 'create', - ]; - - // response - return $this->render('item/form.html.twig', $params); - } - - protected function generateFormSets(EntityManagerInterface $em) - { - // item types - $item_types = $em->getRepository(ItemType::class)->findby([], ['name' => 'asc']); - $item_type_set = []; - foreach ($item_types as $it) - { - $item_type_set[$it->getID()] = $it->getName(); - } - - // batteries - $batts = $em->getRepository(Battery::class)->findAll(); - $batt_set = []; - foreach ($batts as $batt) - { - $batt_set[$batt->getID()] = $batt->getModel()->getName() . ' ' . $batt->getSize()->getName(); - } - - // service offerings - $services = $em->getRepository(ServiceOffering::class)->findBy([],['name' => 'asc']); - $service_set = []; - foreach ($services as $service) - { - $service_set[$service->getID()] = $service->getName(); - } - - return [ - 'item_types' => $item_type_set, - 'batteries' => $batt_set, - 'services' => $service_set, - ]; - } - - protected function setQueryFilters($datatable, QueryBuilder $query) - { - // TODO: add filter for item type. - // TODO: fix filter for name since name is with the associated entity - if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { - $query->where('q.name LIKE :filter') - ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); - } - } -} diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index f7fd78ec..ac093e82 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -63,25 +63,26 @@ class ItemPricingController extends Controller // get the item type $item_type = $em->getRepository(ItemType::class)->find($it_id); + if ($item_type->getCode() == 'battery') + { + // get batteries + $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']); + } + else + { + // get service offerings + $items = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']); + } + + // on default price tier if ($pt_id == 0) { // default price tier, update battery or service offering, depending on item type // NOTE: battery and service offering prices or fees are stored as decimal. - if ($item_type->getCode() == 'battery') - { - // get batteries - $items = $em->getRepository(Battery::class)->findBy(['flag_active' => true], ['id' => 'asc']); - } - else - { - // get service offerings - $items = $em->getRepository(ServiceOffering::class)->findBy([], ['id' => 'asc']); - } - foreach ($items as $item) { $item_id = $item->getID(); - if (isset[$prices[$item_id]]) + if (isset($prices[$item_id])) { // check item type if ($item_type->getCode() == 'battery') @@ -96,8 +97,42 @@ class ItemPricingController extends Controller // get the price tier $price_tier = $em->getRepository(PriceTier::class)->find($pt_id); - // TODO: finish this + $item_prices = $price_tier->getItems(); + + // clear the tier's item prices + foreach ($item_prices as $ip) + { + $em->remove($ip); + } + + // update the tier's item prices + foreach ($items as $item) + { + $item_id = $item->getID(); + + $item_price = new ItemPrice(); + + $item_price->setItemType($item_type) + ->setPriceTier($price_tier) + ->setItemID($item_id); + + if (isset($prices[$item_id])) + { + $item_price->setPrice($price[$item_id] * 100); + } + else + { + $item_price->setPrice($item->getPrice() * 100); + } + + // save + $em->persist($item_price); + } } + + $em->flush(); + + return $this->redirectToRoute('item_pricing'); } /** @@ -117,7 +152,7 @@ class ItemPricingController extends Controller $pt = $em->getRepository(PriceTier::class)->find($pt_id); // get the items under the price tier - $pt_items = $pt->getItems(); + $pt_items = $pt->getItemPrices(); foreach ($pt_items as $pt_item) { // make item price hash diff --git a/src/Entity/Item.php b/src/Entity/ItemPrice.php similarity index 98% rename from src/Entity/Item.php rename to src/Entity/ItemPrice.php index 2e5ed091..143833ee 100644 --- a/src/Entity/Item.php +++ b/src/Entity/ItemPrice.php @@ -9,7 +9,7 @@ use Doctrine\ORM\Mapping as ORM; * @ORM\Table(name="item") */ -class Item +class ItemPrice { // unique id /** @@ -20,7 +20,7 @@ class Item protected $id; /** - * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="items") + * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="item_prices") * @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id") */ protected $price_tier; diff --git a/src/Entity/ItemType.php b/src/Entity/ItemType.php index 3ef422d8..8c24e65c 100644 --- a/src/Entity/ItemType.php +++ b/src/Entity/ItemType.php @@ -37,7 +37,7 @@ class ItemType // items under an item type /** - * @ORM\OneToMany(targetEntity="Item", mappedBy="item_type") + * @ORM\OneToMany(targetEntity="ItemPrice", mappedBy="item_type") */ protected $items; diff --git a/src/Entity/PriceTier.php b/src/Entity/PriceTier.php index d929fc8f..1e2599a5 100644 --- a/src/Entity/PriceTier.php +++ b/src/Entity/PriceTier.php @@ -34,9 +34,9 @@ class PriceTier // items under a price tier /** - * @ORM\OneToMany(targetEntity="Item", mappedBy="price_tier") + * @ORM\OneToMany(targetEntity="ItemPrice", mappedBy="price_tier") */ - protected $items; + protected $item_prices; public function __construct() { @@ -80,9 +80,9 @@ class PriceTier return $this; } - public function getItems() + public function getItemPrices() { - return $this->items; + return $this->item_prices; } } From 9de6fa7999c7465ffad6ba0bb649a1f7529a822b Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 17 Jan 2024 14:25:28 +0800 Subject: [PATCH 15/64] Fix issues found during saving of item prices. #780 --- src/Controller/ItemPricingController.php | 16 +++++++++++----- src/Entity/ItemPrice.php | 2 +- templates/item-pricing/form.html.twig | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index ac093e82..f5e2a532 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -20,6 +20,7 @@ use App\Entity\PriceTier; use App\Entity\Battery; use App\Entity\ServiceOffering; use App\Entity\ItemType; +use App\Entity\ItemPrice; class ItemPricingController extends Controller { @@ -39,12 +40,16 @@ class ItemPricingController extends Controller // load only batteries upon initial loading $items = $this->getBatteries($em); + // set the default item type to battery + $default_it = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); + $params = [ 'sets' => [ 'price_tiers' => $price_tiers, 'item_types' => $item_types, ], 'items' => $items, + 'default_item_type_id' => $default_it->getID(), ]; return $this->render('item-pricing/form.html.twig', $params); @@ -97,7 +102,7 @@ class ItemPricingController extends Controller // get the price tier $price_tier = $em->getRepository(PriceTier::class)->find($pt_id); - $item_prices = $price_tier->getItems(); + $item_prices = $price_tier->getItemPrices(); // clear the tier's item prices foreach ($item_prices as $ip) @@ -118,7 +123,7 @@ class ItemPricingController extends Controller if (isset($prices[$item_id])) { - $item_price->setPrice($price[$item_id] * 100); + $item_price->setPrice($prices[$item_id] * 100); } else { @@ -153,10 +158,11 @@ class ItemPricingController extends Controller // get the items under the price tier $pt_items = $pt->getItemPrices(); + foreach ($pt_items as $pt_item) { // make item price hash - $pt_prices[$pt_item->getID()] = $pt_item->getPrice(); + $pt_prices[$pt_item->getItemID()] = $pt_item->getPrice(); } } @@ -192,10 +198,10 @@ class ItemPricingController extends Controller // check if tier has price for item if (isset($pt_prices[$item_id])) { - $price = $pt_prices[$item_id]; + $pt_price = $pt_prices[$item_id]; // actual price - $actual_price = number_format($price / 100, 2, '.', ''); + $price = number_format($pt_price / 100, 2, '.', ''); } $actual_price = $price; diff --git a/src/Entity/ItemPrice.php b/src/Entity/ItemPrice.php index 143833ee..48130468 100644 --- a/src/Entity/ItemPrice.php +++ b/src/Entity/ItemPrice.php @@ -6,7 +6,7 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity - * @ORM\Table(name="item") + * @ORM\Table(name="item_price") */ class ItemPrice diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig index 0afd23e3..4572a46a 100644 --- a/templates/item-pricing/form.html.twig +++ b/templates/item-pricing/form.html.twig @@ -51,7 +51,7 @@ - +
@@ -118,6 +118,7 @@ function init_item_type_dropdown() { } function load_prices(price_tier_id, item_type_id) { + console.log('loading prices'); var req = new XMLHttpRequest(); req.onreadystatechange = function() { // process response From ee033ddd552719e99b8a90acd1770d964b193600 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 17 Jan 2024 15:31:20 +0800 Subject: [PATCH 16/64] Fix issues found during testing. #780 --- src/Controller/ItemPricingController.php | 5 +++-- templates/item-pricing/form.html.twig | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controller/ItemPricingController.php b/src/Controller/ItemPricingController.php index f5e2a532..3129f362 100644 --- a/src/Controller/ItemPricingController.php +++ b/src/Controller/ItemPricingController.php @@ -104,10 +104,11 @@ class ItemPricingController extends Controller $item_prices = $price_tier->getItemPrices(); - // clear the tier's item prices + // clear the tier's item prices for the specific item type foreach ($item_prices as $ip) { - $em->remove($ip); + if ($ip->getItemType() == $item_type) + $em->remove($ip); } // update the tier's item prices diff --git a/templates/item-pricing/form.html.twig b/templates/item-pricing/form.html.twig index 4572a46a..d6099390 100644 --- a/templates/item-pricing/form.html.twig +++ b/templates/item-pricing/form.html.twig @@ -118,7 +118,6 @@ function init_item_type_dropdown() { } function load_prices(price_tier_id, item_type_id) { - console.log('loading prices'); var req = new XMLHttpRequest(); req.onreadystatechange = function() { // process response From 70ee7fdd8972da0e400f3b36da6d1908478eee7b Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Jan 2024 04:14:06 -0500 Subject: [PATCH 17/64] Add invoice rule for price tier. #782 --- src/InvoiceRule/PriceTier.php | 157 +++++++++++++++++++++++++++++++++ src/Ramcar/InvoiceCriteria.php | 12 +++ 2 files changed, 169 insertions(+) create mode 100644 src/InvoiceRule/PriceTier.php diff --git a/src/InvoiceRule/PriceTier.php b/src/InvoiceRule/PriceTier.php new file mode 100644 index 00000000..4db12b55 --- /dev/null +++ b/src/InvoiceRule/PriceTier.php @@ -0,0 +1,157 @@ +em = $em; + } + + public function getID() + { + return 'price_tier'; + } + + public function compute($criteria, &$total) + { + $pt_id = $criteria->getPriceTier(); + + // get the service type + $service_type = $criteria->getServiceType(); + + // get price tier + $pt = $em->getRepository(PTEntity::class)->find($pt_id); + + // price tier is default + if ($pt == null) + { + // check if service type is battery sales + if ($service_type == ServiceType::BATTERY_REPLACEMENT_NEW) + { + $items = $this->processBatteryEntries($criteria, $total); + } + } + else + { + // get items in price tier + $pt_items = $pt->getItemPrices(); + + foreach ($pt_items as $pt_item) + { + // make item price hash + $pt_prices[$pt_item->getItemID()] = $pt_item->getPrice(); + } + } + + 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. + $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($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 processBatteryEntries($criteria, &$total) + { + $items = []; + + // 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($criteria->getServiceType(), $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; + } + + protected function getTitle($service_type, $battery) + { + $title =''; + + // TODO: check for service type + $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName(); + + return $title; + } + +} diff --git a/src/Ramcar/InvoiceCriteria.php b/src/Ramcar/InvoiceCriteria.php index b27395da..18ebd82a 100644 --- a/src/Ramcar/InvoiceCriteria.php +++ b/src/Ramcar/InvoiceCriteria.php @@ -17,6 +17,7 @@ class InvoiceCriteria protected $service_charges; protected $flag_taxable; protected $source; // use Ramcar's TransactionOrigin + protected $price_tier; // entries are battery and trade-in combos protected $entries; @@ -32,6 +33,7 @@ class InvoiceCriteria $this->service_charges = []; $this->flag_taxable = false; $this->source = ''; + $this->price_tier = 0; // set to default } public function setServiceType($stype) @@ -179,4 +181,14 @@ class InvoiceCriteria return $this->source; } + public function setPriceTier($price_tier) + { + $this->price_tier = $price_tier; + return $this; + } + + public function getPriceTier() + { + return $this->price_tier; + } } From 29ad8d57a44ad1990cfb0e33990b52895f305892 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 22 Jan 2024 04:24:41 -0500 Subject: [PATCH 18/64] Add processing of battery entries and invoice item titles. #782 --- src/InvoiceRule/PriceTier.php | 77 +++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/InvoiceRule/PriceTier.php b/src/InvoiceRule/PriceTier.php index 4db12b55..7db80ccc 100644 --- a/src/InvoiceRule/PriceTier.php +++ b/src/InvoiceRule/PriceTier.php @@ -38,11 +38,18 @@ class PriceTier implements InvoiceRuleInterface // price tier is default if ($pt == null) { - // check if service type is battery sales - if ($service_type == ServiceType::BATTERY_REPLACEMENT_NEW) + // check if service type is battery sales and battery warranty (sometimes they add a battery + // for battery warranty + if (($service_type == ServiceType::BATTERY_REPLACEMENT_NEW) || + ($service_type == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) { $items = $this->processBatteryEntries($criteria, $total); } + else + { + // TODO: process the service fees + $items = $this->processServiceEntries($criteria, $total); + } } else { @@ -54,6 +61,8 @@ class PriceTier implements InvoiceRuleInterface // make item price hash $pt_prices[$pt_item->getItemID()] = $pt_item->getPrice(); } + + // TODO: finish this } return $items; @@ -123,14 +132,17 @@ class PriceTier implements InvoiceRuleInterface if ($trade_in == null) { - // battery purchase - $price = $batt->getSellingPrice(); + // check if battery purchase or battery replacement + if ($service_type == ServiceType::BATTERY_REPLACEMENT_NEW) + $price = $batt->getSellingPrice(); + else + $price = 0; $items[] = [ 'service_type' => $this->getID(), 'battery' => $batt, 'qty' => $qty, - 'title' => $this->getTitle($criteria->getServiceType(), $batt), + 'title' => $this->getItemTitle($criteria->getServiceType(), $batt), 'price' => $price, ]; @@ -144,12 +156,63 @@ class PriceTier implements InvoiceRuleInterface return $items; } - protected function getTitle($service_type, $battery) + protected function processServiceEntries($criteria, &$total) + { + } + + protected function getItemTitle($service_type, $battery) { $title =''; // TODO: check for service type - $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName(); + switch ($service_type) { + case (ServiceType::BATTERY_REPLACEMENT_NEW): + $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName(); + break; + case (ServiceType::BATTERY_REPLACEMENT_WARRANTY): + $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' - Service Unit'; + break; + default: + $title = ''; + break; + } + + return $title; + + protected function getServiceTitle($service_type, $fuel_type) + { + $title = ''; + + switch ($service_type) { + case (ServiceType::JUMPSTART_TROUBLESHOOT): + case (ServiceType::JUMPSTART_WARRANTY): + $title = 'Service - Troubleshooting fee'; + break; + case (ServiceType::OVERHEAT_ASSISTANCE): + $title = 'Service - ' . ServiceType::getName(ServiceType::OVERHEAT_ASSISTANCE); + break; + case (ServiceType::POST_RECHARGED): + $title = 'Recharge fee'; + break; + case (ServiceType::POST_REPLACEMENT): + $title = 'Battery replacement'; + break; + case (ServiceType::TIRE_REPAIR): + $title = 'Service - Flat Tire'; + break; + case (ServiceType::EMERGENCY_REFUEL): + $title = '4L - ' . ucfirst($fuel_type); + break; + default: + $title = ''; + } + + return $title; + } + + protected function getServiceCoolantTitle() + { + $title = '4L Coolant'; return $title; } From b6763bfd3e41452a48048407120fa187f7058fcd Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 24 Jan 2024 02:24:13 -0500 Subject: [PATCH 19/64] Add price tier checking for battery sales. #782 --- config/services.yaml | 5 ++ src/Controller/JobOrderController.php | 22 ++++++- src/InvoiceRule/BatterySales.php | 38 +++++++++++- .../InvoiceGenerator/CMBInvoiceGenerator.php | 2 +- .../InvoiceGenerator/ResqInvoiceGenerator.php | 2 +- src/Service/InvoiceGeneratorInterface.php | 3 +- src/Service/InvoiceManager.php | 12 ++-- .../JobOrderHandler/ResqJobOrderHandler.php | 9 ++- src/Service/PriceTierManager.php | 58 +++++++++++++++++++ templates/job-order/form.html.twig | 6 +- 10 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 src/Service/PriceTierManager.php diff --git a/config/services.yaml b/config/services.yaml index b19ecabc..d20db360 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -310,3 +310,8 @@ services: arguments: $server_key: "%env(FCM_SERVER_KEY)%" $sender_id: "%env(FCM_SENDER_ID)%" + + # price tier manager + App\Service\PriceTierManager: + arguments: + $em: "@doctrine.orm.entity_manager" diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 91921a0f..17c62a45 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -30,6 +30,7 @@ use App\Service\HubSelector; use App\Service\RiderTracker; use App\Service\MotivConnector; +use App\Service\PriceTierManager; use App\Service\GeofenceTracker; use Symfony\Component\HttpFoundation\Request; @@ -42,6 +43,8 @@ use Doctrine\ORM\EntityManagerInterface; use Catalyst\MenuBundle\Annotation\Menu; +use CrEOF\Spatial\PHP\Types\Geometry\Point; + class JobOrderController extends Controller { public function getJobOrders(Request $req, JobOrderHandlerInterface $jo_handler) @@ -741,7 +744,7 @@ class JobOrderController extends Controller } - public function generateInvoice(Request $req, InvoiceGeneratorInterface $ic) + public function generateInvoice(Request $req, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager) { // error_log('generating invoice...'); $error = false; @@ -752,6 +755,19 @@ class JobOrderController extends Controller $cvid = $req->request->get('cvid'); $service_charges = $req->request->get('service_charges', []); + // coordinates + // need to check if lng and lat are set + $lng = $req->request->get('coord_lng', 0); + $lat = $req->request->get('coord_lat', 0); + + $price_tier = 0; + if (($lng != 0) && ($lat != 0)) + { + $coordinates = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + $price_tier = $pt_manager->getPriceTier($coordinates); + } + + $em = $this->getDoctrine()->getManager(); // get customer vehicle @@ -767,8 +783,8 @@ class JobOrderController extends Controller $criteria->setServiceType($stype) ->setCustomerVehicle($cv) ->setIsTaxable() - ->setSource(TransactionOrigin::CALL); - + ->setSource(TransactionOrigin::CALL) + ->setPriceTier($price_tier); /* // if it's a jumpstart or troubleshoot only, we know what to charge already diff --git a/src/InvoiceRule/BatterySales.php b/src/InvoiceRule/BatterySales.php index 45b060d1..3ff9a995 100644 --- a/src/InvoiceRule/BatterySales.php +++ b/src/InvoiceRule/BatterySales.php @@ -10,14 +10,19 @@ use App\Ramcar\TradeInType; use App\Ramcar\ServiceType; use App\Entity\Battery; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class BatterySales implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -28,6 +33,9 @@ class BatterySales implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt = $criteria->getPriceTier(); + + error_log('price tier ' . $pt); $items = []; if ($stype == $this->getID()) @@ -47,8 +55,13 @@ class BatterySales implements InvoiceRuleInterface if ($trade_in == null) { - // battery purchase - $price = $batt->getSellingPrice(); + // check if price tier has item price for battery + $pt_price = $this->getPriceTierItemPrice($pt, $batt); + + if ($pt_price == null) + $price = $batt->getSellingPrice(); + else + $price = $pt_price; $items[] = [ 'service_type' => $this->getID(), @@ -114,6 +127,25 @@ class BatterySales implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id, $batt) + { + // price tier is default + if ($pt_id == 0) + return null; + + // find the item type battery + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); + if ($item_type == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $batt->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getTitle($battery) { $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName(); diff --git a/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php b/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php index 5dc86f8d..c1f40509 100644 --- a/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php @@ -134,7 +134,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface } // generate invoice criteria - public function generateInvoiceCriteria($jo, $discount, $invoice_items, $source = null, &$error_array) + public function generateInvoiceCriteria($jo, $discount, $invoice_items, $price_tier = null, $source = null, &$error_array) { $em = $this->em; diff --git a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php index 3809133b..efe0a9de 100644 --- a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php @@ -144,7 +144,7 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface } // generate invoice criteria - public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source = null, &$error_array) + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $price_tier = null, $source = null, &$error_array) { $em = $this->em; diff --git a/src/Service/InvoiceGeneratorInterface.php b/src/Service/InvoiceGeneratorInterface.php index e2cb2cc3..4c46c38f 100644 --- a/src/Service/InvoiceGeneratorInterface.php +++ b/src/Service/InvoiceGeneratorInterface.php @@ -4,6 +4,7 @@ namespace App\Service; use App\Entity\Invoice; use App\Entity\JobOrder; +use App\Entity\PriceTier; use App\Ramcar\InvoiceCriteria; @@ -13,7 +14,7 @@ interface InvoiceGeneratorInterface public function generateInvoice(InvoiceCriteria $criteria); // generate invoice criteria - public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, $source, array &$error_array); + public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, $source, PriceTier $price_tier, array &$error_array); // prepare draft for invoice public function generateDraftInvoice(InvoiceCriteria $criteria, int $promo_id, array $service_charges, array $items); diff --git a/src/Service/InvoiceManager.php b/src/Service/InvoiceManager.php index 65fc3a43..93d6bafd 100644 --- a/src/Service/InvoiceManager.php +++ b/src/Service/InvoiceManager.php @@ -10,6 +10,7 @@ use Doctrine\ORM\EntityManagerInterface; use App\InvoiceRule; use App\Service\InvoiceGeneratorInterface; +use App\Service\PriceTierManager; use App\Ramcar\InvoiceCriteria; use App\Ramcar\InvoiceStatus; @@ -28,12 +29,14 @@ class InvoiceManager implements InvoiceGeneratorInterface protected $em; protected $validator; protected $available_rules; + protected $pt_manager; - public function __construct(EntityManagerInterface $em, Security $security, ValidatorInterface $validator) + 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(); } @@ -42,7 +45,7 @@ class InvoiceManager implements InvoiceGeneratorInterface { // TODO: get list of invoice rules from .env or a json file? return [ - new InvoiceRule\BatterySales($this->em), + new InvoiceRule\BatterySales($this->em, $this->pt_manager), new InvoiceRule\BatteryReplacementWarranty($this->em), new InvoiceRule\Jumpstart($this->em), new InvoiceRule\JumpstartWarranty($this->em), @@ -58,12 +61,13 @@ class InvoiceManager implements InvoiceGeneratorInterface } // this is called when JO is submitted - public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, &$error_array) + 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()); + ->setCustomerVehicle($jo->getCustomerVehicle()) + ->setPriceTier($price_tier); // set if taxable // NOTE: ideally, this should be a parameter when calling generateInvoiceCriteria. But that diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 361c0b22..28e9035d 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -69,6 +69,7 @@ use App\Service\HubSelector; use App\Service\HubDistributor; use App\Service\HubFilteringGeoChecker; use App\Service\JobOrderManager; +use App\Service\PriceTierManager; use CrEOF\Spatial\PHP\Types\Geometry\Point; @@ -96,6 +97,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface protected $cust_distance_limit; protected $hub_filter_enable; protected $jo_manager; + protected $pt_manager; protected $template_hash; @@ -104,7 +106,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah, string $country_code, WarrantyHandler $wh, RisingTideGateway $rt, PromoLogger $promo_logger, HubDistributor $hub_dist, HubFilteringGeoChecker $hub_geofence, - string $cust_distance_limit, string $hub_filter_enabled, JobOrderManager $jo_manager) + string $cust_distance_limit, string $hub_filter_enabled, JobOrderManager $jo_manager, PriceTierManager $pt_manager) { $this->em = $em; $this->ic = $ic; @@ -121,6 +123,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $this->cust_distance_limit = $cust_distance_limit; $this->hub_filter_enabled = $hub_filter_enabled; $this->jo_manager = $jo_manager; + $this->pt_manager = $pt_manager; $this->loadTemplates(); } @@ -585,7 +588,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface { $source = $jo->getSource(); - $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $error_array); + // TODO: set the price tier according to location. + $price_tier = $this->pt_manager->getPriceTier($jo->getCoordinates()); + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, $error_array); } // validate diff --git a/src/Service/PriceTierManager.php b/src/Service/PriceTierManager.php new file mode 100644 index 00000000..e4174e78 --- /dev/null +++ b/src/Service/PriceTierManager.php @@ -0,0 +1,58 @@ +em = $em; + } + + public function getItemPrice($pt_id, $item_type_id, $item_id) + { + // find the item price, given the price tier, battery id, and item type (battery) + $db_conn = $this->em->getConnection(); + + $ip_sql = 'SELECT ip.price AS price + FROM item_price ip + WHERE ip.price_tier_id = :pt_id + AND ip.item_type_id = :it_id + AND ip.item_id = :item_id'; + + $ip_stmt = $db_conn->prepare($ip_sql); + $ip_stmt->bindValue('pt_id', $pt_id); + $ip_stmt->bindValue('it_id', $item_type_id); + $ip_stmt->bindValue('item_id', $item_id); + + $ip_result = $ip_stmt->executeQuery(); + + $actual_price = 0; + // go through rows + while ($row = $ip_result->fetchAssociative()) + { + // get the price + $price = $row['price']; + + // actual price + $actual_price = number_format($price / 100, 2, '.', ''); + } + + return $actual_price; + } + + public function getPriceTier(Point $point) + { + // TODO: get location's price tier, given a set of coordinates + // for now, hardcoded for testing purposes + return 1; + } +} diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index ca68e922..375e0353 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -1761,6 +1761,8 @@ $(function() { var table = $("#invoice-table tbody"); var stype = $("#service_type").val(); var cvid = $("#customer-vehicle").val(); + var lng = $("#map_lng").val(); + var lat = $("#map_lat").val(); console.log(JSON.stringify(invoiceItems)); @@ -1772,7 +1774,9 @@ $(function() { 'stype': stype, 'items': invoiceItems, 'promo': promo, - 'cvid': cvid + 'cvid': cvid, + 'coord_lng': lng, + 'coord_lat': lat, } }).done(function(response) { // mark as invoice changed From c5b395d720589b6296c58bc158a80e211d8a3e7d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 24 Jan 2024 04:02:14 -0500 Subject: [PATCH 20/64] Add price tier for battery replacement warranty. #782 --- .../BatteryReplacementWarranty.php | 41 ++++++++++++++++++- src/InvoiceRule/BatterySales.php | 2 - src/Service/InvoiceManager.php | 2 +- src/Service/PriceTierManager.php | 4 +- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/InvoiceRule/BatteryReplacementWarranty.php b/src/InvoiceRule/BatteryReplacementWarranty.php index ee1497b5..8c2dafd4 100644 --- a/src/InvoiceRule/BatteryReplacementWarranty.php +++ b/src/InvoiceRule/BatteryReplacementWarranty.php @@ -11,14 +11,19 @@ use App\Ramcar\TradeInType; use App\Entity\Battery; use App\Entity\ServiceOffering; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class BatteryReplacementWarranty implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -29,6 +34,7 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) @@ -40,7 +46,14 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface { $batt = $entry['battery']; $qty = 1; - $price = $this->getServiceTypeFee(); + + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id); + + if ($pt_price == null) + $price = $this->getServiceTypeFee(); + else + $price = $pt_price; $items[] = [ 'service_type' => $this->getID(), @@ -117,6 +130,30 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = 'battery_replacement_warranty_fee'; + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + + protected function getTitle($battery) { $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' - Service Unit'; diff --git a/src/InvoiceRule/BatterySales.php b/src/InvoiceRule/BatterySales.php index 3ff9a995..645e545b 100644 --- a/src/InvoiceRule/BatterySales.php +++ b/src/InvoiceRule/BatterySales.php @@ -35,8 +35,6 @@ class BatterySales implements InvoiceRuleInterface $stype = $criteria->getServiceType(); $pt = $criteria->getPriceTier(); - error_log('price tier ' . $pt); - $items = []; if ($stype == $this->getID()) { diff --git a/src/Service/InvoiceManager.php b/src/Service/InvoiceManager.php index 93d6bafd..6e37a207 100644 --- a/src/Service/InvoiceManager.php +++ b/src/Service/InvoiceManager.php @@ -46,7 +46,7 @@ class InvoiceManager implements InvoiceGeneratorInterface // 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\BatteryReplacementWarranty($this->em, $this->pt_manager), new InvoiceRule\Jumpstart($this->em), new InvoiceRule\JumpstartWarranty($this->em), new InvoiceRule\PostRecharged($this->em), diff --git a/src/Service/PriceTierManager.php b/src/Service/PriceTierManager.php index e4174e78..c572af76 100644 --- a/src/Service/PriceTierManager.php +++ b/src/Service/PriceTierManager.php @@ -35,7 +35,9 @@ class PriceTierManager $ip_result = $ip_stmt->executeQuery(); - $actual_price = 0; + // results found + $actual_price = null; + // go through rows while ($row = $ip_result->fetchAssociative()) { From bc6364ace52058a4e343c784c07016a55731bcea Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 25 Jan 2024 02:11:05 -0500 Subject: [PATCH 21/64] Add price tier to invoice rules. #782 --- .../BatteryReplacementWarranty.php | 4 + src/InvoiceRule/Fuel.php | 94 +++++++- src/InvoiceRule/Jumpstart.php | 60 ++++- src/InvoiceRule/JumpstartWarranty.php | 47 +++- src/InvoiceRule/Overheat.php | 55 ++++- src/InvoiceRule/PostRecharged.php | 47 +++- src/InvoiceRule/PostReplacement.php | 47 +++- src/InvoiceRule/PriceTier.php | 220 ------------------ src/InvoiceRule/TireRepair.php | 54 ++++- src/Service/InvoiceManager.php | 14 +- 10 files changed, 386 insertions(+), 256 deletions(-) delete mode 100644 src/InvoiceRule/PriceTier.php diff --git a/src/InvoiceRule/BatteryReplacementWarranty.php b/src/InvoiceRule/BatteryReplacementWarranty.php index 8c2dafd4..46ba5e8a 100644 --- a/src/InvoiceRule/BatteryReplacementWarranty.php +++ b/src/InvoiceRule/BatteryReplacementWarranty.php @@ -145,6 +145,10 @@ class BatteryReplacementWarranty implements InvoiceRuleInterface $code = 'battery_replacement_warranty_fee'; $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + // check if service is null. If null, return null + if ($service == null) + return null; + $item_type_id = $item_type->getID(); $item_id = $service->getID(); diff --git a/src/InvoiceRule/Fuel.php b/src/InvoiceRule/Fuel.php index f843e1c0..691a1ba2 100644 --- a/src/InvoiceRule/Fuel.php +++ b/src/InvoiceRule/Fuel.php @@ -11,14 +11,19 @@ use App\Ramcar\ServiceType; use App\Entity\ServiceOffering; use App\Entity\CustomerVehicle; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class Fuel implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -29,6 +34,7 @@ class Fuel implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt_id = $criteria->getPriceTier(); $items = []; @@ -36,7 +42,13 @@ class Fuel implements InvoiceRuleInterface { $cv = $criteria->getCustomerVehicle(); - $fee = $this->getServiceTypeFee($cv); + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id, $cv); + + if ($pt_price == null) + $service_price = $this->getServiceTypeFee($cv); + else + $service_price = $pt_price; $ftype = $cv->getFuelType(); @@ -46,10 +58,10 @@ class Fuel implements InvoiceRuleInterface 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle($ftype), - 'price' => $fee, + 'price' => $service_price, ]; - $qty_fee = bcmul($qty, $fee, 2); + $qty_fee = bcmul($qty, $service_price, 2); $total_price = $qty_fee; switch ($ftype) @@ -57,7 +69,15 @@ class Fuel implements InvoiceRuleInterface case FuelType::GAS: case FuelType::DIESEL: $qty = 1; - $price = $this->getFuelFee($ftype); + + // check if price tier has item price for fuel type + $pt_price = $this->getPriceTierFuelItemPrice($pt_id, $ftype); + + if ($pt_price == null) + $price = $this->getFuelFee($ftype); + else + $price = $pt_price; + $items[] = [ 'service_type' => $this->getID(), 'qty' => $qty, @@ -138,6 +158,70 @@ class Fuel implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + // check if customer vehicle has a motolite battery + // if yes, set the code to the motolite user service fee + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + else + $code = 'fuel_service_fee'; + + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + + protected function getPriceTierFuelItemPrice($pt_id, $fuel_type) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = ''; + if ($fuel_type == FuelType::GAS) + $code = 'fuel_gas_fee'; + if ($fuel_type == FuelType::DIESEL) + $code = 'fuel_diesel_fee'; + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getTitle($fuel_type) { $title = '4L - ' . ucfirst($fuel_type); diff --git a/src/InvoiceRule/Jumpstart.php b/src/InvoiceRule/Jumpstart.php index dce41d99..d2e89b0a 100644 --- a/src/InvoiceRule/Jumpstart.php +++ b/src/InvoiceRule/Jumpstart.php @@ -8,16 +8,21 @@ use App\InvoiceRuleInterface; use App\Entity\ServiceOffering; use App\Entity\CustomerVehicle; +use App\Entity\ItemType; use App\Ramcar\TransactionOrigin; +use App\Service\PriceTierManager; + class Jumpstart implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -29,13 +34,21 @@ class Jumpstart implements InvoiceRuleInterface { $stype = $criteria->getServiceType(); $source = $criteria->getSource(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) { $cv = $criteria->getCustomerVehicle(); - $fee = $this->getServiceTypeFee($source, $cv); + + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id, $source, $cv); + + if ($pt_price == null) + $price = $this->getServiceTypeFee($source, $cv); + else + $price = $pt_price; // add the service fee to items $qty = 1; @@ -43,10 +56,10 @@ class Jumpstart implements InvoiceRuleInterface 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle(), - 'price' => $fee, + 'price' => $price, ]; - $qty_price = bcmul($fee, $qty, 2); + $qty_price = bcmul($price, $qty, 2); $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); } @@ -86,6 +99,45 @@ class Jumpstart implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id, $source, $cv) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + // check the source of JO + // (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user. + // (2) any other source, jumpstart fees are charged whether motolite user or not + if ($source == TransactionOrigin::MOBILE_APP) + { + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + else + $code = 'jumpstart_fee_mobile_app'; + } + else + $code = 'jumpstart_fee'; + + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getServiceTitle() { $title = 'Service - Troubleshooting fee'; diff --git a/src/InvoiceRule/JumpstartWarranty.php b/src/InvoiceRule/JumpstartWarranty.php index 9423da44..4c6ac387 100644 --- a/src/InvoiceRule/JumpstartWarranty.php +++ b/src/InvoiceRule/JumpstartWarranty.php @@ -7,14 +7,19 @@ use Doctrine\ORM\EntityManagerInterface; use App\InvoiceRuleInterface; use App\Entity\ServiceOffering; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class JumpstartWarranty implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -25,12 +30,19 @@ class JumpstartWarranty implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) { - $fee = $this->getServiceTypeFee(); + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id); + + if ($pt_price == null) + $price = $this->getServiceTypeFee(); + else + $price = $pt_price; // add the service fee to items $qty = 1; @@ -38,10 +50,10 @@ class JumpstartWarranty implements InvoiceRuleInterface 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle(), - 'price' => $fee, + 'price' => $price, ]; - $qty_price = bcmul($fee, $qty, 2); + $qty_price = bcmul($price, $qty, 2); $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); } @@ -72,6 +84,33 @@ class JumpstartWarranty implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = 'jumpstart_warranty_fee'; + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getServiceTitle() { $title = 'Service - Troubleshooting fee'; diff --git a/src/InvoiceRule/Overheat.php b/src/InvoiceRule/Overheat.php index 4c06bddb..af0ce182 100644 --- a/src/InvoiceRule/Overheat.php +++ b/src/InvoiceRule/Overheat.php @@ -10,14 +10,19 @@ use App\Ramcar\ServiceType; use App\Entity\ServiceOffering; use App\Entity\CustomerVehicle; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class Overheat implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -29,13 +34,22 @@ class Overheat implements InvoiceRuleInterface { $stype = $criteria->getServiceType(); $has_coolant = $criteria->hasCoolant(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) { $cv = $criteria->getCustomerVehicle(); - $fee = $this->getServiceTypeFee($cv); + + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id, $cv); + + if ($pt_price == null) + $price = $this->getServiceTypeFee($cv); + else + + $price = $pt_price; // add the service fee to items $qty = 1; @@ -43,10 +57,10 @@ class Overheat implements InvoiceRuleInterface 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle(), - 'price' => $fee, + 'price' => $price, ]; - $qty_fee = bcmul($qty, $fee, 2); + $qty_fee = bcmul($qty, $price, 2); $total_price = $qty_fee; if ($has_coolant) @@ -112,6 +126,39 @@ class Overheat implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = 'overheat_fee'; + + // check if customer vehicle has a motolite battery + // if yes, set the code to the motolite user service fee + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getServiceTitle() { $title = 'Service - ' . ServiceType::getName(ServiceType::OVERHEAT_ASSISTANCE); diff --git a/src/InvoiceRule/PostRecharged.php b/src/InvoiceRule/PostRecharged.php index 808f2340..b0b20995 100644 --- a/src/InvoiceRule/PostRecharged.php +++ b/src/InvoiceRule/PostRecharged.php @@ -7,14 +7,19 @@ use Doctrine\ORM\EntityManagerInterface; use App\InvoiceRuleInterface; use App\Entity\ServiceOffering; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class PostRecharged implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -25,22 +30,29 @@ class PostRecharged implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) { - $fee = $this->getServiceTypeFee(); + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id); + + if ($pt_price == null) + $price = $this->getServiceTypeFee(); + else + $price = $pt_price; $qty = 1; $items[] = [ 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle(), - 'price' => $fee, + 'price' => $price, ]; - $qty_price = bcmul($fee, $qty, 2); + $qty_price = bcmul($price, $qty, 2); $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); } @@ -72,6 +84,33 @@ class PostRecharged implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = 'post_recharged_fee'; + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getServiceTitle() { $title = 'Recharge fee'; diff --git a/src/InvoiceRule/PostReplacement.php b/src/InvoiceRule/PostReplacement.php index aba6d9aa..774b61de 100644 --- a/src/InvoiceRule/PostReplacement.php +++ b/src/InvoiceRule/PostReplacement.php @@ -7,14 +7,19 @@ use Doctrine\ORM\EntityManagerInterface; use App\InvoiceRuleInterface; use App\Entity\ServiceOffering; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class PostReplacement implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -25,22 +30,29 @@ class PostReplacement implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) { - $fee = $this->getServiceTypeFee(); + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id); + + if ($pt_price == null) + $price = $this->getServiceTypeFee(); + else + $price = $pt_price; $qty = 1; $items[] = [ 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle(), - 'price' => $fee, + 'price' => $price, ]; - $qty_price = bcmul($fee, $qty, 2); + $qty_price = bcmul($price, $qty, 2); $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); } @@ -71,6 +83,33 @@ class PostReplacement implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = 'post_replacement_fee'; + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getServiceTitle() { $title = 'Battery replacement'; diff --git a/src/InvoiceRule/PriceTier.php b/src/InvoiceRule/PriceTier.php deleted file mode 100644 index 7db80ccc..00000000 --- a/src/InvoiceRule/PriceTier.php +++ /dev/null @@ -1,220 +0,0 @@ -em = $em; - } - - public function getID() - { - return 'price_tier'; - } - - public function compute($criteria, &$total) - { - $pt_id = $criteria->getPriceTier(); - - // get the service type - $service_type = $criteria->getServiceType(); - - // get price tier - $pt = $em->getRepository(PTEntity::class)->find($pt_id); - - // price tier is default - if ($pt == null) - { - // check if service type is battery sales and battery warranty (sometimes they add a battery - // for battery warranty - if (($service_type == ServiceType::BATTERY_REPLACEMENT_NEW) || - ($service_type == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) - { - $items = $this->processBatteryEntries($criteria, $total); - } - else - { - // TODO: process the service fees - $items = $this->processServiceEntries($criteria, $total); - } - } - else - { - // get items in price tier - $pt_items = $pt->getItemPrices(); - - foreach ($pt_items as $pt_item) - { - // make item price hash - $pt_prices[$pt_item->getItemID()] = $pt_item->getPrice(); - } - - // TODO: finish this - } - - 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. - $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($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 processBatteryEntries($criteria, &$total) - { - $items = []; - - // 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) - { - // check if battery purchase or battery replacement - if ($service_type == ServiceType::BATTERY_REPLACEMENT_NEW) - $price = $batt->getSellingPrice(); - else - $price = 0; - - $items[] = [ - 'service_type' => $this->getID(), - 'battery' => $batt, - 'qty' => $qty, - 'title' => $this->getItemTitle($criteria->getServiceType(), $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; - } - - protected function processServiceEntries($criteria, &$total) - { - } - - protected function getItemTitle($service_type, $battery) - { - $title =''; - - // TODO: check for service type - switch ($service_type) { - case (ServiceType::BATTERY_REPLACEMENT_NEW): - $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName(); - break; - case (ServiceType::BATTERY_REPLACEMENT_WARRANTY): - $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' - Service Unit'; - break; - default: - $title = ''; - break; - } - - return $title; - - protected function getServiceTitle($service_type, $fuel_type) - { - $title = ''; - - switch ($service_type) { - case (ServiceType::JUMPSTART_TROUBLESHOOT): - case (ServiceType::JUMPSTART_WARRANTY): - $title = 'Service - Troubleshooting fee'; - break; - case (ServiceType::OVERHEAT_ASSISTANCE): - $title = 'Service - ' . ServiceType::getName(ServiceType::OVERHEAT_ASSISTANCE); - break; - case (ServiceType::POST_RECHARGED): - $title = 'Recharge fee'; - break; - case (ServiceType::POST_REPLACEMENT): - $title = 'Battery replacement'; - break; - case (ServiceType::TIRE_REPAIR): - $title = 'Service - Flat Tire'; - break; - case (ServiceType::EMERGENCY_REFUEL): - $title = '4L - ' . ucfirst($fuel_type); - break; - default: - $title = ''; - } - - return $title; - } - - protected function getServiceCoolantTitle() - { - $title = '4L Coolant'; - - return $title; - } - -} diff --git a/src/InvoiceRule/TireRepair.php b/src/InvoiceRule/TireRepair.php index 755c11bd..96d3c525 100644 --- a/src/InvoiceRule/TireRepair.php +++ b/src/InvoiceRule/TireRepair.php @@ -8,14 +8,19 @@ use App\InvoiceRuleInterface; use App\Entity\ServiceOffering; use App\Entity\CustomerVehicle; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class TireRepair implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -26,13 +31,21 @@ class TireRepair implements InvoiceRuleInterface public function compute($criteria, &$total) { $stype = $criteria->getServiceType(); + $pt_id = $criteria->getPriceTier(); $items = []; if ($stype == $this->getID()) { $cv = $criteria->getCustomerVehicle(); - $fee = $this->getServiceTypeFee($cv); + + // check if price tier has item price + $pt_price = $this->getPriceTierItemPrice($pt_id, $cv); + + if ($pt_price == null) + $price = $this->getServiceTypeFee($cv); + else + $price = $pt_price; // add the service fee to items $qty = 1; @@ -40,10 +53,10 @@ class TireRepair implements InvoiceRuleInterface 'service_type' => $this->getID(), 'qty' => $qty, 'title' => $this->getServiceTitle(), - 'price' => $fee, + 'price' => $price, ]; - $qty_price = bcmul($fee, $qty, 2); + $qty_price = bcmul($price, $qty, 2); $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); } @@ -79,6 +92,39 @@ class TireRepair implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv) + { + // price_tier is default + if ($pt_id == 0) + return null; + + // find the item type for service offering + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'service_offering']); + if ($item_type == null) + return null; + + // find the service offering + $code = 'tire_repair_fee'; + + // check if customer vehicle has a motolite battery + // if yes, set the code to the motolite user service fee + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + // check if service is null. If null, return null + if ($service == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $service->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getServiceTitle() { $title = 'Service - Flat Tire'; diff --git a/src/Service/InvoiceManager.php b/src/Service/InvoiceManager.php index 6e37a207..f46c95ab 100644 --- a/src/Service/InvoiceManager.php +++ b/src/Service/InvoiceManager.php @@ -47,13 +47,13 @@ class InvoiceManager implements InvoiceGeneratorInterface return [ new InvoiceRule\BatterySales($this->em, $this->pt_manager), new InvoiceRule\BatteryReplacementWarranty($this->em, $this->pt_manager), - 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\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(), new InvoiceRule\Tax($this->em), From 57fd7fe5ac1e3c49e7e608d6b5c71556f3a46c2c Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 25 Jan 2024 02:23:47 -0500 Subject: [PATCH 22/64] Fix issues found during testing. #782 --- src/InvoiceRule/Fuel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InvoiceRule/Fuel.php b/src/InvoiceRule/Fuel.php index 691a1ba2..e4f365b9 100644 --- a/src/InvoiceRule/Fuel.php +++ b/src/InvoiceRule/Fuel.php @@ -158,7 +158,7 @@ class Fuel implements InvoiceRuleInterface return null; } - protected function getPriceTierItemPrice($pt_id) + protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv) { // price_tier is default if ($pt_id == 0) From c136b0666ba4f3601e9db88baa0441079cc72f3f Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 25 Jan 2024 03:08:00 -0500 Subject: [PATCH 23/64] Fix issues found during testing. #782 --- src/InvoiceRule/Tax.php | 35 +++++++++++++++++-- src/Service/InvoiceManager.php | 2 +- .../JobOrderHandler/ResqJobOrderHandler.php | 10 ++++-- src/Service/PriceTierManager.php | 25 ++++++++++--- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/InvoiceRule/Tax.php b/src/InvoiceRule/Tax.php index b1e6a600..50834e44 100644 --- a/src/InvoiceRule/Tax.php +++ b/src/InvoiceRule/Tax.php @@ -9,14 +9,19 @@ use App\InvoiceRuleInterface; use App\Ramcar\ServiceType; use App\Entity\ServiceOffering; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; class Tax implements InvoiceRuleInterface { protected $em; + protected $pt_manager; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->em = $em; + $this->pt_manager = $pt_manager; } public function getID() @@ -40,6 +45,7 @@ class Tax implements InvoiceRuleInterface // compute tax per item if service type is battery sales $stype = $criteria->getServiceType(); + $pt = $criteria->getPriceTier(); if ($stype == ServiceType::BATTERY_REPLACEMENT_NEW) { @@ -58,7 +64,13 @@ class Tax implements InvoiceRuleInterface $battery = $entry['battery']; $qty = $entry['qty']; - $price = $battery->getSellingPrice(); + // check if price tier has item price for battery + $pt_price = $this->getPriceTierItemPrice($pt, $battery); + + if ($pt_price == null) + $price = $battery->getSellingPrice(); + else + $price = $pt_price; $vat = $this->getTaxAmount($price, $tax_rate); @@ -96,6 +108,25 @@ class Tax implements InvoiceRuleInterface return null; } + protected function getPriceTierItemPrice($pt_id, $batt) + { + // price tier is default + if ($pt_id == 0) + return null; + + // find the item type battery + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); + if ($item_type == null) + return null; + + $item_type_id = $item_type->getID(); + $item_id = $batt->getID(); + + $price = $this->pt_manager->getItemPrice($pt_id, $item_type_id, $item_id); + + return $price; + } + protected function getTaxAmount($price, $tax_rate) { $vat_ex_price = $this->getTaxExclusivePrice($price, $tax_rate); diff --git a/src/Service/InvoiceManager.php b/src/Service/InvoiceManager.php index f46c95ab..8f655224 100644 --- a/src/Service/InvoiceManager.php +++ b/src/Service/InvoiceManager.php @@ -56,7 +56,7 @@ class InvoiceManager implements InvoiceGeneratorInterface new InvoiceRule\TireRepair($this->em, $this->pt_manager), new InvoiceRule\DiscountType($this->em), new InvoiceRule\TradeIn(), - new InvoiceRule\Tax($this->em), + new InvoiceRule\Tax($this->em, $this->pt_manager), ]; } diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 28e9035d..c7c38100 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -588,7 +588,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface { $source = $jo->getSource(); - // TODO: set the price tier according to location. + // get the price tier according to location. $price_tier = $this->pt_manager->getPriceTier($jo->getCoordinates()); $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, $error_array); } @@ -822,7 +822,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface { $source = $obj->getSource(); - $this->ic->generateInvoiceCriteria($obj, $promo_id, $invoice_items, $source, $error_array); + // get the price tier according to location. + $price_tier = $this->pt_manager->getPriceTier($obj->getCoordinates()); + $this->ic->generateInvoiceCriteria($obj, $promo_id, $invoice_items, $source, $price_tier, $error_array); } // validate @@ -2170,7 +2172,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // NOTE: this is CMB code but for compilation purposes we need to add this $source = $jo->getSource(); - $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $error_array); + // get the price tier according to location. + $price_tier = $this->pt_manager->getPriceTier($jo->getCoordinates()); + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $price_tier, $error_array); } // validate diff --git a/src/Service/PriceTierManager.php b/src/Service/PriceTierManager.php index c572af76..1d8bb60c 100644 --- a/src/Service/PriceTierManager.php +++ b/src/Service/PriceTierManager.php @@ -51,10 +51,27 @@ class PriceTierManager return $actual_price; } - public function getPriceTier(Point $point) + public function getPriceTier(Point $coordinates) { - // TODO: get location's price tier, given a set of coordinates - // for now, hardcoded for testing purposes - return 1; + $price_tier_id = 0; + + $long = $coordinates->getLongitude(); + $lat = $coordinates->getLatitude(); + + // get location's price tier, given a set of coordinates + $query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true'); + $area = $query->setParameter('long', $long) + ->setParameter('lat', $lat) + ->setMaxResults(1) + ->getOneOrNullResult(); + + if ($area != null) + { + $price_tier = $area->getPriceTier(); + if ($price_tier != null) + $price_tier_id = $price_tier->getID(); + } + + return $price_tier_id; } } From 213171f4b7e2bd07a65dd826cd0983d2a04b9ab4 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Fri, 26 Jan 2024 04:44:13 -0500 Subject: [PATCH 24/64] Add price tiering for Resq 2 App and TAPI. #782 --- src/Controller/APIController.php | 5 +++ src/Controller/CAPI/RiderAppController.php | 7 ++- .../CustomerAppAPI/InvoiceController.php | 45 ++++++++++++++++++- .../CustomerAppAPI/JobOrderController.php | 15 ++++++- src/Controller/TAPI/JobOrderController.php | 16 ++++++- src/Service/PriceTierManager.php | 31 +++++++------ 6 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 16a9cdf5..0eb148c5 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -50,6 +50,7 @@ use App\Service\HubFilterLogger; use App\Service\HubFilteringGeoChecker; use App\Service\HashGenerator; use App\Service\JobOrderManager; +use App\Service\PriceTierManager; use App\Entity\MobileSession; use App\Entity\Customer; @@ -2911,6 +2912,10 @@ class APIController extends Controller implements LoggedController // old app doesn't have separate jumpstart $icrit->setSource(TransactionOrigin::CALL); + // set price tier + $pt_id = $this->pt_manager->getPriceTier($jo->getCoordinates()); + $icrit->setPriceTier($pt_id); + // check promo $promo_id = $req->request->get('promo_id'); if (!empty($promo_id)) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index d29f6a04..6cce3745 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -34,6 +34,7 @@ use App\Service\JobOrderHandlerInterface; use App\Service\InvoiceGeneratorInterface; use App\Service\RisingTideGateway; use App\Service\RiderTracker; +use App\Service\PriceTierManager; use App\Ramcar\ServiceType; use App\Ramcar\TradeInType; @@ -1099,7 +1100,7 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Batteries found.', $data); } - public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) + public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager) { // $this->debugRequest($req); @@ -1203,6 +1204,10 @@ class RiderAppController extends ApiController $crit->setHasCoolant($jo->hasCoolant()); $crit->setIsTaxable(); + // set price tier + $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); + $icrit->setPriceTier($pt_id); + if ($promo != null) $crit->addPromo($promo); diff --git a/src/Controller/CustomerAppAPI/InvoiceController.php b/src/Controller/CustomerAppAPI/InvoiceController.php index a5c3a8b8..14f1a87e 100644 --- a/src/Controller/CustomerAppAPI/InvoiceController.php +++ b/src/Controller/CustomerAppAPI/InvoiceController.php @@ -4,18 +4,22 @@ namespace App\Controller\CustomerAppAPI; use Symfony\Component\HttpFoundation\Request; use Catalyst\ApiBundle\Component\Response as ApiResponse; +use CrEOF\Spatial\PHP\Types\Geometry\Point; use App\Service\InvoiceGeneratorInterface; +use App\Service\PriceTierManager; use App\Ramcar\InvoiceCriteria; use App\Ramcar\TradeInType; use App\Ramcar\TransactionOrigin; use App\Entity\CustomerVehicle; use App\Entity\Promo; use App\Entity\Battery; +use App\Entity\Customer; +use App\Entity\CustomerMetadata; class InvoiceController extends ApiController { - public function getEstimate(Request $req, InvoiceGeneratorInterface $ic) + public function getEstimate(Request $req, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager) { // $this->debugRequest($req); @@ -36,6 +40,9 @@ class InvoiceController extends ApiController return new ApiResponse(false, 'No customer information found.'); } + // get customer location from customer_metadata using customer id + $coordinates = $this->getCustomerMetadata($cust); + // make invoice criteria $icrit = new InvoiceCriteria(); $icrit->setServiceType($req->request->get('service_type')); @@ -113,6 +120,18 @@ class InvoiceController extends ApiController // set JO source $icrit->setSource(TransactionOrigin::MOBILE_APP); + // set price tier + $pt_id = 0; + if ($coordinates != null) + { + error_log('coordinates are not null'); + $pt_id = $pt_manager->getPriceTier($coordinates); + } + else + error_log('null?'); + + $icrit->setPriceTier($pt_id); + // send to invoice generator $invoice = $ic->generateInvoice($icrit); @@ -148,4 +167,28 @@ class InvoiceController extends ApiController // response return new ApiResponse(true, '', $data); } + + protected function getCustomerMetadata(Customer $cust) + { + $coordinates = null; + + // check if customer already has existing metadata + $c_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); + if ($c_meta != null) + { + $meta_data = $c_meta->getAllMetaInfo(); + foreach ($meta_data as $m_info) + { + if ((isset($m_info['longitude'])) && (isset($m_info['latitude']))) + { + $lng = $m_info['longitude']; + $lat = $m_info['latitude']; + + $coordinates = new Point($lng, $lat); + } + } + } + + return $coordinates; + } } diff --git a/src/Controller/CustomerAppAPI/JobOrderController.php b/src/Controller/CustomerAppAPI/JobOrderController.php index 143f2a2e..fa5a9a85 100644 --- a/src/Controller/CustomerAppAPI/JobOrderController.php +++ b/src/Controller/CustomerAppAPI/JobOrderController.php @@ -21,6 +21,7 @@ use App\Service\HubDistributor; use App\Service\HubFilterLogger; use App\Service\HubFilteringGeoChecker; use App\Service\JobOrderManager; +use App\Service\PriceTierManager; use App\Ramcar\ServiceType; use App\Ramcar\APIRiderStatus; use App\Ramcar\InvoiceCriteria; @@ -484,7 +485,8 @@ class JobOrderController extends ApiController HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, HubFilteringGeoChecker $hub_geofence, - JobOrderManager $jo_manager + JobOrderManager $jo_manager, + PriceTierManager $pt_manager ) { // validate params $validity = $this->validateRequest($req, [ @@ -698,6 +700,10 @@ class JobOrderController extends ApiController // set JO source $icrit->setSource(TransactionOrigin::MOBILE_APP); + // set price tier + $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); + $icrit->setPriceTier($pt_id); + // send to invoice generator $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); @@ -970,7 +976,8 @@ class JobOrderController extends ApiController HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, HubFilteringGeoChecker $hub_geofence, - JobOrderManager $jo_manager + JobOrderManager $jo_manager, + PriceTierManager $pt_manager ) { // validate params $validity = $this->validateRequest($req, [ @@ -1127,6 +1134,10 @@ class JobOrderController extends ApiController // set JO source $icrit->setSource(TransactionOrigin::MOBILE_APP); + // set price tier + $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); + $icrit->setPriceTier($pt_id); + // send to invoice generator $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); diff --git a/src/Controller/TAPI/JobOrderController.php b/src/Controller/TAPI/JobOrderController.php index f7541b50..ae4d5a61 100644 --- a/src/Controller/TAPI/JobOrderController.php +++ b/src/Controller/TAPI/JobOrderController.php @@ -46,6 +46,7 @@ use App\Service\RiderTracker; use App\Service\PromoLogger; use App\Service\MapTools; use App\Service\JobOrderManager; +use App\Service\PriceTierManager; use App\Entity\JobOrder; use App\Entity\CustomerVehicle; @@ -79,7 +80,8 @@ class JobOrderController extends ApiController FCMSender $fcmclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, - HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em, JobOrderManager $jo_manager) + HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em, JobOrderManager $jo_manager, + PriceTierManager $pt_manager) { $this->denyAccessUnlessGranted('tapi_jo.request', null, 'No access.'); @@ -165,7 +167,17 @@ class JobOrderController extends ApiController // set JO source $icrit->setSource(TransactionOrigin::THIRD_PARTY); - $icrit->addEntry($data['batt'], $data['trade_in_type'], 1); + // set price tier + $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); + $icrit->setPriceTier($pt_id); + + // add the actual battery item first + $icrit->addEntry($data['batt'], null, 1); + + // if we have a trade in, add it as well, assuming trade in battery == battery purchased + if (!empty($data['trade_in_type'])) { + $icrit->addEntry($data['batt'], $data['trade_in_type'], 1); + } // send to invoice generator $invoice = $ic->generateInvoice($icrit); diff --git a/src/Service/PriceTierManager.php b/src/Service/PriceTierManager.php index 1d8bb60c..62d657f4 100644 --- a/src/Service/PriceTierManager.php +++ b/src/Service/PriceTierManager.php @@ -55,21 +55,24 @@ class PriceTierManager { $price_tier_id = 0; - $long = $coordinates->getLongitude(); - $lat = $coordinates->getLatitude(); - - // get location's price tier, given a set of coordinates - $query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true'); - $area = $query->setParameter('long', $long) - ->setParameter('lat', $lat) - ->setMaxResults(1) - ->getOneOrNullResult(); - - if ($area != null) + if ($coordinates != null) { - $price_tier = $area->getPriceTier(); - if ($price_tier != null) - $price_tier_id = $price_tier->getID(); + $long = $coordinates->getLongitude(); + $lat = $coordinates->getLatitude(); + + // get location's price tier, given a set of coordinates + $query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true'); + $area = $query->setParameter('long', $long) + ->setParameter('lat', $lat) + ->setMaxResults(1) + ->getOneOrNullResult(); + + if ($area != null) + { + $price_tier = $area->getPriceTier(); + if ($price_tier != null) + $price_tier_id = $price_tier->getID(); + } } return $price_tier_id; From 20f5bb08e02e16d26085d3a75856b1f0b1af0f22 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Fri, 26 Jan 2024 04:50:54 -0500 Subject: [PATCH 25/64] Add checking for longitude and latitude when calling getEstimate. #782 --- src/Controller/CustomerAppAPI/InvoiceController.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Controller/CustomerAppAPI/InvoiceController.php b/src/Controller/CustomerAppAPI/InvoiceController.php index 14f1a87e..2e9779eb 100644 --- a/src/Controller/CustomerAppAPI/InvoiceController.php +++ b/src/Controller/CustomerAppAPI/InvoiceController.php @@ -41,7 +41,16 @@ class InvoiceController extends ApiController } // get customer location from customer_metadata using customer id - $coordinates = $this->getCustomerMetadata($cust); + $lng = $req->request->get('longitude'); + $lat = $req->request->get('latitude'); + + if ((empty($lng)) || (empty($lat))) + { + // use customer metadata location as basis + $coordinates = $this->getCustomerMetadata($cust); + } + else + $coordinates = new Point($lng, $lat); // make invoice criteria $icrit = new InvoiceCriteria(); From d4eae00902393cd67cad6d5c1deab5d7970fe65d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Sun, 28 Jan 2024 22:21:34 -0500 Subject: [PATCH 26/64] Add sql for item types. #782 --- utils/item_types/item_types.sql | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 utils/item_types/item_types.sql diff --git a/utils/item_types/item_types.sql b/utils/item_types/item_types.sql new file mode 100644 index 00000000..7c5aeeba --- /dev/null +++ b/utils/item_types/item_types.sql @@ -0,0 +1,53 @@ +-- MySQL dump 10.19 Distrib 10.3.39-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: resq +-- ------------------------------------------------------ +-- Server version 10.3.39-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `item_type` +-- + +DROP TABLE IF EXISTS `item_type`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `item_type` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL, + `code` varchar(80) NOT NULL, + PRIMARY KEY (`id`), + KEY `item_type_idx` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `item_type` +-- + +LOCK TABLES `item_type` WRITE; +/*!40000 ALTER TABLE `item_type` DISABLE KEYS */; +INSERT INTO `item_type` VALUES (1,'Battery','battery'),(2,'Service Offering','service_offering'); +/*!40000 ALTER TABLE `item_type` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-01-28 20:59:44 From b123be25cc806bc052dea92d1490802f061f422b Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 30 Jan 2024 03:42:05 +0800 Subject: [PATCH 27/64] Add battery sizes endpoint to rider api #783 --- config/routes/capi_rider.yaml | 6 +++++ src/Controller/CAPI/RiderAppController.php | 27 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/config/routes/capi_rider.yaml b/config/routes/capi_rider.yaml index 6ef2e92a..8fc40654 100644 --- a/config/routes/capi_rider.yaml +++ b/config/routes/capi_rider.yaml @@ -94,3 +94,9 @@ capi_rider_jo_start: path: /rider_api/start controller: App\Controller\CAPI\RiderAppController::startJobOrder methods: [POST] + +# trade-ins +capi_rider_battery_sizes: + path: /rider_api/battery_sizes + controller: App\Controller\CAPI\RiderAppController::getBatterySizes + methods: [GET] \ No newline at end of file diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index d29f6a04..ea3128fd 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -758,6 +758,33 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Rider arrive at hub.', $data); } + public function getBatterySizes(Request $req, EntityManagerInterface $em) + { + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get sizes + $qb = $em->getRepository(BatterySize::class) + ->createQueryBuilder('bs'); + + $sizes = $qb->select('bs.id, bs.name') + ->orderBy('bs.name', 'asc') + ->getQuery() + ->getResult(); + + // response + return new APIResponse(true, '', [ + 'sizes' => $sizes, + ]); + } + public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, TranslatorInterface $translator) { From ae46d64f5b3720ed42c68fc0b55302dfd50be562 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 1 Feb 2024 03:10:16 -0500 Subject: [PATCH 28/64] Add API call to return battery data given a serial. #783 --- config/routes/capi_rider.yaml | 7 +++- src/Controller/CAPI/RiderAppController.php | 40 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/config/routes/capi_rider.yaml b/config/routes/capi_rider.yaml index 8fc40654..67c7fa64 100644 --- a/config/routes/capi_rider.yaml +++ b/config/routes/capi_rider.yaml @@ -99,4 +99,9 @@ capi_rider_jo_start: capi_rider_battery_sizes: path: /rider_api/battery_sizes controller: App\Controller\CAPI\RiderAppController::getBatterySizes - methods: [GET] \ No newline at end of file + methods: [GET] + +capi_rider_battery_info: + path: /rider_api/battery/{serial} + controller: App\Controller\CAPI\RiderAppController::getBatteryInfo + methods: [GET] diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index ea3128fd..513bd3c3 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1126,6 +1126,46 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Batteries found.', $data); } + public function getBatteryInfo(Request $req, $serial, EntityManagerInterface $em) + { + if (empty($serial)) + { + return new APIResponse(false, 'Missing parameter(s): serial'); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // find battery given serial/sap_code and flag_active is true + $batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true, 'sap_code' => $serial]); + + $batt_data = []; + foreach ($batts as $batt) + { + $batt_data[] = [ + 'id' => $batt->getID(), + 'model_id' => $batt->getModel()->getID(), + 'model_name' => $batt->getModel()->getName(), + 'size_id' => $batt->getSize()->getID(), + 'size_name' => $batt->getSize()->getName(), + 'selling_price' => $batt->getSellingPrice(), + ]; + } + + $data = [ + 'batteries' => $batt_data, + ]; + + return new APIResponse(true, 'Battery info found.', $data); + } + public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) { // $this->debugRequest($req); From 86744afde3bffd30f8b873a5469f370bcbc20a7d Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Thu, 1 Feb 2024 04:24:09 -0500 Subject: [PATCH 29/64] Add serial as a parameter when rider fulfills a job order. #783 --- src/Controller/CAPI/RiderAppController.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 513bd3c3..ae1d5ee3 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -804,6 +804,15 @@ class RiderAppController extends ApiController if (!empty($msg)) return new APIResponse(false, $msg); + // need to check if service type is battery sales + // if so, serial is a required parameter + $serial = $req->request->get('serial', ''); + if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) + { + if (empty($serial)) + return new APIResponse(false, 'Missing parameter(s): serial'); + } + // set invoice to paid $jo->getInvoice()->setStatus(InvoiceStatus::PAID); @@ -855,7 +864,6 @@ class RiderAppController extends ApiController // create warranty if($jo_handler->checkIfNewBattery($jo)) { - $serial = null; $warranty_class = $jo->getWarrantyClass(); $first_name = $jo->getCustomer()->getFirstName(); $last_name = $jo->getCustomer()->getLastName(); From c9057b9617368a208cea8d29a3663cac76838d3a Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Fri, 2 Feb 2024 04:50:03 -0500 Subject: [PATCH 30/64] Add API call to update invoice. #783 --- config/routes/capi_rider.yaml | 5 + src/Controller/CAPI/RiderAppController.php | 179 +++++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/config/routes/capi_rider.yaml b/config/routes/capi_rider.yaml index 67c7fa64..9866bb82 100644 --- a/config/routes/capi_rider.yaml +++ b/config/routes/capi_rider.yaml @@ -105,3 +105,8 @@ capi_rider_battery_info: path: /rider_api/battery/{serial} controller: App\Controller\CAPI\RiderAppController::getBatteryInfo methods: [GET] + +capi_rider_update_jo: + path: /rider_api/job_order/update + controller: App\Controller\CAPI\RiderAppController::updateJobOrder + methods: [POST] diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index ae1d5ee3..48d328bd 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1174,6 +1174,44 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Battery info found.', $data); } + public function updateJobOrder(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) + { + $items = json_decode(file_get_contents('php://input'), true); + + // get job order id + if (!isset($items['jo_id'])) + return new APIResponse(false, 'Missing parameter(s): jo_id'); + + $jo_id = $items['jo_id']; + if (empty($jo_id) || $jo_id == null) + return new APIResponse(false, 'Missing parameter(s): jo_id'); + + // validate the trade in items first + $ti_items = $items['trade_in_items']; + $msg = $this->validateTradeInItems($em, $ti_items); + if (!empty($msg)) + return new APIResponse(false, $msg); + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // need to get the existing invoice items using jo id and invoice id + $existing_ii = $this->getInvoiceItems($em, $jo_id); + + // $this->generateUpdatedInvoice($em, $jo_id, $existing_ii, $trade_in_items); + + $data = []; + + return new APIResponse(true, 'Job order updated.', $data); + } + public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) { // $this->debugRequest($req); @@ -1316,6 +1354,147 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Job order service changed.', $data); } + protected function generateUpdatedInvoice(EntityManagerInterface $em, $jo_id, $existing_ii, $trade_in_items) + { + // get the job order since we need info in the JO for the invoice criteria + $jo = $em->getRepository(JobOrder::class)->find($jo_id); + + // get the service type + $stype = $jo->getServiceType(); + + // get the source + $source = $jo->getSource(); + + // get the customer vehicle + $cv = $jo->getCustomerVehicle(); + + // get the promo id from existing invoice item + $promo_id = $existing_ii['promo_id']; + if ($promo_id = null) + $promo = null; + else + $promo = $em->getRepository(Promo::class)->find($promo_id); + + // populate Invoice Criteria + $icrit = new InvoiceCriteria(); + $icrit->setServiceType($stype) + ->setCustomerVehicle($cv) + ->setSource($source) + ->setIsTaxable(); + + // at this point, all information should be valid + // assuming JO information is already valid since this + // is in the system already + // add promo if any to criteria + $icrit->addPromo($promo); + + // get the battery purchased from existing invoice items + // add the batteries ordered to criteria + $ii_items = $existing_ii['invoice_items']; + foreach ($ii_items as $ii_item) + { + $batt_id = $ii_item['batt_id']; + $qty = $ii_item['qty']; + + $battery = $em->getRepository(Battery::class)->find($batt_id); + + $icrit->addEntry($battery, null, $qty); + } + + // add the trade in items to the criteria + foreach ($trade_in_items as $ti_item) + { + // TODO: need to discuss how to store since what we have is battery size + // and we're supposed to store battery + } + + // call generateInvoice + $invoice = $ic->generateInvoice($icrit); + + // remove previous invoice + $old_invoice = $jo->getInvoice(); + $em->remove($old_invoice); + $em->flush(); + + // save new invoice + $jo->setInvoice($invoice); + $em->persist($invoice); + + // log event? + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_EDIT) + ->setJobOrder($jo) + ->setRider($jo->getRider()); + $em->persist($event); + + $em->flush(); + } + + protected function getInvoiceItems(EntityManagerInterface $em, $jo_id) + { + $conn = $em->getConnection(); + + // need to get the ordered battery id and quantity from invoice item + // and the promo from invoice + $query_sql = 'SELECT ii.battery_id AS battery_id, ii.qty AS qty, i.promo_id AS promo_id + FROM invoice_item ii, invoice i + WHERE ii.invoice_id = i.id + AND i.job_order_id = :jo_id + AND ii.battery_id IS NOT NULL'; + + $query_stmt = $conn->prepare($query_sql); + $query_stmt->bindValue('jo_id', $jo_id); + + $results = $query_stmt->executeQuery(); + + $promo_id = null; + $invoice_items = []; + while ($row = $results->fetchAssociative()) + { + $promo_id = $row['promo_id']; + $invoice_items[] = [ + 'batt_id' => $row['battery_id'], + 'qty' => $row['qty'], + 'trade_in' => '' + ]; + } + + $data = [ + 'promo_id' => $promo_id, + 'invoice_items' => $invoice_items + ]; + + return $data; + } + + protected function validateTradeInItems(EntityManagerInterface $em, $ti_items) + { + $msg = ''; + foreach ($ti_items as $ti_item) + { + $bs_id = $ti_item['battery_size_id']; + $ti_type = $ti_item['trade_in_type']; + + // validate the battery size id + $batt_size = $em->getRepository(BatterySize::class)->find($bs_id); + if ($batt_size == null) + { + $msg = 'Invalid battery size for trade in: ' . $bs_id; + return $msg; + } + + // validate the trade in type + if (!TradeInType::validate($ti_type)) + { + $msg = 'Invalid trade in type: ' . $ti_type; + return $msg; + } + } + + return $msg; + } + protected function getCAPIUser($id, EntityManagerInterface $em) { $capi_user = $em->getRepository(APIUser::class)->find($id); From 8ca7292a258a5dc25097d51a9122b7ab82cb66db Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 5 Feb 2024 03:06:06 -0500 Subject: [PATCH 31/64] Add processing of trade in items from rider app. #783 --- src/Controller/CAPI/RiderAppController.php | 17 +++++++++------ src/InvoiceRule/BatterySales.php | 11 ++++++---- src/InvoiceRule/TradeIn.php | 24 ++++++++++++++-------- src/Ramcar/InvoiceCriteria.php | 11 ++++++++++ 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 48d328bd..6dc409d6 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -23,6 +23,7 @@ use App\Entity\BatterySize; use App\Entity\RiderAPISession; use App\Entity\User; use App\Entity\ApiUser as APIUser; +use App\Entity\JobOrder; use App\Service\RedisClientProvider; use App\Service\RiderCache; @@ -1205,10 +1206,9 @@ class RiderAppController extends ApiController // need to get the existing invoice items using jo id and invoice id $existing_ii = $this->getInvoiceItems($em, $jo_id); - // $this->generateUpdatedInvoice($em, $jo_id, $existing_ii, $trade_in_items); + $this->generateUpdatedInvoice($em, $ic, $jo_id, $existing_ii, $ti_items); $data = []; - return new APIResponse(true, 'Job order updated.', $data); } @@ -1354,7 +1354,7 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Job order service changed.', $data); } - protected function generateUpdatedInvoice(EntityManagerInterface $em, $jo_id, $existing_ii, $trade_in_items) + protected function generateUpdatedInvoice(EntityManagerInterface $em, InvoiceGeneratorInterface $ic, $jo_id, $existing_ii, $trade_in_items) { // get the job order since we need info in the JO for the invoice criteria $jo = $em->getRepository(JobOrder::class)->find($jo_id); @@ -1370,7 +1370,7 @@ class RiderAppController extends ApiController // get the promo id from existing invoice item $promo_id = $existing_ii['promo_id']; - if ($promo_id = null) + if ($promo_id == null) $promo = null; else $promo = $em->getRepository(Promo::class)->find($promo_id); @@ -1404,8 +1404,13 @@ class RiderAppController extends ApiController // add the trade in items to the criteria foreach ($trade_in_items as $ti_item) { - // TODO: need to discuss how to store since what we have is battery size - // and we're supposed to store battery + $batt_size_id = $ti_item['battery_size_id']; + $qty = $ti_item['qty']; + $trade_in_type = $ti_item['trade_in_type']; + + $batt_size = $em->getRepository(BatterySize::class)->find($batt_size_id); + + $icrit->addTradeInEntry($batt_size, $trade_in_type, $qty); } // call generateInvoice diff --git a/src/InvoiceRule/BatterySales.php b/src/InvoiceRule/BatterySales.php index 45b060d1..97551cb7 100644 --- a/src/InvoiceRule/BatterySales.php +++ b/src/InvoiceRule/BatterySales.php @@ -36,18 +36,21 @@ class BatterySales implements InvoiceRuleInterface $entries = $criteria->getEntries(); foreach($entries as $entry) { - $batt = $entry['battery']; $qty = $entry['qty']; $trade_in = null; + // check if entry is for trade in if (isset($entry['trade_in'])) $trade_in = $entry['trade_in']; - $size = $batt->getSize(); - + // entry is a battery purchase if ($trade_in == null) { - // battery purchase + // safe to get entry with battery key since CRM and apps + // will set this for a battery purchase and trade_in will + // will not be set + $batt = $entry['battery']; + $price = $batt->getSellingPrice(); $items[] = [ diff --git a/src/InvoiceRule/TradeIn.php b/src/InvoiceRule/TradeIn.php index 8e3c6063..224bf098 100644 --- a/src/InvoiceRule/TradeIn.php +++ b/src/InvoiceRule/TradeIn.php @@ -21,7 +21,6 @@ class TradeIn implements InvoiceRuleInterface $entries = $criteria->getEntries(); foreach($entries as $entry) { - $batt = $entry['battery']; $qty = $entry['qty']; $trade_in_type = null; @@ -30,7 +29,18 @@ class TradeIn implements InvoiceRuleInterface if ($trade_in_type != null) { - $ti_rate = $this->getTradeInRate($batt, $trade_in_type); + // at this point, entry is a trade in + // need to check if battery (coming from CRM) is set + // or battery_size is set (coming from rider app) + if (isset($entry['battery'])) + { + $battery = $entry['battery']; + $batt_size = $battery->getSize(); + } + else + $batt_size = $entry['battery_size']; + + $ti_rate = $this->getTradeInRate($batt_size, $trade_in_type); $qty_ti = bcmul($ti_rate, $qty, 2); @@ -41,7 +51,7 @@ class TradeIn implements InvoiceRuleInterface $items[] = [ 'qty' => $qty, - 'title' => $this->getTitle($batt, $trade_in_type), + 'title' => $this->getTitle($batt_size, $trade_in_type), 'price' => $price, ]; } @@ -60,10 +70,8 @@ class TradeIn implements InvoiceRuleInterface return null; } - protected function getTradeInRate($battery, $trade_in_type) + protected function getTradeInRate($size, $trade_in_type) { - $size = $battery->getSize(); - switch ($trade_in_type) { case TradeInType::MOTOLITE: @@ -77,9 +85,9 @@ class TradeIn implements InvoiceRuleInterface return 0; } - protected function getTitle($battery, $trade_in_type) + protected function getTitle($battery_size, $trade_in_type) { - $title = 'Trade-in ' . TradeInType::getName($trade_in_type) . ' ' . $battery->getSize()->getName() . ' battery'; + $title = 'Trade-in ' . TradeInType::getName($trade_in_type) . ' ' . $battery_size->getName() . ' battery'; return $title; } diff --git a/src/Ramcar/InvoiceCriteria.php b/src/Ramcar/InvoiceCriteria.php index b27395da..8b1ee18b 100644 --- a/src/Ramcar/InvoiceCriteria.php +++ b/src/Ramcar/InvoiceCriteria.php @@ -108,6 +108,17 @@ class InvoiceCriteria $this->entries[] = $entry; } + public function addTradeInEntry($battery_size, $trade_in, $qty) + { + $entry = [ + 'battery_size' => $battery_size, + 'trade_in' => $trade_in, + 'qty' => $qty + ]; + + $this->entries[] = $entry; + } + public function getEntries() { return $this->entries; From 8860796db2eed66465e4a891146e17551f160eca Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Wed, 7 Feb 2024 15:02:47 +0800 Subject: [PATCH 32/64] Add endpoint for rider app trade-in types #783 --- config/routes/capi_rider.yaml | 5 +++++ src/Controller/CAPI/RiderAppController.php | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/config/routes/capi_rider.yaml b/config/routes/capi_rider.yaml index 9866bb82..798da1da 100644 --- a/config/routes/capi_rider.yaml +++ b/config/routes/capi_rider.yaml @@ -101,6 +101,11 @@ capi_rider_battery_sizes: controller: App\Controller\CAPI\RiderAppController::getBatterySizes methods: [GET] +capi_rider_trade_in_types: + path: /rider_api/trade_in_types + controller: App\Controller\CAPI\RiderAppController::getTradeInTypes + methods: [GET] + capi_rider_battery_info: path: /rider_api/battery/{serial} controller: App\Controller\CAPI\RiderAppController::getBatteryInfo diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 6dc409d6..945c4ec8 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -786,6 +786,27 @@ class RiderAppController extends ApiController ]); } + public function getTradeInTypes(Request $req, EntityManagerInterface $em) + { + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get trade-in types + $types = TradeInType::getCollection(); + + // response + return new APIResponse(true, '', [ + 'types' => $types, + ]); + } + public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, TranslatorInterface $translator) { From eebd1d93c473510400f9f336a945a662f064d43a Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Thu, 8 Feb 2024 23:17:32 +0800 Subject: [PATCH 33/64] Fix battery info endpoint to work with warranty serials instead #783 --- src/Controller/CAPI/RiderAppController.php | 35 ++++++++++++---------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 945c4ec8..19a8cd7e 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -24,7 +24,8 @@ use App\Entity\RiderAPISession; use App\Entity\User; use App\Entity\ApiUser as APIUser; use App\Entity\JobOrder; - +use App\Entity\SAPBattery; +use App\Entity\WarrantySerial; use App\Service\RedisClientProvider; use App\Service\RiderCache; use App\Service\MQTTClient; @@ -1174,26 +1175,28 @@ class RiderAppController extends ApiController return new APIResponse(false, 'No rider found.'); // find battery given serial/sap_code and flag_active is true - $batts = $em->getRepository(Battery::class)->findBy(['flag_active' => true, 'sap_code' => $serial]); + $serial = $em->getRepository(WarrantySerial::class)->find($serial); - $batt_data = []; - foreach ($batts as $batt) - { - $batt_data[] = [ - 'id' => $batt->getID(), - 'model_id' => $batt->getModel()->getID(), - 'model_name' => $batt->getModel()->getName(), - 'size_id' => $batt->getSize()->getID(), - 'size_name' => $batt->getSize()->getName(), - 'selling_price' => $batt->getSellingPrice(), - ]; + if (empty($serial)) { + return new APIResponse(false, 'Warranty serial number not found.'); } - $data = [ - 'batteries' => $batt_data, + $sap_battery = $em->getRepository(SAPBattery::class)->find($serial->getSKU()); + + if (empty($sap_battery)) { + return new APIResponse(false, 'No battery info found.'); + } + + $battery = [ + 'id' => $sap_battery->getID(), + 'brand' => $sap_battery->getBrand()->getName(), + 'size' => $sap_battery->getSize()->getName(), + 'container_size' => $sap_battery->getContainerSize()->getName(), ]; - return new APIResponse(true, 'Battery info found.', $data); + return new APIResponse(true, 'Battery info found.', [ + 'battery' => $battery, + ]); } public function updateJobOrder(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) From abf4bbfe222c53bc4d2d2deedbb10082bf270067 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 12 Feb 2024 03:46:26 -0500 Subject: [PATCH 34/64] Add checking for promo. #783 --- src/Controller/CAPI/RiderAppController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 19a8cd7e..c88aa72d 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1410,7 +1410,8 @@ class RiderAppController extends ApiController // assuming JO information is already valid since this // is in the system already // add promo if any to criteria - $icrit->addPromo($promo); + if ($promo != null) + $icrit->addPromo($promo); // get the battery purchased from existing invoice items // add the batteries ordered to criteria From 010bdca4587dc4fa730f7fbe8db9905d11abd4e7 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 12 Feb 2024 06:19:00 -0500 Subject: [PATCH 35/64] Add updating of other fields when updating job order. Fix issue found during testing in invoice rule. #783 --- src/Controller/CAPI/RiderAppController.php | 110 +++++++++++++++++---- src/InvoiceRule/Overheat.php | 2 +- 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index c88aa72d..a980be2f 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1207,15 +1207,81 @@ class RiderAppController extends ApiController if (!isset($items['jo_id'])) return new APIResponse(false, 'Missing parameter(s): jo_id'); + // validate jo_id $jo_id = $items['jo_id']; if (empty($jo_id) || $jo_id == null) return new APIResponse(false, 'Missing parameter(s): jo_id'); - // validate the trade in items first - $ti_items = $items['trade_in_items']; - $msg = $this->validateTradeInItems($em, $ti_items); - if (!empty($msg)) - return new APIResponse(false, $msg); + // get the job order + $jo = $em->getRepository(JobOrder::class)->find($jo_id); + + // check if we have trade in items + $ti_items = []; + if (isset($items['trade_in_items'])) + { + // validate the trade in items first + $ti_items = $items['trade_in_items']; + $msg = $this->validateTradeInItems($em, $ti_items); + if (!empty($msg)) + return new APIResponse(false, $msg); + } + + // get the service type + if (!isset($items['stype_id'])) + return new APIResponse(false, 'Missing parameter(s): stype_id'); + + // validate service type + $stype_id = $items['stype_id']; + if (!ServiceType::validate($stype_id)) + return new APIResponse(false, 'Invalid service type - ' . $stype_id); + + // save service type + $jo->setServiceType($stype_id); + + // validate promo if any. Promo not required + $promo = null; + if (isset($items['promo_id'])) + { + $promo_id = $items['promo_id']; + $promo = $em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) + return new APIResponse(false, 'Invalid promo id - ' . $promo_id); + } + + // get other parameters, if any: has motolite battery, has warranty doc, with coolant, payment method + if (isset($items['flag_motolite_battery'])) + { + // get customer vehicle from jo + $cv = $jo->getCustomerVehicle(); + $has_motolite = $items['flag_motolite_battery']; + if ($has_motolite == 'true') + $cv->setHasMotoliteBattery(true); + else + $cv->setHasMotoliteBattery(false); + + $em->persist($cv); + + } + if (isset($items['flag_warranty_doc'])) + { + // TODO: what do we do? + } + if (isset($items['flag_coolant'])) + { + $has_coolant = $items['flag_coolant']; + if ($has_coolant == 'true') + $jo->setHasCoolant(true); + else + $jo->setHasCoolant(false); + + } + if (isset($items['mode_of_payment'])) + { + $payment_method = $items['payment_method']; + if (!ModeOfPayment::validate($payment_method)) + $payment_method = ModeOfPayment::CASH; + $jo->setModeOfPayment($payemnt_method); + } // get capi user $capi_user = $this->getUser(); @@ -1228,9 +1294,9 @@ class RiderAppController extends ApiController return new APIResponse(false, 'No rider found.'); // need to get the existing invoice items using jo id and invoice id - $existing_ii = $this->getInvoiceItems($em, $jo_id); + $existing_ii = $this->getInvoiceItems($em, $jo); - $this->generateUpdatedInvoice($em, $ic, $jo_id, $existing_ii, $ti_items); + $this->generateUpdatedInvoice($em, $ic, $jo, $existing_ii, $ti_items, $promo); $data = []; return new APIResponse(true, 'Job order updated.', $data); @@ -1378,11 +1444,9 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Job order service changed.', $data); } - protected function generateUpdatedInvoice(EntityManagerInterface $em, InvoiceGeneratorInterface $ic, $jo_id, $existing_ii, $trade_in_items) + protected function generateUpdatedInvoice(EntityManagerInterface $em, InvoiceGeneratorInterface $ic, JobOrder $jo, + $existing_ii, $trade_in_items, $promo) { - // get the job order since we need info in the JO for the invoice criteria - $jo = $em->getRepository(JobOrder::class)->find($jo_id); - // get the service type $stype = $jo->getServiceType(); @@ -1392,18 +1456,27 @@ class RiderAppController extends ApiController // get the customer vehicle $cv = $jo->getCustomerVehicle(); - // get the promo id from existing invoice item - $promo_id = $existing_ii['promo_id']; - if ($promo_id == null) - $promo = null; - else - $promo = $em->getRepository(Promo::class)->find($promo_id); + // get coolant if any + $flag_coolant = $jo->hasCoolant(); + + // check if new promo is null + if ($promo == null) + { + // promo not updated from app so check existing invoice + // get the promo id from existing invoice item + $promo_id = $existing_ii['promo_id']; + if ($promo_id == null) + $promo = null; + else + $promo = $em->getRepository(Promo::class)->find($promo_id); + } // populate Invoice Criteria $icrit = new InvoiceCriteria(); $icrit->setServiceType($stype) ->setCustomerVehicle($cv) ->setSource($source) + ->setHasCoolant($flag_coolant) ->setIsTaxable(); // at this point, all information should be valid @@ -1461,8 +1534,9 @@ class RiderAppController extends ApiController $em->flush(); } - protected function getInvoiceItems(EntityManagerInterface $em, $jo_id) + protected function getInvoiceItems(EntityManagerInterface $em, JobOrder $jo) { + $jo_id = $jo->getID(); $conn = $em->getConnection(); // need to get the ordered battery id and quantity from invoice item diff --git a/src/InvoiceRule/Overheat.php b/src/InvoiceRule/Overheat.php index 4c06bddb..d33c219e 100644 --- a/src/InvoiceRule/Overheat.php +++ b/src/InvoiceRule/Overheat.php @@ -94,7 +94,7 @@ class Overheat implements InvoiceRuleInterface // find the service fee using the code // if we can't find the fee, return 0 - $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy($code); + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); if ($fee == null) return 0; From 4d89e7420f142cdd3323bd3dfd8d570506848067 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 20 Feb 2024 02:09:57 -0500 Subject: [PATCH 36/64] Add checking for location and price tier when getting list of compatible batteries. #780 --- config/routes/tapi.yaml | 2 +- .../CustomerAppAPI/VehicleController.php | 41 +++++++++++++++-- src/Controller/TAPI/BatteryController.php | 44 +++++++++++++++++-- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/config/routes/tapi.yaml b/config/routes/tapi.yaml index afa2f084..5bf1a503 100644 --- a/config/routes/tapi.yaml +++ b/config/routes/tapi.yaml @@ -51,7 +51,7 @@ tapi_vehicle_make_list: tapi_battery_list: path: /tapi/vehicles/{vid}/compatible_batteries controller: App\Controller\TAPI\BatteryController::getCompatibleBatteries - methods: [GET] + methods: [POST] # promos tapi_promo_list: diff --git a/src/Controller/CustomerAppAPI/VehicleController.php b/src/Controller/CustomerAppAPI/VehicleController.php index ba76d7a4..c8fd15e9 100644 --- a/src/Controller/CustomerAppAPI/VehicleController.php +++ b/src/Controller/CustomerAppAPI/VehicleController.php @@ -4,16 +4,19 @@ namespace App\Controller\CustomerAppAPI; use Symfony\Component\HttpFoundation\Request; use Catalyst\ApiBundle\Component\Response as ApiResponse; +use CrEOF\Spatial\PHP\Types\Geometry\Point; use App\Entity\CustomerVehicle; use App\Entity\JobOrder; use App\Entity\VehicleManufacturer; use App\Entity\Vehicle; +use App\Entity\ItemType; use App\Ramcar\JOStatus; use App\Ramcar\ServiceType; use App\Ramcar\TradeInType; use App\Ramcar\InsuranceApplicationStatus; use App\Service\PayMongoConnector; +use App\Service\PriceTierManager; use DateTime; class VehicleController extends ApiController @@ -237,7 +240,7 @@ class VehicleController extends ApiController ]); } - public function getCompatibleBatteries(Request $req, $vid) + public function getCompatibleBatteries(Request $req, $vid, PriceTierManager $pt_manager) { // validate params $validity = $this->validateRequest($req); @@ -252,11 +255,43 @@ class VehicleController extends ApiController return new ApiResponse(false, 'Invalid vehicle.'); } + // get location from request + $lng = $req->query->get('longitude', ''); + $lat = $req->query->get('latitude', ''); + + $batts = $vehicle->getActiveBatteries(); + $pt_id = 0; + if ((!(empty($lng))) && (!(empty($lat)))) + { + // get the price tier + $coordinates = new Point($lng, $lat); + + $pt_id = $pt_manager->getPriceTier($coordinates); + } + // batteries $batt_list = []; - $batts = $vehicle->getActiveBatteries(); foreach ($batts as $batt) { // TODO: Add warranty_tnv to battery information + // check if customer location is in a price tier location + if ($pt_id == 0) + $price = $batt->getSellingPrice(); + else + { + // get item type for battery + $item_type = $this->em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); + if ($item_type == null) + $price = $batt->getSellingPrice(); + else + { + $item_type_id = $item_type->getID(); + $batt_id = $batt->getID(); + + // find the item price given price tier id and battery id + $price = $pt_manager->getItemPrice($pt_id, $item_type_id, $batt_id); + } + } + $batt_list[] = [ 'id' => $batt->getID(), 'mfg_id' => $batt->getManufacturer()->getID(), @@ -265,7 +300,7 @@ class VehicleController extends ApiController 'model_name' => $batt->getModel()->getName(), 'size_id' => $batt->getSize()->getID(), 'size_name' => $batt->getSize()->getName(), - 'price' => $batt->getSellingPrice(), + 'price' => $price, 'wty_private' => $batt->getWarrantyPrivate(), 'wty_commercial' => $batt->getWarrantyCommercial(), 'image_url' => $this->getBatteryImageURL($req, $batt), diff --git a/src/Controller/TAPI/BatteryController.php b/src/Controller/TAPI/BatteryController.php index 0705a3ab..0d38dd5f 100644 --- a/src/Controller/TAPI/BatteryController.php +++ b/src/Controller/TAPI/BatteryController.php @@ -13,6 +13,11 @@ use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Ramcar\APIResult; use App\Entity\Vehicle; +use App\Entity\ItemType; + +use App\Service\PriceTierManager; + +use CrEOF\Spatial\PHP\Types\Geometry\Point; use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; @@ -25,7 +30,7 @@ class BatteryController extends ApiController $this->acl_gen = $acl_gen; } - public function getCompatibleBatteries(Request $req, $vid, EntityManagerInterface $em) + public function getCompatibleBatteries(Request $req, $vid, EntityManagerInterface $em, PriceTierManager $pt_manager) { $this->denyAccessUnlessGranted('tapi_battery_compatible.list', null, 'No access.'); @@ -43,13 +48,44 @@ class BatteryController extends ApiController return new APIResponse(false, $message); } + // get location from request + $lng = $req->request->get('longitude', ''); + $lat = $req->request->get('latitude', ''); + + $batts = $vehicle->getActiveBatteries(); + $pt_id = 0; + if ((!(empty($lng))) && (!(empty($lat)))) + { + // get the price tier + $coordinates = new Point($lng, $lat); + + $pt_id = $pt_manager->getPriceTier($coordinates); + } + // batteries $batt_list = []; - // $batts = $vehicle->getBatteries(); - $batts = $vehicle->getActiveBatteries(); foreach ($batts as $batt) { // TODO: Add warranty_tnv to battery information + // check if customer location is in a price tier location + if ($pt_id == 0) + $price = $batt->getSellingPrice(); + else + { + // get item type for battery + $item_type = $em->getRepository(ItemType::class)->findOneBy(['code' => 'battery']); + if ($item_type == null) + $price = $batt->getSellingPrice(); + else + { + $item_type_id = $item_type->getID(); + $batt_id = $batt->getID(); + + // find the item price given price tier id and battery id + $price = $pt_manager->getItemPrice($pt_id, $item_type_id, $batt_id); + } + } + $batt_list[] = [ 'id' => $batt->getID(), 'mfg_id' => $batt->getManufacturer()->getID(), @@ -58,7 +94,7 @@ class BatteryController extends ApiController 'model_name' => $batt->getModel()->getName(), 'size_id' => $batt->getSize()->getID(), 'size_name' => $batt->getSize()->getName(), - 'price' => $batt->getSellingPrice(), + 'price' => $price, 'wty_private' => $batt->getWarrantyPrivate(), 'wty_commercial' => $batt->getWarrantyCommercial(), 'image_url' => $this->getBatteryImageURL($req, $batt), From 7a5583d840d5bf3c2c7f6782512c8d5fbd21b07c Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Thu, 22 Feb 2024 16:08:36 +0800 Subject: [PATCH 37/64] Include trade in type and container size to battery info rider api call #783 --- src/Controller/CAPI/RiderAppController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index a980be2f..cf0b26ab 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1191,6 +1191,8 @@ class RiderAppController extends ApiController 'id' => $sap_battery->getID(), 'brand' => $sap_battery->getBrand()->getName(), 'size' => $sap_battery->getSize()->getName(), + 'size_id' => $sap_battery->getSize()->getID(), + 'trade_in_type' => TradeInType::MOTOLITE, 'container_size' => $sap_battery->getContainerSize()->getName(), ]; From 270a4cfb1082a041ff8daae760e7e45e173638a0 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Thu, 22 Feb 2024 16:51:26 +0800 Subject: [PATCH 38/64] Add endpoint for insurance premiums banner #791 --- .gitignore | 1 + config/routes/apiv2.yaml | 5 +++++ config/services.yaml | 1 + .../CustomerAppAPI/InsuranceController.php | 14 ++++++++++++++ 4 files changed, 21 insertions(+) diff --git a/.gitignore b/.gitignore index cf3b012e..c99c0c14 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ *.swp /public/warranty_uploads/* +.vscode diff --git a/config/routes/apiv2.yaml b/config/routes/apiv2.yaml index 9f183530..deb3b11b 100644 --- a/config/routes/apiv2.yaml +++ b/config/routes/apiv2.yaml @@ -303,3 +303,8 @@ apiv2_insurance_application_create: path: /apiv2/insurance/application controller: App\Controller\CustomerAppAPI\InsuranceController::createApplication methods: [POST] + +apiv2_insurance_premiums_banner: + path: /apiv2/insurance/premiums_banner + controller: App\Controller\CustomerAppAPI\InsuranceController::getPremiumsBanner + methods: [GET] \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index b19ecabc..4cd646ec 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -15,6 +15,7 @@ parameters: api_version: "%env(API_VERSION)%" android_app_version: "%env(ANDROID_APP_VERSION)%" ios_app_version: "%env(IOS_APP_VERSION)%" + insurance_premiums_banner_url: "%env(INSURANCE_PREMIUMS_BANNER_URL)%" services: # default configuration for services in *this* file diff --git a/src/Controller/CustomerAppAPI/InsuranceController.php b/src/Controller/CustomerAppAPI/InsuranceController.php index a3c5624a..8685060b 100644 --- a/src/Controller/CustomerAppAPI/InsuranceController.php +++ b/src/Controller/CustomerAppAPI/InsuranceController.php @@ -293,6 +293,20 @@ class InsuranceController extends ApiController ]); } + public function getPremiumsBanner(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + return new ApiResponse(true, '', [ + 'url' => $this->getParameter('insurance_premiums_banner_url'), + ]); + } + protected function getLineType($mv_type_id, $vehicle_use_type, $is_public = false) { $line = ''; From 78a43ae85cab59d4e9413df4f1160722892112f4 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 23 Feb 2024 16:53:04 +0800 Subject: [PATCH 39/64] Add insurance body types endpoint #791 --- config/routes/apiv2.yaml | 5 +++++ .../CustomerAppAPI/InsuranceController.php | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/config/routes/apiv2.yaml b/config/routes/apiv2.yaml index deb3b11b..dc603c9f 100644 --- a/config/routes/apiv2.yaml +++ b/config/routes/apiv2.yaml @@ -307,4 +307,9 @@ apiv2_insurance_application_create: apiv2_insurance_premiums_banner: path: /apiv2/insurance/premiums_banner controller: App\Controller\CustomerAppAPI\InsuranceController::getPremiumsBanner + methods: [GET] + +apiv2_insurance_body_types: + path: /apiv2/insurance/body_types + controller: App\Controller\CustomerAppAPI\InsuranceController::getBodyTypes methods: [GET] \ No newline at end of file diff --git a/src/Controller/CustomerAppAPI/InsuranceController.php b/src/Controller/CustomerAppAPI/InsuranceController.php index 8685060b..cc7832eb 100644 --- a/src/Controller/CustomerAppAPI/InsuranceController.php +++ b/src/Controller/CustomerAppAPI/InsuranceController.php @@ -307,6 +307,27 @@ class InsuranceController extends ApiController ]); } + public function getBodyTypes(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // TODO: if this changes often, make an entity and make it manageable on CRM + $body_types = [ + 'SEDAN' => 'Sedan', + 'SUV' => 'SUV', + 'TRUCK' => 'Truck', + ]; + + return new ApiResponse(true, '', [ + 'body_types' => $body_types, + ]); + } + protected function getLineType($mv_type_id, $vehicle_use_type, $is_public = false) { $line = ''; From 4dd8efd95a4f648a2811a08f3b659f5d3e398bb8 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 23 Feb 2024 18:39:21 +0800 Subject: [PATCH 40/64] Fix returned format of body types endpoint #791 --- .../CustomerAppAPI/InsuranceController.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Controller/CustomerAppAPI/InsuranceController.php b/src/Controller/CustomerAppAPI/InsuranceController.php index cc7832eb..9d302549 100644 --- a/src/Controller/CustomerAppAPI/InsuranceController.php +++ b/src/Controller/CustomerAppAPI/InsuranceController.php @@ -318,9 +318,18 @@ class InsuranceController extends ApiController // TODO: if this changes often, make an entity and make it manageable on CRM $body_types = [ - 'SEDAN' => 'Sedan', - 'SUV' => 'SUV', - 'TRUCK' => 'Truck', + [ + 'id' => 'SEDAN', + 'name' => 'Sedan', + ], + [ + 'id' => 'SUV', + 'name' => 'SUV', + ], + [ + 'id' => 'TRUCK', + 'name' => 'Truck', + ] ]; return new ApiResponse(true, '', [ From a4b883b7ea486bc419cd6dabbc31bd735239b62c Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Thu, 7 Mar 2024 23:39:57 +0800 Subject: [PATCH 41/64] Fix rider assign MQTT event for new rider app #783 --- src/Service/JobOrderHandler/ResqJobOrderHandler.php | 1 + src/Service/MQTTClient.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 71f91357..419c27c3 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -2021,6 +2021,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // NOTE: for resq2 app $mclientv2->sendEvent($obj, $payload); + $mclientv2->sendRiderEvent($obj, $payload); $fcmclient->sendJoEvent($obj, "jo_fcm_title_driver_assigned", "jo_fcm_body_driver_assigned"); } diff --git a/src/Service/MQTTClient.php b/src/Service/MQTTClient.php index 00229f5f..f1f3f2b3 100644 --- a/src/Service/MQTTClient.php +++ b/src/Service/MQTTClient.php @@ -89,6 +89,6 @@ class MQTTClient */ // NOTE: this is for the new rider app - $this->publish('rider/' . $rider->getID(), json_encode($payload)); + $this->publish('rider/' . $rider->getID() . '/delivery', json_encode($payload)); } } From d603934d933ee755a802770f0c53d22f2332e57b Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Thu, 7 Mar 2024 23:41:21 +0800 Subject: [PATCH 42/64] Update the correct MQTT client with latest changes #783 --- src/Service/MQTTClient.php | 2 +- src/Service/MQTTClientApiv2.php | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Service/MQTTClient.php b/src/Service/MQTTClient.php index f1f3f2b3..00229f5f 100644 --- a/src/Service/MQTTClient.php +++ b/src/Service/MQTTClient.php @@ -89,6 +89,6 @@ class MQTTClient */ // NOTE: this is for the new rider app - $this->publish('rider/' . $rider->getID() . '/delivery', json_encode($payload)); + $this->publish('rider/' . $rider->getID(), json_encode($payload)); } } diff --git a/src/Service/MQTTClientApiv2.php b/src/Service/MQTTClientApiv2.php index 42b85648..f819be53 100644 --- a/src/Service/MQTTClientApiv2.php +++ b/src/Service/MQTTClientApiv2.php @@ -68,4 +68,31 @@ class MQTTClientApiv2 // error_log('sent to ' . $channel); } } + + public function sendRiderEvent(JobOrder $job_order, $payload) + { + // check if a rider is available + $rider = $job_order->getRider(); + if ($rider == null) + return; + + /* + // NOTE: this is for the old rider app + // check if rider has sessions + $sessions = $rider->getSessions(); + if (count($sessions) == 0) + return; + + // send to every rider session + foreach ($sessions as $sess) + { + $sess_id = $sess->getID(); + $channel = self::RIDER_PREFIX . $sess_id; + $this->publish($channel, json_encode($payload)); + } + */ + + // NOTE: this is for the new rider app + $this->publish('rider/' . $rider->getID() . '/delivery', json_encode($payload)); + } } From 801a274e8c167d295c94e5fa98e632b37f4ab7c2 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Mon, 11 Mar 2024 23:45:05 +0800 Subject: [PATCH 43/64] Disable serial number requirement on rider api to allow old rider app to work for now #783 --- src/Controller/CAPI/RiderAppController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index f5c2a558..3bd5e4e8 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -834,7 +834,8 @@ class RiderAppController extends ApiController if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) { if (empty($serial)) - return new APIResponse(false, 'Missing parameter(s): serial'); + // TODO: serial is required but disabling for now so old rider app works + //return new APIResponse(false, 'Missing parameter(s): serial'); } // set invoice to paid From 175ac927658e3be3dc61111649a319fd5da4f40f Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Mon, 11 Mar 2024 23:50:44 +0800 Subject: [PATCH 44/64] Fix syntax error on rider api controller #783 --- src/Controller/CAPI/RiderAppController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 3bd5e4e8..e23d2350 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -833,9 +833,10 @@ class RiderAppController extends ApiController $serial = $req->request->get('serial', ''); if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) { + /* if (empty($serial)) - // TODO: serial is required but disabling for now so old rider app works - //return new APIResponse(false, 'Missing parameter(s): serial'); + return new APIResponse(false, 'Missing parameter(s): serial'); + */ } // set invoice to paid From 9f4c16b1492f82dc35ae6d811ec68800b68cecbc Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 12 Mar 2024 16:34:21 +0800 Subject: [PATCH 45/64] Add debug code for insurance api #783 --- src/Service/InsuranceConnector.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Service/InsuranceConnector.php b/src/Service/InsuranceConnector.php index b56415c0..0b732f65 100644 --- a/src/Service/InsuranceConnector.php +++ b/src/Service/InsuranceConnector.php @@ -111,6 +111,8 @@ class InsuranceConnector error_log("Insurance API Error: " . $error['message']); error_log(Psr7\Message::toString($e->getRequest())); error_log($e->getResponse()->getBody()->getContents()); + error_log("Insurance Creds: " . $this->username . ", " . $this->password); + error_log("Insurance Hash: " . $this->generateHash()); if ($e->hasResponse()) { $error['response'] = Psr7\Message::toString($e->getResponse()); From 47dcd92474c55b8eb9ce2b737b9ebfbd71985cd9 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Wed, 13 Mar 2024 07:09:56 +0800 Subject: [PATCH 46/64] Refactor insurance, paymongo connector logging logic #783 --- src/Controller/InsuranceController.php | 18 +++++---------- src/Controller/PayMongoController.php | 17 +++++--------- src/Service/InsuranceConnector.php | 32 ++++++++++++++++++++++---- src/Service/PayMongoConnector.php | 32 +++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/Controller/InsuranceController.php b/src/Controller/InsuranceController.php index 97744d27..cf3f9967 100644 --- a/src/Controller/InsuranceController.php +++ b/src/Controller/InsuranceController.php @@ -4,6 +4,7 @@ namespace App\Controller; use App\Ramcar\InsuranceApplicationStatus; use App\Service\FCMSender; +use App\Service\InsuranceConnector; use App\Entity\InsuranceApplication; use Doctrine\ORM\EntityManagerInterface; @@ -15,11 +16,13 @@ use DateTime; class InsuranceController extends Controller { + protected $ic; protected $em; protected $fcmclient; - public function __construct(EntityManagerInterface $em, FCMSender $fcmclient) + public function __construct(InsuranceConnector $ic, EntityManagerInterface $em, FCMSender $fcmclient) { + $this->ic = $ic; $this->em = $em; $this->fcmclient = $fcmclient; } @@ -28,17 +31,8 @@ class InsuranceController extends Controller { $payload = $req->request->all(); - // DEBUG - @file_put_contents(__DIR__ . '/../../var/log/insurance.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND); - error_log(print_r($payload, true)); - - /* - return $this->json([ - 'success' => true, - ]); - */ - - // END DEBUG + // log this callback + $this->ic->log('CALLBACK', "[]", json_encode($payload), 'callback'); // if no transaction code given, silently fail if (empty($payload['transaction_code'])) { diff --git a/src/Controller/PayMongoController.php b/src/Controller/PayMongoController.php index 02f75928..21cd864c 100644 --- a/src/Controller/PayMongoController.php +++ b/src/Controller/PayMongoController.php @@ -4,6 +4,7 @@ namespace App\Controller; use App\Entity\GatewayTransaction; use App\Ramcar\TransactionStatus; +use App\Service\PayMongoConnector; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; @@ -12,10 +13,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; class PayMongoController extends Controller { + protected $pm; protected $em; - public function __construct(EntityManagerInterface $em) + public function __construct(PayMongoConnector $pm, EntityManagerInterface $em) { + $this->pm = $pm; $this->em = $em; } @@ -23,16 +26,8 @@ class PayMongoController extends Controller { $payload = json_decode($req->getContent(), true); - // DEBUG - @file_put_contents(__DIR__ . '/../../var/log/paymongo.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND); - - /* - return $this->json([ - 'success' => true, - ]); - */ - - // END DEBUG + // log this callback + $this->pm->log('CALLBACK', "[]", $req->getContent(), 'callback'); // if no event type given, silently fail if (empty($payload['data'])) { diff --git a/src/Service/InsuranceConnector.php b/src/Service/InsuranceConnector.php index 0b732f65..9ad10847 100644 --- a/src/Service/InsuranceConnector.php +++ b/src/Service/InsuranceConnector.php @@ -91,7 +91,7 @@ class InsuranceConnector return base64_encode($this->username . ":" . $this->password); } - protected function doRequest($url, $method, $body = []) + protected function doRequest($url, $method, $request_body = []) { $client = new Client(); $headers = [ @@ -102,7 +102,7 @@ class InsuranceConnector try { $response = $client->request($method, $this->base_url . '/' . $url, [ - 'json' => $body, + 'json' => $request_body, 'headers' => $headers, ]); } catch (RequestException $e) { @@ -114,6 +114,9 @@ class InsuranceConnector error_log("Insurance Creds: " . $this->username . ", " . $this->password); error_log("Insurance Hash: " . $this->generateHash()); + // log this error + $this->log($url, Psr7\Message::toString($e->getRequest()), Psr7\Message::toString($e->getResponse()), 'error'); + if ($e->hasResponse()) { $error['response'] = Psr7\Message::toString($e->getResponse()); } @@ -124,11 +127,32 @@ class InsuranceConnector ]; } - error_log(print_r(json_decode($response->getBody(), true), true)); + $result_body = $response->getBody(); + + // log response + $this->log($url, json_encode($request_body), $result_body); return [ 'success' => true, - 'response' => json_decode($response->getBody(), true) + 'response' => json_decode($result_body, true), ]; } + + // TODO: make this more elegant + public function log($title, $request_body = "[]", $result_body = "[]", $type = 'api') + { + $filename = '/../../var/log/insurance_' . $type . '.log'; + $date = date("Y-m-d H:i:s"); + + // build log entry + $entry = implode("\r\n", [ + $date, + $title, + "REQUEST:\r\n" . $request_body, + "RESPONSE:\r\n" . $result_body, + "\r\n----------------------------------------\r\n\r\n", + ]); + + @file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND); + } } diff --git a/src/Service/PayMongoConnector.php b/src/Service/PayMongoConnector.php index ad2750a2..c34a94e7 100644 --- a/src/Service/PayMongoConnector.php +++ b/src/Service/PayMongoConnector.php @@ -79,7 +79,7 @@ class PayMongoConnector return base64_encode($this->secret_key); } - protected function doRequest($url, $method, $body = []) + protected function doRequest($url, $method, $request_body = []) { $client = new Client(); $headers = [ @@ -90,14 +90,14 @@ class PayMongoConnector try { $response = $client->request($method, $this->base_url . '/' . $url, [ - 'json' => $body, + 'json' => $request_body, 'headers' => $headers, ]); } catch (RequestException $e) { $error = ['message' => $e->getMessage()]; ob_start(); - var_dump($body); + //var_dump($request_body); $varres = ob_get_clean(); error_log($varres); @@ -107,6 +107,9 @@ class PayMongoConnector error_log("PayMongo API Error: " . $error['message']); error_log(Psr7\Message::toString($e->getRequest())); + // log this error + $this->log($url, Psr7\Message::toString($e->getRequest()), Psr7\Message::toString($e->getResponse()), 'error'); + if ($e->hasResponse()) { $error['response'] = Psr7\Message::toString($e->getResponse()); } @@ -117,9 +120,32 @@ class PayMongoConnector ]; } + $result_body = $response->getBody(); + + // log response + $this->log($url, json_encode($request_body), $result_body); + return [ 'success' => true, 'response' => json_decode($response->getBody(), true) ]; } + + // TODO: make this more elegant + public function log($title, $request_body = "[]", $result_body = "[]", $type = 'api') + { + $filename = '/../../var/log/paymongo_' . $type . '.log'; + $date = date("Y-m-d H:i:s"); + + // build log entry + $entry = implode("\r\n", [ + $date, + $title, + "REQUEST:\r\n" . $request_body, + "RESPONSE:\r\n" . $result_body, + "\r\n----------------------------------------\r\n\r\n", + ]); + + @file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND); + } } From 69218aecf4a65e6a07c9c5cfcb4e8365fc4016e9 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Wed, 13 Mar 2024 14:33:29 +0800 Subject: [PATCH 47/64] Update insurance application paid call to use external transaction id #783 --- src/EntityListener/GatewayTransactionListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EntityListener/GatewayTransactionListener.php b/src/EntityListener/GatewayTransactionListener.php index 745fc5f9..c9007aaf 100644 --- a/src/EntityListener/GatewayTransactionListener.php +++ b/src/EntityListener/GatewayTransactionListener.php @@ -66,7 +66,7 @@ class GatewayTransactionListener } // flag on api as paid - $result = $this->ic->tagApplicationPaid($obj->getID()); + $result = $this->ic->tagApplicationPaid($obj->getExtTransactionId()); if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') { error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']); } From a59aa0f66deaf1510173b6028cf0824f7e89a49e Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Thu, 14 Mar 2024 13:40:37 +0800 Subject: [PATCH 48/64] Make insurance body types a namevalue class #783 --- .../CustomerAppAPI/InsuranceController.php | 26 ++++++++----------- src/Ramcar/InsuranceBodyType.php | 18 +++++++++++++ 2 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 src/Ramcar/InsuranceBodyType.php diff --git a/src/Controller/CustomerAppAPI/InsuranceController.php b/src/Controller/CustomerAppAPI/InsuranceController.php index 9d302549..271ebdb8 100644 --- a/src/Controller/CustomerAppAPI/InsuranceController.php +++ b/src/Controller/CustomerAppAPI/InsuranceController.php @@ -18,6 +18,7 @@ use App\Ramcar\InsuranceApplicationStatus; use App\Ramcar\InsuranceMVType; use App\Ramcar\InsuranceClientType; use App\Ramcar\TransactionStatus; +use App\Ramcar\InsuranceBodyType; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use DateTime; @@ -316,21 +317,16 @@ class InsuranceController extends ApiController return new ApiResponse(false, $validity['error']); } - // TODO: if this changes often, make an entity and make it manageable on CRM - $body_types = [ - [ - 'id' => 'SEDAN', - 'name' => 'Sedan', - ], - [ - 'id' => 'SUV', - 'name' => 'SUV', - ], - [ - 'id' => 'TRUCK', - 'name' => 'Truck', - ] - ]; + $bt_collection = InsuranceBodyType::getCollection(); + $body_types = []; + + // NOTE: formatting it this way to match how insurance third party API returns their own stuff, so it's all handled one way on the app + foreach ($bt_collection as $bt_key => $bt_name) { + $body_types[] = [ + 'id' => $bt_key, + 'name' => $bt_name, + ]; + } return new ApiResponse(true, '', [ 'body_types' => $body_types, diff --git a/src/Ramcar/InsuranceBodyType.php b/src/Ramcar/InsuranceBodyType.php new file mode 100644 index 00000000..2abffcdc --- /dev/null +++ b/src/Ramcar/InsuranceBodyType.php @@ -0,0 +1,18 @@ + 'Sedan', + 'SUV' => 'SUV', + 'TRUCK' => 'Truck', + 'MOTORCYCLE' => 'Motorcycle', + ]; +} From bff89a68172dddb101750354495b60a81d4bdc65 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 15 Mar 2024 16:24:09 +0800 Subject: [PATCH 49/64] Add regional pricing support to job order updates on rider app #783 --- src/Controller/CAPI/RiderAppController.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index e23d2350..ad99e56c 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1204,7 +1204,7 @@ class RiderAppController extends ApiController ]); } - public function updateJobOrder(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) + public function updateJobOrder(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic, PriceTierManager $pt_manager) { $items = json_decode(file_get_contents('php://input'), true); @@ -1301,7 +1301,7 @@ class RiderAppController extends ApiController // need to get the existing invoice items using jo id and invoice id $existing_ii = $this->getInvoiceItems($em, $jo); - $this->generateUpdatedInvoice($em, $ic, $jo, $existing_ii, $ti_items, $promo); + $this->generateUpdatedInvoice($em, $ic, $jo, $existing_ii, $ti_items, $promo, $pt_manager); $data = []; return new APIResponse(true, 'Job order updated.', $data); @@ -1453,8 +1453,7 @@ class RiderAppController extends ApiController return new APIResponse(true, 'Job order service changed.', $data); } - protected function generateUpdatedInvoice(EntityManagerInterface $em, InvoiceGeneratorInterface $ic, JobOrder $jo, - $existing_ii, $trade_in_items, $promo) + protected function generateUpdatedInvoice(EntityManagerInterface $em, InvoiceGeneratorInterface $ic, JobOrder $jo, $existing_ii, $trade_in_items, $promo, PriceTierManager $pt_manager) { // get the service type $stype = $jo->getServiceType(); @@ -1488,6 +1487,10 @@ class RiderAppController extends ApiController ->setHasCoolant($flag_coolant) ->setIsTaxable(); + // set price tier + $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); + $icrit->setPriceTier($pt_id); + // at this point, all information should be valid // assuming JO information is already valid since this // is in the system already From 9152370300fd1fe4af02dd625cce35c05851f5a2 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 19 Mar 2024 15:22:34 +0800 Subject: [PATCH 50/64] Add checks to prevent JO progression if cancelled #793 --- src/Controller/CAPI/RiderAppController.php | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index ad99e56c..487133a6 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -411,6 +411,9 @@ class RiderAppController extends ApiController if (!empty($msg)) return new APIResponse(false, $msg); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // TODO: refactor this into a jo handler class, so we don't have to repeat for control center // set jo status to in transit @@ -461,6 +464,9 @@ class RiderAppController extends ApiController // TODO: this is a workaround for requeue, because rider app gets stuck in accept / decline screen return new APIResponse(true, $msg); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // requeue it, instead of cancelling it $jo->requeue(); @@ -519,6 +525,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB); @@ -559,6 +568,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_PRE_JO); @@ -599,6 +611,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_PRE_JO); @@ -639,6 +654,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_START); @@ -680,6 +698,9 @@ class RiderAppController extends ApiController // set jo status to in progress $jo->setStatus(JOStatus::IN_PROGRESS); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE); @@ -738,6 +759,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB); @@ -828,6 +852,9 @@ class RiderAppController extends ApiController if (!empty($msg)) return new APIResponse(false, $msg); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // need to check if service type is battery sales // if so, serial is a required parameter $serial = $req->request->get('serial', ''); @@ -973,6 +1000,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_POST_JO); @@ -1014,6 +1044,9 @@ class RiderAppController extends ApiController // get rider's current job order $jo = $rider->getCurrentJobOrder(); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_POST_JO); @@ -1217,9 +1250,22 @@ class RiderAppController extends ApiController if (empty($jo_id) || $jo_id == null) return new APIResponse(false, 'Missing parameter(s): jo_id'); + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + // get the job order $jo = $em->getRepository(JobOrder::class)->find($jo_id); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // check if we have trade in items $ti_items = []; if (isset($items['trade_in_items'])) @@ -1328,6 +1374,9 @@ class RiderAppController extends ApiController if (!empty($msg)) return new APIResponse(false, $msg); + // check if JO can be modified first + $this->allowJOProgress($jo, $rider); + // check service type $stype_id = $req->request->get('stype_id'); if (!ServiceType::validate($stype_id)) @@ -1690,6 +1739,24 @@ class RiderAppController extends ApiController return $msg; } + protected function allowJOProgress($rider, JobOrder $jo) + { + // TODO: add more statuses to block if needed, hence. this is a failsafe in case MQTT is not working. + switch ($jo->getStatus()) + { + case JOStatus::CANCELLED: + // if this is the rider's current JO, set to null + if ($rider->getCurrentJobOrder() === $jo) { + $rider->setCurrentJobOrder(); + } + + return new APIResponse(false, 'Job order can no longer be modified.'); + break; + default: + return true; + } + } + protected function debugRequest(Request $req) { $all = $req->request->all(); From ca513355fe74970e377e02de9c058e3560a0a869 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 19 Mar 2024 15:25:32 +0800 Subject: [PATCH 51/64] Switch parameters of allowJOProgress to fix error #793 --- src/Controller/CAPI/RiderAppController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 487133a6..e54ed55c 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -1739,7 +1739,7 @@ class RiderAppController extends ApiController return $msg; } - protected function allowJOProgress($rider, JobOrder $jo) + protected function allowJOProgress(JobOrder $jo, $rider) { // TODO: add more statuses to block if needed, hence. this is a failsafe in case MQTT is not working. switch ($jo->getStatus()) From 1a6af003996d0344dd4d6d22bb688a089ca33cb1 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 19 Mar 2024 16:35:51 +0800 Subject: [PATCH 52/64] Rename JO progression check method #793 --- src/Controller/CAPI/RiderAppController.php | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index e54ed55c..0141617f 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -412,7 +412,7 @@ class RiderAppController extends ApiController return new APIResponse(false, $msg); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // TODO: refactor this into a jo handler class, so we don't have to repeat for control center @@ -465,7 +465,7 @@ class RiderAppController extends ApiController return new APIResponse(true, $msg); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // requeue it, instead of cancelling it $jo->requeue(); @@ -526,7 +526,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB); @@ -569,7 +569,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_PRE_JO); @@ -612,7 +612,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_PRE_JO); @@ -655,7 +655,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_START); @@ -699,7 +699,7 @@ class RiderAppController extends ApiController $jo->setStatus(JOStatus::IN_PROGRESS); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE); @@ -760,7 +760,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB); @@ -853,7 +853,7 @@ class RiderAppController extends ApiController return new APIResponse(false, $msg); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // need to check if service type is battery sales // if so, serial is a required parameter @@ -1001,7 +1001,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_POST_JO); @@ -1045,7 +1045,7 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_POST_JO); @@ -1264,7 +1264,7 @@ class RiderAppController extends ApiController $jo = $em->getRepository(JobOrder::class)->find($jo_id); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // check if we have trade in items $ti_items = []; @@ -1375,7 +1375,7 @@ class RiderAppController extends ApiController return new APIResponse(false, $msg); // check if JO can be modified first - $this->allowJOProgress($jo, $rider); + $this->checkJOProgressionAllowed($jo, $rider); // check service type $stype_id = $req->request->get('stype_id'); @@ -1739,7 +1739,7 @@ class RiderAppController extends ApiController return $msg; } - protected function allowJOProgress(JobOrder $jo, $rider) + protected function checkJOProgressionAllowed(JobOrder $jo, $rider) { // TODO: add more statuses to block if needed, hence. this is a failsafe in case MQTT is not working. switch ($jo->getStatus()) From c4e03f861de0fffecad5ed0f3c2df3a2fa8b2611 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 19 Mar 2024 17:33:13 +0800 Subject: [PATCH 53/64] Exclude cancelled JOs on rider app API #783 --- src/Controller/CAPI/RiderAppController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 0141617f..694ffdd0 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -289,8 +289,9 @@ class RiderAppController extends ApiController // do we have a job order? // $jo = $rider->getActiveJobOrder(); + // NOTE: we do not include job orders that have been cancelled $jo = $rider->getCurrentJobOrder(); - if ($jo == null) + if ($jo == null || $jo->getStatus() == JOStatus::CANCELLED) { $data = [ 'job_order' => null From 07b459e7a3810d6ab3e5f02433a3bc5104ca1669 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Tue, 19 Mar 2024 19:16:34 +0800 Subject: [PATCH 54/64] Add pycache and test insurance banner to gitignore #783 --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c99c0c14..2e46e65c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ *.swp /public/warranty_uploads/* .vscode +*__pycache__ +/public/assets/images/insurance-premiums.png \ No newline at end of file From bf0d1f664bfa2435abdd34efa5028d5740ef0e1e Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Fri, 22 Mar 2024 05:35:06 -0400 Subject: [PATCH 55/64] Add CAPI calls for hub list and job order details. #794 --- config/packages/catalyst_auth.yaml | 10 ++ config/routes/capi.yaml | 13 ++ src/Controller/CAPI/HubController.php | 53 ++++++++ src/Controller/CAPI/JobOrderController.php | 139 +++++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 src/Controller/CAPI/HubController.php create mode 100644 src/Controller/CAPI/JobOrderController.php diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index 9707fc93..b02b1fc9 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -751,6 +751,16 @@ catalyst_auth: acls: - id: warrantyserial.upload label: Upload + - id: hub + label: Hub Access + acls: + - id: hub.list + label: List + - id: joborder + label: Job Order Access + acls: + - id: joborder.find + label: Find Job Order - id: tapi_vmanufacturer label: Third Party Vehicle Manufacturer Access diff --git a/config/routes/capi.yaml b/config/routes/capi.yaml index 44f57c99..fe104b37 100644 --- a/config/routes/capi.yaml +++ b/config/routes/capi.yaml @@ -194,3 +194,16 @@ capi_warranty_serial_upload: path: /capi/warranty_serial/upload controller: App\Controller\CAPI\WarrantySerialController::uploadWarrantySerialFile methods: [POST] + +# pullout form system +# hub +capi_hub_list: + path: /capi/hubs + controller: App\Controller\CAPI\HubController::getAll + methods: [GET] + +# job order details +capi_job_order: + path: /capi/job_order/{id} + controller: App\Controller\CAPI\JobOrderController::getJobOrder + methods: [GET] diff --git a/src/Controller/CAPI/HubController.php b/src/Controller/CAPI/HubController.php new file mode 100644 index 00000000..8634d05c --- /dev/null +++ b/src/Controller/CAPI/HubController.php @@ -0,0 +1,53 @@ +acl_gen = $acl_gen; + } + + public function getAll(EntityManagerInterface $em) + { + // get all hub data order by name + $this->denyAccessUnlessGranted('hub.list', null, 'No access.'); + + $results = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); + + $hubs = []; + foreach($results as $res) + { + $hub_id = $res->getId(); + $hub_name = $res->getName(); + $hub_address = $res->getAddress(); + $hub_branch_code = $res->getBranchCode(); + + $hubs[$hub_id] = [ + 'id' => $hub_id, + 'name' => $hub_name, + 'address' => $hub_address, + 'branch_code' => $hub_branch_code, + ]; + } + + $data = [ + 'hubs' => $hubs, + ]; + return new APIResponse(true, 'Hubs loaded.', $data); + } +} diff --git a/src/Controller/CAPI/JobOrderController.php b/src/Controller/CAPI/JobOrderController.php new file mode 100644 index 00000000..e340ae95 --- /dev/null +++ b/src/Controller/CAPI/JobOrderController.php @@ -0,0 +1,139 @@ +acl_gen = $acl_gen; + } + + public function getJobOrder($id, EntityManagerInterface $em) + { + $this->denyAccessUnlessGranted('joborder.find', null, 'No access.'); + + $jo = $em->getRepository(JobOrder::class)->find($id); + + if ($jo == null) + return new APIResponse(false, 'No job order found with that number.', null, 404); + + $data = $this->generateJobOrderData($jo, $em); + + return new APIResponse(true, 'Job order found.', $data); + } + + protected function generateJobOrderData($jo, EntityManagerInterface $em) + { + // customer vehicle + $cv = $jo->getCustomerVehicle(); + + // customer information + $customer = $jo->getCustomer(); + + // hub + $hub_name = ''; + $hub = $jo->getHub(); + if ($hub != null) + $hub_name = $hub->getName(); + + // check if JO is fulfilled, if not, we leave date_purchase blank + $date_purchase = ''; + $serial = ''; + $status = $jo->getStatus(); + if ($status == JOStatus::FULFILLED) + { + if ($jo->getDateFulfill() != null) + $date_purchase = $jo->getDateFulfill()->format('M d, Y H:i'); + + // find warranty to get the serial using plate number + $serial = $this->getSerialFromWarranty($cv->getPlateNumber(), $em); + } + + $jo_data = [ + 'id' => $jo->getID(), + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'mobile_number' => $customer->getPhoneMobile(), + 'email' => $customer->getEmail(), + 'plate_number' => $cv->getPlateNumber(), + 'date_purchase' => $date_purchase, + 'address' => $jo->getDeliveryAddress(), + 'hub' => $hub_name, + 'serial' => $serial, + ]; + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + $non_battery_item_titles = ['Promo discount', 'Trade-in', 'Service']; + foreach ($jo_items as $item) + { + $item_title = $item->getTitle(); + + // check if title has Promo discount, Trade-in, or Service + $flag_battery = $this->checkIfBatteryInvoiceItem($item_title, $non_battery_item_titles); + if ($flag_battery == true) + { + $items[] = [ + 'title' => $item->getTitle(), + ]; + } + } + + $jo_data['items'] = $items; + + return $jo_data; + } + + protected function checkIfBatteryInvoiceItem($item_title, $non_battery_item_titles) + { + foreach ($non_battery_item_titles as $nb_item_title) + { + $pos_result = stripos($item_title, $nb_item_title); + + // if found, invoice item is not a battery item + if ($pos_result !== false) + return false; + } + + return true; + } + + protected function getSerialFromWarranty($plate_number, EntityManagerInterface $em) + { + // NOTE: Modify the search for the latest warranty. This seems hacky. + // get latest warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + $serial = ''; + if (!empty($warranty_results)) + { + // get first entry + $warranty = current($warranty_results); + + $serial = $warranty->getSerial(); + } + + return $serial; + } +} From 5af3a3cb5ed3d59cdf5ee9a97cae2d982856b38b Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Sat, 23 Mar 2024 04:01:16 +0800 Subject: [PATCH 56/64] Add check for if serial is present before registering warranty #783 --- src/Controller/CAPI/RiderAppController.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 694ffdd0..a4b50099 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -953,7 +953,11 @@ class RiderAppController extends ApiController // for riders, use rider id $user_id = $rider->getID(); $source = WarrantySource::RAPI; - $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class, $user_id, $source, $jo->getCustomer(), $jo->getCustomerVehicle()->getVehicle()); + + // create warranty if serial is set + if (!empty($serial)) { + $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class, $user_id, $source, $jo->getCustomer(), $jo->getCustomerVehicle()->getVehicle()); + } } // send mqtt event (fulfilled) From 25e0931f6fe52e9c0b35c7d37646e4b5319ad68b Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Sat, 23 Mar 2024 04:03:00 +0800 Subject: [PATCH 57/64] Revert warranty serial check for now #783 --- src/Controller/CAPI/RiderAppController.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index a4b50099..694ffdd0 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -953,11 +953,7 @@ class RiderAppController extends ApiController // for riders, use rider id $user_id = $rider->getID(); $source = WarrantySource::RAPI; - - // create warranty if serial is set - if (!empty($serial)) { - $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class, $user_id, $source, $jo->getCustomer(), $jo->getCustomerVehicle()->getVehicle()); - } + $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class, $user_id, $source, $jo->getCustomer(), $jo->getCustomerVehicle()->getVehicle()); } // send mqtt event (fulfilled) From 93c313f111e90b576142ab08bb174bc8f4c697f7 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Sun, 24 Mar 2024 23:38:08 -0400 Subject: [PATCH 58/64] Fix issues found during testing. #794 --- src/Controller/CAPI/JobOrderController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/CAPI/JobOrderController.php b/src/Controller/CAPI/JobOrderController.php index e340ae95..dfe7ace6 100644 --- a/src/Controller/CAPI/JobOrderController.php +++ b/src/Controller/CAPI/JobOrderController.php @@ -120,7 +120,7 @@ class JobOrderController extends ApiController { // NOTE: Modify the search for the latest warranty. This seems hacky. // get latest warranty using plate number - $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + $warranty_results = $em->getRepository(Warranty::class)->findBy( ['plate_number' => $plate_number], ['date_create' => 'desc'] ); From 90aada4bf3f2d0276d89183c47dce579b18652f1 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 25 Mar 2024 04:51:40 -0400 Subject: [PATCH 59/64] ADd service offering for motolite users for jumpstart warranty. Add sql file to add new service offering. #795 --- src/InvoiceRule/JumpstartWarranty.php | 26 ++++++++++++++----- ...t_motolite_user_jumpstart_warranty_fee.sql | 1 + 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 utils/service_offering/insert_motolite_user_jumpstart_warranty_fee.sql diff --git a/src/InvoiceRule/JumpstartWarranty.php b/src/InvoiceRule/JumpstartWarranty.php index 4c6ac387..c627a574 100644 --- a/src/InvoiceRule/JumpstartWarranty.php +++ b/src/InvoiceRule/JumpstartWarranty.php @@ -8,6 +8,7 @@ use App\InvoiceRuleInterface; use App\Entity\ServiceOffering; use App\Entity\ItemType; +use App\Entity\CustomerVehicle; use App\Service\PriceTierManager; @@ -36,11 +37,13 @@ class JumpstartWarranty implements InvoiceRuleInterface if ($stype == $this->getID()) { + $cv = $criteria->getCustomerVehicle(); + // check if price tier has item price - $pt_price = $this->getPriceTierItemPrice($pt_id); + $pt_price = $this->getPriceTierItemPrice($pt_id, $cv); if ($pt_price == null) - $price = $this->getServiceTypeFee(); + $price = $this->getServiceTypeFee($cv); else $price = $pt_price; @@ -60,9 +63,14 @@ class JumpstartWarranty implements InvoiceRuleInterface return $items; } - public function getServiceTypeFee() + public function getServiceTypeFee(CustomerVehicle $cv) { - $code = 'jumpstart_warranty_fee'; + // check if user has motolite battery. + // Motolite users now have a service fee for jumpstart warranty + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_jumpstart_warranty_fee'; + else + $code = 'jumpstart_warranty_fee'; // find the service fee using the code // if we can't find the fee, return 0 @@ -84,7 +92,7 @@ class JumpstartWarranty implements InvoiceRuleInterface return null; } - protected function getPriceTierItemPrice($pt_id) + protected function getPriceTierItemPrice($pt_id, CustomerVehicle $cv) { // price_tier is default if ($pt_id == 0) @@ -96,7 +104,13 @@ class JumpstartWarranty implements InvoiceRuleInterface return null; // find the service offering - $code = 'jumpstart_warranty_fee'; + // check if user has motolite battery. + // Motolite users now have a service fee for jumpstart warranty + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_jumpstart_warranty_fee'; + else + $code = 'jumpstart_warranty_fee'; + $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); // check if service is null. If null, return null diff --git a/utils/service_offering/insert_motolite_user_jumpstart_warranty_fee.sql b/utils/service_offering/insert_motolite_user_jumpstart_warranty_fee.sql new file mode 100644 index 00000000..afe55b2f --- /dev/null +++ b/utils/service_offering/insert_motolite_user_jumpstart_warranty_fee.sql @@ -0,0 +1 @@ + INSERT INTO service_offering (name, code, fee) VALUES ('Motolite User Jumpstart Warranty Fee', 'motolite_user_jumpstart_warranty_fee', 200.00); From b80ece90843be4734f324d47d86a8f92969d9b47 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 26 Mar 2024 01:41:48 -0400 Subject: [PATCH 60/64] Fix display issue for invoice. #796 --- templates/job-order/form.html.twig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index edbd97a0..afebb564 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -1266,6 +1266,9 @@ $(function() { $('#map_lat').val(lat); $('#map_lng').val(lng); + // regenerate invoice + generateInvoice(); + } osm_map.on('click', function(e) { From 6f2fca292ecf22ee6ea198788c23661b5228d722 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 1 Apr 2024 06:55:23 -0400 Subject: [PATCH 61/64] Add use statement. #797 --- src/Controller/ResqJobOrderController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controller/ResqJobOrderController.php b/src/Controller/ResqJobOrderController.php index 2462706a..0ef01588 100644 --- a/src/Controller/ResqJobOrderController.php +++ b/src/Controller/ResqJobOrderController.php @@ -23,6 +23,7 @@ use App\Ramcar\JOStatus; use App\Ramcar\ServiceType; use App\Ramcar\WillingToWaitContent; use App\Ramcar\CustomerClassification; +use App\Ramcar\CustomerNotWaitReason; use DateTime; From c8e2c02be192deaa377da02b8b4702d852d39f9b Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 2 Apr 2024 01:11:32 -0400 Subject: [PATCH 62/64] Add a separate service fee for motolite users for jumpstart. #798 --- src/InvoiceRule/Jumpstart.php | 59 ++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/InvoiceRule/Jumpstart.php b/src/InvoiceRule/Jumpstart.php index d2e89b0a..141e648e 100644 --- a/src/InvoiceRule/Jumpstart.php +++ b/src/InvoiceRule/Jumpstart.php @@ -68,18 +68,8 @@ class Jumpstart implements InvoiceRuleInterface public function getServiceTypeFee($source, CustomerVehicle $cv) { - // check the source of JO - // (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user. - // (2) any other source, jumpstart fees are charged whether motolite user or not - if ($source == TransactionOrigin::MOBILE_APP) - { - if ($cv->hasMotoliteBattery()) - $code = 'motolite_user_service_fee'; - else - $code = 'jumpstart_fee_mobile_app'; - } - else - $code = 'jumpstart_fee'; + // get the service fee code, depending on the JO source and if customer vehicle has a motolite battery + $code = $this->getServiceFeeCode($cv, $source); $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); @@ -110,20 +100,10 @@ class Jumpstart implements InvoiceRuleInterface if ($item_type == null) return null; - // find the service offering - // check the source of JO - // (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user. - // (2) any other source, jumpstart fees are charged whether motolite user or not - if ($source == TransactionOrigin::MOBILE_APP) - { - if ($cv->hasMotoliteBattery()) - $code = 'motolite_user_service_fee'; - else - $code = 'jumpstart_fee_mobile_app'; - } - else - $code = 'jumpstart_fee'; + // get the service fee code, depending on the JO source and if customer vehicle has a motolite battery + $code = $this->getServiceFeeCode($cv, $source); + // find the service offering $service = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); // check if service is null. If null, return null @@ -144,4 +124,33 @@ class Jumpstart implements InvoiceRuleInterface return $title; } + + protected function getServiceFeeCode(CustomerVehicle $cv, $source) + { + // check the source of JO + // (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user. + // (2) any other source, jumpstart fees are charged whether motolite user or not. Service fees for non-motolite + // and motolite users are now different (used to be the same) + if ($source == TransactionOrigin::MOBILE_APP) + { + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + else + $code = 'jumpstart_fee_mobile_app'; + } + else + { + error_log('hotline'); + if ($cv->hasMotoliteBattery()) + { + error_log('has motolite battery'); + $code = 'motolite_user_jumpstart_fee'; + } + else + $code = 'jumpstart_fee'; + } + + return $code; + + } } From 6a804c11dfd27a259bc746e1d7112fc55e993e3c Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 2 Apr 2024 01:21:23 -0400 Subject: [PATCH 63/64] Add sql file to update service offering table. #798 --- .../updated_jumpstart_service_offering.sql | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 utils/service_offering/updated_jumpstart_service_offering.sql diff --git a/utils/service_offering/updated_jumpstart_service_offering.sql b/utils/service_offering/updated_jumpstart_service_offering.sql new file mode 100644 index 00000000..88805a02 --- /dev/null +++ b/utils/service_offering/updated_jumpstart_service_offering.sql @@ -0,0 +1,54 @@ +-- MySQL dump 10.19 Distrib 10.3.39-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: resq +-- ------------------------------------------------------ +-- Server version 10.3.39-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `service_offering` +-- + +DROP TABLE IF EXISTS `service_offering`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `service_offering` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL, + `code` varchar(80) NOT NULL, + `fee` decimal(9,2) NOT NULL, + PRIMARY KEY (`id`), + KEY `service_offering_idx` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `service_offering` +-- + +LOCK TABLES `service_offering` WRITE; +/*!40000 ALTER TABLE `service_offering` DISABLE KEYS */; +INSERT INTO `service_offering` VALUES (1,'Tax','tax',0.12),(2,'Motolite User Service Fee','motolite_user_service_fee',0.00),(3,'Battery Replacement Warranty Fee','battery_replacement_warranty_fee',0.00),(4,'Fuel Service Fee','fuel_service_fee',300.00),(5,'Fuel Gas Fee','fuel_gas_fee',340.00),(6,'Fuel Diesel Fee','fuel_diesel_fee',320.00),(7,'Jumpstart Fee','jumpstart_fee',300.00),(8,'Jumpstart Fee Mobile App','jumpstart_fee_mobile_app',300.00),(9,'Jumpstart Warranty Fee','jumpstart_warranty_fee',300.00),(10,'Overheat Fee','overheat_fee',300.00),(11,'Coolant Fee','coolant_fee',1600.00),(12,'Post Recharged Fee','post_recharged_fee',300.00),(13,'Post Replacement Fee','post_replacement_fee',0.00),(14,'Tire Repair Fee','tire_repair_fee',300.00),(17,'Motolite User Jumpstart Warranty Fee','motolite_user_jumpstart_warranty_fee',200.00),(18,'Motolite User Jumpstart Fee','motolite_user_jumpstart_fee',200.00); +/*!40000 ALTER TABLE `service_offering` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-04-02 1:17:40 From a64557ffcd9418c7c51ba50d7d84738ca43208e7 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Sat, 6 Apr 2024 14:56:16 +0800 Subject: [PATCH 64/64] Fix API roles permissions list to properly display the correct ACL group #783 --- src/Controller/APIRoleController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/APIRoleController.php b/src/Controller/APIRoleController.php index 4c60b4cd..190095c5 100644 --- a/src/Controller/APIRoleController.php +++ b/src/Controller/APIRoleController.php @@ -326,7 +326,7 @@ class APIRoleController extends Controller protected function padAPIACLHierarchy(&$params) { // get acl keys hierarchy - $api_acl_data = $this->api_acl_gen->getACL(); + $api_acl_data = $this->api_acl_gen->getACL('api'); $params['api_acl_hierarchy'] = $api_acl_data['hierarchy']; }