Skip to content

Commit

Permalink
Merge branch 'statistics' into v2
Browse files Browse the repository at this point in the history
  • Loading branch information
jbtronics committed Nov 27, 2024
2 parents 8014d5f + 0c9052a commit 353e248
Show file tree
Hide file tree
Showing 6 changed files with 473 additions and 3 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"ext-iconv": "*",
"ext-mbstring": "*",
"ext-zip": "*",
"beberlei/doctrineextensions": "^1.5",
"composer/package-versions-deprecated": "1.11.99.4",
"damienharper/auditor-bundle": "^6.0.0",
"digitick/sepa-xml": "^2.0.0",
Expand Down
64 changes: 63 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions config/packages/doctrine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ doctrine:
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true

dql:
numeric_functions:
stddev: DoctrineExtensions\Query\Mysql\StdDev
unix_timestamp: DoctrineExtensions\Query\Mysql\UnixTimestamp

mappings:
App:
type: attribute
Expand Down
7 changes: 5 additions & 2 deletions src/Controller/Admin/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use App\Entity\PaymentOrder;
use App\Entity\User;
use App\Services\GitVersionInfo;
use App\Services\Statistics\PaymentOrderStatistics;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets;
Expand All @@ -43,7 +44,7 @@ final class DashboardController extends AbstractDashboardController
{
private const FILTER_DATETIME_FORMAT = 'Y-m-d\TH:i:s';

public function __construct(private readonly string $app_version, private readonly GitVersionInfo $gitVersionInfo)
public function __construct(private readonly string $app_version, private readonly GitVersionInfo $gitVersionInfo, private readonly PaymentOrderStatistics $orderStatistics)
{
}

Expand All @@ -56,7 +57,9 @@ public function configureDashboard(): Dashboard
#[Route(path: '/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
return $this->render('admin/dashboard.html.twig', [
'statistics' => $this->orderStatistics,
]);
}

private function addFiltersToMenuItem(CrudMenuItem $menuItem, array $filters): CrudMenuItem
Expand Down
220 changes: 220 additions & 0 deletions src/Services/Statistics/PaymentOrderStatistics.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<?php

declare(strict_types=1);


namespace App\Services\Statistics;

use App\Entity\PaymentOrder;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;

final readonly class PaymentOrderStatistics
{
public function __construct(private EntityManagerInterface $entityManager)
{
}

private function addFromToRange(QueryBuilder $qb, null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null, string $field = "po.creation_date"): void
{
if ($from !== null) {
if (!$from instanceof \DateTimeInterface) {
$from = new \DateTime($from);
}
$qb->andWhere(sprintf('%s >= :from', $field))
->setParameter('from', $from);
}

if ($to !== null) {
if (!$to instanceof \DateTimeInterface) {
$to = new \DateTime($to);
}
$qb->andWhere(sprintf('%s <= :to', $field))
->setParameter('to', $to);
}
}

/**
* Returns the number of submitted payment orders within the given time range.
* @return int
*/
public function getSubmittedPaymentOrdersCount(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): int
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('COUNT(po.id)')
->from(PaymentOrder::class, 'po');

$this->addFromToRange($qb, $from, $to);

return (int) $qb->getQuery()->getSingleScalarResult();
}

/**
* Returns the number of booked payment orders within the given time range.
* @param \DateTimeInterface|string|null $from
* @param \DateTimeInterface|string|null $to
* @return int
* @throws \DateMalformedStringException
*/
public function getBookedPaymentOrdersCount(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): int
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('COUNT(po.id)')
->from(PaymentOrder::class, 'po')
->where('po.booking_date IS NOT NULL');

$this->addFromToRange($qb, $from, $to, "po.booking_date");

return (int) $qb->getQuery()->getSingleScalarResult();
}

/**
* Returns the number of payment orders that have been checked for mathematical correctness within the given time range.
* @param \DateTimeInterface|string|null $from
* @param \DateTimeInterface|string|null $to
* @return int
* @throws \DateMalformedStringException
*/
public function getMathematicallyCheckedPaymentOrdersCount(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): int
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('COUNT(po.id)')
->from(PaymentOrder::class, 'po')
->where('po.mathematically_correct.checked = true');

$this->addFromToRange($qb, $from, $to, "po.mathematically_correct.timestamp");

return (int) $qb->getQuery()->getSingleScalarResult();
}

/**
* Returns the number of payment orders that have been checked for factual correctness within the given time range.
* @param \DateTimeInterface|string|null $from
* @param \DateTimeInterface|string|null $to
* @return int
* @throws \DateMalformedStringException
*/
public function getFactuallyCheckedPaymentOrdersCount(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): int
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('COUNT(po.id)')
->from(PaymentOrder::class, 'po')
->where('po.factually_correct.checked = true');

$this->addFromToRange($qb, $from, $to, "po.factually_correct.timestamp");

return (int) $qb->getQuery()->getSingleScalarResult();
}

/**
* Returns the total value of submitted payment orders within the given time range.
* @return float
*/
public function getPaymentOrdersTotalValue(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('SUM(po.amount)')
->from(PaymentOrder::class, 'po');

$this->addFromToRange($qb, $from, $to);

// We need to divide the result by 100 because the value is stored in cents.
return ((float) $qb->getQuery()->getSingleScalarResult()) / 100;
}

public function getPaymentOrdersAverageValue(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('AVG(po.amount)')
->from(PaymentOrder::class, 'po');

$this->addFromToRange($qb, $from, $to);

// We need to divide the result by 100 because the value is stored in cents.
return ((float) $qb->getQuery()->getSingleScalarResult()) / 100;
}

public function getPaymentOrdersStdDevValue(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('STDDEV(po.amount)')
->from(PaymentOrder::class, 'po');

$this->addFromToRange($qb, $from, $to);

// We need to divide the result by 100 because the value is stored in cents.
return ((float) $qb->getQuery()->getSingleScalarResult()) / 100;
}

public function getPaymentOrdersMaxValue(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('MAX(po.amount)')
->from(PaymentOrder::class, 'po');

$this->addFromToRange($qb, $from, $to);

// We need to divide the result by 100 because the value is stored in cents.
return ((float) $qb->getQuery()->getSingleScalarResult()) / 100;
}

public function getPaymentOrdersMinValue(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('MIN(po.amount)')
->from(PaymentOrder::class, 'po');

$this->addFromToRange($qb, $from, $to);

// We need to divide the result by 100 because the value is stored in cents.
return ((float) $qb->getQuery()->getSingleScalarResult()) / 100;
}

/**
* Returns the average time (in days) it took to process a payment order from submission to booking within the given time range.
* @param \DateTimeInterface|string|null $from
* @param \DateTimeInterface|string|null $to
* @return float
*/
public function getAverageProcessingTime(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): ?float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('AVG(UNIX_TIMESTAMP(po.booking_date) - UNIX_TIMESTAMP(po.creation_date))')
->from(PaymentOrder::class, 'po')
->where('po.booking_date IS NOT NULL');

$this->addFromToRange($qb, $from, $to, 'po.booking_date');

$res = $qb->getQuery()->getSingleScalarResult();
if ($res === null) {
return null;
}

//Res is in seconds, we need to convert it to days
return (float)$res / 86400;
}

/**
* Returns the standard deviation of the time (in days) it took to process a payment order from submission to booking within the given time range.
* @param \DateTimeInterface|null $from
* @param \DateTimeInterface|null $to
* @return float|null
*/
public function getStddevProcessingTime(null|\DateTimeInterface|string $from = null, null|\DateTimeInterface|string $to = null): ?float
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('STDDEV(UNIX_TIMESTAMP(po.booking_date) - UNIX_TIMESTAMP(po.creation_date))')
->from(PaymentOrder::class, 'po')
->where('po.booking_date IS NOT NULL');

$this->addFromToRange($qb, $from, $to, 'po.booking_date');

$res = $qb->getQuery()->getSingleScalarResult();
if ($res === null) {
return null;
}

//Res is in seconds, we need to convert it to days
return (float)$res / 86400;
}
}
Loading

0 comments on commit 353e248

Please sign in to comment.