209 lines
8.0 KiB
PHP
209 lines
8.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Entity;
|
|
|
|
use App\Entity\Application;
|
|
use App\Entity\Assignment;
|
|
use App\Entity\Destination;
|
|
use App\Entity\Disposition;
|
|
use App\Entity\Teamer;
|
|
use App\Entity\Upload;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* The two "document is due" flags on the teamer dashboard. Both rest on CarbonPeriodImmutable,
|
|
* whose isStarted() stays true once the period has begun - which is right for one of them and
|
|
* was a bug in the other, so the difference is pinned here.
|
|
*/
|
|
class DispositionTest extends TestCase
|
|
{
|
|
private const CONTRACT_DEADLINE_DAYS = 5;
|
|
private const INVOICE_DEADLINE_DAYS = 14;
|
|
|
|
public function testTheContractIsNotDueBeforeTheUploadPeriodHasRunOut(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -2 days', assignmentEndsIn: '+30 days');
|
|
|
|
$this->assertFalse($disposition->isContractDue(self::CONTRACT_DEADLINE_DAYS));
|
|
}
|
|
|
|
public function testTheContractIsDueOnceTheUploadPeriodHasRunOut(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -6 days', assignmentEndsIn: '+30 days');
|
|
|
|
$this->assertTrue($disposition->isContractDue(self::CONTRACT_DEADLINE_DAYS));
|
|
}
|
|
|
|
/**
|
|
* The upload is blocked from the day before the assignment ends, so past that point the
|
|
* dashboard would be asking for something the workflow guard refuses.
|
|
*/
|
|
public function testTheContractStopsBeingDueOnceTheUploadIsBlocked(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -60 days', assignmentEndsIn: '+1 day');
|
|
|
|
$this->assertFalse($disposition->isContractDue(self::CONTRACT_DEADLINE_DAYS));
|
|
}
|
|
|
|
/**
|
|
* A short-notice assignment can end before the normal $deadlineDays window would even
|
|
* start (assignment ends in 4 days, but the normal deadline is 5 days out). Left uncapped,
|
|
* dueDateFrom > dueDateTo made isStarted() and isEnded() equal at every instant, so this
|
|
* never returned true.
|
|
*/
|
|
public function testTheContractIsDueImmediatelyWhenTheAssignmentEndsBeforeTheNormalDeadline(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today', assignmentEndsIn: '+4 days');
|
|
|
|
$this->assertTrue($disposition->isContractDue(self::CONTRACT_DEADLINE_DAYS));
|
|
}
|
|
|
|
public function testAnUploadedContractIsNeverDue(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -60 days', assignmentEndsIn: '+30 days');
|
|
$disposition->addDocument((new Upload())->setType(Upload::TYPE_CONTRACT));
|
|
|
|
$this->assertFalse($disposition->isContractDue(self::CONTRACT_DEADLINE_DAYS));
|
|
}
|
|
|
|
public function testTheInvoiceIsNotDueWhileTheAssignmentIsStillRunning(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -30 days', assignmentEndsIn: '+5 days');
|
|
|
|
$this->assertFalse($disposition->isInvoiceDue(self::INVOICE_DEADLINE_DAYS));
|
|
}
|
|
|
|
public function testTheInvoiceIsDueOnceTheAssignmentHasEnded(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -30 days', assignmentEndsIn: '-1 day');
|
|
|
|
$this->assertTrue($disposition->isInvoiceDue(self::INVOICE_DEADLINE_DAYS));
|
|
}
|
|
|
|
/**
|
|
* Nothing blocks a late invoice upload, so unlike the contract this must keep asking.
|
|
*/
|
|
public function testTheInvoiceStaysDueAfterTheUploadPeriodHasRunOut(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today -90 days', assignmentEndsIn: '-60 days');
|
|
|
|
$this->assertTrue($disposition->isInvoiceDue(self::INVOICE_DEADLINE_DAYS));
|
|
}
|
|
|
|
/**
|
|
* A skip-formalities assignment has no contract step at all, so a placement on one starts
|
|
* where a signed and checked contract would otherwise have put it.
|
|
*/
|
|
public function testAPlacementOnASkipFormalitiesAssignmentIsConfirmedRightAway(): void
|
|
{
|
|
$disposition = $this->createDisposition(
|
|
createdAt: 'today',
|
|
assignmentEndsIn: '+30 days',
|
|
skipFormalities: true,
|
|
);
|
|
|
|
$this->assertSame(Disposition::STATUS_CONFIRMED, $disposition->getStatus());
|
|
$this->assertTrue($disposition->isSkipFormalities());
|
|
}
|
|
|
|
public function testAPlacementOnANormalAssignmentStillStartsAsNew(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today', assignmentEndsIn: '+30 days');
|
|
|
|
$this->assertSame(Disposition::STATUS_NEW, $disposition->getStatus());
|
|
$this->assertFalse($disposition->isSkipFormalities());
|
|
}
|
|
|
|
/**
|
|
* Both dates are chosen so the unflagged disposition would be due - what is pinned here is
|
|
* that the flag wins over the period maths, not that the period happens to be closed.
|
|
*/
|
|
public function testNoDocumentIsEverDueOnASkipFormalitiesAssignment(): void
|
|
{
|
|
$disposition = $this->createDisposition(
|
|
createdAt: 'today -60 days',
|
|
assignmentEndsIn: '-1 day',
|
|
skipFormalities: true,
|
|
);
|
|
|
|
$this->assertFalse($disposition->isContractDue(self::CONTRACT_DEADLINE_DAYS));
|
|
$this->assertFalse($disposition->isInvoiceDue(self::INVOICE_DEADLINE_DAYS));
|
|
}
|
|
|
|
public function testARejectedContractIsNotReportedOnASkipFormalitiesAssignment(): void
|
|
{
|
|
$disposition = $this->createDisposition(
|
|
createdAt: 'today -60 days',
|
|
assignmentEndsIn: '+30 days',
|
|
skipFormalities: true,
|
|
);
|
|
$disposition->addDocument(
|
|
(new Upload())->setType(Upload::TYPE_CONTRACT)->setStatus(Upload::STATUS_REJECTED)
|
|
);
|
|
|
|
$this->assertFalse($disposition->isContractRejected());
|
|
}
|
|
|
|
/**
|
|
* getDocumentByType() answers with the first match and is right for the Honorarvertrag and the
|
|
* Honorarnote, of which there is one each. Belege are the first type a disposition can hold
|
|
* several of, so they need the collection-returning sibling.
|
|
*/
|
|
public function testTheReceiptsOfADispositionAreReturnedAsACollection(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today', assignmentEndsIn: '-1 day');
|
|
|
|
$disposition
|
|
->addDocument((new Upload())->setType(Upload::TYPE_INVOICE))
|
|
->addDocument((new Upload())->setType(Upload::TYPE_RECEIPT)->setOriginalFilename('bahn.pdf'))
|
|
->addDocument((new Upload())->setType(Upload::TYPE_CONTRACT))
|
|
->addDocument((new Upload())->setType(Upload::TYPE_RECEIPT)->setOriginalFilename('taxi.jpg'))
|
|
;
|
|
|
|
$receipts = $disposition->getDocumentsByType(Upload::TYPE_RECEIPT);
|
|
|
|
$this->assertCount(2, $receipts);
|
|
$this->assertSame(
|
|
['bahn.pdf', 'taxi.jpg'],
|
|
array_values(array_map(
|
|
fn (Upload $upload): string => $upload->getOriginalFilename(),
|
|
$receipts->toArray()
|
|
))
|
|
);
|
|
|
|
// The single-result sibling keeps working for the types that only ever have one.
|
|
$this->assertSame(Upload::TYPE_INVOICE, $disposition->getDocumentByType(Upload::TYPE_INVOICE)?->getType());
|
|
}
|
|
|
|
public function testADispositionWithoutReceiptsReturnsAnEmptyCollection(): void
|
|
{
|
|
$disposition = $this->createDisposition(createdAt: 'today', assignmentEndsIn: '-1 day');
|
|
$disposition->addDocument((new Upload())->setType(Upload::TYPE_INVOICE));
|
|
|
|
$this->assertCount(0, $disposition->getDocumentsByType(Upload::TYPE_RECEIPT));
|
|
}
|
|
|
|
private function createDisposition(
|
|
string $createdAt,
|
|
string $assignmentEndsIn,
|
|
bool $skipFormalities = false,
|
|
): Disposition {
|
|
$destination = (new Destination())
|
|
->setProduct('Skireise')
|
|
->setDateFrom(new \DateTimeImmutable($assignmentEndsIn.' -7 days'))
|
|
->setDateTo(new \DateTimeImmutable($assignmentEndsIn))
|
|
;
|
|
|
|
$assignment = (new Assignment())
|
|
->setDestination($destination)
|
|
->setSkipFormalities($skipFormalities)
|
|
;
|
|
|
|
$disposition = new Disposition(new Application($assignment, new Teamer()));
|
|
|
|
return $disposition->setCreatedAt(new \DateTimeImmutable($createdAt));
|
|
}
|
|
}
|