Skip to content

Commit

Permalink
imp: Add information about the renewed contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed Dec 12, 2024
1 parent 4eb3d3c commit d310130
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 7 deletions.
51 changes: 51 additions & 0 deletions migrations/Version20241212082910AddRenewedByToContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?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;

// phpcs:disable Generic.Files.LineLength
final class Version20241212082910AddRenewedByToContract extends AbstractMigration
{
public function getDescription(): string
{
return 'Add the renewed_by_id to the contract table';
}

public function up(Schema $schema): void
{
$dbPlatform = $this->connection->getDatabasePlatform();
if ($dbPlatform instanceof PostgreSQLPlatform) {
$this->addSql('ALTER TABLE contract ADD renewed_by_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE contract ADD CONSTRAINT FK_E98F2859B26CE351 FOREIGN KEY (renewed_by_id) REFERENCES contract (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE UNIQUE INDEX UNIQ_E98F2859B26CE351 ON contract (renewed_by_id)');
} elseif ($dbPlatform instanceof MariaDBPlatform) {
$this->addSql('ALTER TABLE contract ADD renewed_by_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE contract ADD CONSTRAINT FK_E98F2859B26CE351 FOREIGN KEY (renewed_by_id) REFERENCES contract (id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_E98F2859B26CE351 ON contract (renewed_by_id)');
}
}

public function down(Schema $schema): void
{
$dbPlatform = $this->connection->getDatabasePlatform();
if ($dbPlatform instanceof PostgreSQLPlatform) {
$this->addSql('ALTER TABLE contract DROP CONSTRAINT FK_E98F2859B26CE351');
$this->addSql('DROP INDEX UNIQ_E98F2859B26CE351');
$this->addSql('ALTER TABLE contract DROP renewed_by_id');
} elseif ($dbPlatform instanceof MariaDBPlatform) {
$this->addSql('ALTER TABLE contract DROP FOREIGN KEY FK_E98F2859B26CE351');
$this->addSql('DROP INDEX UNIQ_E98F2859B26CE351 ON contract');
$this->addSql('ALTER TABLE contract DROP renewed_by_id');
}
}
}
19 changes: 15 additions & 4 deletions src/Controller/Organizations/ContractsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,19 @@ public function new(

$fromContractUid = $request->query->getString('from');

$contract = null;
$renewedContract = null;
if ($fromContractUid) {
$contract = $contractRepository->findOneBy([
$renewedContract = $contractRepository->findOneBy([
'uid' => $fromContractUid,
]);
}

if ($contract) {
$contract = $contract->getRenewed();
if ($renewedContract && $renewedContract->getRenewedBy()) {
throw $this->createAccessDeniedException('You cannot renew a contract that has already been renewed');
}

if ($renewedContract) {
$contract = $renewedContract->getRenewed();
} else {
$contract = new Entity\Contract();
}
Expand All @@ -93,6 +97,13 @@ public function new(
$contract = $form->getData();
$contract->setOrganization($organization);
$contract->initDefaultAlerts();

if ($renewedContract) {
$renewedContract->setRenewedBy($contract);

$contractRepository->save($renewedContract);
}

$contractRepository->save($contract, true);

$contractTickets = $ticketRepository->findAssociableTickets($contract);
Expand Down
15 changes: 15 additions & 0 deletions src/Entity/Contract.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class Contract implements EntityInterface, MonitorableEntityInterface, UidEntity
#[ORM\Column(options: ['default' => 0])]
private ?int $dateAlert = null;

#[ORM\OneToOne(targetEntity: self::class)]
private ?self $renewedBy = null;

public function __construct()
{
$this->tickets = new ArrayCollection();
Expand Down Expand Up @@ -487,4 +490,16 @@ public function getUniqueKey(): string

return md5("{$this->name}-{$organization}-{$startAt}-{$endAt}");
}

public function getRenewedBy(): ?self
{
return $this->renewedBy;
}

public function setRenewedBy(?self $renewedBy): static
{
$this->renewedBy = $renewedBy;

return $this;
}
}
4 changes: 4 additions & 0 deletions templates/contracts/_list.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<time datetime="{{ contract.endAt | dateIso }}">
{{ contract.endAt | dateTrans('dd MMM yyyy') }}
</time>

{% if contract.renewedBy %}
({{ 'contracts.index.renewed' | trans }})
{% endif %}
</p>
</div>

Expand Down
18 changes: 15 additions & 3 deletions templates/contracts/show.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@
</span>
</div>

{% if contract.renewedBy %}
<p class="text--center">
{{ 'contracts.show.renewed_by' | trans }}

<a href="{{ path('contract', { uid: contract.renewedBy.uid }) }} ">
{{ contract.renewedBy.name }}
</a>
</p>
{% endif %}

{% if is_granted('orga:manage:contracts', organization) %}
<div class="text--right">
<details
Expand Down Expand Up @@ -79,9 +89,11 @@
{{ 'contracts.show.edit' | trans }}
</a>

<a class="popup__item" href="{{ path('new organization contract', { uid: organization.uid, from: contract.uid }) }}">
{{ 'contracts.show.renew' | trans }}
</a>
{% if not contract.renewedBy %}
<a class="popup__item" href="{{ path('new organization contract', { uid: organization.uid, from: contract.uid }) }}">
{{ 'contracts.show.renew' | trans }}
</a>
{% endif %}
</nav>
</details>
</div>
Expand Down
70 changes: 70 additions & 0 deletions tests/Controller/Organizations/ContractsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@ public function testGetNewFailsIfAccessIsForbidden(): void
$client->request(Request::METHOD_GET, "/organizations/{$organization->getUid()}/contracts/new");
}

public function testGetNewFailsIfRenewedContractIsAlreadyRenewed(): void
{
$this->expectException(AccessDeniedException::class);
$this->expectExceptionMessage('You cannot renew a contract that has already been renewed');

$client = static::createClient();
$user = Factory\UserFactory::createOne();
$client->loginUser($user->_real());
$organization = Factory\OrganizationFactory::createOne();
$this->grantOrga($user->_real(), [
'orga:see:contracts',
'orga:manage:contracts',
]);
$renewedContract = Factory\ContractFactory::createOne([
'organization' => $organization,
'renewedBy' => Factory\ContractFactory::createOne(),
]);

$client->catchExceptions(false);
$client->request(Request::METHOD_GET, "/organizations/{$organization->getUid()}/contracts/new", [
'from' => $renewedContract->getUid(),
]);
}

public function testPostNewCreatesAContractAndRedirects(): void
{
$client = static::createClient();
Expand Down Expand Up @@ -285,6 +309,52 @@ public function testPostNewCanAttachUnaccountedTimeSpentsToContract(): void
$this->assertSame(30, $timeSpent->getTime());
}

public function testPostNewCanRenewAContract(): void
{
$client = static::createClient();
$user = Factory\UserFactory::createOne();
$client->loginUser($user->_real());
$organization = Factory\OrganizationFactory::createOne();
$this->grantOrga($user->_real(), [
'orga:see:contracts',
'orga:manage:contracts',
]);
$renewedContract = Factory\ContractFactory::createOne([
'organization' => $organization,
]);
$name = 'My contract';
$maxHours = 10;
$startAt = new \DateTimeImmutable('2023-09-01');
$endAt = new \DateTimeImmutable('2023-12-31');
$timeAccountingUnit = 30;
$notes = 'Some notes';

$this->assertSame(1, Factory\ContractFactory::count());

$client->request(
Request::METHOD_POST,
"/organizations/{$organization->getUid()}/contracts/new?from={$renewedContract->getUid()}",
[
'contract' => [
'_token' => $this->generateCsrfToken($client, 'contract'),
'name' => $name,
'maxHours' => $maxHours,
'startAt' => $startAt->format('Y-m-d'),
'endAt' => $endAt->format('Y-m-d'),
'timeAccountingUnit' => $timeAccountingUnit,
'notes' => $notes,
],
]
);

$this->assertSame(2, Factory\ContractFactory::count());
$newContract = Factory\ContractFactory::last();
$this->assertResponseRedirects("/contracts/{$newContract->getUid()}", 302);
$renewedContract->_refresh();
$this->assertSame($name, $newContract->getName());
$this->assertSame($newContract->getId(), $renewedContract->getRenewedBy()?->getId());
}

public function testPostNewFailsIfNameIsInvalid(): void
{
$client = static::createClient();
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 @@ -121,6 +121,7 @@ contracts.index.alert: Alert
contracts.index.new_contract: 'New contract'
contracts.index.no_contracts: 'No contracts'
contracts.index.number: '{count, plural, =0 {No contracts} one {1 contract} other {# contracts}}'
contracts.index.renewed: renewed
contracts.index.tickets: '{count, plural, =0 {No tickets} one {1 ticket} other {# tickets}}'
contracts.index.title: Contracts
contracts.new.open_contract: 'Open a contract'
Expand All @@ -132,6 +133,7 @@ contracts.show.time_accounting_unit: 'Time accounting unit: {count, plural, one
contracts.show.edit: 'Edit the contract'
contracts.show.notes_confidential: Confidential
contracts.show.renew: Renew
contracts.show.renewed_by: 'Renewed by'
contracts.show.set_up_alerts: 'Set up alerts'
contracts.show.warning: Warning
contracts.sort.alphabetical: 'Alphabetical order'
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 @@ -121,6 +121,7 @@ contracts.index.alert: Alerte
contracts.index.new_contract: 'Nouveau contrat'
contracts.index.no_contracts: 'Aucun contrat'
contracts.index.number: '{count, plural, =0 {Aucun contrat} one {1 contrat} other {# contrats}}'
contracts.index.renewed: renouvelé
contracts.index.tickets: '{count, plural, =0 {Aucun ticket} one {1 ticket} other {# tickets}}'
contracts.index.title: Contrats
contracts.new.open_contract: 'Ouvrir un contrat'
Expand All @@ -132,6 +133,7 @@ contracts.show.time_accounting_unit: "Unité de comptabilisation du temps\_: {co
contracts.show.edit: 'Modifier le contrat'
contracts.show.notes_confidential: Confidentielles
contracts.show.renew: Renouveler
contracts.show.renewed_by: 'Renouvelé par'
contracts.show.set_up_alerts: 'Configurer les alertes'
contracts.show.warning: Attention
contracts.sort.alphabetical: 'Ordre alphabétique'
Expand Down

0 comments on commit d310130

Please sign in to comment.