Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2617: Project forecast report #175

Merged
merged 43 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
08add27
Added data model structure for forecast report
jeppekroghitk Oct 9, 2024
1e4ad57
Forecast report form data model
jeppekroghitk Oct 9, 2024
86e713c
Forecast report form type
jeppekroghitk Oct 9, 2024
ddb2354
Forecast report service
jeppekroghitk Oct 9, 2024
9df37f2
Forecast report template
jeppekroghitk Oct 9, 2024
14d521a
Added whitespace formatting in IssueRepository
jeppekroghitk Oct 9, 2024
e074da8
Added getWorklogsAttachedToInvoiceInDateRange method to WorklogReposi…
jeppekroghitk Oct 9, 2024
61e4273
Added navigation item for forecast report in navigation.html.twig
jeppekroghitk Oct 9, 2024
158d5ca
Added loading class to planning-wrapper in reports.html.twig
jeppekroghitk Oct 9, 2024
0953d9e
Added translations for forecast report in messages.da.yaml
jeppekroghitk Oct 9, 2024
bdae0a5
Added forecast report controller
jeppekroghitk Oct 9, 2024
6a8520f
Refactor code and improve comments for readability
jeppekroghitk Oct 9, 2024
f48ebcf
Added exception handling and date form changes in ForecastReportContr…
jeppekroghitk Oct 9, 2024
74bac34
Modified data value providers for date fields in ForecastReportType form
jeppekroghitk Oct 9, 2024
07e417e
Removed unused ArrayCollection import in ForecastReportIssueData
jeppekroghitk Oct 9, 2024
db4c494
Removed extra space in ForecastReportIssueVersionData
jeppekroghitk Oct 9, 2024
ef89016
Removed extra space in ForecastReportProjectData
jeppekroghitk Oct 9, 2024
c4bc990
Restructured properties and added comments in ForecastReportWorklogData
jeppekroghitk Oct 9, 2024
100a92a
Removed trailing space in IssueRepository
jeppekroghitk Oct 9, 2024
084dcdc
Refactored getForecastReport function and added date helper functions…
jeppekroghitk Oct 9, 2024
cc2042e
Added a new translation entry in messages.da.yaml
jeppekroghitk Oct 9, 2024
4568083
Refactored null check for workerName in ForecastReportService
jeppekroghitk Oct 9, 2024
bc5230f
Updated changelog
jeppekroghitk Oct 9, 2024
5c23a8f
Merge branch 'develop' into feature/2617-project-forecast-report
jeppekroghitk Oct 9, 2024
4f4db4e
Minor adjustments
jeppekroghitk Oct 16, 2024
a1f856e
Added reports description
jeppekroghitk Oct 16, 2024
2b57781
Coding standards
jeppekroghitk Oct 17, 2024
e66b403
Removed unused DataProviderRepository from ForecastReportController
jeppekroghitk Oct 23, 2024
33c3703
Removed DataProvider related fields and methods from ForecastReportType
jeppekroghitk Oct 23, 2024
89cbbd8
Removed DataProvider property from ForecastReportFormData
jeppekroghitk Oct 23, 2024
0842ff2
Added condition for rendering form.dataProvider in reports.html.twig
jeppekroghitk Oct 23, 2024
416ec3f
Added pagination to the method getWorklogsAttachedToInvoiceInDateRang…
jeppekroghitk Oct 23, 2024
a7e0f5a
Modified getForecastReport method to use the paginated version of get…
jeppekroghitk Oct 23, 2024
d867208
Coding standards
jeppekroghitk Oct 24, 2024
294093a
Refactored ForecastReportService to optimize memory usage
jeppekroghitk Oct 24, 2024
5c10b98
Changed pagesize from 50 to 200
jeppekroghitk Oct 24, 2024
d0fe61b
Optimized worker name retrieval by moving operations outside of the l…
jeppekroghitk Oct 24, 2024
2d87008
Update src/Service/ForecastReportService.php
jeppekroghitk Oct 24, 2024
9cddd13
Refactor project name assignment in Forecast Report service
jeppekroghitk Oct 24, 2024
50d9976
Clean up
jeppekroghitk Oct 24, 2024
79c4745
Removed unused variable
jeppekroghitk Oct 24, 2024
7414bc8
Changed return value to be a direct Paginator object instead of an it…
jeppekroghitk Oct 25, 2024
9da95fa
Refactored to use PAGE_SIZE constant and rearranged code for clarity
jeppekroghitk Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

* [PR-175](https://github.com/itk-dev/economics/pull/175)
2617: Added forecast report.
* [PR-173](https://github.com/itk-dev/economics/pull/173)
2663: Workload report loading speed improvement.
* [PR-167](https://github.com/itk-dev/economics/pull/167)
Expand Down
33 changes: 33 additions & 0 deletions reports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Reports in Economics

Overview of the various reports found in Economics.

## Reports

### Sprint rapport

This report offers an overview of a project with its name, version, and work hours. It details hours
spent and remaining. If applicable, project budget details are included. The report shows tables with
work hours breakdown for each task in current and future sprints. Thus, giving a view of project progress.

### Ledelsesrapport

This financial report provides an itemized overview of an organization's invoices by year and
quarter. It offers yearly totals and individual quarter breakdowns. There's functionality to export
data. The report timeframe can be adjusted, aiding comprehensive financial analysis.

### Forecast rapport

This report presents invoiced and recorded work hours for each project and its issues. A
hierarchical view for clarity is available. It indicates unbilled hours and provides an overall total.

### Timerapport

This report offers an overview of estimated and logged work hours grouped by project tags. Each tag
represents a project and the detailed hours per ticket. The report concludes with totals.

### Normtidsrapport

This workforce report provides a detailed breakdown of individual workloads over different
periods. It presents each worker's total workload and specific contribution percentages each period.
The report also includes an average workload percentage.
60 changes: 60 additions & 0 deletions src/Controller/ForecastReportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace App\Controller;

use App\Form\ForecastReportType;
use App\Model\Reports\ForecastReportFormData;
use App\Service\ForecastReportService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/admin/reports/forecast_report')]
#[IsGranted('ROLE_REPORT')]
class ForecastReportController extends AbstractController
{
public function __construct(
private readonly ForecastReportService $forecastReportService,
) {
}

/**
* @throws \Exception
*/
#[Route('/', name: 'app_forecast_report')]
public function index(Request $request): Response
{
$reportData = null;
$error = null;
$mode = 'forecast_report';
$reportFormData = new ForecastReportFormData();

$form = $this->createForm(ForecastReportType::class, $reportFormData, [
'action' => $this->generateUrl('app_forecast_report'),
'method' => 'GET',
'attr' => [
'id' => 'sprint_report',
],
'csrf_protection' => false,
]);

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$fromDate = $form->get('dateFrom')->getData();
$toDate = $form->get('dateTo')->getData();

$reportData = $this->forecastReportService->getForecastReport($fromDate, $toDate);
}

return $this->render('reports/reports.html.twig', [
'controller_name' => 'ForecastReportController',
'form' => $form,
'error' => $error,
'data' => $reportData,
'mode' => $mode,
]);
}
}
65 changes: 65 additions & 0 deletions src/Form/ForecastReportType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\Form;

use App\Model\Reports\ForecastReportFormData;
use App\Service\ForecastReportService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ForecastReportType extends AbstractType
{
public function __construct(
private readonly ForecastReportService $forecastReportService,
) {
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('dateFrom', DateType::class, [
'widget' => 'single_text',
'input' => 'datetime',
'required' => false,
'label' => 'hour_report.from_date',
'label_attr' => ['class' => 'label'],
'by_reference' => true,
'data' => $options['fromDate'] ?? $this->forecastReportService->getDefaultFromDate(),
'attr' => [
'class' => 'form-element',
],
])
->add('dateTo', DateType::class, [
'widget' => 'single_text',
'input' => 'datetime',
'required' => false,
'label' => 'hour_report.to_date',
'label_attr' => ['class' => 'label'],
'data' => $options['fromDate'] ?? $this->forecastReportService->getDefaultToDate(),
'by_reference' => true,
'attr' => [
'class' => 'form-element',
],
])
->add('submit', SubmitType::class, [
'label' => 'workload_report.submit',
'attr' => [
'class' => 'hour-report-submit button',
],
]
);
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ForecastReportFormData::class,
'attr' => [
'data-sprint-report-target' => 'form',
],
]);
}
}
18 changes: 18 additions & 0 deletions src/Model/Reports/ForecastReportData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Model\Reports;

use Doctrine\Common\Collections\ArrayCollection;

class ForecastReportData
{
public float $totalInvoiced = 0;
public float $totalInvoicedAndRecorded = 0;
/** @var ArrayCollection<string, ForecastReportProjectData> */
public ArrayCollection $projects;

public function __construct()
{
$this->projects = new ArrayCollection();
}
}
9 changes: 9 additions & 0 deletions src/Model/Reports/ForecastReportFormData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Model\Reports;

class ForecastReportFormData
{
public \DateTimeInterface $dateFrom;
public \DateTimeInterface $dateTo;
}
19 changes: 19 additions & 0 deletions src/Model/Reports/ForecastReportIssueData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Model\Reports;

class ForecastReportIssueData
{
public string $issueId;
public string $issueTag;
public string $issueLink;
public float $invoiced = 0.0;
public float $invoicedAndRecorded = 0.0;
/** @var array<string, ForecastReportIssueVersionData> */
public array $versions = [];

public function __construct(string $issueTag)
{
$this->issueTag = $issueTag;
}
}
18 changes: 18 additions & 0 deletions src/Model/Reports/ForecastReportIssueVersionData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Model\Reports;

class ForecastReportIssueVersionData
{
public string $issueVersion;
public string $issueVersionIdentifier;
public float $invoiced = 0.0;
public float $invoicedAndRecorded = 0.0;
/** @var array<string, ForecastReportWorklogData> */
public array $worklogs = [];

public function __construct(string $issueVersion)
{
$this->issueVersion = $issueVersion;
}
}
18 changes: 18 additions & 0 deletions src/Model/Reports/ForecastReportProjectData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Model\Reports;

class ForecastReportProjectData
{
public string $projectId;
public string $projectName;
public float $invoiced = 0.0;
public float $invoicedAndRecorded = 0.0;
/** @var array<string, ForecastReportIssueData> */
public array $issues = [];

public function __construct(string $projectId)
{
$this->projectId = $projectId;
}
}
19 changes: 19 additions & 0 deletions src/Model/Reports/ForecastReportWorklogData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Model\Reports;

class ForecastReportWorklogData
{
public string $description;
public string $worker;
public float $invoiced = 0.0;
public float $invoicedAndRecorded = 0.0;

/**
* @param $worklogId
* @param $description
*/
public function __construct($worklogId, $description)
{
}
}
34 changes: 34 additions & 0 deletions src/Repository/WorklogRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Enum\BillableKindsEnum;
use App\Model\Invoices\InvoiceEntryWorklogsFilterData;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Persistence\ManagerRegistry;

/**
Expand Down Expand Up @@ -133,4 +134,37 @@ public function findBillableWorklogsByWorkerAndDateRange(string $workerIdentifie
])
->getQuery()->getResult();
}

/**
* @throws \Exception
*/
public function getWorklogsAttachedToInvoiceInDateRange(\DateTimeInterface $periodStart, \DateTimeInterface $periodEnd, int $page = 1, int $pageSize = 50): array
{
$from = new \DateTimeImmutable($periodStart->format('Y-m-d').' 00:00:00');
$to = new \DateTimeImmutable($periodEnd->format('Y-m-d').' 23:59:59');

$query = $this->createQueryBuilder('worklog')
->leftJoin(Issue::class, 'issue', 'WITH', 'worklog.issue = issue.id')
->leftJoin(Project::class, 'project', 'WITH', 'issue.project = project.id')
->where('worklog.invoiceEntry IS NOT NULL')
->andWhere('worklog.started BETWEEN :from AND :to')
->setParameter('from', $from)
->setParameter('to', $to)
->getQuery()
->setFirstResult(($page - 1) * $pageSize)
->setMaxResults($pageSize);

$paginator = new Paginator($query, true);

$totalItemCount = count($paginator);
$pagesCount = ceil($totalItemCount / $pageSize);

return [
'total_count' => $totalItemCount,
'pages_count' => $pagesCount,
'current_page' => $page,
'page_size' => $pageSize,
'paginator' => $paginator,
];
}
}
Loading
Loading