<?php
namespace App\Entity\ChannelUserData\Cursus;
use App\Entity\Account\User;
use App\Entity\Channel\ChannelLicencesResourceInterface;
use App\Entity\Channel\ChannelUserCursusPermission;
use App\Entity\Channel\Organization;
use App\Entity\Channel\Place;
use App\Entity\Channel\Training;
use App\Entity\ChannelUserData\Activity\ActivityFeed;
use App\Entity\ChannelUserData\ChannelUserData;
use App\Entity\Common\ImportableData\ImportableDataInterface;
use App\Entity\Common\ImportableData\ImportableDataTrait;
use App\Entity\Sync\SyncableInterface;
use App\Enum\ChannelUserData\Activity\CursusActivityTagEnum;
use App\Enum\ChannelUserData\CursusFoundingStatusEnum;
use App\Enum\ChannelUserData\CursusInternshipStatus;
use App\Enum\ChannelUserData\CursusStoppedStatusEnum;
use App\Enum\ChannelUserData\ExamStatusEnum;
use App\Repository\ChannelUserData\Cursus\AbstractCursusRepository;
use App\Validator\Constraints as AppAssert;
use App\Validator\Constraints\AbstractCursusConstraint;
use App\Validator\Constraints\ChannelLicensesConstraint;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Drosalys\Bundle\ApiBundle\Serializer\Attributes\IdGroups;
use Nellapp\Bundle\SDKBundle\Auth\Entity\UserInterface;
use Nellapp\Bundle\SDKBundle\Channel\Entity\ChannelInterface;
use Nellapp\Bundle\SDKBundle\Channel\Entity\Cursus\AbstractCursusInterface;
use Nellapp\Bundle\SDKBundle\Channel\Entity\Cursus\AbstractCursusMethodTrait;
use Nellapp\Bundle\SDKBundle\Enum\ChannelUserData\CursusCourseEnum;
use Nellapp\Bundle\SDKBundle\Enum\ChannelUserData\CursusModalityEnum;
use Nellapp\Bundle\SDKBundle\Permission\Entity\ChannelResourceInterface;
use Nellapp\Bundle\SDKBundle\Permission\UserOwner\UserOwnerResourceInterface;
use Nellapp\Bundle\SDKBundle\Permission\UsersResource\UsersResourceInterface;
use Nellapp\Bundle\SDKBundle\Sync\EntityKeys;
use Nellapp\Bundle\SDKBundle\TrackData\Attribute\TrackEntity;
use Nellapp\Bundle\SDKBundle\TrackData\Attribute\TrackFIeld;
use Nellapp\Bundle\SDKBundle\Utils\NellappFormatService;
use Symfony\Component\Serializer\Annotation as Serial;
use Symfony\Component\Validator\Constraints as Assert;
#[
ORM\Entity(repositoryClass: AbstractCursusRepository::class),
ORM\Table(name: 'channel_user_data_cursus'),
ORM\InheritanceType('JOINED'),
ORM\DiscriminatorColumn(name: 'type', type: 'string'),
ORM\DiscriminatorMap(['session-type' => SessionCursus::class, 'training-type' => TrainingCursus::class]),
AbstractCursusConstraint(message: 'abstract_cursus.abstract_cursus_constraint.message'),
TrackEntity(
fieldGroups: [
'progress' => [
'tags' => [CursusActivityTagEnum::PROGRESSION],
],
'internship' => [
'tags' => [CursusActivityTagEnum::INTERNSHIP],
],
'founding' => [
'tags' => [CursusActivityTagEnum::FOUNDING]
],
'exam' => [
'tags' => [CursusActivityTagEnum::EXAM],
]
]
),
ChannelLicensesConstraint
]
abstract class AbstractCursus implements AbstractCursusInterface, SyncableInterface, ChannelLicencesResourceInterface, ChannelResourceInterface, ImportableDataInterface, UserOwnerResourceInterface, UsersResourceInterface
{
use AbstractCursusMethodTrait;
use ImportableDataTrait;
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
#[Serial\Groups(['Sync', 'GlobalJSVariable'])]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: ChannelUserData::class, cascade: ['persist'], inversedBy: 'cursuses')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[IdGroups(['Sync'])]
#[Assert\NotBlank]
private ?ChannelUserData $channelUserData = null;
#[
ORM\ManyToOne(targetEntity: Training::class),
ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')
]
#[IdGroups(['Sync'])]
private ?Training $training = null;
#[ORM\Column(type: 'datetime')]
#[Serial\Groups(['Sync'])]
#[Assert\NotBlank(groups: ['CursusProgress'])]
#[TrackFIeld(name: 'cursus.entity.startAt.label', fieldKey: 'progress', callable: 'format', callableParams: ['d/m/Y'])]
private ?DateTimeInterface $startAt = null;
#[ORM\Column(type: 'datetime')]
#[Serial\Groups(['Sync'])]
#[
Assert\GreaterThanOrEqual(propertyPath: 'startAt', message: 'abstract_cursus.endAt.GreaterThan', groups: ['CursusProgress', 'Default']),
AppAssert\DateWithinIntervalOf(reference: 'startAt', maxInterval: 'P5Y', message: 'abstract_cursus.endAt.MaxDate', groups: ['CursusProgress', 'Default']),
Assert\NotBlank(groups: ['CursusProgress'])
]
#[TrackFIeld(name: 'cursus.entity.endAt.label', fieldKey: 'progress', callable: 'format', callableParams: ['d/m/Y'])]
private ?DateTimeInterface $endAt = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[
Assert\Type(type: 'integer', groups: ['CursusProgress']),
Assert\GreaterThanOrEqual(value: 1),
Assert\LessThanOrEqual(value: 24)
]
#[TrackFIeld(name: 'cursus.entity.monthDuration.label', fieldKey: 'progress')]
private ?int $monthDuration = null;
#[ORM\OneToOne(inversedBy: 'cursus', targetEntity: AbstractCursusProgression::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?AbstractCursusProgression $cursusProgression = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.physicalDuration.label', fieldKey: 'progress', callable: 'formatDuration', callableClass: NellappFormatService::class)]
#[
Assert\Type(type: 'integer'),
Assert\GreaterThanOrEqual(value: 0)
]
private ?int $physicalDuration = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.distanceSyncDuration.label', fieldKey: 'progress', callable: 'formatDuration', callableClass: NellappFormatService::class)]
#[
Assert\Type(type: 'integer'),
Assert\GreaterThanOrEqual(value: 0)
]
private ?int $distanceSyncDuration = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.distanceAsyncDuration.label', fieldKey: 'progress', callable: 'formatDuration', callableClass: NellappFormatService::class)]
#[
Assert\Type(type: 'integer'),
Assert\GreaterThanOrEqual(value: 0)
]
private ?int $distanceAsyncDuration = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.examDuration.label', fieldKey: 'progress', callable: 'formatDuration', callableClass: NellappFormatService::class)]
#[
Assert\Type(type: 'integer'),
Assert\GreaterThanOrEqual(value: 0)
]
private ?int $examDuration = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.internshipDuration.label', fieldKey: 'progress', callable: 'formatDuration', callableClass: NellappFormatService::class)]
#[
Assert\Type(type: 'integer'),
Assert\GreaterThanOrEqual(value: 0)
]
private ?int $internshipDuration = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.cursusModality.label', fieldKey: 'progress', callable: 'getStatusKey', callableClass: CursusModalityEnum::class)]
#[Assert\NotBlank(groups: ['CursusProgress'])]
#[Serial\Groups(['Sync'])]
private ?int $cursusModality = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.cursusCourse.label', fieldKey: 'progress', callable: 'getStatusKey', callableClass: CursusCourseEnum::class)]
#[Assert\NotBlank(groups: ['CursusProgress'])]
#[Serial\Groups(['Sync'])]
private ?int $cursusCourse = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.foundingStatus.label', fieldKey: 'founding', callable: 'getStatusKey', callableClass: CursusFoundingStatusEnum::class)]
#[Assert\Choice(
callback: [CursusFoundingStatusEnum::class, 'getChoices'],
groups: ['CursusFounding']
)]
private ?int $foundingStatus = null;
#[ORM\ManyToOne(targetEntity: Organization::class)]
#[ORM\JoinColumn(nullable: true, onDelete: "SET NULL")]
private ?Organization $organization = null;
#[ORM\OneToMany(mappedBy: 'cursus', targetEntity: Founder::class, cascade: ['remove'], orphanRemoval: true)]
private Collection $founders;
#[ORM\Column(type: 'integer', nullable: true)]
#[
Assert\NotBlank(groups: ['CursusInternship']),
Assert\Type(type: 'integer', groups: ['CursusInternship'])
]
#[TrackFIeld(name: 'cursus.entity.internshipStatus.label', fieldKey: 'internship', callable: 'getStatusKey', callableClass: CursusInternshipStatus::class)]
private ?int $internshipStatus = 0;
#[ORM\OneToMany(mappedBy: 'cursus', targetEntity: Internship::class, cascade: ['remove', 'persist'], orphanRemoval: true)]
private Collection $internships;
#[ORM\Column(type: 'integer', nullable: true)]
#[Serial\Groups(['Sync'])]
#[TrackFIeld(name: 'cursus.entity.stoppedStatus.label', fieldKey: 'progress', callable: 'getStatusKey', callableClass: CursusStoppedStatusEnum::class)]
private ?int $stoppedStatus = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[Assert\NotBlank(groups: ['CursusStatus'])]
#[TrackFIeld(name: 'cursus.entity.stoppedDetails.label', fieldKey: 'progress', callable: 'getStatusKey', callableClass: CursusStoppedStatusEnum::class)]
private ?int $stoppedDetails = null;
#[ORM\Column(type: 'datetime', nullable: true)]
#[Serial\Groups(['Sync'])]
#[
Assert\NotBlank(message: 'abstract_cursus.stoppedAt.NotBlank', groups: ['CursusStatus']),
Assert\GreaterThanOrEqual('-45 days', message: 'abstract_cursus.stoppedAt.GreaterThanOrEqual', groups: ['CursusStatus']),
]
#[TrackFIeld(name: 'cursus.entity.stoppedAt.label', fieldKey: 'progress', callable: 'format', callableParams: ['d/m/Y'])]
private ?\DateTimeInterface $stoppedAt = null;
#[ORM\Column(type: 'text', nullable: true)]
#[
Assert\Length(max: 500, groups: ['CursusStatus'])
]
private ?string $stoppedInfos = null;
#[ORM\ManyToOne(targetEntity: Place::class)]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
#[TrackFIeld(name: 'cursus.entity.examPlace.label', fieldKey: 'exam')]
private ?Place $examPlace = null;
#[ORM\Column(type: 'datetime', nullable: true)]
#[TrackFIeld(
name: 'cursus.entity.examSession.label',
fieldKey: 'exam',
callable: 'format',
callableParams: ['m/Y'])
]
#[Assert\NotBlank(groups: ['CursusExam'])]
private ?DateTimeInterface $examSession = null;
#[ORM\Column(type: 'string', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.examCeres.label', fieldKey: 'exam')]
#[
Assert\Length(max: 255, groups: ['CursusExam'])
]
private ?string $examCeres = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.examStatus.label', fieldKey: 'exam', callable: 'getStatusKey', callableClass: ExamStatusEnum::class)]
#[Assert\Choice(
callback: [ExamStatusEnum::class, 'getChoices'],
groups: ['CursusExam']
)]
private ?int $examStatus = null;
#[ORM\Column(type: 'string', nullable: true)]
#[TrackFIeld(name: 'cursus.entity.examCCP.label', fieldKey: 'exam')]
#[
Assert\Length(max: 255, groups: ['CursusExam'])
]
private ?string $examCCP = null;
#[ORM\OneToMany(mappedBy: 'cursus', targetEntity: Exam::class, cascade: ['remove'], orphanRemoval: true)]
private Collection $exams;
#[ORM\ManyToOne(targetEntity: Place::class)]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
#[TrackFIeld(name: 'cursus.entity.place.label', fieldKey: 'progress')]
#[IdGroups(['Sync'])]
private ?Place $place = null;
#[ORM\OneToMany(mappedBy: 'cursus', targetEntity: ReportDetails::class, cascade: ['remove'], fetch: 'EXTRA_LAZY', orphanRemoval: true)]
private Collection $reportDetailsList;
#[ORM\OneToOne(targetEntity: ActivityFeed::class, cascade: ['remove', 'persist'], orphanRemoval: true)]
#[ORM\JoinColumn(nullable: false)]
private ?ActivityFeed $activityFeed = null;
public function __construct()
{
$this->founders = new ArrayCollection();
$this->internships = new ArrayCollection();
$this->exams = new ArrayCollection();
$this->reportDetailsList = new ArrayCollection();
}
public static function getSyncKey(): string
{
return EntityKeys::ABSTRACT_CURSUS;
}
public function getSyncDisplayLabel(): string
{
return $this->getLabel() ?? 'unknown';
}
abstract public function getLabelType(): string;
public static function getPermissionResourceClass(): ?string
{
return ChannelUserCursusPermission::class;
}
public function getUserOwners(): array
{
$learner = $this->getChannelUserData()?->getUser();
return $learner ? [$learner] : [];
}
#[Serial\Groups(['GlobalJSVariable'])]
public function getLearnerName(): ?string
{
return $this->getChannelUserData()?->getUser();
}
public function __toString(): string
{
return 'cursus ' . $this->getLabel() . ' pour ' . $this->getChannelUserData()?->getUser();
}
public function isTrainingType(): bool
{
return $this instanceof TrainingCursus;
}
public function isSessionType(): bool
{
return $this instanceof SessionCursus;
}
public function getChannel(): ?ChannelInterface
{
return $this->getChannelUserData()?->getChannel();
}
public function getOwnerChannel(): ?ChannelInterface
{
return $this->getChannel();
}
public function hasOneOralExam(): bool
{
if (!$this->getExams()->isEmpty()) {
/** @var Exam $exam */
foreach ($this->getExams() as $exam) {
if ($exam->getType() === $exam::ORAL_TYPE) {
return true;
}
}
}
return false;
}
public function hasUser(?UserInterface $user, array $tags = []): bool
{
if (!in_array('internship_tutor', $tags)) {
return false;
}
if (!$user instanceof User) {
return false;
}
return $this->getInternships()->exists(function ($k, Internship $internship) use ($user): bool {
return $internship->getTutor()?->getUser() === $user && $internship->isActiveWithDelays();
});
}
public function getLabel(): ?string
{
return $this->training?->getName();
}
public function getTotalDuration(): ?int
{
return $this->getPhysicalDurationDisplayed()
+ $this->getDistanceAsyncDurationDisplayed()
+ $this->getDistanceSyncDurationDisplayed()
+ $this->getInternshipDurationDisplayed()
+ $this->getExamDurationDisplayed();
}
public function getPhysicalDuration(): ?int
{
return $this->physicalDuration;
}
public function setPhysicalDuration(?int $physicalDuration): self
{
$this->physicalDuration = $physicalDuration;
return $this;
}
public function getDistanceSyncDuration(): ?int
{
return $this->distanceSyncDuration;
}
public function setDistanceSyncDuration(?int $distanceSyncDuration): self
{
$this->distanceSyncDuration = $distanceSyncDuration;
return $this;
}
public function getDistanceAsyncDuration(): ?int
{
return $this->distanceAsyncDuration;
}
public function setDistanceAsyncDuration(?int $distanceAsyncDuration): self
{
$this->distanceAsyncDuration = $distanceAsyncDuration;
return $this;
}
public function getExamDuration(): ?int
{
return $this->examDuration;
}
public function setExamDuration(?int $examDuration): AbstractCursus
{
$this->examDuration = $examDuration;
return $this;
}
public function getInternshipDuration(): ?int
{
return $this->internshipDuration;
}
public function setInternshipDuration(?int $internshipDuration): AbstractCursus
{
$this->internshipDuration = $internshipDuration;
return $this;
}
public function getFoundingStatus(): ?int
{
return $this->foundingStatus;
}
public function setFoundingStatus(?int $foundingStatus): AbstractCursus
{
$this->foundingStatus = $foundingStatus;
return $this;
}
public function getFounders(): Collection
{
return $this->founders;
}
public function addFounder(Founder $founder): static
{
if (!$this->founders->contains($founder)) {
$this->founders->add($founder);
}
return $this;
}
public function setFounders(Collection $founders): AbstractCursus
{
$this->founders = $founders;
return $this;
}
/**
* @return Collection<Internship>
*/
public function getInternships(): Collection
{
return $this->internships;
}
public function addInternship(Internship $internship): static
{
if (!$this->internships->contains($internship)) {
$this->internships->add($internship);
}
return $this;
}
public function setInternships(Collection $internships): AbstractCursus
{
$this->internships = $internships;
return $this;
}
public function getInternshipStatus(): ?int
{
return $this->internshipStatus;
}
public function setInternshipStatus(?int $internshipStatus): AbstractCursus
{
$this->internshipStatus = $internshipStatus;
return $this;
}
public function getMonthDuration(): ?int
{
return $this->monthDuration;
}
public function setMonthDuration(?int $monthDuration): AbstractCursus
{
$this->monthDuration = $monthDuration;
return $this;
}
public function getCursusProgression(): ?AbstractCursusProgression
{
return $this->cursusProgression;
}
public function setCursusProgression(?AbstractCursusProgression $cursusProgression): AbstractCursus
{
$this->cursusProgression = $cursusProgression;
return $this;
}
public function getExamPlace(): ?Place
{
return $this->examPlace;
}
public function setExamPlace(?Place $examPlace): AbstractCursus
{
$this->examPlace = $examPlace;
return $this;
}
public function getExamSession(): ?DateTimeInterface
{
return $this->examSession;
}
public function setExamSession(?DateTimeInterface $examSession): AbstractCursus
{
$this->examSession = $examSession;
return $this;
}
public function getExamCeres(): ?string
{
return $this->examCeres;
}
public function setExamCeres(?string $examCeres): AbstractCursus
{
$this->examCeres = $examCeres;
return $this;
}
public function getExamStatus(): ?int
{
return $this->examStatus;
}
public function setExamStatus(?int $examStatus): AbstractCursus
{
$this->examStatus = $examStatus;
return $this;
}
public function getExamCCP(): ?string
{
return $this->examCCP;
}
public function setExamCCP(?string $examCCP): AbstractCursus
{
$this->examCCP = $examCCP;
return $this;
}
public function getExams(): Collection
{
return $this->exams;
}
public function addExam(Exam $exam): AbstractCursus
{
if (!$this->getExams()->contains($exam)) {
$this->getExams()->add($exam);
}
return $this;
}
public function setExams(Collection $exams): AbstractCursus
{
$this->exams = $exams;
return $this;
}
public function getStoppedInfos(): ?string
{
return $this->stoppedInfos;
}
public function setStoppedInfos(?string $stoppedInfos): AbstractCursus
{
$this->stoppedInfos = $stoppedInfos;
return $this;
}
public function getStoppedDetails(): ?int
{
return $this->stoppedDetails;
}
public function setStoppedDetails(?int $stoppedDetails): AbstractCursus
{
$this->stoppedDetails = $stoppedDetails;
return $this;
}
public function getPlace(): ?Place
{
return $this->place;
}
public function setPlace(?Place $place): static
{
$this->place = $place;
return $this;
}
public function getPhysicalDurationDisplayed(): ?int
{
if ($this->getPhysicalDuration() !== null && $this->getPhysicalDuration() !== 0) {
return $this->getPhysicalDuration();
}
if ($this instanceof SessionCursus) {
return $this->getSession()?->getDurationInCenter();
}
return null;
}
public function getDistanceAsyncDurationDisplayed(): ?int
{
if ($this->getDistanceAsyncDuration() !== null && $this->getDistanceAsyncDuration() !== 0) {
return $this->getDistanceAsyncDuration();
}
if ($this instanceof SessionCursus) {
return $this->getSession()?->getDurationInELearning();
}
return null;
}
public function getDistanceSyncDurationDisplayed(): ?int
{
if ($this->getDistanceSyncDuration() !== null && $this->getDistanceSyncDuration() !== 0) {
return $this->getDistanceSyncDuration();
}
if ($this instanceof SessionCursus) {
return $this->getSession()?->getDurationInVisio();
}
return null;
}
public function getInternshipDurationDisplayed(): ?int
{
if ($this->getInternshipDuration() !== null && $this->getInternshipDuration() !== 0) {
return $this->getInternshipDuration();
}
if ($this instanceof SessionCursus) {
return $this->getSession()?->getDurationInCompany();
}
if (!$this->getInternships()->isEmpty()) {
$internshipDuration = 0;
/** @var Internship $internship */
foreach ($this->getInternships() as $internship) {
if ($internship->getDuration() !== null) {
$internshipDuration += $internship->getDuration();
}
}
return $internshipDuration;
}
return null;
}
public function getInternshipDurationForProgress(): ?int
{
if ($this->getInternshipDuration() !== null && $this->getInternshipDuration() !== 0) {
return $this->getInternshipDuration();
}
if (!$this->getInternships()->isEmpty()) {
$internshipDuration = 0;
/** @var Internship $internship */
foreach ($this->getInternships() as $internship) {
if ($internship->getDuration() !== null) {
$internshipDuration += $internship->getDuration();
}
}
return $internshipDuration;
}
return null;
}
public function getExamDurationDisplayed(): ?int
{
if ($this->getExamDuration() !== null && $this->getExamDuration() !== 0) {
return $this->getExamDuration();
}
if ($this instanceof SessionCursus) {
return $this->getSession()?->getDurationInExam();
}
return null;
}
public function getOrganization(): ?Organization
{
return $this->organization;
}
public function setOrganization(?Organization $organization): AbstractCursus
{
$this->organization = $organization;
return $this;
}
public function getReportDetailsList(): Collection
{
return $this->reportDetailsList ?? $this->reportDetailsList = new ArrayCollection();
}
public function setReportDetailsList(Collection $reportDetailsList): AbstractCursus
{
$this->reportDetailsList = $reportDetailsList;
return $this;
}
public function getActivityFeed(): ?ActivityFeed
{
return $this->activityFeed;
}
public function setActivityFeed(?ActivityFeed $activityFeed): AbstractCursus
{
$this->activityFeed = $activityFeed;
return $this;
}
}