mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 13:08:11 +02:00
Grille - Corrige le lot 4 : au changement de magasin, les chiffres et le tri affichés suivent tout de suite (nouvelle liste), la sélection est gardée. - Lignes toujours chargées « Nos 2 magasins » ; changement de magasin sans aller-retour serveur (URL mise à jour par l'historique du navigateur). - Complément de l'API FF par magasin servi à part (/api/grid/rows/store-patch), appliqué à son arrivée, calcul identique à l'ancien (lib/store-patch.ts). - La recherche ne reconstruit plus toutes les lignes à chaque frappe ; la sélection n'est remise à zéro qu'au changement de fournisseur. Sécurité - lib/authz.ts : session relue en base (cache 1 min), rôle de la base prioritaire ; variantes pour actions, routes et pages. - Paramètres réservés aux administrateurs ; session vérifiée dans les actions Grille, Commandes, Historique et les routes ; diagnostics réservés admin. - Mots de passe et clés d'IA plus envoyés au navigateur ; champ vide = mot de passe conservé ; plus de mot de passe dans le localStorage ; l'enregistrement de la base n'efface plus les clés d'IA. - admin/admin créé seulement si aucun utilisateur ; fichier de secours utilisé uniquement si la base est injoignable ; data/ ignoré par git. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
92 lines
3.5 KiB
TypeScript
92 lines
3.5 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
import { asc, eq, inArray, sql } from "drizzle-orm";
|
|
import { adminOuReponse } from "@/lib/authz";
|
|
import { db } from "@/db";
|
|
import { syncFournisseurs } from "@/db/schema";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* GET — la liste complète des fournisseurs paramétrés, avec l'état de leur
|
|
* dernière synchronisation.
|
|
*
|
|
* Pas de pagination : quelques centaines de lignes tout au plus, et l'admin a
|
|
* besoin de la vue d'ensemble pour décider qui cadencer.
|
|
*/
|
|
export async function GET() {
|
|
const denied = await adminOuReponse();
|
|
if (denied instanceof Response) return denied;
|
|
|
|
const rows = await db
|
|
.select()
|
|
.from(syncFournisseurs)
|
|
.orderBy(asc(sql`coalesce(${syncFournisseurs.nomFournisseur}, ${syncFournisseurs.codeFournisseur})`));
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
fournisseurs: rows,
|
|
resume: {
|
|
total: rows.length,
|
|
actifsSql: rows.filter((r) => r.actifSql).length,
|
|
actifsQlik: rows.filter((r) => r.actifQlik).length,
|
|
desactivesAuto: rows.filter((r) => r.desactiveMotif).length,
|
|
enEchecSql: rows.filter((r) => r.dernierSqlStatut === "echec").length,
|
|
enEchecQlik: rows.filter((r) => r.dernierQlikStatut === "echec").length,
|
|
jamaisSql: rows.filter((r) => !r.dernierSqlAt).length,
|
|
},
|
|
});
|
|
}
|
|
|
|
const patchSchema = z.object({
|
|
/** Codes visés. Permet d'activer ou désactiver en masse depuis la page. */
|
|
codes: z.array(z.string().min(1)).min(1).max(2000),
|
|
actifSql: z.boolean().optional(),
|
|
actifQlik: z.boolean().optional(),
|
|
}).refine((v) => v.actifSql !== undefined || v.actifQlik !== undefined, {
|
|
message: "Préciser au moins actifSql ou actifQlik",
|
|
});
|
|
|
|
/**
|
|
* PATCH — active ou désactive des fournisseurs, pour l'une ou l'autre source.
|
|
*
|
|
* Réactiver lève le motif de désactivation automatique : sans cela, un
|
|
* fournisseur remis en service par l'admin garderait un « aucun article »
|
|
* trompeur, alors même qu'il vient d'être rouvert volontairement.
|
|
*/
|
|
export async function PATCH(req: NextRequest) {
|
|
const denied = await adminOuReponse();
|
|
if (denied instanceof Response) return denied;
|
|
|
|
let body: unknown;
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "Corps JSON invalide" }, { status: 400 });
|
|
}
|
|
const parsed = patchSchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return NextResponse.json(
|
|
{ error: "Paramètres invalides", details: parsed.error.issues.map((i) => i.message) },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
const { codes, actifSql, actifQlik } = parsed.data;
|
|
|
|
const reactivation = actifSql === true || actifQlik === true;
|
|
const rows = await db
|
|
.update(syncFournisseurs)
|
|
.set({
|
|
...(actifSql !== undefined ? { actifSql } : {}),
|
|
...(actifQlik !== undefined ? { actifQlik } : {}),
|
|
...(reactivation ? { desactiveMotif: null } : {}),
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(codes.length === 1 ? eq(syncFournisseurs.codeFournisseur, codes[0]) : inArray(syncFournisseurs.codeFournisseur, codes))
|
|
.returning({ code: syncFournisseurs.codeFournisseur });
|
|
|
|
console.log(`[api/admin/sync] ${rows.length} fournisseur(s) mis à jour (sql=${actifSql} qlik=${actifQlik})`);
|
|
return NextResponse.json({ success: true, misAJour: rows.length });
|
|
}
|