src/Entity/ContratCadre.php line 16

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use App\Repository\ContratCadreRepository;
  4. use Doctrine\Common\Collections\ArrayCollection;
  5. use Doctrine\Common\Collections\Collection;
  6. use Doctrine\ORM\Mapping as ORM;
  7. use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
  8. use Symfony\Component\Validator\Constraints as Assert;
  9. #[ORM\Entity(repositoryClass: ContratCadreRepository::class)]
  10. #[ORM\Table(name: 'contrat_cadre')]
  11. #[ORM\HasLifecycleCallbacks]
  12. #[UniqueEntity(fields: ['reference'], message: 'Cette référence existe déjà.')]
  13. class ContratCadre
  14. {
  15. const STATUT_ACTIF = 'actif';
  16. const STATUT_EXPIRE = 'expire';
  17. const STATUT_EPUISE = 'epuise';
  18. const STATUT_INACTIF = 'inactif';
  19. const SEUIL_ALERTE_PLAFOND = 0.80; // 80% consommé → alerte
  20. const SEUIL_ALERTE_EXPIRATION = 30; // 30 jours avant fin → alerte
  21. #[ORM\Id]
  22. #[ORM\GeneratedValue]
  23. #[ORM\Column]
  24. private ?int $id = null;
  25. #[ORM\Column(length: 50, unique: true)]
  26. #[Assert\NotBlank(message: 'La référence est obligatoire.')]
  27. #[Assert\Regex(pattern: '/^[A-Z0-9\-]+$/', message: 'Lettres majuscules, chiffres et tirets uniquement.')]
  28. private ?string $reference = null;
  29. #[ORM\Column(length: 255)]
  30. #[Assert\NotBlank(message: "L'intitulé est obligatoire.")]
  31. private ?string $intitule = null;
  32. #[ORM\Column(type: 'decimal', precision: 15, scale: 2, options: ['default' => '0.00'])]
  33. private string $montantReserve = '0.00';
  34. /**
  35. * Fournisseur — on filtre statut=actif dans le formulaire.
  36. * Label affiché : getRaisonSociale()
  37. */
  38. #[ORM\ManyToOne(targetEntity: Fournisseur::class)]
  39. #[ORM\JoinColumn(nullable: false)]
  40. #[Assert\NotNull(message: 'Le fournisseur est obligatoire.')]
  41. private ?Fournisseur $fournisseur = null;
  42. /**
  43. * CategorieMarche — votre entité "catégorie" dans cet ERP.
  44. * Label affiché : getLibelle()
  45. */
  46. #[ORM\ManyToOne(targetEntity: CategorieMarche::class)]
  47. #[ORM\JoinColumn(nullable: false)]
  48. #[Assert\NotNull(message: 'La catégorie de marché est obligatoire.')]
  49. private ?CategorieMarche $categorieMarche = null;
  50. #[ORM\Column(type: 'decimal', precision: 15, scale: 2)]
  51. #[Assert\NotBlank]
  52. #[Assert\Positive(message: 'Le montant plafond doit être positif.')]
  53. private ?string $montantPlafond = null;
  54. #[ORM\Column(type: 'decimal', precision: 15, scale: 2, options: ['default' => '0.00'])]
  55. private string $montantConsomme = '0.00';
  56. #[ORM\Column(type: 'date')]
  57. #[Assert\NotNull(message: 'La date de début est obligatoire.')]
  58. private ?\DateTimeInterface $dateDebut = null;
  59. #[ORM\Column(type: 'date')]
  60. #[Assert\NotNull(message: 'La date de fin est obligatoire.')]
  61. private ?\DateTimeInterface $dateFin = null;
  62. #[ORM\Column(type: 'smallint')]
  63. #[Assert\NotNull]
  64. #[Assert\Range(min: 1, max: 10, notInRangeMessage: 'La priorité doit être entre 1 et 10.')]
  65. private ?int $niveauPriorite = 5;
  66. #[ORM\Column(options: ['default' => true])]
  67. private bool $actif = true;
  68. #[ORM\Column(type: 'text', nullable: true)]
  69. private ?string $description = null;
  70. #[ORM\Column(type: 'datetime')]
  71. private ?\DateTimeInterface $createdAt = null;
  72. #[ORM\Column(type: 'datetime')]
  73. private ?\DateTimeInterface $updatedAt = null;
  74. #[ORM\OneToMany(mappedBy: 'contratCadre', targetEntity: CommandeAchat::class)]
  75. private Collection $bonsDeCommande;
  76. public function __construct()
  77. {
  78. $this->bonsDeCommande = new ArrayCollection();
  79. }
  80. // ─── Lifecycle callbacks ──────────────────────────────────────────────────
  81. #[ORM\PrePersist]
  82. public function onPrePersist(): void
  83. {
  84. $this->createdAt = new \DateTime();
  85. $this->updatedAt = new \DateTime();
  86. }
  87. #[ORM\PreUpdate]
  88. public function onPreUpdate(): void
  89. {
  90. $this->updatedAt = new \DateTime();
  91. }
  92. // ─── Propriétés calculées ─────────────────────────────────────────────────
  93. public function getMontantRestant(): float
  94. {
  95. return (float) $this->montantPlafond - (float) $this->montantConsomme;
  96. }
  97. public function getMontantDisponible(): float
  98. {
  99. return
  100. (float)$this->montantPlafond
  101. - (float)$this->montantConsomme
  102. - (float)$this->montantReserve;
  103. }
  104. public function consommerMontant(float $montant): void
  105. {
  106. $this->montantReserve =
  107. max(0, (float)$this->montantReserve - $montant);
  108. $this->montantConsomme =
  109. (float)$this->montantConsomme + $montant;
  110. }
  111. public function reserverMontant(float $montant): void
  112. {
  113. $this->montantReserve =
  114. (float)$this->montantReserve + $montant;
  115. }
  116. public function libererReservation(float $montant): void
  117. {
  118. $this->montantReserve =
  119. max(0, (float)$this->montantReserve - $montant);
  120. }
  121. public function getTauxConsommation(): float
  122. {
  123. if ((float) $this->montantPlafond <= 0) {
  124. return 0;
  125. }
  126. return ((float) $this->montantConsomme / (float) $this->montantPlafond) * 100;
  127. }
  128. public function getJoursRestants(): int
  129. {
  130. if (!$this->dateFin) {
  131. return 0;
  132. }
  133. $diff = (new \DateTime())->diff($this->dateFin);
  134. return $diff->invert ? 0 : (int) $diff->days;
  135. }
  136. public function getStatut(): string
  137. {
  138. if (!$this->actif) {
  139. return self::STATUT_INACTIF;
  140. }
  141. if ($this->dateFin < new \DateTime()) {
  142. return self::STATUT_EXPIRE;
  143. }
  144. if ((float) $this->montantConsomme >= (float) $this->montantPlafond) {
  145. return self::STATUT_EPUISE;
  146. }
  147. return self::STATUT_ACTIF;
  148. }
  149. /*public function isDisponible(float $montantDemande = 0): bool
  150. {
  151. return $this->getStatut() === self::STATUT_ACTIF
  152. && $this->getMontantRestant() >= $montantDemande;
  153. }*/
  154. public function isDisponible(float $montantDemande = 0): bool
  155. {
  156. return $this->getStatut() === self::STATUT_ACTIF
  157. && $this->getMontantDisponible() >= $montantDemande;
  158. }
  159. public function isEnAlerteExpiration(): bool
  160. {
  161. return $this->getStatut() === self::STATUT_ACTIF
  162. && $this->getJoursRestants() <= self::SEUIL_ALERTE_EXPIRATION;
  163. }
  164. public function isEnAlertePlafond(): bool
  165. {
  166. return $this->getTauxConsommation() >= (self::SEUIL_ALERTE_PLAFOND * 100);
  167. }
  168. // ─── Getters / Setters ────────────────────────────────────────────────────
  169. public function getId(): ?int { return $this->id; }
  170. public function getReference(): ?string { return $this->reference; }
  171. public function setReference(string $reference): self
  172. {
  173. $this->reference = strtoupper($reference);
  174. return $this;
  175. }
  176. public function getIntitule(): ?string { return $this->intitule; }
  177. public function setIntitule(string $intitule): self { $this->intitule = $intitule; return $this; }
  178. public function getFournisseur(): ?Fournisseur { return $this->fournisseur; }
  179. public function setFournisseur(?Fournisseur $fournisseur): self { $this->fournisseur = $fournisseur; return $this; }
  180. public function getCategorieMarche(): ?CategorieMarche { return $this->categorieMarche; }
  181. public function setCategorieMarche(?CategorieMarche $categorieMarche): self { $this->categorieMarche = $categorieMarche; return $this; }
  182. public function getMontantPlafond(): ?string { return $this->montantPlafond; }
  183. public function setMontantPlafond(string $montantPlafond): self { $this->montantPlafond = $montantPlafond; return $this; }
  184. public function getMontantConsomme(): string { return $this->montantConsomme; }
  185. public function setMontantConsomme(string $montantConsomme): self { $this->montantConsomme = $montantConsomme; return $this; }
  186. public function getDateDebut(): ?\DateTimeInterface { return $this->dateDebut; }
  187. public function setDateDebut(?\DateTimeInterface $dateDebut): self { $this->dateDebut = $dateDebut; return $this; }
  188. public function getDateFin(): ?\DateTimeInterface { return $this->dateFin; }
  189. public function setDateFin(?\DateTimeInterface $dateFin): self { $this->dateFin = $dateFin; return $this; }
  190. public function getNiveauPriorite(): ?int { return $this->niveauPriorite; }
  191. public function setNiveauPriorite(int $niveauPriorite): self { $this->niveauPriorite = $niveauPriorite; return $this; }
  192. public function isActif(): bool { return $this->actif; }
  193. public function setActif(bool $actif): self { $this->actif = $actif; return $this; }
  194. public function getDescription(): ?string { return $this->description; }
  195. public function setDescription(?string $description): self { $this->description = $description; return $this; }
  196. public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; }
  197. public function getUpdatedAt(): ?\DateTimeInterface { return $this->updatedAt; }
  198. public function getBonsDeCommande(): Collection { return $this->bonsDeCommande; }
  199. public function getMontantReserve(): string{ return $this->montantReserve;}
  200. public function setMontantReserve(string $montantReserve): self{$this->montantReserve = $montantReserve;return $this;}
  201. public function __toString(): string
  202. {
  203. return $this->reference . ' – ' . $this->intitule;
  204. }
  205. }