Lot 4 : Grille servie depuis l'instantané et requêtes allégées

- Grille : lecture de l'instantané grid_rows (gamme INIT et dernières gammes
  enregistrées reportées), recalcul en arrière-plan au-delà de 20 h, version de
  format dans chaque ligne pour écarter les anciens instantanés.
- Navigateur : lignes réutilisées au retour sur la Grille (< 10 min), Grille
  conservée pendant un changement de magasin ou une actualisation, React.memo
  sur la Grille, les filtres et la barre du bas, en-tête mémorisé.
- Fiche produit : toutes les lectures en parallèle.
- Ventes par mois et stock sans vente : plages de dates au lieu de TO_CHAR.
- Accueil : vagues de 12 appels, cache de 30 min.
- API v1 : comptage et page en parallèle, last_used_at au plus une fois par
  minute, version interne non exposée.
- Plan : suivi du lot 4, avec ce qui reste à valider sur la base réelle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
This commit is contained in:
Claude committed 2026-10-03 21:15:33 +00:00
1 parent 220ca2263a
commit b7ee413d67
13 files changed
+393 -119

No files matched your search

+29 -5
View File
@@ -674,8 +674,11 @@ export async function pgGetDashboardData(): Promise<DashboardData> {
if (allCodeins.length > 0) {
try {
for (let i = 0; i < allCodeins.length; i += 5) {
const batch = allCodeins.slice(i, i + 5);
// Vagues de 12 articles au lieu de 5 : chaque article coûte deux allers-
// retours successifs, et la page d'accueil attendait une douzaine de vagues.
const VAGUE = 12;
for (let i = 0; i < allCodeins.length; i += VAGUE) {
const batch = allCodeins.slice(i, i + VAGUE);
await Promise.all(batch.map(async (codein) => {
try {
const artRes = await fetch(`${FF_API_BASE}/api/articles?codein=${encodeURIComponent(codein)}`, ffFetchInit());
@@ -816,6 +819,27 @@ export interface CaByNomenclatureRow {
* via une sous-requête artfou1 dédupliquée (DISTINCT ON) : un seul code par
* article, aucune duplication possible des mouvements dans la somme.
*/
/**
* Bornes d'un mois « YYYY-MM » : [premier jour, premier jour du mois suivant[.
*
* Filtrer `datmvt` sur ces bornes plutôt que sur `TO_CHAR(datmvt, 'YYYY-MM')`
* donne exactement les mêmes lignes, mais sans formater en texte la date de
* chaque mouvement de l'historique (et reste utilisable par un index sur datmvt).
*/
function bornesMois(mois: string): { debut: string; fin: string } {
const [annee, m] = mois.split("-").map(Number);
const suivant = m === 12 ? `${annee + 1}-01` : `${annee}-${String(m + 1).padStart(2, "0")}`;
return { debut: `${mois}-01`, fin: `${suivant}-01` };
}
/** Mouvements du mois OU du mois N-1 (cf. `bornesMois`). */
function filtreDeuxMois(mois: string, moisN1: string): SQL {
const a = bornesMois(mois);
const b = bornesMois(moisN1);
return sql`((m.datmvt >= ${a.debut}::date AND m.datmvt < ${a.fin}::date)
OR (m.datmvt >= ${b.debut}::date AND m.datmvt < ${b.fin}::date))`;
}
export async function pgGetCaByFournisseur(
mois: string,
moisN1: string
@@ -836,7 +860,7 @@ export async function pgGetCaByFournisseur(
ORDER BY art_no_id, code
) af ON af.art_no_id = a.no_id
LEFT JOIN fouident fi ON fi.code = af.code
WHERE (TO_CHAR(m.datmvt, 'YYYY-MM') = ${mois} OR TO_CHAR(m.datmvt, 'YYYY-MM') = ${moisN1})
WHERE ${filtreDeuxMois(mois, moisN1)}
AND m.site IN ('292', '579')
AND m.genremvt = 3
GROUP BY af.code, fi.nom, m.site, TO_CHAR(m.datmvt, 'YYYY-MM')
@@ -865,7 +889,7 @@ export async function pgGetCaByNomenclature(
FROM mvtart m
JOIN articles a ON a.no_id = m.artnoid
LEFT JOIN nomenclature n ON n.no_id = a.nom_no_id
WHERE (TO_CHAR(m.datmvt, 'YYYY-MM') = ${mois} OR TO_CHAR(m.datmvt, 'YYYY-MM') = ${moisN1})
WHERE ${filtreDeuxMois(mois, moisN1)}
AND m.site IN ('292', '579')
AND m.genremvt = 3
GROUP BY n.no_id, n.code, n.libelle, m.site, TO_CHAR(m.datmvt, 'YYYY-MM')
@@ -1211,7 +1235,7 @@ export async function pgGetStockSansVente(site?: string): Promise<PgStockSansVen
${lastEntryJoin(link, sql`a.no_id`, sql`m.site`)}
WHERE m.genremvt IN (1, 2)
AND m.site IN ('292', '579')
AND date_trunc('month', m.datmvt) < date_trunc('month', CURRENT_DATE)
AND m.datmvt < date_trunc('month', CURRENT_DATE)
${siteFilter}
AND NOT EXISTS (
SELECT 1 FROM mvtart mv2