<?php
namespace App\Entity;
use App\Repository\ContratCadreRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: ContratCadreRepository::class)]
#[ORM\Table(name: 'contrat_cadre')]
#[ORM\HasLifecycleCallbacks]
#[UniqueEntity(fields: ['reference'], message: 'Cette référence existe déjà.')]
class ContratCadre
{
const STATUT_ACTIF = 'actif';
const STATUT_EXPIRE = 'expire';
const STATUT_EPUISE = 'epuise';
const STATUT_INACTIF = 'inactif';
const SEUIL_ALERTE_PLAFOND = 0.80; // 80% consommé → alerte
const SEUIL_ALERTE_EXPIRATION = 30; // 30 jours avant fin → alerte
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 50, unique: true)]
#[Assert\NotBlank(message: 'La référence est obligatoire.')]
#[Assert\Regex(pattern: '/^[A-Z0-9\-]+$/', message: 'Lettres majuscules, chiffres et tirets uniquement.')]
private ?string $reference = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message: "L'intitulé est obligatoire.")]
private ?string $intitule = null;
#[ORM\Column(type: 'decimal', precision: 15, scale: 2, options: ['default' => '0.00'])]
private string $montantReserve = '0.00';
/**
* Fournisseur — on filtre statut=actif dans le formulaire.
* Label affiché : getRaisonSociale()
*/
#[ORM\ManyToOne(targetEntity: Fournisseur::class)]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotNull(message: 'Le fournisseur est obligatoire.')]
private ?Fournisseur $fournisseur = null;
/**
* CategorieMarche — votre entité "catégorie" dans cet ERP.
* Label affiché : getLibelle()
*/
#[ORM\ManyToOne(targetEntity: CategorieMarche::class)]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotNull(message: 'La catégorie de marché est obligatoire.')]
private ?CategorieMarche $categorieMarche = null;
#[ORM\Column(type: 'decimal', precision: 15, scale: 2)]
#[Assert\NotBlank]
#[Assert\Positive(message: 'Le montant plafond doit être positif.')]
private ?string $montantPlafond = null;
#[ORM\Column(type: 'decimal', precision: 15, scale: 2, options: ['default' => '0.00'])]
private string $montantConsomme = '0.00';
#[ORM\Column(type: 'date')]
#[Assert\NotNull(message: 'La date de début est obligatoire.')]
private ?\DateTimeInterface $dateDebut = null;
#[ORM\Column(type: 'date')]
#[Assert\NotNull(message: 'La date de fin est obligatoire.')]
private ?\DateTimeInterface $dateFin = null;
#[ORM\Column(type: 'smallint')]
#[Assert\NotNull]
#[Assert\Range(min: 1, max: 10, notInRangeMessage: 'La priorité doit être entre 1 et 10.')]
private ?int $niveauPriorite = 5;
#[ORM\Column(options: ['default' => true])]
private bool $actif = true;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description = null;
#[ORM\Column(type: 'datetime')]
private ?\DateTimeInterface $createdAt = null;
#[ORM\Column(type: 'datetime')]
private ?\DateTimeInterface $updatedAt = null;
#[ORM\OneToMany(mappedBy: 'contratCadre', targetEntity: CommandeAchat::class)]
private Collection $bonsDeCommande;
public function __construct()
{
$this->bonsDeCommande = new ArrayCollection();
}
// ─── Lifecycle callbacks ──────────────────────────────────────────────────
#[ORM\PrePersist]
public function onPrePersist(): void
{
$this->createdAt = new \DateTime();
$this->updatedAt = new \DateTime();
}
#[ORM\PreUpdate]
public function onPreUpdate(): void
{
$this->updatedAt = new \DateTime();
}
// ─── Propriétés calculées ─────────────────────────────────────────────────
public function getMontantRestant(): float
{
return (float) $this->montantPlafond - (float) $this->montantConsomme;
}
public function getMontantDisponible(): float
{
return
(float)$this->montantPlafond
- (float)$this->montantConsomme
- (float)$this->montantReserve;
}
public function consommerMontant(float $montant): void
{
$this->montantReserve =
max(0, (float)$this->montantReserve - $montant);
$this->montantConsomme =
(float)$this->montantConsomme + $montant;
}
public function reserverMontant(float $montant): void
{
$this->montantReserve =
(float)$this->montantReserve + $montant;
}
public function libererReservation(float $montant): void
{
$this->montantReserve =
max(0, (float)$this->montantReserve - $montant);
}
public function getTauxConsommation(): float
{
if ((float) $this->montantPlafond <= 0) {
return 0;
}
return ((float) $this->montantConsomme / (float) $this->montantPlafond) * 100;
}
public function getJoursRestants(): int
{
if (!$this->dateFin) {
return 0;
}
$diff = (new \DateTime())->diff($this->dateFin);
return $diff->invert ? 0 : (int) $diff->days;
}
public function getStatut(): string
{
if (!$this->actif) {
return self::STATUT_INACTIF;
}
if ($this->dateFin < new \DateTime()) {
return self::STATUT_EXPIRE;
}
if ((float) $this->montantConsomme >= (float) $this->montantPlafond) {
return self::STATUT_EPUISE;
}
return self::STATUT_ACTIF;
}
/*public function isDisponible(float $montantDemande = 0): bool
{
return $this->getStatut() === self::STATUT_ACTIF
&& $this->getMontantRestant() >= $montantDemande;
}*/
public function isDisponible(float $montantDemande = 0): bool
{
return $this->getStatut() === self::STATUT_ACTIF
&& $this->getMontantDisponible() >= $montantDemande;
}
public function isEnAlerteExpiration(): bool
{
return $this->getStatut() === self::STATUT_ACTIF
&& $this->getJoursRestants() <= self::SEUIL_ALERTE_EXPIRATION;
}
public function isEnAlertePlafond(): bool
{
return $this->getTauxConsommation() >= (self::SEUIL_ALERTE_PLAFOND * 100);
}
// ─── Getters / Setters ────────────────────────────────────────────────────
public function getId(): ?int { return $this->id; }
public function getReference(): ?string { return $this->reference; }
public function setReference(string $reference): self
{
$this->reference = strtoupper($reference);
return $this;
}
public function getIntitule(): ?string { return $this->intitule; }
public function setIntitule(string $intitule): self { $this->intitule = $intitule; return $this; }
public function getFournisseur(): ?Fournisseur { return $this->fournisseur; }
public function setFournisseur(?Fournisseur $fournisseur): self { $this->fournisseur = $fournisseur; return $this; }
public function getCategorieMarche(): ?CategorieMarche { return $this->categorieMarche; }
public function setCategorieMarche(?CategorieMarche $categorieMarche): self { $this->categorieMarche = $categorieMarche; return $this; }
public function getMontantPlafond(): ?string { return $this->montantPlafond; }
public function setMontantPlafond(string $montantPlafond): self { $this->montantPlafond = $montantPlafond; return $this; }
public function getMontantConsomme(): string { return $this->montantConsomme; }
public function setMontantConsomme(string $montantConsomme): self { $this->montantConsomme = $montantConsomme; return $this; }
public function getDateDebut(): ?\DateTimeInterface { return $this->dateDebut; }
public function setDateDebut(?\DateTimeInterface $dateDebut): self { $this->dateDebut = $dateDebut; return $this; }
public function getDateFin(): ?\DateTimeInterface { return $this->dateFin; }
public function setDateFin(?\DateTimeInterface $dateFin): self { $this->dateFin = $dateFin; return $this; }
public function getNiveauPriorite(): ?int { return $this->niveauPriorite; }
public function setNiveauPriorite(int $niveauPriorite): self { $this->niveauPriorite = $niveauPriorite; return $this; }
public function isActif(): bool { return $this->actif; }
public function setActif(bool $actif): self { $this->actif = $actif; return $this; }
public function getDescription(): ?string { return $this->description; }
public function setDescription(?string $description): self { $this->description = $description; return $this; }
public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; }
public function getUpdatedAt(): ?\DateTimeInterface { return $this->updatedAt; }
public function getBonsDeCommande(): Collection { return $this->bonsDeCommande; }
public function getMontantReserve(): string{ return $this->montantReserve;}
public function setMontantReserve(string $montantReserve): self{$this->montantReserve = $montantReserve;return $this;}
public function __toString(): string
{
return $this->reference . ' – ' . $this->intitule;
}
}