feat: add sous-devis generation from commandes

From a commande, users can now create one or more sub-quotes
(sous-devis) to ventilate purchases across communes for approval.

- Add parent_devis_id and commande_id FKs on devis table
- Order model: sousDevis() hasMany relation
- Devis model: commande() and parent() relations
- DevisController: createFromCommande / storeFromCommande methods
- OrderController: load and pass sous_devis on show
- Commandes/Show: "Créer un sous-devis" button + ventilation panel
- Devis/CreateFromCommande: form pre-loaded with commande context
- Devis/Index: badge "Sous-devis · CMD-XXXX" on linked quotes
- Devis/Show: back-link to originating commande

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mrKamoo
2026-06-19 15:14:45 +02:00
co-authored by Claude Sonnet 4.6
parent 7312c7640b
commit 8d51f4de3e
12 changed files with 874 additions and 15 deletions
+72
View File
@@ -68,6 +68,16 @@ const printDevis = () => {
>
Modifier
</Link>
<Link
v-if="!devis.parent_devis_id"
:href="route('devis.sous-devis.create', devis.id)"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-violet-600 hover:bg-violet-500 rounded-lg transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
Créer un sous-devis
</Link>
<a
:href="route('devis.pdf', devis.id)"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-sky-600 hover:bg-sky-550 rounded-lg transition-colors shadow-sm"
@@ -224,6 +234,68 @@ const printDevis = () => {
</div>
<!-- Badge : lien vers la commande d'origine si sous-devis -->
<div v-if="devis.commande_id" class="pt-6 border-t border-slate-200 dark:border-slate-800">
<div class="flex items-center gap-3 text-sm">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-bold bg-violet-100 text-violet-800 dark:bg-violet-950/40 dark:text-violet-300 border border-violet-200 dark:border-violet-900/60">
Sous-devis
</span>
<span class="text-slate-500 dark:text-slate-400">Issu de la commande :</span>
<Link :href="route('commandes.show', devis.commande_id)" class="font-mono text-xs font-bold text-sky-600 dark:text-sky-400 hover:underline">
{{ devis.commande_number }}
</Link>
</div>
</div>
<!-- Badge : lien vers le devis parent si sous-devis (legacy) -->
<div v-if="devis.parent_devis_id && devis.parent && !devis.commande_id" class="pt-6 border-t border-slate-200 dark:border-slate-800">
<div class="flex items-center gap-3 text-sm">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-bold bg-violet-100 text-violet-800 dark:bg-violet-950/40 dark:text-violet-300 border border-violet-200 dark:border-violet-900/60">
Sous-devis
</span>
<span class="text-slate-500 dark:text-slate-400">Issu du devis fournisseur :</span>
<Link :href="route('devis.show', devis.parent.id)" class="font-mono text-xs font-bold text-sky-600 dark:text-sky-400 hover:underline">
{{ devis.parent.number }}
</Link>
<span class="text-slate-400 dark:text-slate-600"></span>
<span class="text-slate-600 dark:text-slate-400">{{ devis.parent.title }}</span>
</div>
</div>
<!-- Section sous-devis enfants (si devis parent) -->
<div v-if="!devis.parent_devis_id && devis.sous_devis && devis.sous_devis.length > 0" class="pt-6 border-t border-slate-200 dark:border-slate-800 space-y-4 no-print">
<h3 class="text-sm font-black uppercase tracking-wider text-slate-500 dark:text-slate-400">
Sous-devis générés ({{ devis.sous_devis.length }})
</h3>
<div class="space-y-2">
<div
v-for="sd in devis.sous_devis"
:key="sd.id"
class="flex items-center justify-between px-4 py-3 rounded-xl border border-slate-200 dark:border-slate-800 bg-slate-50 dark:bg-slate-950/40"
>
<div class="flex items-center gap-3">
<span class="font-mono text-xs font-bold text-violet-600 dark:text-violet-400">{{ sd.number }}</span>
<span class="text-sm text-slate-700 dark:text-slate-300">{{ sd.title }}</span>
<span class="text-xs text-slate-500 dark:text-slate-400"> {{ sd.destinataire_nom }}</span>
</div>
<div class="flex items-center gap-3">
<span class="text-sm font-bold text-slate-900 dark:text-slate-100">
{{ new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(sd.total_ttc) }}
</span>
<Link :href="route('devis.show', sd.id)" class="text-xs font-semibold text-sky-600 dark:text-sky-400 hover:underline">
Voir
</Link>
</div>
</div>
</div>
<Link
:href="route('devis.sous-devis.create', devis.id)"
class="inline-flex items-center text-xs font-semibold text-violet-600 dark:text-violet-400 hover:underline"
>
+ Ajouter un sous-devis
</Link>
</div>
</div>
</div>
</div>