feat(commandes-auto): cadencier d'alertes de commande par fournisseur et magasin

Ajoute un onglet "Cadencier / Alertes" dans la page Commandes auto.
Depuis la liste des fournisseurs, on active un fournisseur en alerte de
commande sur un magasin (292/579) avec une fréquence en X semaines,
gérée indépendamment par magasin.

L'échéance est calculée à partir de la dernière réception réelle
(mvtart, genremvt 1/2, fournisseur principal artfou1.preference=1) +
X semaines. Statut: à commander / bientôt / OK, avec temps restant.

- Table commande_cadences (schema + db-init)
- pgGetDerniereReceptionParFournisseur (mvtart par fournisseur/site)
- Server actions CRUD (list/upsert/toggle/remove)
- UI à onglets + cadencier (ajout, édition intervalle, activer/supprimer)
This commit is contained in:
Claude committed 2026-06-03 08:19:50 +00:00
1 parent cd7b87aef8
commit ae0d8d3a4c
7 files changed
+668 -4

No files matched your search

+47
View File
@@ -1211,3 +1211,50 @@ export async function pgGetCommandesAuto(): Promise<PgCommandeAutoRow[]> {
return [];
}
}
// ---------------------------------------------------------------------------
// 12. Dernière réception par fournisseur ET par site (cadencier)
// ---------------------------------------------------------------------------
/**
* Retourne la date de la dernière réception (entrée de marchandise) par
* fournisseur et par site, pour piloter le cadencier de commande.
*
* Source : mvtart (genremvt IN (1, 2) = réceptions, voir pgGetMensuelByFournisseur).
* La réception est rattachée à l'article ; on l'attribue au fournisseur
* principal de l'article (artfou1.preference = 1), comme partout ailleurs.
*
* Clé de la Map : `${codefou}|${site}`. Map vide en cas d'erreur
* (l'UI affichera alors « échéance inconnue »).
*/
export async function pgGetDerniereReceptionParFournisseur(): Promise<Map<string, string>> {
const map = new Map<string, string>();
try {
const result = await pgNoParallel(sql`
SELECT
af.code AS codefou,
m.site AS site,
MAX(m.datmvt) AS derniere
FROM mvtart m
JOIN articles a ON a.no_id = m.artnoid
JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
WHERE m.genremvt IN (1, 2)
AND m.site IN ('292', '579')
AND m.datmvt IS NOT NULL
GROUP BY af.code, m.site
`);
for (const row of result.rows as { codefou: string; site: string; derniere: string | Date }[]) {
if (!row.codefou || !row.derniere) continue;
const iso = row.derniere instanceof Date
? row.derniere.toISOString()
: new Date(row.derniere).toISOString();
map.set(`${String(row.codefou).trim()}|${String(row.site ?? "").trim()}`, iso);
}
console.log(`[pg-ff] pgGetDerniereReceptionParFournisseur: ${map.size} entrées (mvtart)`);
return map;
} catch (e) {
console.error("[pg-ff] pgGetDerniereReceptionParFournisseur error:", (e as Error).message?.slice(0, 250));
return map;
}
}