diff --git a/src/Controller/StatisticsController.php b/src/Controller/StatisticsController.php new file mode 100644 index 00000000..2352db7f --- /dev/null +++ b/src/Controller/StatisticsController.php @@ -0,0 +1,44 @@ +denyAccessUnlessGranted('@tools.statistics'); + + return $this->render('/Statistics/statistics.html.twig', [ + 'helper' => $helper, + ]); + } +} \ No newline at end of file diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 1e289547..3d886ce1 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -33,7 +33,7 @@ use LogicException; /** * Class Attachment. * - * @ORM\Entity + * @ORM\Entity(repositoryClass="App\Repository\AttachmentRepository") * @ORM\Table(name="`attachments`") * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="class_name", type="string") diff --git a/src/Repository/AttachmentRepository.php b/src/Repository/AttachmentRepository.php new file mode 100644 index 00000000..37900f2c --- /dev/null +++ b/src/Repository/AttachmentRepository.php @@ -0,0 +1,80 @@ +createQueryBuilder('attachment'); + $qb->select('COUNT(attachment)') + ->where('attachment.path LIKE :like'); + $qb->setParameter('like', '\\%SECURE\\%%'); + $query = $qb->getQuery(); + return (int) $query->getSingleScalarResult(); + } + + /** + * Gets the count of all external attachments (attachments only containing an URL) + * @return int + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getExternalAttachments(): int + { + $qb = $this->createQueryBuilder('attachment'); + $qb->select('COUNT(attachment)') + ->where('attachment.path LIKE :http') + ->orWhere('attachment.path LIKE :https'); + $qb->setParameter('http', 'http://%'); + $qb->setParameter('https', 'https://%'); + $query = $qb->getQuery(); + return (int) $query->getSingleScalarResult(); + } + + /** + * Gets the count of all attachments where an user uploaded an file. + * @return int + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getUserUploadedAttachments(): int + { + $qb = $this->createQueryBuilder('attachment'); + $qb->select('COUNT(attachment)') + ->where('attachment.path LIKE :base') + ->orWhere('attachment.path LIKE :media') + ->orWhere('attachment.path LIKE :secure'); + $qb->setParameter('secure', '\\%SECURE\\%%'); + $qb->setParameter('base', '\\%BASE\\%%'); + $qb->setParameter('media', '\\%MEDIA\\%%'); + $query = $qb->getQuery(); + return (int) $query->getSingleScalarResult(); + } +} \ No newline at end of file diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 91762310..f6ee3e94 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -24,9 +24,45 @@ declare(strict_types=1); namespace App\Repository; +use App\Entity\Parts\PartLot; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; class PartRepository extends EntityRepository { - //TODO + /** + * Gets the summed up instock of all parts (only parts without an measurent unit) + * @return string + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getPartsInstockSum(): string + { + $qb = new QueryBuilder($this->getEntityManager()); + $qb->select('SUM(part_lot.amount)') + ->from(PartLot::class, 'part_lot') + ->leftJoin('part_lot.part', 'part') + ->where('part.partUnit IS NULL'); + + $query = $qb->getQuery(); + return $query->getSingleScalarResult(); + } + + /** + * Gets the number of parts that has price informations. + * @return int + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getPartsCountWithPrice(): int + { + $qb = $this->createQueryBuilder('part'); + $qb->select('COUNT(part)') + ->innerJoin('part.orderdetails', 'orderdetail') + ->innerJoin('orderdetail.pricedetails', 'pricedetail') + ->where('pricedetail.price > 0.0'); + + $query = $qb->getQuery(); + return (int) $query->getSingleScalarResult(); + } } diff --git a/src/Services/StatisticsHelper.php b/src/Services/StatisticsHelper.php new file mode 100644 index 00000000..095c0479 --- /dev/null +++ b/src/Services/StatisticsHelper.php @@ -0,0 +1,149 @@ +em = $em; + $this->part_repo = $this->em->getRepository(Part::class); + $this->attachment_repo = $this->em->getRepository(Attachment::class); + } + + /** + * Returns the count of distinct parts + */ + public function getDistinctPartsCount(): int + { + return $this->part_repo->count([]); + } + + /** + * Returns the summed instocked over all parts (only parts without a measurement unit) + * @return string + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getPartsInstockSum(): string + { + return $this->part_repo->getPartsInstockSum(); + } + + /** + * Returns the number of all parts which have price informations + * @return int + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getPartsCountWithPrice(): int + { + return $this->part_repo->getPartsCountWithPrice(); + } + + /** + * Returns the number of datastructures for the given type. + * @param string $type + * @return int + */ + public function getDataStructuresCount(string $type): int + { + $arr = [ + 'attachment_type' => AttachmentType::class, + 'category' => Category::class, + 'device' => Device::class, + 'footprint' => Footprint::class, + 'manufacturer' => Manufacturer::class, + 'measurement_unit' => MeasurementUnit::class, + 'storelocation' => Storelocation::class, + 'supplier' => Supplier::class, + 'currency' => Currency::class, + ]; + + if (!isset($arr[$type])) { + throw new \InvalidArgumentException('No count for the given type available!'); + } + + /** @var EntityRepository $repo */ + $repo = $this->em->getRepository($arr[$type]); + return $repo->count([]); + } + + /** + * Gets the count of all attachments. + * @return int + */ + public function getAttachmentsCount(): int + { + return $this->attachment_repo->count([]); + } + + /** + * Gets the count of all private/secure attachments + * @return int + */ + public function getPrivateAttachmentsCount(): int + { + return $this->attachment_repo->getPrivateAttachmentsCount(); + } + + /** + * Gets the count of all external (only containing an URL) attachments + * @return int + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getExternalAttachmentsCount(): int + { + return $this->attachment_repo->getExternalAttachments(); + } + + /** + * Gets the count of all attachments where the user uploaded an file. + * @return int + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function getUserUploadedAttachmentsCount(): int + { + return $this->attachment_repo->getUserUploadedAttachments(); + } +} \ No newline at end of file diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index b482c8b2..b09111c7 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -58,8 +58,8 @@ class ToolsTreeBuilder protected $security; public function __construct(TranslatorInterface $translator, UrlGeneratorInterface $urlGenerator, - TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator, - Security $security) + TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator, + Security $security) { $this->translator = $translator; $this->urlGenerator = $urlGenerator; @@ -187,6 +187,13 @@ class ToolsTreeBuilder ); } + if ($this->security->isGranted('@tools.statistics')) { + $show_nodes[] = new TreeViewNode( + $this->translator->trans('tree.tools.show.statistics'), + $this->urlGenerator->generate('statistics_view') + ); + } + return $show_nodes; } diff --git a/templates/Statistics/statistics.html.twig b/templates/Statistics/statistics.html.twig new file mode 100644 index 00000000..5cc2f259 --- /dev/null +++ b/templates/Statistics/statistics.html.twig @@ -0,0 +1,134 @@ +{% extends "main_card.html.twig" %} + +{# @var StatisticsHelper helper #} + +{% block title %}{% trans %}statistics.title{% endtrans %}{% endblock %} + +{% block card_title %} + {% trans %}statistics.title{% endtrans %}{% endblock %} + +{% block card_body %} + + +
+
+ + + + + + + + + + + + + + + + + + + + + +
{% trans %}statistics.property{% endtrans %}{% trans %}statistics.value{% endtrans %}
{% trans %}statistics.distinct_parts_count{% endtrans %}{{ helper.distinctPartsCount }}
{% trans %}statistics.parts_instock_sum{% endtrans %}{{ helper.PartsInstockSum }}
{% trans %}statistics.parts_with_price{% endtrans %}{{ helper.partsCountWithPrice }}
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans %}statistics.property{% endtrans %}{% trans %}statistics.value{% endtrans %}
{% trans %}statistics.categories_count{% endtrans %}{{ helper.dataStructuresCount("category") }}
{% trans %}statistics.footprints_count{% endtrans %}{{ helper.dataStructuresCount("footprint") }}
{% trans %}statistics.manufacturers_count{% endtrans %}{{ helper.dataStructuresCount("manufacturer") }}
{% trans %}statistics.storelocations_count{% endtrans %}{{ helper.dataStructuresCount("storelocation") }}
{% trans %}statistics.suppliers_count{% endtrans %}{{ helper.dataStructuresCount("supplier") }}
{% trans %}statistics.currencies_count{% endtrans %}{{ helper.dataStructuresCount("currency") }}
{% trans %}statistics.measurement_units_count{% endtrans %}{{ helper.dataStructuresCount("measurement_unit") }}
{% trans %}statistics.devices_count{% endtrans %}{{ helper.dataStructuresCount("device") }}
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans %}statistics.property{% endtrans %}{% trans %}statistics.value{% endtrans %}
{% trans %}statistics.attachment_types_count{% endtrans %}{{ helper.dataStructuresCount("attachment_type") }}
{% trans %}statistics.all_attachments_count{% endtrans %}{{ helper.AttachmentsCount }}
{% trans %}statistics.user_uploaded_attachments_count{% endtrans %}{{ helper.UserUploadedAttachmentsCount }}
{% trans %}statistics.private_attachments_count{% endtrans %}{{ helper.PrivateAttachmentsCount }}
{% trans %}statistics.external_attachments_count{% endtrans %}{{ helper.ExternalAttachmentsCount }}
+
+
+{% endblock %} \ No newline at end of file