diff --git a/api/composer.json b/api/composer.json index 8eed869be14c53e2a433996bbb16a0336ecb970f..eb987c7510418ba7b4c92e1785072c90a58bb6fb 100644 --- a/api/composer.json +++ b/api/composer.json @@ -6,6 +6,7 @@ "ext-ctype": "*", "ext-iconv": "*", "api-platform/core": "^3.2", + "beberlei/doctrineextensions": "dev-master", "doctrine/doctrine-bundle": "^2.7", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.12", diff --git a/api/composer.lock b/api/composer.lock index 4fc11b5129d1df4ae3ce5933160fabb7833a9887..0663f0e9673f88c6dcd17383c5b7ef4deafed07f 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "934976d853cfcd0af63e3e1658634266", + "content-hash": "bd6b6c41a9c7342b985895c36f7ab96c", "packages": [ { "name": "api-platform/core", @@ -169,6 +169,65 @@ }, "time": "2023-11-30T13:51:25+00:00" }, + { + "name": "beberlei/doctrineextensions", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/beberlei/DoctrineExtensions.git", + "reference": "67f32c184e80085e170b6e4e8f4f94bc92394c72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/67f32c184e80085e170b6e4e8f4f94bc92394c72", + "reference": "67f32c184e80085e170b6e4e8f4f94bc92394c72", + "shasum": "" + }, + "require": { + "doctrine/orm": "^2.7", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/cache": "^1.11", + "friendsofphp/php-cs-fixer": "^2.14", + "nesbot/carbon": "*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "symfony/yaml": "^4.4 || ^5.3 || ^6.0", + "zf1/zend-date": "^1.12", + "zf1/zend-registry": "^1.12" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "DoctrineExtensions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Lacey", + "email": "steve@steve.ly" + } + ], + "description": "A set of extensions to Doctrine 2 that add support for additional query functions available in MySQL, Oracle, PostgreSQL and SQLite.", + "keywords": [ + "database", + "doctrine", + "orm" + ], + "support": { + "source": "https://github.com/beberlei/DoctrineExtensions/tree/master" + }, + "time": "2023-02-25T19:57:36+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -8207,7 +8266,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "beberlei/doctrineextensions": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/api/config/packages/doctrine.yaml b/api/config/packages/doctrine.yaml index 559641f79e97fe65de4b01d1ed46ced14fde2d35..7492f195c2b11bc451948f25b53e77fe8e3607fe 100644 --- a/api/config/packages/doctrine.yaml +++ b/api/config/packages/doctrine.yaml @@ -21,6 +21,22 @@ doctrine: dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App + dql: + datetime_functions: + month: DoctrineExtensions\Query\Postgresql\Month + year: DoctrineExtensions\Query\Postgresql\Year + date_format: DoctrineExtensions\Query\Postgresql\DateFormat + date: DoctrineExtensions\Query\Postgresql\Date + + string_functions: + str_to_date: DoctrineExtensions\Query\Postgresql\StrToDate + count_filter: DoctrineExtensions\Query\Postgresql\CountFilterFunction + string_agg: DoctrineExtensions\Query\Postgresql\StringAgg + greatest: DoctrineExtensions\Query\Postgresql\Greatest + least: DoctrineExtensions\Query\Postgresql\Least + regexp_replace: DoctrineExtensions\Query\Postgresql\RegexpReplace + + when@test: doctrine: diff --git a/api/config/routes.yaml b/api/config/routes.yaml index 41ef8140ba811c0da46ce1962ffa50d4c56b9840..031ef7c958c59d78457f5c4b767526a25680f411 100644 --- a/api/config/routes.yaml +++ b/api/config/routes.yaml @@ -3,3 +3,12 @@ controllers: path: ../src/Controller/ namespace: App\Controller type: attribute + +bar_chart_route: + path: '/sales/bar-chart/{startDate}/{endDate}/{granularity}' + controller: 'BarChartController::getChartData' + methods: ['GET'] + requirements: + startDate: '\d{4}-\d{2}-\d{2}' + endDate: '\d{4}-\d{2}-\d{2}' + granularity: 'day|month|year' diff --git a/api/config/services.yaml b/api/config/services.yaml index 2d6a76f94dce138741e2d63ae83a11c1879031d9..e8a165f2bca82fa60ac5920fda6e30d459f9b7c4 100644 --- a/api/config/services.yaml +++ b/api/config/services.yaml @@ -6,6 +6,7 @@ parameters: services: + DoctrineExtensions\Query\Mysql\DateFormat: '@doctrine_extensions.query_mysql.date_format' # default configuration for services in *this* file _defaults: autowire: true # Automatically injects dependencies in your services. diff --git a/api/public/index.php b/api/public/index.php index 9982c218d6969c72d4c91e3834e3f535e2dfe68b..870bf7ca4134b0ed866302e018163e6c892c80b3 100644 --- a/api/public/index.php +++ b/api/public/index.php @@ -5,5 +5,6 @@ use App\Kernel; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (array $context) { + ini_set('memory_limit', '8192M'); return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); }; diff --git a/api/src/Controller/BarChartController.php b/api/src/Controller/BarChartController.php new file mode 100644 index 0000000000000000000000000000000000000000..4b62fa4413012e8cd46ef50d7fd5eb129a5f45e8 --- /dev/null +++ b/api/src/Controller/BarChartController.php @@ -0,0 +1,43 @@ +chartService = $chartService; + } + + #[Route('/sales/bar-chart/{startDate}/{endDate}/{granularity}', name: 'bar-chart', requirements: [ + 'startDate' => '\d{4}-\d{2}-\d{2}', // YYYY-MM-DD + 'endDate' => '\d{4}-\d{2}-\d{2}', // YYYY-MM-DD + 'granularity' => 'day|month|year', // day, month, ou year + ])] + public function getChartData(string $startDate, string $endDate, string $granularity): JsonResponse + { + $input = new BarChartInput(); + $input->start = $startDate; + $input->end = $endDate; + $input->granularity = $granularity; + + $chartData = $this->chartService->getBarChartData($input); + $output = []; + foreach ($chartData as $data) { + $output[] = [ + 'date' => $data->date, + 'occurrences' => $data->occurrences, + ]; + } + return $this->json($output); + } +} diff --git a/api/src/Dto/BarChart/BarChartInput.php b/api/src/Dto/BarChart/BarChartInput.php new file mode 100644 index 0000000000000000000000000000000000000000..becb0bba555f6745930d8543bf7bbb6b0343b81a --- /dev/null +++ b/api/src/Dto/BarChart/BarChartInput.php @@ -0,0 +1,8 @@ +date = $date; + $this->occurrences = $occurrences; + } +} diff --git a/api/src/Entity/Sale.php b/api/src/Entity/Sale.php index 9ceb2c1394e677d4aaf043dbebfadd1bdbbd695a..309712a4acb2c31df1c6ca1691be60aae86f813f 100644 --- a/api/src/Entity/Sale.php +++ b/api/src/Entity/Sale.php @@ -4,11 +4,27 @@ namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Validator\Constraints as Assert; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use App\Controller\BarChartController; + + +#[ApiResource( + operations: [ + new GetCollection( + uriTemplate: '/sales/bar-chart/{startDate}/{endDate}/{granularity}', + requirements: [ + 'startDate' => '\d{4}-\d{2}-\d{2}', + 'endDate' => '\d{4}-\d{2}-\d{2}', + 'granularity' => 'day|month|year', + ], + controller: BarChartController::class, + name: 'bar-chart' + ) + ] +)] -/** A sale. */ #[ORM\Entity] -#[ApiResource] class Sale { diff --git a/api/src/Service/BarChartService.php b/api/src/Service/BarChartService.php new file mode 100644 index 0000000000000000000000000000000000000000..efa52f8b842504ac9ce5f4a0c4fd22a41b5e089a --- /dev/null +++ b/api/src/Service/BarChartService.php @@ -0,0 +1,72 @@ +entityManager = $entityManager; + $emConfig = $this->entityManager->getConfiguration(); + $emConfig->addCustomDatetimeFunction('YEAR', 'DoctrineExtensions\Query\Postgresql\Year'); + $emConfig->addCustomDatetimeFunction('MONTH', 'DoctrineExtensions\Query\Postgresql\Month'); + $emConfig->addCustomDatetimeFunction('DATE_FORMAT', 'DoctrineExtensions\Query\Postgresql\DateFormat'); + + } + + public function getBarChartData(BarChartInput $input): array + { + $startDate = new \DateTime($input->start); + $endDate = new \DateTime($input->end); + + $queryBuilder = $this->entityManager->createQueryBuilder(); + + switch ($input->granularity) { + case 'day': + $groupByExpression = 's.date'; + $groupByAlias = 'date'; + $dateFormat = 'Y-m-d'; + break; + case 'month': + $groupByExpression = 'MONTH(s.date)'; + $groupByAlias = 'month'; + $dateFormat = 'Y-m'; + break; + case 'year': + $groupByExpression = 'YEAR(s.date)'; + $groupByAlias = 'year'; + $dateFormat = 'Y'; + break; + default: + throw new \InvalidArgumentException('Invalid granularity'); + } + + $result = $queryBuilder + ->select("{$groupByExpression} as {$groupByAlias}", 'COUNT(s.id) as occurrences') + ->from(Sale::class, 's') + ->where('s.date BETWEEN :start AND :end') + ->setParameter('start', $startDate->format('Y-m-d')) + ->setParameter('end', $endDate->format('Y-m-d')) + ->groupBy("{$groupByAlias}") + ->orderBy("{$groupByAlias}") + ->getQuery() + ->getResult(); + + $output = []; + foreach ($result as $row) { + $dateString = $row[$groupByAlias] instanceof \DateTimeInterface ? $row[$groupByAlias]->format($dateFormat) : $row[$groupByAlias]; + $output[] = new BarChartOutput($dateString, (int)$row['occurrences']); + } + + return $output; + } + +}