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

Invoicing rate report #182

Open
wants to merge 97 commits into
base: feature/3489-workload-report-period-average-etc
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
afc2f0d
Removed DataProvider related fields from WorkloadReport form
jeppekroghitk Nov 13, 2024
fbbce5f
Added new tag entity
jeppekroghitk Nov 14, 2024
27b5b8e
Added tag repo
jeppekroghitk Nov 14, 2024
d59b13c
Modified getWorklogDataCollection method signature
jeppekroghitk Nov 14, 2024
b8ca65f
Changed method for getting paged Worklog data in LeantimeApiService
jeppekroghitk Nov 14, 2024
bc6406c
Introduced new Entity IssueEpic
jeppekroghitk Nov 22, 2024
ff7bf4d
Removed irrelevant comment
jeppekroghitk Jan 2, 2025
5c682ee
Added example SQL query for fetching billable worklogs
jeppekroghitk Nov 22, 2024
0df5ea2
Minor formatting improvements in WorkloadReportService
jeppekroghitk Nov 22, 2024
a4d61c6
Deleted tag related files
jeppekroghitk Nov 22, 2024
6cdf5ac
Created migration
jeppekroghitk Nov 22, 2024
bf34980
Improve memory usage output and progress logic
jeppekroghitk Nov 22, 2024
8e689c6
Create new VersionFilterType form
jeppekroghitk Nov 22, 2024
aef86af
Update VersionFilterData properties
jeppekroghitk Nov 22, 2024
b0e4b7f
Add method to get paginated and filtered versions
jeppekroghitk Nov 22, 2024
cd54e72
Modify the method for syncing worklogs to process in batches
jeppekroghitk Nov 22, 2024
43408d2
Remove unused method from LeantimeApiService
jeppekroghitk Nov 22, 2024
e541ad3
Add navigation link to versions in the template
jeppekroghitk Nov 22, 2024
f296fef
Added version controller
jeppekroghitk Nov 22, 2024
b935df2
Added version templates
jeppekroghitk Nov 22, 2024
66fea24
Minor adjustments
jeppekroghitk Nov 25, 2024
b7bb7a7
Removed ui for versions
jeppekroghitk Dec 17, 2024
9270686
Removed version page from navigation and fixed key value in worklaod …
jeppekroghitk Dec 17, 2024
91e39bf
Updated changelog
jeppekroghitk Dec 17, 2024
b90670a
Coding standards
jeppekroghitk Dec 17, 2024
c3cf0e8
Coding standards
jeppekroghitk Dec 17, 2024
e418f96
Removed migration
jeppekroghitk Dec 17, 2024
34485b6
Added migration
jeppekroghitk Dec 17, 2024
0e51203
Removed sql example
jeppekroghitk Jan 2, 2025
2f96091
added invoicing rate report controller
jeppekroghitk Dec 17, 2024
337a8a2
Added controller form definitions
jeppekroghitk Dec 17, 2024
0b82b9a
Added service
jeppekroghitk Dec 17, 2024
12bbec7
Added invoicing report template
jeppekroghitk Dec 17, 2024
a64a55b
Added method for only getting workers that has includeInReports set t…
jeppekroghitk Dec 17, 2024
c7aa16c
Added ui for new includeInReports for worker
jeppekroghitk Dec 17, 2024
e488260
Added stimulus controller for highlighting th and first td for hovere…
jeppekroghitk Dec 17, 2024
eb0392f
Added translations
jeppekroghitk Dec 17, 2024
633b297
Added invoicing report to navigation
jeppekroghitk Dec 17, 2024
a50f1ed
Added classes for special cell display and table highlighting
jeppekroghitk Dec 17, 2024
bd83e24
Added worker includeInReports boolean
jeppekroghitk Dec 17, 2024
6a18845
Defined invoicing report data structure
jeppekroghitk Dec 17, 2024
b9dc9d9
Added nonbillableversionsenum
jeppekroghitk Dec 17, 2024
9d0f34f
Coding standards
jeppekroghitk Dec 17, 2024
2111aa5
Coding standards
jeppekroghitk Dec 17, 2024
92d59c2
Fixed migrations
jeppekroghitk Dec 18, 2024
2562c75
Fixed issue regarding averages, and revised rounding
jeppekroghitk Dec 18, 2024
dc12de5
Fixed migrations
jeppekroghitk Dec 18, 2024
b00d8f1
Coding standards
jeppekroghitk Dec 18, 2024
9309fb2
Improve table highlight logic and enhance comments
jeppekroghitk Dec 19, 2024
c73944a
Add projectData property to InvoicingRateReportWorker
jeppekroghitk Dec 19, 2024
2c8c758
Refactor InvoicingRateReportService to handle project-based calculati…
jeppekroghitk Dec 19, 2024
9cdabdd
Enhance invoicing rate report template with project and issue details
jeppekroghitk Dec 19, 2024
68ede5d
Updated icons to use FontAwesome caret-right and caret-down SVGs
jeppekroghitk Dec 20, 2024
90f9729
Added caret-right and caret-down icons to FontAwesome library
jeppekroghitk Dec 20, 2024
52afdac
Added style for parent-child toggle with proper borders
jeppekroghitk Dec 20, 2024
96ad061
Added year and includeIssues handling in InvoicingRateReportController
jeppekroghitk Dec 20, 2024
88458ec
Added previous, current, and next years to PlanningController
jeppekroghitk Dec 20, 2024
f0c91a4
Integrated year and includeIssues inputs in InvoicingRateReportType form
jeppekroghitk Dec 20, 2024
d8de07c
Renamed includeInReports label in WorkerType form
jeppekroghitk Dec 20, 2024
51bab7e
Added includeIssues field to InvoicingRateReportData model
jeppekroghitk Dec 20, 2024
ff0aa37
Added year and includeIssues fields to InvoicingRateReportFormData
jeppekroghitk Dec 20, 2024
35c930f
Enhanced invoicing report with year and includeIssues support in serv…
jeppekroghitk Dec 20, 2024
c3d66ec
Updated invoicing rate report template with caret icons and condition…
jeppekroghitk Dec 20, 2024
acefc3d
Updated Danish translations for invoicing rate and workload reports
jeppekroghitk Dec 20, 2024
9e282d4
Removed inclusion of normal logged hours in dataset, as it is not showed
jeppekroghitk Dec 20, 2024
c14da1e
Revised load time estimation based on selections
jeppekroghitk Dec 20, 2024
b6f5a7d
Adjusted rendering of 0-values
jeppekroghitk Dec 20, 2024
4af65a1
Coding standards
jeppekroghitk Dec 20, 2024
2c47ebf
Updated changelog
jeppekroghitk Jan 2, 2025
21533f7
Fixed migrations
jeppekroghitk Jan 2, 2025
c4f7db8
Made selections for include_in_reports translatable
jeppekroghitk Jan 2, 2025
c7415ee
Re-added year selector for workload report
jeppekroghitk Jan 2, 2025
6c83165
Corrected syncworklogscommand
jeppekroghitk Jan 2, 2025
c67628a
Re-added years for workload report type
jeppekroghitk Jan 2, 2025
ceb8750
Removed paginatorinterface from versionRepo
jeppekroghitk Jan 2, 2025
f85fb40
Undid changes to datasyncservice
jeppekroghitk Jan 2, 2025
39d0098
Undid changes to api service
jeppekroghitk Jan 2, 2025
89ed499
Removed traces of removed method
jeppekroghitk Jan 2, 2025
5d27379
Fixed translations
jeppekroghitk Jan 2, 2025
be44345
Coding standards
jeppekroghitk Jan 2, 2025
94895e1
Fixed calculation of worker average on years not current
jeppekroghitk Jan 2, 2025
76d43af
Corrected year selection to not include next year
jeppekroghitk Jan 2, 2025
b312ec8
Coding standards
jeppekroghitk Jan 2, 2025
06a8e42
Added translations for list view of 'include in report'
jeppekroghitk Jan 3, 2025
cd1caad
Re-added removed line break
jeppekroghitk Jan 3, 2025
e0c5293
Added needed fields to invoicing rate report worker instead of extend…
jeppekroghitk Jan 3, 2025
5e82681
Coding standards
jeppekroghitk Jan 3, 2025
275ceb0
Consised setting of currentPeriodNumeric when showing years not current
jeppekroghitk Jan 6, 2025
d7f7a77
Update src/Service/InvoicingRateReportService.php
jeppekroghitk Jan 6, 2025
d926a96
Update src/Service/InvoicingRateReportService.php
jeppekroghitk Jan 6, 2025
7d854a5
Update src/Service/InvoicingRateReportService.php
jeppekroghitk Jan 6, 2025
a2c2c7b
Update src/Service/InvoicingRateReportService.php
jeppekroghitk Jan 6, 2025
0f1b48a
Update src/Service/InvoicingRateReportService.php
jeppekroghitk Jan 6, 2025
a21a67a
Update src/Service/InvoicingRateReportService.php
jeppekroghitk Jan 6, 2025
0486a85
Corrected changelog
jeppekroghitk Jan 6, 2025
e57d813
Removed issueepic entity
jeppekroghitk Jan 6, 2025
e360934
Removed duplicate definitions of sum arrays
jeppekroghitk Jan 6, 2025
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-182](https://github.com/itk-dev/economics/pull/182)
2597: Added invoicing rate report
* [PR-184](https://github.com/itk-dev/economics/pull/184)
3489: Workload report period averages.
* [PR-183](https://github.com/itk-dev/economics/pull/183)
Expand Down
71 changes: 71 additions & 0 deletions assets/controllers/table_highlight_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Controller } from "@hotwired/stimulus";

/**
* Table highlight controller.
*
* Highlights the `<thead>` and first cell (`<th>` or `<td>`) of the row when
* hovering over a cell.
*/
export default class extends Controller {
connect() {
this.element.addEventListener(
"mouseenter",
(event) => this.highlight(event),
true,
);
this.element.addEventListener(
"mouseleave",
(event) => this.clearHighlights(event),
true,
);
}

/**
* Handles the highlighting of the appropriate `<thead>` column and row
* header.
*
* @param {MouseEvent} event
*/
highlight(event) {
// Only run if hovering a `<td>` (and not child elements like <span> or <a>)
if (event.target.tagName !== "TD") return;

const cell = event.target; // The actual hovered cell

// Find the index of the hovered cell (column index)
const cellIndex = Array.from(cell.parentNode.children).indexOf(cell);

// Highlight the corresponding column header (<th>) in the `<thead>`
const columnHeader = this.element.querySelector(
`thead th:nth-child(${cellIndex + 1})`,
);
if (columnHeader) columnHeader.classList.add("highlight-column");

// Highlight the first cell in the row (supports both <td> and <th>)
const rowStartCell = cell.parentNode.querySelector(
"th:first-child, td:first-child",
);
if (rowStartCell) rowStartCell.classList.add("highlight-row");
}

/**
* Clears all highlights when leaving a cell.
*
* @param {MouseEvent} event
*/
clearHighlights(event) {
// Only run if leaving a `<td>` (and not child elements)
if (event.target.tagName !== "TD") return;

// Remove the highlight class from all highlighted elements
this.element.querySelectorAll(".highlight-column").forEach((header) => {
header.classList.remove("highlight-column");
});

this.element
.querySelectorAll(".highlight-row")
.forEach((rowStartCell) => {
rowStartCell.classList.remove("highlight-row");
});
}
}
8 changes: 2 additions & 6 deletions assets/controllers/toggle-parent-child_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ export default class extends Controller {

displayChildrenForParentIds = [];

svgExpand = `<svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"></path>
</svg>`;
svgExpand = `<svg class="svg-inline--fa fa-caret-right" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-right" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512" data-fa-i2svg=""><path fill="currentColor" d="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"></path></svg>`;

svgCollapse = `<svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"></path>
</svg>`;
svgCollapse = `<svg class="svg-inline--fa fa-caret-down" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-down" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" data-fa-i2svg=""><path fill="currentColor" d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"></path></svg>`;

connect() {
this.parentTargets.forEach((target) => {
Expand Down
4 changes: 3 additions & 1 deletion assets/fontawesome.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
faMaximize,
faEyeSlash,
faMinimize,
faCaretRight,
faCaretDown,
} from "@fortawesome/free-solid-svg-icons";

library.add(faMaximize, faEyeSlash, faMinimize);
library.add(faMaximize, faEyeSlash, faMinimize, faCaretRight, faCaretDown);
dom.i2svg();
12 changes: 12 additions & 0 deletions assets/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -454,5 +454,17 @@
@apply text-right;
max-width: 200px;
}
.double-number-container {
span {
max-width: 47px;
}
}
.highlight-column, .highlight-row {
border: 1px solid #fff !important;
}

tr[data-toggle-parent-child-target="child"]:not(.hidden) + tr[data-toggle-parent-child-target="parent"] > td,
tr[data-toggle-parent-child-target="child"]:not(.hidden) + tr[data-toggle-parent-child-target="parent"] > th {
border-top: 1px solid #fff;
}
}
31 changes: 31 additions & 0 deletions migrations/Version20250102120303.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250102120303 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE worker ADD include_in_reports TINYINT(1) DEFAULT 1 NOT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE worker DROP include_in_reports');
}
}
72 changes: 72 additions & 0 deletions src/Controller/InvoicingRateReportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Controller;

use App\Form\InvoicingRateReportType;
use App\Model\Reports\InvoicingRateReportFormData;
use App\Model\Reports\InvoicingRateReportViewModeEnum;
use App\Model\Reports\WorkloadReportPeriodTypeEnum as PeriodTypeEnum;
use App\Service\InvoicingRateReportService;
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/invoicing_rate_report')]
#[IsGranted('ROLE_REPORT')]
class InvoicingRateReportController extends AbstractController
{
public function __construct(
private readonly InvoicingRateReportService $invoicingRateReportService,
) {
}

/**
* @throws \DateMalformedStringException
*/
#[Route('/', name: 'app_invoicing_rate_report')]
public function index(Request $request): Response
{
$reportData = null;
$error = null;
$mode = 'invoicing_rate_report';
$reportFormData = new InvoicingRateReportFormData();

$form = $this->createForm(InvoicingRateReportType::class, $reportFormData, [
'action' => $this->generateUrl('app_invoicing_rate_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()) {
$viewPeriodType = $form->get('viewPeriodType')->getData() ?? PeriodTypeEnum::WEEK;
$viewMode = InvoicingRateReportViewModeEnum::SUMMARY;
$year = $form->get('year')->getData();
$includeIssues = $form->get('includeIssues')->getData();

try {
$reportData = $this->invoicingRateReportService->getInvoicingRateReport($year, $viewPeriodType, $viewMode, $includeIssues);
} catch (\Exception $e) {
$error = $e->getMessage();
}
}

return $this->render('reports/reports.html.twig', [
'controller_name' => 'InvoicingRateReportController',
'form' => $form,
'error' => $error,
'data' => $reportData,
'mode' => $mode,
]);
}
}
6 changes: 5 additions & 1 deletion src/Controller/PlanningController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ private function preparePlanningData(Request $request): array
'attr' => [
'id' => 'sprint_report',
],
'years' => [(new \DateTime())->format('Y'), (new \DateTime())->modify('+1 year')->format('Y')],
'years' => [
(new \DateTime())->modify('-1 year')->format('Y'),
(new \DateTime())->format('Y'),
(new \DateTime())->modify('+1 year')->format('Y'),
],
'method' => 'GET',
'csrf_protection' => false,
]);
Expand Down
32 changes: 16 additions & 16 deletions src/Entity/Issue.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,49 +182,49 @@ public function setEpicName(?string $epicName): self
}

/**
* @return Collection<int, Epic>
* @return Collection<int, Version>
*/
public function getEpics(): Collection
public function getVersions(): Collection
{
return $this->epics;
return $this->versions;
}

public function addEpic(Epic $epic): self
public function addVersion(Version $version): self
{
if (!$this->epics->contains($epic)) {
$this->epics->add($epic);
if (!$this->versions->contains($version)) {
$this->versions->add($version);
}

return $this;
}

public function removeEpic(Epic $epic): self
public function removeVersion(Version $version): self
{
$this->epics->removeElement($epic);
$this->versions->removeElement($version);

return $this;
}

/**
* @return Collection<int, Version>
* @return Collection<int, Epic>
*/
public function getVersions(): Collection
public function getEpics(): Collection
{
return $this->versions;
return $this->epics;
}

public function addVersion(Version $version): self
public function addEpic(Epic $epic): self
{
if (!$this->versions->contains($version)) {
$this->versions->add($version);
if (!$this->epics->contains($epic)) {
$this->epics->add($epic);
}

return $this;
}

public function removeVersion(Version $version): self
public function removeEpic(Epic $epic): self
{
$this->versions->removeElement($version);
$this->epics->removeElement($epic);

return $this;
}
Expand Down
15 changes: 15 additions & 0 deletions src/Entity/Worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class Worker
#[ORM\Column(length: 255, nullable: true)]
private ?string $name = null;

#[ORM\Column(nullable: false, options: ['default' => true])]
private bool $includeInReports = true;

public function __construct()
{
}
Expand Down Expand Up @@ -77,4 +80,16 @@ public function setName(?string $name): static

return $this;
}

public function getIncludeInReports(): bool
{
return $this->includeInReports;
}

public function setIncludeInReports(bool $includeInReports): self
{
$this->includeInReports = $includeInReports;

return $this;
}
}
4 changes: 0 additions & 4 deletions src/Enum/NonBillableEpicsEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

namespace App\Enum;

/*
* Kind is a term on a worklog in Leantime:
* https://github.com/Leantime/leantime/blob/80c4542e19692e423820bd9030907070d281571e/app/Domain/Timesheets/Services/Timesheets.php#L22
* */
enum NonBillableEpicsEnum: string
{
case UB = 'UB';
Expand Down
Loading
Loading