345 lines
9.4 KiB
PHP
345 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Entity;
|
|
|
|
use App\Entity\Traits\BlameableEntity;
|
|
use App\Entity\Traits\TimestampableEntity;
|
|
use App\Repository\DispositionRepository;
|
|
use Carbon\CarbonPeriodImmutable;
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
use Doctrine\Common\Collections\Collection;
|
|
use Doctrine\DBAL\Types\Types;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Uid\Uuid;
|
|
|
|
#[ORM\Entity(repositoryClass: DispositionRepository::class)]
|
|
class Disposition implements BlameableEntityInterface, TimestampableEntityInterface
|
|
{
|
|
use BlameableEntity;
|
|
use TimestampableEntity;
|
|
|
|
public const STATUS_NEW = 'new';
|
|
public const STATUS_CHECKING_CONTRACT = 'checking_contract';
|
|
public const STATUS_CONFIRMED = 'confirmed';
|
|
public const STATUS_ENDED = 'ended';
|
|
public const STATUS_CHECKING_INVOICE = 'checking_invoice';
|
|
public const STATUS_PAID = 'paid';
|
|
public const STATUS_COMPLETED = 'completed';
|
|
public const STATUS_CALLED_OFF = 'called_off';
|
|
|
|
public const CALLED_OFF_BY_TEAMER = 'teamer';
|
|
public const CALLED_OFF_BY_OFFICE = 'office';
|
|
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
|
private string $uuid;
|
|
|
|
#[ORM\Column(length: 64)]
|
|
private ?string $status;
|
|
|
|
#[ORM\ManyToOne(inversedBy: 'dispositions')]
|
|
private ?Assignment $assignment;
|
|
|
|
#[ORM\ManyToOne(inversedBy: 'dispositions')]
|
|
private ?Teamer $teamer;
|
|
|
|
#[ORM\Column(nullable: true)]
|
|
private ?int $pickup;
|
|
|
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
|
private ?string $remarks;
|
|
|
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
|
private ?string $specialAgreements;
|
|
|
|
#[ORM\OneToMany(mappedBy: 'disposition', targetEntity: Upload::class, cascade: ['persist', 'remove'])]
|
|
private Collection $documents;
|
|
|
|
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
|
|
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
|
private ?Feedback $feedback = null;
|
|
|
|
#[ORM\Column(length: 64, nullable: true)]
|
|
private ?string $calledOffBy = null;
|
|
|
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
|
private ?string $calledOffReason = null;
|
|
|
|
#[ORM\Column(type: Types::BOOLEAN)]
|
|
private bool $syncedWithBusProNet = false;
|
|
|
|
public function __construct(Application $application)
|
|
{
|
|
$this->uuid = Uuid::v4();
|
|
// A skip-formalities assignment has no contract step to wait for, so the placement starts
|
|
// out where a signed and checked contract would otherwise have put it.
|
|
$this->status = true === $application->getAssignment()?->isSkipFormalities()
|
|
? static::STATUS_CONFIRMED
|
|
: static::STATUS_NEW;
|
|
$this->assignment = $application->getAssignment();
|
|
$this->teamer = $application->getTeamer();
|
|
$this->pickup = $application->getPickup();
|
|
$this->specialAgreements = $application->getRequests();
|
|
$this->documents = new ArrayCollection();
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getUuid(): string
|
|
{
|
|
return $this->uuid;
|
|
}
|
|
|
|
public function getStatus(): ?string
|
|
{
|
|
return $this->status;
|
|
}
|
|
|
|
public function setStatus(string $status): static
|
|
{
|
|
$this->status = $status;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getAssignment(): ?Assignment
|
|
{
|
|
return $this->assignment;
|
|
}
|
|
|
|
public function setAssignment(?Assignment $assignment): static
|
|
{
|
|
$this->assignment = $assignment;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getTeamer(): ?Teamer
|
|
{
|
|
return $this->teamer;
|
|
}
|
|
|
|
public function setTeamer(?Teamer $teamer): static
|
|
{
|
|
$this->teamer = $teamer;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getPickup(): ?int
|
|
{
|
|
return $this->pickup;
|
|
}
|
|
|
|
public function setPickup(?int $pickup): static
|
|
{
|
|
$this->pickup = $pickup;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getRemarks(): ?string
|
|
{
|
|
return $this->remarks;
|
|
}
|
|
|
|
public function setRemarks(?string $remarks): static
|
|
{
|
|
$this->remarks = $remarks;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getSpecialAgreements(): ?string
|
|
{
|
|
return $this->specialAgreements;
|
|
}
|
|
|
|
public function setSpecialAgreements(?string $specialAgreements): static
|
|
{
|
|
$this->specialAgreements = $specialAgreements;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return Collection<int, Upload>
|
|
*/
|
|
public function getDocuments(): Collection
|
|
{
|
|
return $this->documents;
|
|
}
|
|
|
|
public function getDocumentByType(string $type): ?Upload
|
|
{
|
|
foreach ($this->getDocuments() as $upload) {
|
|
if ($type === $upload->getType()) {
|
|
return $upload;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function addDocument(Upload $document): static
|
|
{
|
|
if (!$this->documents->contains($document)) {
|
|
$this->documents->add($document);
|
|
$document->setDisposition($this);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeDocument(Upload $document): static
|
|
{
|
|
if ($this->documents->removeElement($document)) {
|
|
// set the owning side to null (unless already changed)
|
|
if ($document->getDisposition() === $this) {
|
|
$document->setDisposition(null);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getFeedback(): ?Feedback
|
|
{
|
|
return $this->feedback;
|
|
}
|
|
|
|
public function setFeedback(?Feedback $feedback): static
|
|
{
|
|
$this->feedback = $feedback;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Whether the owning assignment switches off the contract, invoice and feedback process.
|
|
* Kept here so call sites do not have to repeat the null check on the assignment.
|
|
*/
|
|
public function isSkipFormalities(): bool
|
|
{
|
|
return true === $this->getAssignment()?->isSkipFormalities();
|
|
}
|
|
|
|
/**
|
|
* The period is passed in rather than hardcoded so that this, the reminder mail and the
|
|
* admin overdue list all work from the same %contract_upload_deadline_days%.
|
|
*/
|
|
public function isContractDue(int $deadlineDays): bool
|
|
{
|
|
if (true === $this->isSkipFormalities()) {
|
|
return false;
|
|
}
|
|
|
|
if (null !== $this->getDocumentByType(Upload::TYPE_CONTRACT)) {
|
|
return false;
|
|
}
|
|
|
|
$assignment = $this->getAssignment();
|
|
$assignmentPeriod = $assignment->getEffectivePeriod();
|
|
$dueDateFrom = $this->getCreatedAt()->modify(sprintf('+%d days', $deadlineDays));
|
|
$dueDateTo = $assignmentPeriod->end->modify('-1 day');
|
|
|
|
// A short-notice assignment can end before the normal $deadlineDays window would even
|
|
// start. Left uncapped, dueDateFrom >= dueDateTo makes isStarted() and isEnded() equal
|
|
// at every instant below, so the flag would never light up; treat the contract as due
|
|
// right away instead.
|
|
if ($dueDateFrom >= $dueDateTo) {
|
|
$dueDateFrom = $this->getCreatedAt();
|
|
}
|
|
|
|
$period = CarbonPeriodImmutable::create($dueDateFrom, $dueDateTo);
|
|
|
|
// isStarted() alone stays true once the period has begun, so the dashboard kept
|
|
// asking for a contract on the last day of the assignment - by which point
|
|
// DispositionWorkflowGuardSubscriber already refuses the upload.
|
|
return $period->isStarted() && false === $period->isEnded();
|
|
}
|
|
|
|
public function isContractRejected(): bool
|
|
{
|
|
if (true === $this->isSkipFormalities()) {
|
|
return false;
|
|
}
|
|
|
|
if (null === $contractDocument = $this->getDocumentByType(Upload::TYPE_CONTRACT)) {
|
|
return false;
|
|
}
|
|
|
|
return $contractDocument->isRejected();
|
|
}
|
|
|
|
/**
|
|
* The period is passed in rather than hardcoded so that this and the two invoice reminder
|
|
* mails all work from the same %invoice_upload_deadline_days%.
|
|
*
|
|
* Unlike isContractDue() this deliberately stays true once the period has run out: nothing
|
|
* blocks a late invoice upload, so the teamer should keep being asked for it.
|
|
*/
|
|
public function isInvoiceDue(int $deadlineDays): bool
|
|
{
|
|
if (true === $this->isSkipFormalities()) {
|
|
return false;
|
|
}
|
|
|
|
if (null !== $this->getDocumentByType(Upload::TYPE_INVOICE)) {
|
|
return false;
|
|
}
|
|
|
|
$assignment = $this->getAssignment();
|
|
$assignmentPeriod = $assignment->getEffectivePeriod();
|
|
$dueDateFrom = $assignmentPeriod->getEndDate();
|
|
$dueDateTo = $dueDateFrom->addDays($deadlineDays);
|
|
|
|
$period = CarbonPeriodImmutable::create($dueDateFrom, $dueDateTo);
|
|
|
|
return $period->isStarted();
|
|
}
|
|
|
|
public function getCalledOffBy(): ?string
|
|
{
|
|
return $this->calledOffBy;
|
|
}
|
|
|
|
public function setCalledOffBy(?string $calledOffBy): static
|
|
{
|
|
$this->calledOffBy = $calledOffBy;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getCalledOffReason(): ?string
|
|
{
|
|
return $this->calledOffReason;
|
|
}
|
|
|
|
public function setCalledOffReason(?string $calledOffReason): static
|
|
{
|
|
$this->calledOffReason = $calledOffReason;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isSyncedWithBusProNet(): bool
|
|
{
|
|
return $this->syncedWithBusProNet;
|
|
}
|
|
|
|
public function setSyncedWithBusProNet(bool $syncedWithBusProNet): static
|
|
{
|
|
$this->syncedWithBusProNet = $syncedWithBusProNet;
|
|
|
|
return $this;
|
|
}
|
|
}
|