feat: Add FF Nancy API client and status endpoint, implement grid product fetching and draft change saving, and introduce AI Copilot core logic.

This commit is contained in:
Michael committed 2026-03-18 14:31:29 +01:00
1 parent 22aa6f101a
commit 1035489db1
13 files changed
+706 -265

No files matched your search

+46 -25
View File
@@ -1,18 +1,20 @@
"use server";
import { db } from "@/db";
import { ventesProduits } from "@/db/schema";
import { eq, and, sql } from "drizzle-orm";
import { sessionSnapshots } from "@/db/schema";
import { eq, desc } from "drizzle-orm";
import { z } from "zod";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
const SaveDraftsSchema = z.object({
codeFournisseur: z.string(),
nomFournisseur: z.string().optional(),
magasin: z.string(),
changes: z.array(
z.object({
codein: z.string(),
magasin: z.string(),
codeGamme: z.enum(["A", "B", "C", "Z"]),
codeGammeBefore: z.string().nullable(),
codeGamme: z.string(),
})
),
});
@@ -25,29 +27,48 @@ export async function saveDraftChanges(
return { success: false, saved: 0, error: "Validation failed: " + parsed.error.message };
}
const { changes } = parsed.data;
const { codeFournisseur, nomFournisseur, magasin, changes } = parsed.data;
const session = await auth();
const rawUserId = (session?.user as { id?: string | number })?.id;
const userId = rawUserId ? parseInt(String(rawUserId), 10) : null;
const finalUserId = userId && !isNaN(userId) ? userId : null;
try {
// Batch update all rows for given codein.
// If magasin is "TOTAL", update ALL stores for that product.
await Promise.all(
changes.map(({ codein, magasin, codeGamme }) => {
const whereClause = [eq(ventesProduits.codein, codein)];
if (magasin !== "TOTAL") {
whereClause.push(eq(ventesProduits.magasin, magasin));
}
// Charger le dernier snapshot existant pour merger les changements
const existing = await db
.select()
.from(sessionSnapshots)
.where(eq(sessionSnapshots.codeFournisseur, codeFournisseur))
.orderBy(desc(sessionSnapshots.createdAt))
.limit(1);
return db
.update(ventesProduits)
.set({
codeGamme,
codeGammeInit: sql`COALESCE(ventes_produits.code_gamme_init, ventes_produits.code_gamme)`
})
.where(and(...whereClause));
})
);
const prevChanges: Record<string, { before: string | null; after: string }> =
existing.length > 0
? (existing[0].changes as Record<string, { before: string | null; after: string }>)
: {};
// Merger les nouveaux changements par-dessus l'existant
const mergedChanges = { ...prevChanges };
for (const c of changes) {
mergedChanges[c.codein] = {
before: c.codeGammeBefore,
after: c.codeGamme,
};
}
// Upsert : insérer un nouveau snapshot "draft" pour ce fournisseur
await db.insert(sessionSnapshots).values({
userId: finalUserId,
codeFournisseur,
nomFournisseur: nomFournisseur ?? null,
magasin,
label: `Draft — ${new Date().toLocaleDateString("fr-FR")}`,
changes: mergedChanges,
summaryJson: null,
type: "snapshot",
});
revalidatePath("/grid");
return { success: true, saved: changes.length };
} catch (err) {
const msg = err instanceof Error ? err.message : "Unknown error";