mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-15 11:18:42 +02:00
feat: flag products whose last registered supplier differs from current view
Instead of unreliable preference=1, use artfou1.no_id DESC (most recently added artfou1 entry) to identify the last supplier for each product. Shows an amber warning icon in the Désignation column when the last supplier ≠ current supplier view, with tooltip showing who to attribute the sales to. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ef23de317
commit
6b26676836
4 files changed
+61
-31
No files matched your search
@@ -70,6 +70,8 @@ export async function getProductRows(input: GetProductRowsInput): Promise<Produc
|
||||
codein: art.codein,
|
||||
codeFournisseur: art.codefou ?? codeFournisseur,
|
||||
nomFournisseur: art.nomfou ?? "",
|
||||
fournisseurPrincipalCode: art.codefou_principal ?? undefined,
|
||||
fournisseurPrincipalNom: art.nomfou_principal ?? undefined,
|
||||
libelle1: art.libelle1 ?? "",
|
||||
gtin: art.gtin ?? "",
|
||||
reference: art.reference ?? "",
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
RowSelectionState,
|
||||
} from "@tanstack/react-table";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { ChevronUp, ChevronDown, ChevronsUpDown, Copy, Check, Store, SlidersHorizontal, ShoppingCart, PackageOpen, Warehouse } from "lucide-react";
|
||||
import { ChevronUp, ChevronDown, ChevronsUpDown, Copy, Check, Store, SlidersHorizontal, ShoppingCart, PackageOpen, Warehouse, AlertTriangle } from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
@@ -430,33 +430,45 @@ export function HeatmapGrid({ onSelectionChange, isAdmin }: HeatmapGridProps) {
|
||||
accessorKey: "libelle1",
|
||||
header: "Désignation",
|
||||
size: 280,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2 overflow-hidden w-full pr-1">
|
||||
<span className="text-[13px] font-bold truncate flex-1" title={row.original.libelle1} style={{ color: "var(--text-primary)" }}>
|
||||
{row.original.libelle1}
|
||||
</span>
|
||||
<div className="flex gap-1.5 shrink-0">
|
||||
{row.original.workingStores.map((magasin) => {
|
||||
const config = getStoreConfig(magasin);
|
||||
return (
|
||||
<div
|
||||
key={magasin}
|
||||
title={`Travaillé par : ${SITE_LABELS[magasin]?.nom ?? magasin}`}
|
||||
className="px-1.5 py-0.5 rounded-md flex items-center gap-1 text-[10px] font-black border shadow-sm transition-transform hover:scale-110"
|
||||
style={{
|
||||
background: config.bg,
|
||||
borderColor: config.border,
|
||||
color: config.text,
|
||||
}}
|
||||
>
|
||||
<Store className="w-2.5 h-2.5" strokeWidth={3} />
|
||||
<span>{config.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
cell: ({ row }) => {
|
||||
const r = row.original;
|
||||
const isDifferentSupplier = r.fournisseurPrincipalCode && r.fournisseurPrincipalCode !== r.codeFournisseur;
|
||||
const warnTitle = isDifferentSupplier
|
||||
? `Dernier fournisseur enregistré : ${r.fournisseurPrincipalNom ?? r.fournisseurPrincipalCode} (${r.fournisseurPrincipalCode}) — les ventes sont à attribuer à ce fournisseur`
|
||||
: undefined;
|
||||
return (
|
||||
<div className="flex items-center gap-2 overflow-hidden w-full pr-1">
|
||||
{isDifferentSupplier && (
|
||||
<span title={warnTitle} className="shrink-0 flex items-center">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-amber-500" />
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[13px] font-bold truncate flex-1" title={r.libelle1} style={{ color: "var(--text-primary)" }}>
|
||||
{r.libelle1}
|
||||
</span>
|
||||
<div className="flex gap-1.5 shrink-0">
|
||||
{r.workingStores.map((magasin) => {
|
||||
const config = getStoreConfig(magasin);
|
||||
return (
|
||||
<div
|
||||
key={magasin}
|
||||
title={`Travaillé par : ${SITE_LABELS[magasin]?.nom ?? magasin}`}
|
||||
className="px-1.5 py-0.5 rounded-md flex items-center gap-1 text-[10px] font-black border shadow-sm transition-transform hover:scale-110"
|
||||
style={{
|
||||
background: config.bg,
|
||||
borderColor: config.border,
|
||||
color: config.text,
|
||||
}}
|
||||
>
|
||||
<Store className="w-2.5 h-2.5" strokeWidth={3} />
|
||||
<span>{config.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "libelle3",
|
||||
|
||||
+17
-4
@@ -42,9 +42,11 @@ export interface PgArticle {
|
||||
pcb?: number;
|
||||
reference?: string;
|
||||
ean13?: string;
|
||||
gtin?: string; // art_gtin.gtin (préférentiel)
|
||||
pv_central?: number; // article_infosup.prix_vente_mini
|
||||
pa?: number; // cube_pa.pa
|
||||
gtin?: string; // art_gtin.gtin (préférentiel)
|
||||
pv_central?: number; // article_infosup.prix_vente_mini
|
||||
pa?: number; // cube_pa.pa
|
||||
codefou_principal?: string; // artfou1.code où preference=1
|
||||
nomfou_principal?: string; // nom du fournisseur principal
|
||||
}
|
||||
|
||||
export interface PgMensuelRow {
|
||||
@@ -117,6 +119,7 @@ export async function pgGetArticlesByFournisseur(codefou: string): Promise<PgArt
|
||||
// Pas de filtre "actif" : on retourne tous les articles pour ne pas exclure les articles
|
||||
// d'une session précédente (snapshot) qui n'ont pas de gamme DB ni de ventes récentes.
|
||||
// Le filtrage gamme-Y sans ventes est fait en aval dans getProductRows (Phase 10).
|
||||
// afpref : fournisseur préférentiel (preference=1) de l'article — peut différer du fournisseur consulté.
|
||||
const result = await pgNoParallel(sql`
|
||||
SELECT DISTINCT ON (a.no_id)
|
||||
a.no_id,
|
||||
@@ -129,13 +132,23 @@ export async function pgGetArticlesByFournisseur(codefou: string): Promise<PgArt
|
||||
af.ean13,
|
||||
COALESCE(ag.gtin, af.ean13) AS gtin,
|
||||
ai.prix_vente_mini AS pv_central,
|
||||
pa.pa
|
||||
pa.pa,
|
||||
last_fournisseur.codefou_dernier AS codefou_principal,
|
||||
last_fournisseur.nomfou_dernier AS nomfou_principal
|
||||
FROM artfou1 af
|
||||
JOIN articles a ON a.no_id = af.art_no_id
|
||||
LEFT JOIN fouident fi ON fi.code = af.code
|
||||
LEFT JOIN article_infosup ai ON ai.artnoid = a.no_id
|
||||
LEFT JOIN cube_pa pa ON pa.artnoid = a.no_id
|
||||
LEFT JOIN art_gtin ag ON ag.idarticle = a.no_id AND ag.preferentiel = 1
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT af2.code AS codefou_dernier, fi2.nom AS nomfou_dernier
|
||||
FROM artfou1 af2
|
||||
LEFT JOIN fouident fi2 ON fi2.code = af2.code
|
||||
WHERE af2.art_no_id = a.no_id
|
||||
ORDER BY af2.no_id DESC
|
||||
LIMIT 1
|
||||
) last_fournisseur ON true
|
||||
WHERE af.code = ${codefou}
|
||||
AND a.codein IS NOT NULL
|
||||
ORDER BY a.no_id, af.no_id
|
||||
|
||||
@@ -33,6 +33,9 @@ export interface ProductRow {
|
||||
codein: string;
|
||||
codeFournisseur: string;
|
||||
nomFournisseur: string;
|
||||
/** Dernier fournisseur enregistré pour cet article (artfou1.no_id DESC). Peut différer du fournisseur consulté. */
|
||||
fournisseurPrincipalCode?: string;
|
||||
fournisseurPrincipalNom?: string;
|
||||
libelle1: string;
|
||||
gtin: string;
|
||||
reference: string;
|
||||
|
||||
Reference in new issue
Block a user