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 %}