Skip to content

Commit

Permalink
imp: Display total spent time in tickets lists
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed Dec 10, 2024
1 parent 1ed9ade commit 97189b5
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/Entity/Ticket.php
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,44 @@ public function removeTimeSpent(TimeSpent $timeSpent): static
return $this;
}

/**
* @param 'accounted'|'real'|'unaccounted' $type
*/
public function getSumTimeSpent(string $type): int
{
/** @var ArrayCollection<int, TimeSpent> */
$timeSpents = $this->timeSpents;

if ($type === 'accounted') {
$criteria = Criteria::create();
$expr = Criteria::expr()->neq('contract', null);
$criteria->where($expr);

$timeSpents = $timeSpents->matching($criteria)->toArray();
$times = array_map(function ($timeSpent): int {
return $timeSpent->getTime();
}, $timeSpents);
} elseif ($type === 'unaccounted') {
$criteria = Criteria::create();
$expr = Criteria::expr()->isNull('contract');
$criteria->where($expr);

$timeSpents = $timeSpents->matching($criteria)->toArray();
$times = array_map(function ($timeSpent): int {
return $timeSpent->getTime();
}, $timeSpents);
} elseif ($type === 'real') {
$timeSpents = $timeSpents->toArray();
$times = array_map(function ($timeSpent): int {
return $timeSpent->getRealTime();
}, $timeSpents);
} else {
throw new \DomainException("Unexpected type {$type} (possible values: accounted, unaccounted, real)");
}

return array_sum($times);
}

public function getUniqueKey(): string
{
$createdAt = '';
Expand Down
40 changes: 40 additions & 0 deletions templates/tickets/_list.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,46 @@
{% endif %}
</p>

{% set canSeeRealTimeSpent = is_granted('orga:see:tickets:time_spent:real', ticket.organization) %}
{% set canSeeAccountedTimeSpent = is_granted('orga:see:tickets:time_spent:accounted', ticket.organization) %}

{% if canSeeRealTimeSpent and canSeeAccountedTimeSpent %}
{% set accountedTime = ticket.sumTimeSpent('accounted') %}
{% set unaccountedTime = ticket.sumTimeSpent('unaccounted') %}

{% if accountedTime > 0 or unaccountedTime > 0 %}
<p class="text--small">
{% if accountedTime > 0 %}
{{ 'tickets.list.time_spent' | trans({ count: accountedTime | formatMinutes('long') }) }}

{% if unaccountedTime > 0 %}
{% endif %}
{% endif %}

{% if unaccountedTime > 0 %}
{{ 'tickets.list.time_spent.unaccounted' | trans({ count: unaccountedTime | formatMinutes('long') }) }}
{% endif %}
</p>
{% endif %}
{% elseif canSeeRealTimeSpent %}
{% set realTime = ticket.sumTimeSpent('real') %}

{% if realTime > 0 %}
<p class="text--small">
{{ 'tickets.list.time_spent' | trans({ count: realTime | formatMinutes('long') }) }}
</p>
{% endif %}
{% elseif canSeeAccountedTimeSpent %}
{% set accountedTime = ticket.sumTimeSpent('accounted') %}

{% if accountedTime > 0 %}
<p class="text--small">
{{ 'tickets.list.time_spent' | trans({ count: accountedTime | formatMinutes('long') }) }}
</p>
{% endif %}
{% endif %}

{% if ticket.labels %}
<ul class="list--nostyle flow flow--inline flow--smaller text--small">
{% for label in ticket.labels %}
Expand Down
173 changes: 173 additions & 0 deletions tests/Entity/TicketTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

// This file is part of Bileto.
// Copyright 2022-2024 Probesys
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace App\Tests\Entity;

use App\Entity\Ticket;
use App\Tests\Factory;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
//use PHPUnit\Framework\TestCase;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;

class TicketTest extends WebTestCase
{
use Factories;
use ResetDatabase;

public function testGetSumTimeSpentWithAccountedType(): void
{
$contract = Factory\ContractFactory::createOne([
'timeAccountingUnit' => 30,
]);
$ticket = Factory\TicketFactory::createOne([
'contracts' => [$contract],
]);
$accountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 30,
'contract' => $contract,
]);
$accountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 30,
'contract' => $contract,
]);
$unaccountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 15,
'contract' => null,
]);
$unaccountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 5,
'contract' => null,
]);

$time = $ticket->getSumTimeSpent('accounted');

$this->assertSame(60, $time);
}

public function testGetSumTimeSpentWithUnaccountedType(): void
{
$contract = Factory\ContractFactory::createOne([
'timeAccountingUnit' => 30,
]);
$ticket = Factory\TicketFactory::createOne([
'contracts' => [$contract],
]);
$accountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 30,
'contract' => $contract,
]);
$accountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 30,
'contract' => $contract,
]);
$unaccountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 15,
'contract' => null,
]);
$unaccountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 5,
'contract' => null,
]);

$time = $ticket->getSumTimeSpent('unaccounted');

$this->assertSame(20, $time);
}

public function testGetSumTimeSpentWithRealType(): void
{
$contract = Factory\ContractFactory::createOne([
'timeAccountingUnit' => 30,
]);
$ticket = Factory\TicketFactory::createOne([
'contracts' => [$contract],
]);
$accountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 30,
'contract' => $contract,
]);
$accountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 30,
'contract' => $contract,
]);
$unaccountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 15,
'contract' => null,
]);
$unaccountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 5,
'contract' => null,
]);

$time = $ticket->getSumTimeSpent('real');

$this->assertSame(40, $time);
}

public function testGetSumTimeSpentWithAnotherType(): void
{
$contract = Factory\ContractFactory::createOne([
'timeAccountingUnit' => 30,
]);
$ticket = Factory\TicketFactory::createOne([
'contracts' => [$contract],
]);
$accountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 30,
'contract' => $contract,
]);
$accountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 30,
'contract' => $contract,
]);
$unaccountedTimeSpent1 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 15,
'time' => 15,
'contract' => null,
]);
$unaccountedTimeSpent2 = Factory\TimeSpentFactory::createOne([
'ticket' => $ticket,
'realTime' => 5,
'time' => 5,
'contract' => null,
]);

$this->expectException(\DomainException::class);

/** @phpstan-ignore argument.type */
$ticket->getSumTimeSpent('invalid value');
}
}
7 changes: 7 additions & 0 deletions tests/Factory/TimeSpentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ final class TimeSpentFactory extends PersistentProxyObjectFactory
*/
protected function defaults(): array
{
$user = UserFactory::new();
$date = \DateTimeImmutable::createFromMutable(self::faker()->dateTime());

return [
'createdAt' => $date,
'createdBy' => $user,
'updatedAt' => $date,
'updatedBy' => $user,
'ticket' => TicketFactory::new(),
'time' => self::faker()->numberBetween(1, 100),
'realTime' => self::faker()->numberBetween(1, 100),
Expand Down
2 changes: 2 additions & 0 deletions translations/messages+intl-icu.en_GB.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ tickets.list.incident_opened_on: 'Incident opened on <time datetime="{dateIso}">
tickets.list.incident_updated_on: 'Incident updated on <time datetime="{dateIso}">{date}</time>'
tickets.list.request_opened_on: 'Request opened on <time datetime="{dateIso}">{date}</time>'
tickets.list.request_updated_on: 'Request updated on <time datetime="{dateIso}">{date}</time>'
tickets.list.time_spent: '{ count } spent'
tickets.list.time_spent.unaccounted: '{ count } unaccounted'
tickets.new.mark_as_resolved: 'Ticket already resolved'
tickets.new.submit: 'Create the ticket'
tickets.new.title: 'New ticket'
Expand Down
2 changes: 2 additions & 0 deletions translations/messages+intl-icu.fr_FR.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ tickets.list.incident_opened_on: 'Incident ouvert le <time datetime="{dateIso}">
tickets.list.incident_updated_on: 'Incident mis à jour le <time datetime="{dateIso}">{date}</time>'
tickets.list.request_opened_on: 'Demande ouverte le <time datetime="{dateIso}">{date}</time>'
tickets.list.request_updated_on: 'Demande mise à jour le <time datetime="{dateIso}">{date}</time>'
tickets.list.time_spent: '{ count } passée(s)'
tickets.list.time_spent.unaccounted: '{ count } non comptabilisée(s)'
tickets.new.mark_as_resolved: 'Ticket déjà résolu'
tickets.new.submit: 'Créer le ticket'
tickets.new.title: 'Nouveau ticket'
Expand Down

0 comments on commit 97189b5

Please sign in to comment.