From dfa027297c2196cafb9784a475250bb22f4bef25 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:21:51 +0100 Subject: [PATCH 01/22] Added report data model --- .../BillableUnbilledHoursReportData.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/Model/Reports/BillableUnbilledHoursReportData.php diff --git a/src/Model/Reports/BillableUnbilledHoursReportData.php b/src/Model/Reports/BillableUnbilledHoursReportData.php new file mode 100644 index 00000000..95f0ce5b --- /dev/null +++ b/src/Model/Reports/BillableUnbilledHoursReportData.php @@ -0,0 +1,22 @@ + */ + public ArrayCollection $projectData; + + + public function __construct() + { + $this->projectData = new ArrayCollection(); + } + +} From c0cf7ed09cb1528724427240ce93879db7a58f87 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:22:06 +0100 Subject: [PATCH 02/22] Added form data model --- src/Model/Reports/BillableUnbilledHoursReportFormData.php | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/Model/Reports/BillableUnbilledHoursReportFormData.php diff --git a/src/Model/Reports/BillableUnbilledHoursReportFormData.php b/src/Model/Reports/BillableUnbilledHoursReportFormData.php new file mode 100644 index 00000000..03f29839 --- /dev/null +++ b/src/Model/Reports/BillableUnbilledHoursReportFormData.php @@ -0,0 +1,8 @@ + Date: Fri, 17 Jan 2025 14:22:29 +0100 Subject: [PATCH 03/22] Added type --- src/Form/BillableUnbilledHoursReportType.php | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/Form/BillableUnbilledHoursReportType.php diff --git a/src/Form/BillableUnbilledHoursReportType.php b/src/Form/BillableUnbilledHoursReportType.php new file mode 100644 index 00000000..b33f0108 --- /dev/null +++ b/src/Form/BillableUnbilledHoursReportType.php @@ -0,0 +1,55 @@ +add('year', ChoiceType::class, [ + 'label' => 'billable_unbilled_hours_report.year', + 'label_attr' => ['class' => 'label'], + 'attr' => ['class' => 'form-element '], + 'help_attr' => ['class' => 'form-help'], + 'row_attr' => ['class' => 'form-row'], + 'required' => false, + 'data' => $yearChoices[date('Y')], + 'choices' => $yearChoices, + 'placeholder' => null, + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'billable_unbilled_hours_report.submit', + 'attr' => [ + 'class' => 'hour-report-submit button', + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => BillableUnbilledHoursReportFormData::class, + 'attr' => [ + 'data-sprint-report-target' => 'form', + ], + 'years' => null, + ]); + } +} From c6aab0533aa37bae587c8cd8e86472c770a6edb0 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:22:56 +0100 Subject: [PATCH 04/22] Controller progress --- .../BillableUnbilledHoursReportController.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/Controller/BillableUnbilledHoursReportController.php diff --git a/src/Controller/BillableUnbilledHoursReportController.php b/src/Controller/BillableUnbilledHoursReportController.php new file mode 100644 index 00000000..9228a652 --- /dev/null +++ b/src/Controller/BillableUnbilledHoursReportController.php @@ -0,0 +1,65 @@ +createForm(BillableUnbilledHoursReportType::class, $reportFormData, [ + 'action' => $this->generateUrl('app_billable_unbilled_hours_report'), + 'method' => 'GET', + 'attr' => [ + 'id' => 'sprint_report', + ], + 'years' => [ + (new \DateTime())->modify('-1 year')->format('Y'), + (new \DateTime())->format('Y'), + ], + 'csrf_protection' => false, + ]); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $year = $form->get('year')->getData(); + + try { + $reportData = $this->billableUnbilledHoursReportService->getBillableUnbilledHoursReport($year); + } catch (\Exception $e) { + $error = $e->getMessage(); + } + } + + return $this->render('reports/reports.html.twig', [ + 'controller_name' => 'BillableUnbilledHoursReportController', + 'form' => $form, + 'error' => $error, + 'data' => $reportData, + 'mode' => $mode, + ]); + } +} From 21dbe614d74e01638d8fc7a98359740aa56f2cb8 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:23:36 +0100 Subject: [PATCH 05/22] Modified method in worklogRepository to return a broader set of data --- src/Repository/WorklogRepository.php | 34 +++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Repository/WorklogRepository.php b/src/Repository/WorklogRepository.php index 76160a0b..d969ac08 100644 --- a/src/Repository/WorklogRepository.php +++ b/src/Repository/WorklogRepository.php @@ -113,7 +113,7 @@ public function findWorklogsByWorkerAndDateRange(string $workerIdentifier, \Date ->getQuery()->getResult(); } - public function findBillableWorklogsByWorkerAndDateRange(string $workerIdentifier, \DateTime $dateFrom, \DateTime $dateTo) + public function findBillableWorklogsByWorkerAndDateRange(\DateTime $dateFrom, \DateTime $dateTo, ?string $workerIdentifier = null) { $nonBillableEpics = NonBillableEpicsEnum::getAsArray(); $nonBillableVersions = NonBillableVersionsEnum::getAsArray(); @@ -125,29 +125,31 @@ public function findBillableWorklogsByWorkerAndDateRange(string $workerIdentifie ->leftJoin('issue.epics', 'epic') ->leftJoin('issue.versions', 'version'); - return $qb - ->where($qb->expr()->between('worklog.started', ':dateFrom', ':dateTo')) - ->andWhere('worklog.worker = :worker') + $qb->where($qb->expr()->between('worklog.started', ':dateFrom', ':dateTo')) ->andWhere($qb->expr()->andX( - $qb->expr()->eq('project.isBillable', '1'), + $qb->expr()->eq('project.isBillable', '1') )) - // notIn will only work if the string it is checked against is not null ->andWhere($qb->expr()->orX( $qb->expr()->isNull('epic.title'), - $qb->expr()->notIn('epic.title', ':nonBillableEpics'), + $qb->expr()->notIn('epic.title', ':nonBillableEpics') )) ->andWhere($qb->expr()->orX( $qb->expr()->isNull('version.name'), $qb->expr()->notIn('version.name', ':nonBillableVersions') - )) - ->setParameters([ - 'worker' => $workerIdentifier, - 'dateFrom' => $dateFrom, - 'dateTo' => $dateTo, - 'nonBillableEpics' => array_values($nonBillableEpics), - 'nonBillableVersions' => array_values($nonBillableVersions), - ]) - ->getQuery()->getResult(); + )); + + // Add the worker condition only when provided + if ($workerIdentifier !== null) { + $qb->andWhere('worklog.worker = :worker') + ->setParameter('worker', $workerIdentifier); + } + + return $qb->setParameters([ + 'dateFrom' => $dateFrom, + 'dateTo' => $dateTo, + 'nonBillableEpics' => array_values($nonBillableEpics), + 'nonBillableVersions' => array_values($nonBillableVersions), + ])->getQuery()->getResult(); } public function findBilledWorklogsByWorkerAndDateRange(string $workerIdentifier, \DateTime $dateFrom, \DateTime $dateTo) From da2f4c7e9b89d25f4227ec21e830b1e7a2a70d1a Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:26:20 +0100 Subject: [PATCH 06/22] Added service --- .../BillableUnbilledHoursReportService.php | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/Service/BillableUnbilledHoursReportService.php diff --git a/src/Service/BillableUnbilledHoursReportService.php b/src/Service/BillableUnbilledHoursReportService.php new file mode 100644 index 00000000..f2ff6d33 --- /dev/null +++ b/src/Service/BillableUnbilledHoursReportService.php @@ -0,0 +1,79 @@ + $dateFrom, 'dateTo' => $dateTo] = $this->dateTimeHelper->getFirstAndLastDateOfYear($year); + + $billableWorklogs = $this->worklogRepository->findBillableWorklogsByWorkerAndDateRange($dateFrom, $dateTo); + + $projectData = []; + $projectTotals = []; // To store total hours per project + $totalHoursForAllProjects = 0; // To store the global total hours across all projects + + foreach ($billableWorklogs as $billableWorklog) { + if ($billableWorklog->isBilled() === false) { + $projectName = $billableWorklog->getProject()->getName(); + $issueName = $billableWorklog->getIssue()->getName(); + + // Initialize issue data if not already set + if (!isset($projectData[$projectName][$issueName])) { + $projectData[$projectName][$issueName] = [ + 'worklogs' => [], + 'totalHours' => 0 + ]; + } + + $workerIdentifier = $billableWorklog->getWorker(); + $workerName = $this-> + // Add the worklog to the issue + $projectData[$projectName][$issueName]['worklogs'][] = [ + "worker" => $billableWorklog->getWorker(), + "description" => $billableWorklog->getDescription(), + "hours" => $billableWorklog->getTimeSpentSeconds() * self::SECONDS_TO_HOURS + ]; + + // Increment the issue total hours + $projectData[$projectName][$issueName]['totalHours'] += $billableWorklog->getTimeSpentSeconds() * self::SECONDS_TO_HOURS; + + // Initialize project total if not already set + if (!isset($projectTotals[$projectName])) { + $projectTotals[$projectName] = 0; + } + + // Add to the project total hours + $projectTotals[$projectName] += $billableWorklog->getTimeSpentSeconds() * self::SECONDS_TO_HOURS; + + // Add to the global total hours + $totalHoursForAllProjects += $billableWorklog->getTimeSpentSeconds() * self::SECONDS_TO_HOURS; + } + } + + // Add project data, project totals, and global total to the report data + $billableUnbilledHoursReportData->projectData->add($projectData); + $billableUnbilledHoursReportData->projectTotals = $projectTotals; + $billableUnbilledHoursReportData->totalHoursForAllProjects = $totalHoursForAllProjects; + + return $billableUnbilledHoursReportData; + } + +} From dee4aa8689d75ac9c0d16b76fc33a31bbcc5d24f Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:26:58 +0100 Subject: [PATCH 07/22] Corrected use of modified worklog repo method --- src/Service/InvoicingRateReportService.php | 2 +- src/Service/WorkloadReportService.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/InvoicingRateReportService.php b/src/Service/InvoicingRateReportService.php index 1ebd8b29..849df0bb 100644 --- a/src/Service/InvoicingRateReportService.php +++ b/src/Service/InvoicingRateReportService.php @@ -254,7 +254,7 @@ private function getWorklogs(InvoicingRateReportViewModeEnum $viewMode, string $ return match ($viewMode) { InvoicingRateReportViewModeEnum::SUMMARY => [ $this->worklogRepository->findWorklogsByWorkerAndDateRange($workerIdentifier, $dateFrom, $dateTo), - $this->worklogRepository->findBillableWorklogsByWorkerAndDateRange($workerIdentifier, $dateFrom, $dateTo), + $this->worklogRepository->findBillableWorklogsByWorkerAndDateRange($dateFrom, $dateTo, $workerIdentifier), $this->worklogRepository->findBilledWorklogsByWorkerAndDateRange($workerIdentifier, $dateFrom, $dateTo), ], }; diff --git a/src/Service/WorkloadReportService.php b/src/Service/WorkloadReportService.php index b8e4a621..e3ddbd5d 100644 --- a/src/Service/WorkloadReportService.php +++ b/src/Service/WorkloadReportService.php @@ -221,7 +221,7 @@ private function getWorklogs(ViewModeEnum $viewMode, string $workerIdentifier, \ { return match ($viewMode) { ViewModeEnum::WORKLOAD => $this->worklogRepository->findWorklogsByWorkerAndDateRange($workerIdentifier, $dateFrom, $dateTo), - ViewModeEnum::BILLABLE => $this->worklogRepository->findBillableWorklogsByWorkerAndDateRange($workerIdentifier, $dateFrom, $dateTo), + ViewModeEnum::BILLABLE => $this->worklogRepository->findBillableWorklogsByWorkerAndDateRange($dateFrom, $dateTo, $workerIdentifier), ViewModeEnum::BILLED => $this->worklogRepository->findBilledWorklogsByWorkerAndDateRange($workerIdentifier, $dateFrom, $dateTo), }; } From 9fb6d64218ccd1a60fe4ea6a96b26780056d0680 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Fri, 17 Jan 2025 14:27:45 +0100 Subject: [PATCH 08/22] Added new report to navigation --- templates/components/navigation.html.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/components/navigation.html.twig b/templates/components/navigation.html.twig index 5683c5bb..134859b0 100644 --- a/templates/components/navigation.html.twig +++ b/templates/components/navigation.html.twig @@ -31,6 +31,7 @@