mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
Export des gammes découpé en fichiers de 5 000 lignes maximum
Le logiciel de mise à jour des gammes refuse les fichiers trop gros (ex. 28 590 lignes). L'export est désormais découpé en plusieurs fichiers (_partie_1_sur_6, …) à intégrer un par un. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011jxxh5X1FHMUPFu2S2JpyT
This commit is contained in:
2 files changed
+44
-16
No files matched your search
@@ -27,6 +27,7 @@ import {
|
|||||||
exporterGammeA,
|
exporterGammeA,
|
||||||
exporterTousProduits,
|
exporterTousProduits,
|
||||||
imprimerPdf,
|
imprimerPdf,
|
||||||
|
LIGNES_MAX_PAR_FICHIER_GAMMES,
|
||||||
lignesModifiees,
|
lignesModifiees,
|
||||||
} from "@/features/grid/lib/exports";
|
} from "@/features/grid/lib/exports";
|
||||||
|
|
||||||
@@ -42,6 +43,14 @@ function Stat({ label, value, sub, subColor }: { label: string; value: string; s
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Prévient quand le fichier d'import des gammes a été découpé ; vrai si quelque chose a été exporté. */
|
||||||
|
function signalerDecoupage(nbFichiers: number): boolean {
|
||||||
|
if (nbFichiers > 1) {
|
||||||
|
toast.info(`Export découpé en ${nbFichiers} fichiers de ${fmtEntier(LIGNES_MAX_PAR_FICHIER_GAMMES)} lignes maximum : intégrez-les un par un.`);
|
||||||
|
}
|
||||||
|
return nbFichiers > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function messageErreur(err: unknown): string {
|
function messageErreur(err: unknown): string {
|
||||||
if (isStaleServerActionError(err)) return STALE_ACTION_MESSAGE;
|
if (isStaleServerActionError(err)) return STALE_ACTION_MESSAGE;
|
||||||
return err instanceof Error ? err.message : String(err);
|
return err instanceof Error ? err.message : String(err);
|
||||||
@@ -217,7 +226,7 @@ function FloatingSummaryBarInner({ isAdmin, nomFournisseur }: { isAdmin: boolean
|
|||||||
const nom = lignesModifiees()[0].nomFournisseur;
|
const nom = lignesModifiees()[0].nomFournisseur;
|
||||||
// Copie dans l'Historique, onglet « Exports »
|
// Copie dans l'Historique, onglet « Exports »
|
||||||
await handleSnapshot(`Export ${nom} — ${new Date().toLocaleString("fr-FR", { dateStyle: "short", timeStyle: "short" })}`, "export");
|
await handleSnapshot(`Export ${nom} — ${new Date().toLocaleString("fr-FR", { dateStyle: "short", timeStyle: "short" })}`, "export");
|
||||||
return exporterFichierGammes(true);
|
return exporterFichierGammes(true).then(signalerDecoupage);
|
||||||
}, "Aucune gamme n'a été modifiée par rapport à FF.")}
|
}, "Aucune gamme n'a été modifiée par rapport à FF.")}
|
||||||
>
|
>
|
||||||
<FileSpreadsheet />
|
<FileSpreadsheet />
|
||||||
@@ -228,7 +237,7 @@ function FloatingSummaryBarInner({ isAdmin, nomFournisseur }: { isAdmin: boolean
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onSelect={() => lancer("complet", () => exporterFichierGammes(false), "Aucun produit à exporter.")}
|
onSelect={() => lancer("complet", () => exporterFichierGammes(false).then(signalerDecoupage), "Aucun produit à exporter.")}
|
||||||
>
|
>
|
||||||
<FileSpreadsheet />
|
<FileSpreadsheet />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|||||||
@@ -32,14 +32,24 @@ export function lignesModifiees(): ProductRow[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fichier d'import des gammes (CODE FOURNISSEUR, GENCOD, GAMME), généré par le
|
* Nombre maximal de produits par fichier d'import des gammes. Le logiciel de
|
||||||
* serveur. `seulementModifiees` : uniquement les gammes changées.
|
* mise à jour des gammes refuse les fichiers trop gros (~28 000 lignes) : au-delà,
|
||||||
* Renvoie `false` s'il n'y a rien à exporter.
|
* l'export est découpé en plusieurs fichiers à intégrer un par un.
|
||||||
*/
|
*/
|
||||||
export async function exporterFichierGammes(seulementModifiees: boolean): Promise<boolean> {
|
export const LIGNES_MAX_PAR_FICHIER_GAMMES = 5000;
|
||||||
|
|
||||||
|
const pause = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fichier d'import des gammes (CODE FOURNISSEUR, CODEIN, GAMME), généré par le
|
||||||
|
* serveur. `seulementModifiees` : uniquement les gammes changées.
|
||||||
|
* Découpé en plusieurs fichiers de `LIGNES_MAX_PAR_FICHIER_GAMMES` lignes au plus.
|
||||||
|
* Renvoie le nombre de fichiers téléchargés (0 s'il n'y a rien à exporter).
|
||||||
|
*/
|
||||||
|
export async function exporterFichierGammes(seulementModifiees: boolean): Promise<number> {
|
||||||
const { rows, draftChanges } = useGridStore.getState();
|
const { rows, draftChanges } = useGridStore.getState();
|
||||||
const lignes = seulementModifiees ? lignesModifiees() : rows;
|
const lignes = seulementModifiees ? lignesModifiees() : rows;
|
||||||
if (lignes.length === 0) return false;
|
if (lignes.length === 0) return 0;
|
||||||
|
|
||||||
const nomFournisseur = lignes[0].nomFournisseur;
|
const nomFournisseur = lignes[0].nomFournisseur;
|
||||||
const changes = lignes.map((r) => ({
|
const changes = lignes.map((r) => ({
|
||||||
@@ -49,16 +59,25 @@ export async function exporterFichierGammes(seulementModifiees: boolean): Promis
|
|||||||
gamme: gammeEffective(r, draftChanges) as string,
|
gamme: gammeEffective(r, draftChanges) as string,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const res = await fetch("/api/export/modified-gammes", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ nomFournisseur, changes }),
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error(`le serveur a répondu ${res.status}`);
|
|
||||||
|
|
||||||
const prefixe = seulementModifiees ? "Modifications_Gammes" : "Export_Complet";
|
const prefixe = seulementModifiees ? "Modifications_Gammes" : "Export_Complet";
|
||||||
telecharger(await res.blob(), `${prefixe}_${nomFichierSur(nomFournisseur)}_${aujourdhui()}.xlsx`);
|
const base = `${prefixe}_${nomFichierSur(nomFournisseur)}_${aujourdhui()}`;
|
||||||
return true;
|
const nbFichiers = Math.ceil(changes.length / LIGNES_MAX_PAR_FICHIER_GAMMES);
|
||||||
|
|
||||||
|
for (let i = 0; i < nbFichiers; i++) {
|
||||||
|
const paquet = changes.slice(i * LIGNES_MAX_PAR_FICHIER_GAMMES, (i + 1) * LIGNES_MAX_PAR_FICHIER_GAMMES);
|
||||||
|
const res = await fetch("/api/export/modified-gammes", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ nomFournisseur, changes: paquet }),
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error(`le serveur a répondu ${res.status}`);
|
||||||
|
|
||||||
|
const suffixe = nbFichiers > 1 ? `_partie_${i + 1}_sur_${nbFichiers}` : "";
|
||||||
|
telecharger(await res.blob(), `${base}${suffixe}.xlsx`);
|
||||||
|
// Les navigateurs ignorent les téléchargements trop rapprochés
|
||||||
|
if (i < nbFichiers - 1) await pause(500);
|
||||||
|
}
|
||||||
|
return nbFichiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tous les produits avec gamme avant/après et chiffres sur 12 mois (Excel, généré par le serveur). */
|
/** Tous les produits avec gamme avant/après et chiffres sur 12 mois (Excel, généré par le serveur). */
|
||||||
|
|||||||
Reference in new issue
Block a user