mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-17 04:09:39 +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
@@ -9,7 +9,7 @@ import { Select } from "@/components/ui/form-controls";
|
||||
import { DeltaBadge } from "@/components/ui/badge";
|
||||
import { Terme } from "@/components/ui/tooltip";
|
||||
import { MAGASINS, nomMagasin } from "@/lib/magasins";
|
||||
import { fmtEur0 } from "@/lib/format";
|
||||
import { fmtEur2 } from "@/lib/format";
|
||||
import { telechargerExcel } from "@/lib/export-excel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -81,7 +81,7 @@ const evolution = (ca: number, caN1: number) => (caN1 > 0 ? ((ca - caN1) / caN1)
|
||||
const recherche = (r: AnalyticsRow) => [r.label, r.key];
|
||||
|
||||
function Montant({ v }: { v: number }) {
|
||||
return <span className={cn("whitespace-nowrap", v === 0 && "text-[var(--text-muted)]")}>{fmtEur0(v)}</span>;
|
||||
return <span className={cn("whitespace-nowrap", v === 0 && "text-[var(--text-muted)]")}>{fmtEur2(v)}</span>;
|
||||
}
|
||||
|
||||
/** En-tête sur deux lignes : magasin au-dessus, mesure en dessous. */
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Badge, DeltaBadge } from "@/components/ui/badge";
|
||||
import { EmptyState, ErrorState } from "@/components/ui/states";
|
||||
import { Terme } from "@/components/ui/tooltip";
|
||||
import { LIBELLE_TOUS_MAGASINS, nomMagasin } from "@/lib/magasins";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0, fmtEur2 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
|
||||
export const metadata: Metadata = { title: "Accueil" };
|
||||
@@ -76,8 +76,8 @@ function CarteMagasin({ site }: { site: DashboardSiteStats }) {
|
||||
<div className="grid gap-3 p-4 sm:grid-cols-3">
|
||||
<Indicateur
|
||||
label="Chiffre d'affaires"
|
||||
valeur={fmtEur0(site.ca_hier)}
|
||||
valeurN1={site.ca_n1 > 0 ? fmtEur0(site.ca_n1) : null}
|
||||
valeur={fmtEur2(site.ca_hier)}
|
||||
valeurN1={site.ca_n1 > 0 ? fmtEur2(site.ca_n1) : null}
|
||||
delta={evolution(site.ca_hier, site.ca_n1)}
|
||||
/>
|
||||
<Indicateur
|
||||
@@ -107,8 +107,8 @@ const CRITERES: Record<Critere, { titre: string; icone: typeof Euro }> = {
|
||||
|
||||
function valeurPrincipale(item: DashboardTopItem, critere: Critere): string {
|
||||
if (critere === "qte") return `${fmtEntier(item.qte)} vendus`;
|
||||
if (critere === "marge") return fmtEur0(item.marge);
|
||||
return fmtEur0(item.ca);
|
||||
if (critere === "marge") return fmtEur2(item.marge);
|
||||
return fmtEur2(item.ca);
|
||||
}
|
||||
|
||||
function ListeTop10({ items, critere }: { items: DashboardTopItem[]; critere: Critere }) {
|
||||
@@ -150,7 +150,7 @@ function ListeTop10({ items, critere }: { items: DashboardTopItem[]; critere: Cr
|
||||
</div>
|
||||
<div className="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[var(--text-muted)]">
|
||||
<span className="truncate">{item.fournisseur}</span>
|
||||
{critere !== "ca" && <span>CA {fmtEur0(item.ca)}</span>}
|
||||
{critere !== "ca" && <span>CA {fmtEur2(item.ca)}</span>}
|
||||
{critere !== "qte" && <span>{fmtEntier(item.qte)} vendus</span>}
|
||||
<span
|
||||
style={{ color: item.stock <= 0 ? "var(--accent-error)" : undefined }}
|
||||
@@ -259,9 +259,9 @@ export default async function DashboardPage() {
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<StatCard
|
||||
label="Chiffre d'affaires"
|
||||
value={fmtEur0(totalCa)}
|
||||
value={fmtEur2(totalCa)}
|
||||
delta={evolution(totalCa, totalCaN1)}
|
||||
hint={totalCaN1 > 0 ? `N-1 : ${fmtEur0(totalCaN1)}` : undefined}
|
||||
hint={totalCaN1 > 0 ? `N-1 : ${fmtEur2(totalCaN1)}` : undefined}
|
||||
icon={Euro}
|
||||
/>
|
||||
<StatCard
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DataTable, type DataColumn, type DataFilter } from "@/components/ui/dat
|
||||
import { Input, Label } from "@/components/ui/form-controls";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MAGASINS, nomMagasin } from "@/lib/magasins";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
import { telechargerExcel } from "@/lib/export-excel";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -99,8 +99,8 @@ function colonnesGroupe(g: Groupe): DataColumn<Ligne>[] {
|
||||
hint: `Chiffre d'affaires TTC (${g.nom}) sur la période, retours déduits`,
|
||||
align: "right",
|
||||
sortValue: r => r[g.ca],
|
||||
cell: r => (r[g.ca] !== 0 ? fmtEur0(r[g.ca]) : vide),
|
||||
footer: rows => fmtEur0(somme(rows, g.ca)),
|
||||
cell: r => (r[g.ca] !== 0 ? fmtEur2(r[g.ca]) : vide),
|
||||
footer: rows => fmtEur2(somme(rows, g.ca)),
|
||||
className: cn("whitespace-nowrap", total && "font-semibold"),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ import { EmptyState } from "@/components/ui/states";
|
||||
import { Terme, Tooltip } from "@/components/ui/tooltip";
|
||||
import { toast } from "@/components/ui/feedback";
|
||||
import { CODE_TOUS_MAGASINS, LIBELLE_TOUS_MAGASINS, nomMagasin } from "@/lib/magasins";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0, fmtEur2 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtDecimal2, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { OpportunitesFamille } from "./opportunites";
|
||||
@@ -129,7 +129,7 @@ const LIGNES_MENSUELLES_RESEAU: Array<{
|
||||
{ label: "Quantité vendue", get: (v) => v.qte, fmt: fmtEntier },
|
||||
{ label: "Magasins vendeurs", get: (v) => v.nbMag, fmt: fmtEntier },
|
||||
{ label: "Quantité par magasin", get: (v) => (v.nbMag && v.nbMag > 0 ? v.qte / v.nbMag : undefined), fmt: fmtDecimal1 },
|
||||
{ label: "Chiffre d'affaires (€)", get: (v) => v.ca, fmt: fmtEntier },
|
||||
{ label: "Chiffre d'affaires (€)", get: (v) => v.ca, fmt: fmtDecimal2 },
|
||||
{ label: "Prix moyen", get: (v) => (v.ca != null && v.qte > 0 ? v.ca / v.qte : undefined), fmt: fmtEur2 },
|
||||
{ label: "Taux de marge", get: (v) => normalizeMargePct(v.margePct) ?? undefined, fmt: (n) => `${fmtDecimal1(n)} %` },
|
||||
];
|
||||
@@ -327,8 +327,8 @@ export function ProduitFicheView({ fiche, backQuery }: { fiche: ProduitFiche; ba
|
||||
value={prixMoyenReseau != null ? fmtEur2(prixMoyenReseau) : "—"}
|
||||
hint="chiffre d'affaires ÷ quantité"
|
||||
/>
|
||||
<StatCard label="Chiffre d'affaires" value={fmtEur0(reseau.caReseau)} hint="de tout le réseau" />
|
||||
<StatCard label={<Terme id="caParMagasin" />} value={fmtEur0(reseau.caParMagasinReseau)} />
|
||||
<StatCard label="Chiffre d'affaires" value={fmtEur2(reseau.caReseau)} hint="de tout le réseau" />
|
||||
<StatCard label={<Terme id="caParMagasin" />} value={fmtEur2(reseau.caParMagasinReseau)} />
|
||||
<StatCard
|
||||
label={<Terme id="marge">Taux de marge</Terme>}
|
||||
value={
|
||||
@@ -556,7 +556,7 @@ export function ProduitFicheView({ fiche, backQuery }: { fiche: ProduitFiche; ba
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<Field label="Stock disponible"><span className="tabular-nums">{fmtEntier(s.stockdispo)}</span></Field>
|
||||
<Field label="Valeur du stock"><span className="tabular-nums">{fmtEur0(s.valstock)}</span></Field>
|
||||
<Field label="Valeur du stock"><span className="tabular-nums">{fmtEur2(s.valstock)}</span></Field>
|
||||
<Field label={<Terme id="prmp" />}><span className="tabular-nums">{s.prmp > 0 ? fmtEur2(s.prmp) : "—"}</span></Field>
|
||||
<Field label="Dernière vente">{formatDate(s.dernierevente)}</Field>
|
||||
<Field label="Dernière réception">{formatDate(s.dernierereception)}</Field>
|
||||
@@ -587,8 +587,8 @@ export function ProduitFicheView({ fiche, backQuery }: { fiche: ProduitFiche; ba
|
||||
>
|
||||
<div className="mb-5 grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
<StatCard label="Quantité vendue" value={fmtEntier(totauxAffiches.qte)} />
|
||||
<StatCard label="Chiffre d'affaires TTC" value={fmtEur0(totauxAffiches.ca)} />
|
||||
<StatCard label={<Terme id="marge" />} value={fmtEur0(totauxAffiches.marge)} />
|
||||
<StatCard label="Chiffre d'affaires TTC" value={fmtEur2(totauxAffiches.ca)} />
|
||||
<StatCard label={<Terme id="marge" />} value={fmtEur2(totauxAffiches.marge)} />
|
||||
<StatCard
|
||||
label="Taux de marge"
|
||||
value={
|
||||
@@ -626,7 +626,7 @@ export function ProduitFicheView({ fiche, backQuery }: { fiche: ProduitFiche; ba
|
||||
<th scope="row" className={TH_LIGNE}>Chiffre d'affaires TTC (€)</th>
|
||||
{months.map((m) => (
|
||||
<td key={m} className={TD}>
|
||||
{serie[m]?.ca ? fmtEntier(serie[m].ca) : <span className="text-[var(--text-muted)]">—</span>}
|
||||
{serie[m]?.ca ? fmtDecimal2(serie[m].ca) : <span className="text-[var(--text-muted)]">—</span>}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
@@ -690,8 +690,8 @@ export function ProduitFicheView({ fiche, backQuery }: { fiche: ProduitFiche; ba
|
||||
<div className="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<Comparaison
|
||||
titre={<Terme id="caParMagasin">Chiffre d'affaires par magasin</Terme>}
|
||||
nous={fmtEur0(comparatif.caLocalParMagasin)}
|
||||
reseau={comparatif.caReseauParMagasin != null ? fmtEur0(comparatif.caReseauParMagasin) : "—"}
|
||||
nous={fmtEur2(comparatif.caLocalParMagasin)}
|
||||
reseau={comparatif.caReseauParMagasin != null ? fmtEur2(comparatif.caReseauParMagasin) : "—"}
|
||||
/>
|
||||
<Comparaison
|
||||
titre={<Terme id="marge">Taux de marge</Terme>}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { DataTable, type DataColumn } from "@/components/ui/data-table";
|
||||
import { EmptyState, ErrorState } from "@/components/ui/states";
|
||||
import { Terme } from "@/components/ui/tooltip";
|
||||
import { GLOSSAIRE } from "@/lib/glossaire";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0, fmtEur2 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { couleurMarge } from "@/lib/marge";
|
||||
|
||||
/** Intervalle d'interrogation de l'état du job de recherche. */
|
||||
@@ -401,7 +401,7 @@ function ResultTable({ rows, query }: { rows: ProduitRechercheRow[]; query: stri
|
||||
hint: GLOSSAIRE.caParMagasin.definition,
|
||||
align: "right",
|
||||
sortValue: (r) => (r.caParMagasinReseau > 0 ? r.caParMagasinReseau : null),
|
||||
cell: (r) => (r.caParMagasinReseau > 0 ? fmtEur0(r.caParMagasinReseau) : "—"),
|
||||
cell: (r) => (r.caParMagasinReseau > 0 ? fmtEur2(r.caParMagasinReseau) : "—"),
|
||||
},
|
||||
{
|
||||
id: "marge",
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Pagination } from "@/components/ui/pagination";
|
||||
import { ErrorState } from "@/components/ui/states";
|
||||
import { Tabs, useUrlTab } from "@/components/ui/tabs";
|
||||
import { Terme, InfoBulle } from "@/components/ui/tooltip";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur0 } from "@/lib/format";
|
||||
import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format";
|
||||
import { nomMagasin } from "@/lib/magasins";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -205,8 +205,8 @@ export function PublicitesClient({
|
||||
header: <InfoBulle explication={EXPLICATION_CA_PUB}>CA pub période</InfoBulle>,
|
||||
align: "right",
|
||||
sortValue: (p) => nombre(p.ca_pub_periode_pub),
|
||||
cell: (p) => <span className="font-semibold">{fmtEur0(nombre(p.ca_pub_periode_pub))}</span>,
|
||||
footer: (rows) => fmtEur0(somme(rows, (r) => r.ca_pub_periode_pub)),
|
||||
cell: (p) => <span className="font-semibold">{fmtEur2(nombre(p.ca_pub_periode_pub))}</span>,
|
||||
footer: (rows) => fmtEur2(somme(rows, (r) => r.ca_pub_periode_pub)),
|
||||
},
|
||||
{
|
||||
id: "partCa",
|
||||
@@ -297,8 +297,8 @@ export function PublicitesClient({
|
||||
hint: "Chiffre d'affaires de l'opération, tous magasins confondus.",
|
||||
align: "right",
|
||||
sortValue: (h) => nombre(h.ca_total),
|
||||
cell: (h) => <span className="font-semibold">{fmtEur0(nombre(h.ca_total))}</span>,
|
||||
footer: (rows) => fmtEur0(somme(rows, (r) => r.ca_total)),
|
||||
cell: (h) => <span className="font-semibold">{fmtEur2(nombre(h.ca_total))}</span>,
|
||||
footer: (rows) => fmtEur2(somme(rows, (r) => r.ca_total)),
|
||||
},
|
||||
{
|
||||
id: "quantite",
|
||||
|
||||
Reference in new issue
Block a user