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 */ 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; } }