Skip to content

Commit

Permalink
imp: Change wording from billed/charged to accounted time
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed Feb 1, 2024
1 parent 77ab61f commit ea707ae
Show file tree
Hide file tree
Showing 18 changed files with 165 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

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

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Platforms\MariaDBPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20240201145738RenameContractBillingIntervalToTimeAccountingUnit extends AbstractMigration
{
public function getDescription(): string
{
return 'Rename the contract billingInterval column to timeAccountingUnit.';
}

public function up(Schema $schema): void
{
$dbPlatform = $this->connection->getDatabasePlatform();
if ($dbPlatform instanceof PostgreSQLPlatform) {
$this->addSql('ALTER TABLE contract RENAME COLUMN billing_interval TO time_accounting_unit');
} elseif ($dbPlatform instanceof MariaDBPlatform) {
$this->addSql('ALTER TABLE contract CHANGE billing_interval time_accounting_unit INT DEFAULT 0 NOT NULL');
}
}

public function down(Schema $schema): void
{
$dbPlatform = $this->connection->getDatabasePlatform();
if ($dbPlatform instanceof PostgreSQLPlatform) {
$this->addSql('ALTER TABLE contract RENAME COLUMN time_accounting_unit TO billing_interval');
} elseif ($dbPlatform instanceof MariaDBPlatform) {
$this->addSql('ALTER TABLE contract CHANGE time_accounting_unit billing_interval INT DEFAULT 0 NOT NULL');
}
}
}
12 changes: 6 additions & 6 deletions src/Controller/Tickets/ContractsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use App\Repository\TicketRepository;
use App\Repository\TimeSpentRepository;
use App\Service\Sorter\ContractSorter;
use App\Service\ContractBilling;
use App\Service\ContractTimeAccounting;
use App\Utils\ConstraintErrorsFormatter;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -58,7 +58,7 @@ public function update(
EntityEventRepository $entityEventRepository,
TicketRepository $ticketRepository,
TimeSpentRepository $timeSpentRepository,
ContractBilling $contractBilling,
ContractTimeAccounting $contractTimeAccounting,
ContractSorter $contractSorter,
TranslatorInterface $translator,
): Response {
Expand All @@ -74,7 +74,7 @@ public function update(

$ongoingContractUid = $request->request->getString('ongoingContractUid');

$chargeTimeSpent = $request->request->getBoolean('chargeTimeSpent');
$includeUnaccountedTime = $request->request->getBoolean('includeUnaccountedTime');

$csrfToken = $request->request->getString('_csrf_token');

Expand Down Expand Up @@ -126,9 +126,9 @@ public function update(
$entityEventRepository->save($entityEvent, true);
}

if ($chargeTimeSpent && $newOngoingContract) {
$timeSpents = $ticket->getTimeSpentsNotCharged()->getValues();
$contractBilling->chargeTimeSpents($newOngoingContract, $timeSpents);
if ($includeUnaccountedTime && $newOngoingContract) {
$timeSpents = $ticket->getUnaccountedTimeSpents()->getValues();
$contractTimeAccounting->accountTimeSpents($newOngoingContract, $timeSpents);
$timeSpentRepository->saveBatch($timeSpents, true);
}

Expand Down
16 changes: 8 additions & 8 deletions src/Controller/Tickets/MessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use App\Repository\TicketRepository;
use App\Repository\TimeSpentRepository;
use App\Security\Authorizer;
use App\Service\ContractBilling;
use App\Service\ContractTimeAccounting;
use App\Service\TicketTimeline;
use App\TicketActivity\MessageEvent;
use App\TicketActivity\TicketEvent;
Expand All @@ -41,7 +41,7 @@ public function create(
OrganizationRepository $organizationRepository,
TicketRepository $ticketRepository,
TimeSpentRepository $timeSpentRepository,
ContractBilling $contractBilling,
ContractTimeAccounting $contractTimeAccounting,
TicketTimeline $ticketTimeline,
Authorizer $authorizer,
ValidatorInterface $validator,
Expand Down Expand Up @@ -153,18 +153,18 @@ public function create(
$contract = $ticket->getOngoingContract();

if ($contract) {
$timeSpent = $contractBilling->chargeTime($contract, $minutesSpent);
$timeSpent = $contractTimeAccounting->accountTime($contract, $minutesSpent);
$timeSpent->setTicket($ticket);
$timeSpentRepository->save($timeSpent, true);

// Calculate the remaining time that is not charged (e.g.
// Calculate the remaining time that is not accounted (i.e.
// because there wasn't enough time in the contract).
$remainingNotChargedTime = $minutesSpent - $timeSpent->getRealTime();
if ($remainingNotChargedTime > 0) {
$remainingUnaccountedTime = $minutesSpent - $timeSpent->getRealTime();
if ($remainingUnaccountedTime > 0) {
$timeSpent = new TimeSpent();
$timeSpent->setTicket($ticket);
$timeSpent->setTime($remainingNotChargedTime);
$timeSpent->setRealTime($remainingNotChargedTime);
$timeSpent->setTime($remainingUnaccountedTime);
$timeSpent->setRealTime($remainingUnaccountedTime);
$timeSpentRepository->save($timeSpent, true);
}
} else {
Expand Down
14 changes: 7 additions & 7 deletions src/Entity/Contract.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class Contract implements MonitorableEntityInterface, UidEntityInterface
private Collection $timeSpents;

#[ORM\Column(options: ["default" => 0])]
private ?int $billingInterval = null;
private ?int $timeAccountingUnit = null;

#[ORM\Column(options: ['default' => 0])]
private ?int $hoursAlert = null;
Expand All @@ -121,7 +121,7 @@ public function __construct()
$this->maxHours = 10;
$this->startAt = Utils\Time::now();
$this->endAt = Utils\Time::relative('last day of december');
$this->billingInterval = 0;
$this->timeAccountingUnit = 0;
$this->notes = '';
$this->hoursAlert = 0;
$this->dateAlert = 0;
Expand Down Expand Up @@ -349,14 +349,14 @@ public function removeTimeSpent(TimeSpent $timeSpent): static
return $this;
}

public function getBillingInterval(): ?int
public function getTimeAccountingUnit(): ?int
{
return $this->billingInterval;
return $this->timeAccountingUnit;
}

public function setBillingInterval(int $billingInterval): static
public function setTimeAccountingUnit(int $timeAccountingUnit): static
{
$this->billingInterval = $billingInterval;
$this->timeAccountingUnit = $timeAccountingUnit;

return $this;
}
Expand Down Expand Up @@ -457,7 +457,7 @@ public function getRenewed(): Contract
$contract->setStartAt($startAt);
$endAt = $startAt->modify('last day of december');
$contract->setEndAt($endAt);
$contract->setBillingInterval($this->getBillingInterval());
$contract->setTimeAccountingUnit($this->getTimeAccountingUnit());
$contract->setNotes($this->getNotes());
$contract->setHoursAlert($this->getHoursAlert());
$contract->setDateAlert($this->getDateAlert());
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Ticket.php
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ public function getTimeSpents(): Collection
/**
* @return Collection<int, TimeSpent>
*/
public function getTimeSpentsNotCharged(): Collection
public function getUnaccountedTimeSpents(): Collection
{
$criteria = Criteria::create();
$expr = Criteria::expr()->isNull('contract');
Expand Down
2 changes: 1 addition & 1 deletion src/Form/Type/ContractType.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
$builder->add('maxHours', Type\IntegerType::class, [
'empty_data' => '0',
]);
$builder->add('billingInterval', Type\IntegerType::class, [
$builder->add('timeAccountingUnit', Type\IntegerType::class, [
'required' => false,
'empty_data' => '0',
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,35 @@
use App\Entity\Contract;
use App\Entity\TimeSpent;

class ContractBilling
class ContractTimeAccounting
{
/**
* Create a TimeSpent associated to the given contract.
*
* The resulting TimeSpent will not necessarily have the exact $time
* amount. For instance, if $time is greater than the time available in the
* contract, the TimeSpent will only be charged with the available time
* contract, the TimeSpent will only be accounted for the available time
* (i.e. an additional TimeSpent must be created outside of this method).
*
* The time is expressed in minutes.
*/
public function chargeTime(Contract $contract, int $time): TimeSpent
public function accountTime(Contract $contract, int $time): TimeSpent
{
$availableTime = $contract->getRemainingMinutes();
$billingInterval = $contract->getBillingInterval();
$timeAccountingUnit = $contract->getTimeAccountingUnit();

// If there is more spent time than time available in the contract, we
// don't want to charge the entire time. So we just charge the
// don't want to account the entire time. So we just account the
// available time. Then, the remaining time will be set in a separated
// uncharged TimeSpent.
// TimeSpent.
if ($time > $availableTime) {
$time = $availableTime;
}

$timeCharged = $this->calculateChargedTime($time, $billingInterval, $availableTime);
$timeAccounted = $this->calculateAccountedTime($time, $timeAccountingUnit, $availableTime);

$timeSpent = new TimeSpent();
$timeSpent->setTime($timeCharged);
$timeSpent->setTime($timeAccounted);
$timeSpent->setRealTime($time);
$timeSpent->setContract($contract);

Expand All @@ -53,40 +53,44 @@ public function chargeTime(Contract $contract, int $time): TimeSpent
*
* @param TimeSpent[] $timeSpents
*/
public function chargeTimeSpents(Contract $contract, array $timeSpents): void
public function accountTimeSpents(Contract $contract, array $timeSpents): void
{
$availableTime = $contract->getRemainingMinutes();
$billingInterval = $contract->getBillingInterval();
$timeAccountingUnit = $contract->getTimeAccountingUnit();

foreach ($timeSpents as $timeSpent) {
// If there is more spent time than time available in the contract,
// we consider that we can't charge more time so we stop here.
// we consider that we can't account more time so we stop here.
if ($timeSpent->getRealTime() > $availableTime) {
break;
}

$timeCharged = $this->calculateChargedTime($timeSpent->getRealTime(), $billingInterval, $availableTime);
$timeAccounted = $this->calculateAccountedTime(
$timeSpent->getRealTime(),
$timeAccountingUnit,
$availableTime
);

$timeSpent->setTime($timeCharged);
$timeSpent->setTime($timeAccounted);
$timeSpent->setContract($contract);

$availableTime = $availableTime - $timeCharged;
$availableTime = $availableTime - $timeAccounted;
}
}

/**
* Round up time to a multiplier of billing interval (in the limit of the
* available time).
* Round up time to a multiplier of the time accounting unit (in the limit
* of the available time).
*/
private function calculateChargedTime(int $time, int $billingInterval, int $availableTime): int
private function calculateAccountedTime(int $time, int $timeAccountingUnit, int $availableTime): int
{
if ($billingInterval > 0) {
// If the billing interval is set, round up the time charged.
$timeCharged = intval(ceil($time / $billingInterval)) * $billingInterval;
if ($timeAccountingUnit > 0) {
// If the time accounting unit is set, round up the time charged.
$timeAccounted = intval(ceil($time / $timeAccountingUnit)) * $timeAccountingUnit;
// But keep it lower than the available time in the contract.
// Note: this could be debated as, contractually, more time should
// be charged. But in our case, it's how we handle the case.
return min($timeCharged, $availableTime);
return min($timeAccounted, $availableTime);
} else {
return $time;
}
Expand Down
22 changes: 11 additions & 11 deletions templates/contracts/_form.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -104,32 +104,32 @@
</div>

<div class="flow flow--small">
<label for="{{ field_id(form.billingInterval) }}">
{{ 'contracts.form.billing_interval' | trans }}
<label for="{{ field_id(form.timeAccountingUnit) }}">
{{ 'contracts.form.time_accounting_unit' | trans }}

<small class="text--secondary">
{{ 'forms.optional' | trans }}
</small>
</label>

<p class="form__caption" id="{{ field_id(form.billingInterval, 'caption') }}">
{{ 'contracts.form.billing_interval.caption' | trans }}
<p class="form__caption" id="{{ field_id(form.timeAccountingUnit, 'caption') }}">
{{ 'contracts.form.time_accounting_unit.caption' | trans }}
</p>

{{ form_errors(form.billingInterval) }}
{{ form_errors(form.timeAccountingUnit) }}

<input
type="number"
id="{{ field_id(form.billingInterval) }}"
name="{{ field_name(form.billingInterval) }}"
value="{{ field_value(form.billingInterval) }}"
id="{{ field_id(form.timeAccountingUnit) }}"
name="{{ field_name(form.timeAccountingUnit) }}"
value="{{ field_value(form.timeAccountingUnit) }}"
min="0"
step="1"
class="input--size3"
aria-describedby="{{ field_id(form.billingInterval, 'caption') }}"
{% if field_has_errors(form.billingInterval) %}
aria-describedby="{{ field_id(form.timeAccountingUnit, 'caption') }}"
{% if field_has_errors(form.timeAccountingUnit) %}
aria-invalid="true"
aria-errormessage="{{ field_id(form.billingInterval, 'error') }}"
aria-errormessage="{{ field_id(form.timeAccountingUnit, 'error') }}"
{% endif %}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/contracts/show.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
{% endif %}
</div>

{% if contract.billingInterval %}
{% if contract.timeAccountingUnit %}
<p>
{{ 'contracts.show.billing_interval' | trans({ count: contract.billingInterval }) }}
{{ 'contracts.show.time_accounting_unit' | trans({ count: contract.timeAccountingUnit }) }}
</p>
{% endif %}

Expand Down
4 changes: 2 additions & 2 deletions templates/tickets/_timeline_time_spent.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

{% if timeSpent.contract is null %}
<span class="text--secondary text--small">
{{ 'tickets.show.time_spent.not_charged' | trans }}
{{ 'tickets.show.time_spent.unaccounted' | trans }}
</span>
{% elseif timeSpent.realTime != timeSpent.time %}
<span class="text--secondary text--small">
{{ 'tickets.show.time_spent.charged' | trans({ count: timeSpent.time }) }}
{{ 'tickets.show.time_spent.accounted' | trans({ count: timeSpent.time }) }}
</span>
{% endif %}
</div>
Expand Down
8 changes: 4 additions & 4 deletions templates/tickets/contracts/edit.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@
<div>
<input
type="checkbox"
id="charge-time-spent"
name="chargeTimeSpent"
id="include-unaccounted-time"
name="includeUnaccountedTime"
/>

<label for="charge-time-spent">
{{ 'tickets.contracts.edit.bill_not_charged_time_spent' | trans }}
<label for="include-unaccounted-time">
{{ 'tickets.contracts.edit.include_unaccounted_time' | trans }}
</label>
</div>

Expand Down
Loading

0 comments on commit ea707ae

Please sign in to comment.