278 lines
9.2 KiB
PHP
278 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Carbon\Carbon;
|
|
|
|
class Commande extends Model
|
|
{
|
|
use SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'numero_commande', 'service_id', 'fournisseur_id', 'user_id', 'commune_id',
|
|
'validateur_id', 'acheteur_id', 'objet', 'description', 'justification',
|
|
'statut', 'priorite', 'reference_fournisseur', 'imputation_budgetaire',
|
|
'montant_ht', 'montant_ttc',
|
|
'date_demande', 'date_souhaitee', 'date_validation', 'date_commande',
|
|
'date_livraison_prevue', 'date_reception', 'date_reception_complete', 'date_cloture',
|
|
'notes', 'notes_fournisseur',
|
|
];
|
|
|
|
protected $casts = [
|
|
'montant_ht' => 'decimal:2',
|
|
'montant_ttc' => 'decimal:2',
|
|
'date_demande' => 'date',
|
|
'date_souhaitee' => 'date',
|
|
'date_validation' => 'datetime',
|
|
'date_commande' => 'datetime',
|
|
'date_livraison_prevue' => 'date',
|
|
'date_reception' => 'datetime',
|
|
'date_reception_complete' => 'datetime',
|
|
'date_cloture' => 'datetime',
|
|
];
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Constantes
|
|
// -----------------------------------------------------------------------
|
|
|
|
const STATUTS_LABELS = [
|
|
'brouillon' => 'Brouillon',
|
|
'en_attente_validation' => 'En attente de validation',
|
|
'validee' => 'Validée',
|
|
'commandee' => 'Commandée',
|
|
'partiellement_recue' => 'Partiellement reçue',
|
|
'recue_complete' => 'Reçue complète',
|
|
'cloturee' => 'Clôturée',
|
|
'annulee' => 'Annulée',
|
|
];
|
|
|
|
const STATUTS_COULEURS = [
|
|
'brouillon' => 'gray',
|
|
'en_attente_validation' => 'yellow',
|
|
'validee' => 'blue',
|
|
'commandee' => 'indigo',
|
|
'partiellement_recue' => 'orange',
|
|
'recue_complete' => 'green',
|
|
'cloturee' => 'slate',
|
|
'annulee' => 'red',
|
|
];
|
|
|
|
const PRIORITES_LABELS = [
|
|
'normale' => 'Normale',
|
|
'haute' => 'Haute',
|
|
'urgente' => 'Urgente',
|
|
];
|
|
|
|
const STATUT_TRANSITIONS = [
|
|
'brouillon' => ['en_attente_validation', 'annulee'],
|
|
'en_attente_validation' => ['validee', 'brouillon', 'annulee'],
|
|
'validee' => ['commandee', 'annulee'],
|
|
'commandee' => ['partiellement_recue', 'recue_complete', 'annulee'],
|
|
'partiellement_recue' => ['recue_complete', 'annulee'],
|
|
'recue_complete' => ['cloturee'],
|
|
'cloturee' => [],
|
|
'annulee' => [],
|
|
];
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Relations
|
|
// -----------------------------------------------------------------------
|
|
|
|
public function service(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Service::class);
|
|
}
|
|
|
|
public function fournisseur(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Fournisseur::class);
|
|
}
|
|
|
|
public function demandeur(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'user_id');
|
|
}
|
|
|
|
public function commune(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Commune::class);
|
|
}
|
|
|
|
public function validateur(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'validateur_id');
|
|
}
|
|
|
|
public function assets(): HasMany
|
|
{
|
|
return $this->hasMany(Asset::class);
|
|
}
|
|
|
|
public function acheteur(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'acheteur_id');
|
|
}
|
|
|
|
public function lignes(): HasMany
|
|
{
|
|
return $this->hasMany(LigneCommande::class)->orderBy('ordre');
|
|
}
|
|
|
|
public function historique(): HasMany
|
|
{
|
|
return $this->hasMany(HistoriqueCommande::class)->orderBy('created_at', 'desc');
|
|
}
|
|
|
|
public function piecesJointes(): HasMany
|
|
{
|
|
return $this->hasMany(PieceJointe::class)->orderBy('created_at', 'desc');
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Scopes
|
|
// -----------------------------------------------------------------------
|
|
|
|
public function scopeEnCours(Builder $query): Builder
|
|
{
|
|
return $query->whereNotIn('statut', ['cloturee', 'annulee']);
|
|
}
|
|
|
|
public function scopeEnRetard(Builder $query): Builder
|
|
{
|
|
return $query->enCours()->whereNotNull('date_souhaitee')->where('date_souhaitee', '<', now()->toDateString());
|
|
}
|
|
|
|
public function scopeUrgentes(Builder $query): Builder
|
|
{
|
|
return $query->where('priorite', 'urgente');
|
|
}
|
|
|
|
public function scopeParService(Builder $query, int $serviceId): Builder
|
|
{
|
|
return $query->where('service_id', $serviceId);
|
|
}
|
|
|
|
public function scopeParStatut(Builder $query, string $statut): Builder
|
|
{
|
|
return $query->where('statut', $statut);
|
|
}
|
|
|
|
public function scopeParFournisseur(Builder $query, int $fournisseurId): Builder
|
|
{
|
|
return $query->where('fournisseur_id', $fournisseurId);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Accesseurs
|
|
// -----------------------------------------------------------------------
|
|
|
|
public function getStatutLabelAttribute(): string
|
|
{
|
|
return self::STATUTS_LABELS[$this->statut] ?? $this->statut;
|
|
}
|
|
|
|
public function getStatutCouleurAttribute(): string
|
|
{
|
|
return self::STATUTS_COULEURS[$this->statut] ?? 'gray';
|
|
}
|
|
|
|
public function getEstEnRetardAttribute(): bool
|
|
{
|
|
return !in_array($this->statut, ['cloturee', 'annulee'])
|
|
&& $this->date_souhaitee !== null
|
|
&& $this->date_souhaitee->isPast();
|
|
}
|
|
|
|
public function getTauxReceptionAttribute(): float
|
|
{
|
|
$total = $this->lignes->sum(fn ($l) => (float) $l->quantite);
|
|
if ($total <= 0) {
|
|
return 0.0;
|
|
}
|
|
$recu = $this->lignes->sum(fn ($l) => (float) $l->quantite_recue);
|
|
|
|
return round(min($recu / $total * 100, 100), 1);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Numérotation automatique
|
|
// -----------------------------------------------------------------------
|
|
|
|
public static function genererNumero(): string
|
|
{
|
|
$annee = now()->year;
|
|
$derniere = static::withTrashed()
|
|
->where('numero_commande', 'like', "CMD-IT-{$annee}-%")
|
|
->orderByDesc('id')
|
|
->value('numero_commande');
|
|
|
|
$sequence = 1;
|
|
if ($derniere) {
|
|
$sequence = (int) substr($derniere, strrpos($derniere, '-') + 1) + 1;
|
|
}
|
|
|
|
return sprintf('CMD-IT-%d-%04d', $annee, $sequence);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Moteur de transitions
|
|
// -----------------------------------------------------------------------
|
|
|
|
public function peutTransitionnerVers(string $statut): bool
|
|
{
|
|
return in_array($statut, self::STATUT_TRANSITIONS[$this->statut] ?? []);
|
|
}
|
|
|
|
public function transitionnerVers(string $statut, User $user, ?string $commentaire = null): bool
|
|
{
|
|
if (!$this->peutTransitionnerVers($statut)) {
|
|
return false;
|
|
}
|
|
|
|
$ancienStatut = $this->statut;
|
|
$this->statut = $statut;
|
|
|
|
// Mettre à jour les dates et les acteurs selon le nouveau statut
|
|
match ($statut) {
|
|
'validee' => $this->fill(['date_validation' => now(), 'validateur_id' => $user->id]),
|
|
'commandee' => $this->fill(['date_commande' => now(), 'acheteur_id' => $user->id]),
|
|
'partiellement_recue' => $this->fill(['date_reception' => $this->date_reception ?? now()]),
|
|
'recue_complete' => $this->fill(['date_reception_complete' => now(), 'date_reception' => $this->date_reception ?? now()]),
|
|
'cloturee' => $this->fill(['date_cloture' => now()]),
|
|
default => null,
|
|
};
|
|
|
|
$this->save();
|
|
|
|
HistoriqueCommande::create([
|
|
'commande_id' => $this->id,
|
|
'user_id' => $user->id,
|
|
'ancien_statut' => $ancienStatut,
|
|
'nouveau_statut' => $statut,
|
|
'commentaire' => $commentaire,
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Recalcul des montants (appelé depuis LigneCommande)
|
|
// -----------------------------------------------------------------------
|
|
|
|
public function recalculerMontants(): void
|
|
{
|
|
$this->lignes()->get()->tap(function ($lignes) {
|
|
$ht = $lignes->sum(fn ($l) => (float) $l->montant_ht);
|
|
$ttc = $lignes->sum(fn ($l) => (float) $l->montant_ttc);
|
|
$this->withoutEvents(function () use ($ht, $ttc) {
|
|
$this->update(['montant_ht' => $ht, 'montant_ttc' => $ttc]);
|
|
});
|
|
});
|
|
}
|
|
}
|