mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-15 03:08:31 +02:00
Afficher les montants au centime dans toute l'application
Les CA, marges et valeurs de stock étaient arrondis à l'euro (fmtEur0, fmtEntier suivi de « € », Math.round dans les exports). Tous les montants sont désormais affichés avec deux décimales : accueil, grille, fiche produit, hit-parade, publicités, analytics, snapshots, export PDF et export Excel des manques d'assortiment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VEXT61tGbsXHUDNBeghCXQ
This commit is contained in:
13 files changed
+58
-55
No files matched your search
@@ -30,7 +30,6 @@ const PROFONDEURS = [100, 200, 500] as const;
|
||||
export const PROFONDEUR_MAX = PROFONDEURS[PROFONDEURS.length - 1];
|
||||
|
||||
const nb = (v: number) => Math.round(v).toLocaleString("fr-FR");
|
||||
const eur0 = (v: number) => `${Math.round(v).toLocaleString("fr-FR")} €`;
|
||||
const eur2 = (v: number) => v.toLocaleString("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + " €";
|
||||
|
||||
/**
|
||||
@@ -51,18 +50,18 @@ interface Critere {
|
||||
}
|
||||
|
||||
const CRITERES: Record<string, Critere> = {
|
||||
caReseau: { label: "CA réseau", format: eur0, xlsx: '#,##0 "€"' },
|
||||
caReseau: { label: "CA réseau", format: eur2, xlsx: '#,##0.00 "€"' },
|
||||
qteReseau: { label: "Qté réseau", format: nb, xlsx: "#,##0" },
|
||||
nbMagasinsReseau: { label: "Magasins réseau", format: nb, xlsx: "#,##0" },
|
||||
tendanceReseau: { label: "Tendance réseau", format: (v) => `${v >= 0 ? "+" : ""}${Math.round(v * 100)} %`, xlsx: "0 %" },
|
||||
prixMoyenReseau: { label: "PV moyen réseau", format: eur2, xlsx: '#,##0.00 "€"' },
|
||||
prixVente: { label: "PV magasin", format: eur2, xlsx: '#,##0.00 "€"' },
|
||||
caParMagasinReseau: { label: "CA / magasin réseau", format: eur0, xlsx: '#,##0 "€"' },
|
||||
caParMagasinReseau: { label: "CA / magasin réseau", format: eur2, xlsx: '#,##0.00 "€"' },
|
||||
margePctReseau: { label: "Marge % réseau", format: (v) => `${(Math.abs(v) <= 1 ? v * 100 : v).toFixed(1)} %`,
|
||||
xlsx: '0.0 "%"', brut: (v) => (Math.abs(v) <= 1 ? v * 100 : v) },
|
||||
totalQuantite: { label: "Qté 12 m", format: nb, xlsx: "#,##0" },
|
||||
totalCa: { label: "CA 12 m", format: eur0, xlsx: '#,##0 "€"' },
|
||||
totalMarge: { label: "Marge 12 m", format: eur0, xlsx: '#,##0 "€"' },
|
||||
totalCa: { label: "CA 12 m", format: eur2, xlsx: '#,##0.00 "€"' },
|
||||
totalMarge: { label: "Marge 12 m", format: eur2, xlsx: '#,##0.00 "€"' },
|
||||
};
|
||||
|
||||
function libelleCritere(id: string | null): string {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { confirmer, toast } from "@/components/ui/feedback";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
import {
|
||||
exporterFichierGammes,
|
||||
@@ -165,10 +165,10 @@ function FloatingSummaryBarInner({ isAdmin, nomFournisseur }: { isAdmin: boolean
|
||||
<div className="flex flex-wrap items-center gap-x-8 gap-y-1">
|
||||
<Stat label="Produits actifs" value={fmtEntier(summary.totalRows)} sub={summary.totalProducts !== summary.totalRows ? `sur ${fmtEntier(summary.totalProducts)}` : undefined} subColor="var(--text-muted)" />
|
||||
<Stat label="Quantités vendues (12 mois)" value={fmtEntier(summary.totalQuantite)} />
|
||||
<Stat label="Chiffre d'affaires" value={fmtEur0(summary.totalCa)} />
|
||||
<Stat label="Chiffre d'affaires" value={fmtEur2(summary.totalCa)} />
|
||||
<Stat
|
||||
label="Marge"
|
||||
value={fmtEur0(summary.totalMarge)}
|
||||
value={fmtEur2(summary.totalMarge)}
|
||||
sub={`${fmtDecimal1(summary.tauxMargeGlobal)} %`}
|
||||
subColor={couleurMarge(summary.tauxMargeGlobal)}
|
||||
/>
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
} from "@/features/grid/lib/network-trend";
|
||||
import { TrendSparkline } from "@/features/grid/components/network-charts";
|
||||
import type { NetworkTrend } from "@/features/grid/lib/network-trend";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0, fmtEur2 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { CLASSES_SANS_GAMME, trouverGamme } from "@/lib/gammes";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -891,7 +891,7 @@ function HeatmapGridInner({ codeFournisseur, onSelectionChange, isAdmin, nomFour
|
||||
const val = row.original.caReseau;
|
||||
return (
|
||||
<div className="text-center tabular-nums text-[12px] font-bold text-emerald-600 dark:text-emerald-400">
|
||||
{val != null ? fmtEur0(val) : "-"}
|
||||
{val != null ? fmtEur2(val) : "-"}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -1020,7 +1020,7 @@ function HeatmapGridInner({ codeFournisseur, onSelectionChange, isAdmin, nomFour
|
||||
const val = row.original.caParMagasinReseau;
|
||||
return (
|
||||
<div className="text-center tabular-nums text-[12px] font-bold" style={{ color: "var(--text-secondary)" }}>
|
||||
{val != null ? fmtEur0(val) : "-"}
|
||||
{val != null ? fmtEur2(val) : "-"}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -1110,14 +1110,14 @@ function HeatmapGridInner({ codeFournisseur, onSelectionChange, isAdmin, nomFour
|
||||
id: "totalCa",
|
||||
accessorFn: (row: ProductRow) => activeMagasin === "TOTAL" ? row.totalCa : (row.caByStore?.[activeMagasin] ?? 0),
|
||||
header: () => <div className="text-center w-full" title="Chiffre d'affaires sur 12 mois">CA</div>,
|
||||
size: 90,
|
||||
size: 105,
|
||||
cell: ({ row }: { row: { original: ProductRow } }) => {
|
||||
const ca = activeMagasin === "TOTAL"
|
||||
? row.original.totalCa
|
||||
: (row.original.caByStore?.[activeMagasin] ?? 0);
|
||||
return (
|
||||
<div className="text-center tabular-nums text-[13px] font-bold" style={{ color: "var(--text-primary)" }}>
|
||||
{fmtEntier(ca)} €
|
||||
{fmtEur2(ca)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -1126,7 +1126,7 @@ function HeatmapGridInner({ codeFournisseur, onSelectionChange, isAdmin, nomFour
|
||||
id: "totalMarge",
|
||||
accessorFn: (row: ProductRow) => activeMagasin === "TOTAL" ? row.totalMarge : (row.margeByStore?.[activeMagasin] ?? 0),
|
||||
header: () => <div className="text-center w-full" title="Marge en euros et taux de marge sur 12 mois">Marge</div>,
|
||||
size: 110,
|
||||
size: 115,
|
||||
cell: ({ row }: { row: { original: ProductRow } }) => {
|
||||
const marge = activeMagasin === "TOTAL"
|
||||
? row.original.totalMarge
|
||||
@@ -1138,7 +1138,7 @@ function HeatmapGridInner({ codeFournisseur, onSelectionChange, isAdmin, nomFour
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<span className="tabular-nums text-[13px] font-bold" style={{ color: "var(--text-primary)" }}>
|
||||
{fmtEntier(marge)} €
|
||||
{fmtEur2(marge)}
|
||||
</span>
|
||||
<span className="tabular-nums text-xs font-semibold" style={{ color: couleurMarge(taux) }}>
|
||||
{taux.toFixed(1)}%
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
} from "@/features/grid/lib/network-trend";
|
||||
import { formatDate, formatMonthLabel, qlikMonthToFf } from "@/features/grid/lib/months";
|
||||
import { MAGASINS, LIBELLE_TOUS_MAGASINS } from "@/lib/magasins";
|
||||
import { fmtEur0 } from "@/lib/format";
|
||||
import { fmtEur2 } from "@/lib/format";
|
||||
import type { ProductRow } from "@/types/grid";
|
||||
|
||||
const fmt = (v: number, d = 0) => v.toLocaleString("fr-FR", { minimumFractionDigits: d, maximumFractionDigits: d });
|
||||
@@ -285,7 +285,7 @@ export function NetworkMonthlyModal({ row, trend, mois, onClose }: {
|
||||
{row.caReseau != null && row.caReseau > 0 && (
|
||||
<TuileStat
|
||||
label="CA réseau 12 m"
|
||||
valeur={fmtEur0(row.caReseau)}
|
||||
valeur={fmtEur2(row.caReseau)}
|
||||
indice={row.margePctReseau ? `marge ${fmt(row.margePctReseau * 100, 1)} %` : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -143,6 +143,7 @@ export async function imprimerPdf(nomFournisseur: string): Promise<boolean> {
|
||||
doc.setTextColor(100);
|
||||
doc.text(`Export généré le : ${new Date().toLocaleDateString("fr-FR")}`, 14, 30);
|
||||
|
||||
const eur = (v: number) => v.toLocaleString("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
const head = [["Code-barres", "Code article", "Réf.", "Libellé", "Magasins réseau", "Qté", "CA", "Marge", "Gamme"]];
|
||||
const body = rows.map((r) => [
|
||||
r.gtin || r.codein,
|
||||
@@ -151,8 +152,8 @@ export async function imprimerPdf(nomFournisseur: string): Promise<boolean> {
|
||||
r.libelle1 ? r.libelle1.substring(0, 60) + (r.libelle1.length > 60 ? "..." : "") : "",
|
||||
r.nbMagasinsReseau != null ? r.nbMagasinsReseau.toString() : "-",
|
||||
Math.round(r.totalQuantite).toLocaleString("fr-FR"),
|
||||
`${Math.round(r.totalCa).toLocaleString("fr-FR")} €`,
|
||||
`${Math.round(r.totalMarge).toLocaleString("fr-FR")} €\n(${r.tauxMarge.toFixed(1)}%)`,
|
||||
`${eur(r.totalCa)} €`,
|
||||
`${eur(r.totalMarge)} €\n(${r.tauxMarge.toFixed(1)}%)`,
|
||||
gammeEffective(r, draftChanges) || "-",
|
||||
]);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { StoreBadge } from "@/components/ui/badge";
|
||||
import { EmptyState, ErrorState, Skeleton } from "@/components/ui/states";
|
||||
import { confirmer, toast } from "@/components/ui/feedback";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action";
|
||||
|
||||
@@ -228,7 +228,7 @@ export function SnapshotList({ type }: SnapshotListProps) {
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-[var(--text-muted)]">Chiffre d'affaires</dt>
|
||||
<dd className="text-sm font-semibold tabular-nums text-[var(--text-primary)]">
|
||||
{fmtEur0(resume.totalCa ?? 0)}
|
||||
{fmtEur2(resume.totalCa ?? 0)}
|
||||
</dd>
|
||||
</div>
|
||||
{taux != null && Number.isFinite(taux) && (
|
||||
|
||||
Reference in new issue
Block a user