<?php
namespace App\Entity;
use App\Repository\LignePlanAchatRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* Ligne du plan d'achats.
* La procédure applicable est détectée automatiquement depuis la matrice des seuils.
*/
#[ORM\Entity(repositoryClass: LignePlanAchatRepository::class)]
#[ORM\Table(name: 'ligne_plan_achat')]
#[ORM\HasLifecycleCallbacks]
class LignePlanAchat
{
const STATUT_PLANIFIE = 'planifie';
const STATUT_EN_COURS = 'en_cours';
const STATUT_DA_CREEE = 'da_creee';
const STATUT_REALISE = 'realise';
const STATUT_ANNULE = 'annule';
const STATUT_REPORTE = 'reporte';
const TRIMESTRE_T1 = 'T1';
const TRIMESTRE_T2 = 'T2';
const TRIMESTRE_T3 = 'T3';
const TRIMESTRE_T4 = 'T4';
public static function getStatuts(): array
{
return [
self::STATUT_PLANIFIE => 'Planifié',
self::STATUT_EN_COURS => 'En cours',
self::STATUT_DA_CREEE => 'DA créée',
self::STATUT_REALISE => 'Réalisé',
self::STATUT_ANNULE => 'Annulé',
self::STATUT_REPORTE => 'Reporté',
];
}
public static function getTrimestres(): array
{
return [
self::TRIMESTRE_T1 => '1er trimestre (Jan-Mar)',
self::TRIMESTRE_T2 => '2ème trimestre (Avr-Jun)',
self::TRIMESTRE_T3 => '3ème trimestre (Jul-Sep)',
self::TRIMESTRE_T4 => '4ème trimestre (Oct-Déc)',
];
}
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: PlanAchatAnnuel::class, inversedBy: 'lignes')]
#[ORM\JoinColumn(name: 'plan_achat_id', nullable: false, onDelete: 'CASCADE')]
private ?PlanAchatAnnuel $planAchat = null;
#[ORM\Column(name: 'montant_reserve', type: Types::DECIMAL, precision: 15, scale: 2, nullable: true)]
private ?string $montantReserve = '0';
// Numéro de ligne dans le plan
#[ORM\Column(name: 'num_ligne', nullable: true)]
private ?int $numLigne = null;
// Désignation de l'article/service à acheter
#[ORM\Column(name: 'designation', length: 255)]
private ?string $designation = null;
#[ORM\Column(name: 'description', type: Types::TEXT, nullable: true)]
private ?string $description = null;
// Quantité prévue
#[ORM\Column(name: 'quantite', type: Types::DECIMAL, precision: 10, scale: 2, nullable: true)]
private ?string $quantite = null;
#[ORM\Column(name: 'unite', length: 50, nullable: true)]
private ?string $unite = null;
// Budget estimé — CLEF pour la détection automatique du seuil
#[ORM\Column(name: 'budget_estime', type: Types::DECIMAL, precision: 15, scale: 2, nullable: true)]
private ?string $budgetEstime = null;
// Montant réalisé (après exécution)
#[ORM\Column(name: 'montant_realise', type: Types::DECIMAL, precision: 15, scale: 2, nullable: true)]
private ?string $montantRealise = null;
// Catégorie de marché (lien vers CategorieMarche)
#[ORM\ManyToOne(targetEntity: CategorieMarche::class)]
#[ORM\JoinColumn(name: 'categorie_marche_id', nullable: true)]
private ?CategorieMarche $categorieMarche = null;
// Seuil détecté automatiquement
#[ORM\ManyToOne(targetEntity: SeuilPassation::class)]
#[ORM\JoinColumn(name: 'seuil_passation_id', nullable: true)]
private ?SeuilPassation $seuilPassation = null;
// Trimestre prévu
#[ORM\Column(name: 'trimestre_prevu', length: 5, nullable: true)]
private ?string $trimestrePrevu = null;
// Mois prévu (1-12)
#[ORM\Column(name: 'mois_prevu', nullable: true)]
private ?int $moisPrevu = null;
#[ORM\Column(name: 'statut', length: 20)]
private string $statut = self::STATUT_PLANIFIE;
// Observation / justification
#[ORM\Column(name: 'observation', type: Types::TEXT, nullable: true)]
private ?string $observation = null;
// Lien vers la DA créée depuis cette ligne
#[ORM\OneToMany(mappedBy: 'lignePlanAchat', targetEntity: demAchat::class)]
private Collection $demandesAchat;
// Priorité (1 = haute, 2 = moyenne, 3 = faible)
#[ORM\Column(name: 'priorite', nullable: true)]
private ?int $priorite = null;
#[ORM\Column(name: 'created_at', type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $createdAt = null;
#[ORM\Column(name: 'updated_at', type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $updatedAt = null;
public function __construct()
{
$this->createdAt = new \DateTime();
$this->statut = self::STATUT_PLANIFIE;
$this->demandesAchat = new ArrayCollection(); // Indispensable
}
#[ORM\PreUpdate]
public function onPreUpdate(): void
{
$this->updatedAt = new \DateTime();
}
// ---------------------------------------------------------------
// Méthodes métier
// ---------------------------------------------------------------
/**
* Retourne l'écart entre budget estimé et montant réalisé.
*/
public function getEcartBudget(): ?float
{
if ($this->budgetEstime === null || $this->montantRealise === null) return null;
return floatval($this->montantRealise) - floatval($this->budgetEstime);
}
/**
* Libellé de priorité.
*/
public function getPrioriteLabel(): string
{
return match($this->priorite) {
1 => 'Haute',
2 => 'Moyenne',
3 => 'Faible',
default => '—',
};
}
// ---------------------------------------------------------------
// Getters / Setters
// ---------------------------------------------------------------
public function getId(): ?int { return $this->id; }
public function getPlanAchat(): ?PlanAchatAnnuel { return $this->planAchat; }
public function setPlanAchat(?PlanAchatAnnuel $p): static { $this->planAchat = $p; return $this; }
public function getNumLigne(): ?int { return $this->numLigne; }
public function setNumLigne(?int $n): static { $this->numLigne = $n; return $this; }
public function getDesignation(): ?string { return $this->designation; }
public function setDesignation(string $d): static { $this->designation = $d; return $this; }
public function getDescription(): ?string { return $this->description; }
public function setDescription(?string $d): static { $this->description = $d; return $this; }
public function getQuantite(): ?string { return $this->quantite; }
public function setQuantite(?string $q): static { $this->quantite = $q; return $this; }
public function getUnite(): ?string { return $this->unite; }
public function setUnite(?string $u): static { $this->unite = $u; return $this; }
public function getBudgetEstime(): ?string { return $this->budgetEstime; }
public function setBudgetEstime(?string $b): static { $this->budgetEstime = $b; return $this; }
public function getMontantRealise(): ?string { return $this->montantRealise; }
public function setMontantRealise(?string $m): static { $this->montantRealise = $m; return $this; }
public function getCategorieMarche(): ?CategorieMarche { return $this->categorieMarche; }
public function setCategorieMarche(?CategorieMarche $c): static { $this->categorieMarche = $c; return $this; }
public function getSeuilPassation(): ?SeuilPassation { return $this->seuilPassation; }
public function setSeuilPassation(?SeuilPassation $s): static { $this->seuilPassation = $s; return $this; }
public function getTrimestrePrevu(): ?string { return $this->trimestrePrevu; }
public function setTrimestrePrevu(?string $t): static { $this->trimestrePrevu = $t; return $this; }
public function getMoisPrevu(): ?int { return $this->moisPrevu; }
public function setMoisPrevu(?int $m): static { $this->moisPrevu = $m; return $this; }
public function getStatut(): string { return $this->statut; }
public function setStatut(string $s): static { $this->statut = $s; return $this; }
public function getObservation(): ?string { return $this->observation; }
public function setObservation(?string $o): static { $this->observation = $o; return $this; }
public function getPriorite(): ?int { return $this->priorite; }
public function setPriorite(?int $p): static { $this->priorite = $p; return $this; }
public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; }
public function getUpdatedAt(): ?\DateTimeInterface { return $this->updatedAt; }
public function getMontantConsomme(): float
{
$total = 0;
foreach ($this->demandesAchat as $da) {
// On suppose que votre entité demAchat a une méthode getMontant()
$total += (float) $da->getMontant();
}
return $total;
}
/**
* @return Collection<int, demAchat>
*/
public function getDemandesAchat(): Collection
{
return $this->demandesAchat;
}
public function getMontantReserve(): ?string { return $this->montantReserve; }
public function setMontantReserve(?string $m): static {
$this->montantReserve = $m;
return $this;
}
/**
* Budget réellement disponible =
* budgetEstime - montantRealise - montantReserve
*/
public function getResteDisponible(): float
{
return (float)$this->getBudgetEstime()
- (float)($this->getMontantRealise() ?? 0)
- (float)($this->getMontantReserve() ?? 0);
}
/**
* Récapitulatif complet du budget
*/
public function getBudgetRecap(): array
{
$estime = (float)$this->getBudgetEstime();
$realise = (float)($this->getMontantRealise() ?? 0);
$reserve = (float)($this->getMontantReserve() ?? 0);
$disponible = $estime - $realise - $reserve;
return [
'estime' => $estime,
'realise' => $realise,
'reserve' => $reserve,
'disponible' => $disponible,
'taux' => $estime > 0
? round((($realise + $reserve) / $estime) * 100, 1)
: 0,
];
}
}