mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
Merge pull request #6 from R0m1k3/claude/exciting-goldberg-wILkj
Claude/exciting goldberg w i lkj
This commit is contained in:
4 files changed
+165
-97
No files matched your search
@@ -28,6 +28,43 @@ interface Props {
|
||||
pivotted: HitParadePivotRow[];
|
||||
}
|
||||
|
||||
function ColHeader({
|
||||
label,
|
||||
sortable,
|
||||
group,
|
||||
sortKey,
|
||||
sortDir,
|
||||
onSort,
|
||||
}: {
|
||||
label: string;
|
||||
sortable?: SortKey;
|
||||
group?: "292" | "579" | "total";
|
||||
sortKey: SortKey;
|
||||
sortDir: SortDir;
|
||||
onSort: (key: SortKey) => void;
|
||||
}) {
|
||||
const isActive = sortable && sortKey === sortable;
|
||||
const groupBg = group === "292" ? "bg-blue-50" : group === "579" ? "bg-violet-50" : group === "total" ? "bg-emerald-50" : "";
|
||||
const activeBg = group === "292" ? "bg-blue-100 text-blue-700" : group === "579" ? "bg-violet-100 text-violet-700" : group === "total" ? "bg-emerald-100 text-emerald-700" : "bg-blue-50 text-blue-700";
|
||||
return (
|
||||
<th
|
||||
onClick={sortable ? () => onSort(sortable) : undefined}
|
||||
className={[
|
||||
"px-2 py-2 text-center text-xs font-semibold whitespace-nowrap select-none",
|
||||
sortable ? "cursor-pointer" : "",
|
||||
isActive ? activeBg : `${groupBg} text-gray-600`,
|
||||
].join(" ")}
|
||||
>
|
||||
{label}
|
||||
{sortable && (
|
||||
<span className="ml-1 inline-block w-3 text-center">
|
||||
{isActive ? (sortDir === "desc" ? "↓" : "↑") : <span className="text-gray-300">↕</span>}
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
||||
const router = useRouter();
|
||||
const [sortKey, setSortKey] = useState<SortKey>(DEFAULT_SORT);
|
||||
@@ -73,6 +110,32 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
||||
|
||||
const isSorted = sortKey !== DEFAULT_SORT || sortDir !== DEFAULT_DIR;
|
||||
|
||||
const filtered = useMemo(() =>
|
||||
pivotted.filter(r =>
|
||||
(filterFournisseur === "Tous" || r.fournisseur === filterFournisseur) &&
|
||||
(filterNomenclature === "Tous" || r.nomenclature_code === filterNomenclature)
|
||||
),
|
||||
[pivotted, filterFournisseur, filterNomenclature]
|
||||
);
|
||||
|
||||
const sorted = useMemo(() =>
|
||||
[...filtered].sort((a, b) => {
|
||||
const diff = a[sortKey] - b[sortKey];
|
||||
return sortDir === "desc" ? -diff : diff;
|
||||
}),
|
||||
[filtered, sortKey, sortDir]
|
||||
);
|
||||
|
||||
const totals = useMemo(() => sorted.reduce(
|
||||
(acc, r) => ({
|
||||
qte292: acc.qte292 + r.qte292, ca292: acc.ca292 + r.ca292, marge292: acc.marge292 + r.marge292,
|
||||
qte579: acc.qte579 + r.qte579, ca579: acc.ca579 + r.ca579, marge579: acc.marge579 + r.marge579,
|
||||
qteTotal: acc.qteTotal + r.qteTotal, caTotal: acc.caTotal + r.caTotal, margeTotal: acc.margeTotal + r.margeTotal,
|
||||
stock292: acc.stock292 + r.stock292, stock579: acc.stock579 + r.stock579, stockTotal: acc.stockTotal + r.stockTotal,
|
||||
}),
|
||||
{ qte292: 0, ca292: 0, marge292: 0, qte579: 0, ca579: 0, marge579: 0, qteTotal: 0, caTotal: 0, margeTotal: 0, stock292: 0, stock579: 0, stockTotal: 0 }
|
||||
), [sorted]);
|
||||
|
||||
function exportToExcel() {
|
||||
const headers = [
|
||||
"Code", "Désignation", "Fournisseur", "Nomenclature",
|
||||
@@ -115,56 +178,6 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
||||
XLSX.writeFile(wb, `hit-parade_${dateDebut}_${dateFin}.xlsx`);
|
||||
}
|
||||
|
||||
const filtered = useMemo(() =>
|
||||
pivotted.filter(r =>
|
||||
(filterFournisseur === "Tous" || r.fournisseur === filterFournisseur) &&
|
||||
(filterNomenclature === "Tous" || r.nomenclature_code === filterNomenclature)
|
||||
),
|
||||
[pivotted, filterFournisseur, filterNomenclature]
|
||||
);
|
||||
|
||||
const sorted = useMemo(() =>
|
||||
[...filtered].sort((a, b) => {
|
||||
const diff = a[sortKey] - b[sortKey];
|
||||
return sortDir === "desc" ? -diff : diff;
|
||||
}),
|
||||
[filtered, sortKey, sortDir]
|
||||
);
|
||||
|
||||
const totals = useMemo(() => sorted.reduce(
|
||||
(acc, r) => ({
|
||||
qte292: acc.qte292 + r.qte292, ca292: acc.ca292 + r.ca292, marge292: acc.marge292 + r.marge292,
|
||||
qte579: acc.qte579 + r.qte579, ca579: acc.ca579 + r.ca579, marge579: acc.marge579 + r.marge579,
|
||||
qteTotal: acc.qteTotal + r.qteTotal, caTotal: acc.caTotal + r.caTotal, margeTotal: acc.margeTotal + r.margeTotal,
|
||||
stock292: acc.stock292 + r.stock292, stock579: acc.stock579 + r.stock579, stockTotal: acc.stockTotal + r.stockTotal,
|
||||
}),
|
||||
{ qte292: 0, ca292: 0, marge292: 0, qte579: 0, ca579: 0, marge579: 0, qteTotal: 0, caTotal: 0, margeTotal: 0, stock292: 0, stock579: 0, stockTotal: 0 }
|
||||
), [sorted]);
|
||||
|
||||
function ColHeader({ label, sortable, k, group }: { label: string; sortable?: SortKey; k: string; group?: "292" | "579" | "total" }) {
|
||||
const isActive = sortable && sortKey === sortable;
|
||||
const groupBg = group === "292" ? "bg-blue-50" : group === "579" ? "bg-violet-50" : group === "total" ? "bg-emerald-50" : "";
|
||||
const activeBg = group === "292" ? "bg-blue-100 text-blue-700" : group === "579" ? "bg-violet-100 text-violet-700" : group === "total" ? "bg-emerald-100 text-emerald-700" : "bg-blue-50 text-blue-700";
|
||||
return (
|
||||
<th
|
||||
key={k}
|
||||
onClick={sortable ? () => handleSort(sortable) : undefined}
|
||||
className={[
|
||||
"px-2 py-2 text-center text-xs font-semibold whitespace-nowrap select-none",
|
||||
sortable ? "cursor-pointer" : "",
|
||||
isActive ? activeBg : `${groupBg} text-gray-600`,
|
||||
].join(" ")}
|
||||
>
|
||||
{label}
|
||||
{sortable && (
|
||||
<span className="ml-1 inline-block w-3 text-center">
|
||||
{isActive ? (sortDir === "desc" ? "↓" : "↑") : <span className="text-gray-300">↕</span>}
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Barre de contrôles */}
|
||||
@@ -277,22 +290,22 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="border-b-2 border-gray-200">
|
||||
<ColHeader label="Qté" sortable="qte292" k="qte292" group="292" />
|
||||
<ColHeader label="CA TTC" sortable="ca292" k="ca292" group="292" />
|
||||
<ColHeader label="Qté" sortable="qte292" group="292" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<ColHeader label="CA TTC" sortable="ca292" group="292" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<th className="bg-blue-50 px-2 py-2 text-center text-xs font-semibold text-blue-500">% Marge</th>
|
||||
<ColHeader label="Stock" sortable="stock292" k="stock292" group="292" />
|
||||
<ColHeader label="Qté" sortable="qte579" k="qte579" group="579" />
|
||||
<ColHeader label="CA TTC" sortable="ca579" k="ca579" group="579" />
|
||||
<ColHeader label="Stock" sortable="stock292" group="292" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<ColHeader label="Qté" sortable="qte579" group="579" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<ColHeader label="CA TTC" sortable="ca579" group="579" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<th className="bg-violet-50 px-2 py-2 text-center text-xs font-semibold text-violet-500">% Marge</th>
|
||||
<ColHeader label="Stock" sortable="stock579" k="stock579" group="579" />
|
||||
<ColHeader label="Qté" sortable="qteTotal" k="qteTotal" group="total" />
|
||||
<ColHeader label="CA TTC" sortable="caTotal" k="caTotal" group="total" />
|
||||
<ColHeader label="Stock" sortable="stock579" group="579" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<ColHeader label="Qté" sortable="qteTotal" group="total" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<ColHeader label="CA TTC" sortable="caTotal" group="total" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
<th className="bg-emerald-50 px-2 py-2 text-center text-xs font-semibold text-emerald-600">% Marge</th>
|
||||
<ColHeader label="Stock" sortable="stockTotal" k="stockTotal" group="total" />
|
||||
<ColHeader label="Stock" sortable="stockTotal" group="total" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sorted.map((row, idx) => (
|
||||
{sorted.map((row) => (
|
||||
<tr key={row.codein} className={`border-b border-gray-100 hover:brightness-95 transition-colors`}>
|
||||
<td className="bg-white px-2 py-2.5 font-mono text-xs text-gray-400 text-center">{row.codein}</td>
|
||||
<td className="bg-white px-2 py-2.5 text-gray-900 font-medium min-w-[200px]">{row.libelle.trim()}</td>
|
||||
|
||||
@@ -42,14 +42,17 @@ function pivotHitParade(rows: HitParadeRow[]): HitParadePivotRow[] {
|
||||
});
|
||||
}
|
||||
const entry = map.get(row.codein)!;
|
||||
if (row.site === "292") {
|
||||
entry.qte292 = row.qte_vendue;
|
||||
entry.ca292 = row.ca_ttc;
|
||||
entry.marge292 = row.marge;
|
||||
} else if (row.site === "579") {
|
||||
entry.qte579 = row.qte_vendue;
|
||||
entry.ca579 = row.ca_ttc;
|
||||
entry.marge579 = row.marge;
|
||||
// Accumulation (+=) et non affectation : si la requête renvoyait plusieurs
|
||||
// lignes pour un même (codein, site), aucune valeur ne serait écrasée.
|
||||
const site = String(row.site).trim();
|
||||
if (site === "292") {
|
||||
entry.qte292 += row.qte_vendue;
|
||||
entry.ca292 += row.ca_ttc;
|
||||
entry.marge292 += row.marge;
|
||||
} else if (site === "579") {
|
||||
entry.qte579 += row.qte_vendue;
|
||||
entry.ca579 += row.ca_ttc;
|
||||
entry.marge579 += row.marge;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -391,8 +391,10 @@ async function reconcileSelectedStoreFromMensuelApi(
|
||||
for (const entry of entries) {
|
||||
const period = entry.mois.replace("-", "");
|
||||
if (!sortedPeriods.includes(period)) continue;
|
||||
const qty = Math.abs(Number(entry.ventes?.qte_vendue ?? 0) || 0);
|
||||
const ca = Math.abs(Number(entry.ventes?.ca_ht ?? 0) || 0);
|
||||
// qte_vendue / ca_ht sont NÉGATIFS côté API (ventes nettes) : on
|
||||
// les nie au lieu de Math.abs pour que les retours restent déduits.
|
||||
const qty = -(Number(entry.ventes?.qte_vendue ?? 0) || 0);
|
||||
const ca = -(Number(entry.ventes?.ca_ht ?? 0) || 0);
|
||||
const marge = Number(entry.ventes?.marge ?? 0) || 0;
|
||||
const stock = Number(entry.stock_fin_mois ?? 0) || 0;
|
||||
const receptions = Number(entry.receptions?.qte_recue ?? 0) || 0;
|
||||
|
||||
+77
-27
@@ -674,6 +674,10 @@ export interface CaByNomenclatureRow {
|
||||
/**
|
||||
* Retourne le CA TTC par fournisseur et site pour deux mois donnés (mois et mois N-1).
|
||||
* Attribution via artfou1.preference = true (fournisseur principal de l'article).
|
||||
* CA NET : SUM(-mntmvtttc) — les retours clients sont déduits (même convention
|
||||
* que pgGetMensuelByFournisseur / pgGetHitParade). Fournisseur préféré résolu
|
||||
* via une sous-requête artfou1 dédupliquée (DISTINCT ON) : un seul code par
|
||||
* article, aucune duplication possible des mouvements dans la somme.
|
||||
*/
|
||||
export async function pgGetCaByFournisseur(
|
||||
mois: string,
|
||||
@@ -685,10 +689,15 @@ export async function pgGetCaByFournisseur(
|
||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS nom,
|
||||
m.site,
|
||||
TO_CHAR(m.datmvt, 'YYYY-MM') AS mois,
|
||||
ABS(SUM(m.mntmvtttc))::float AS ca_ttc
|
||||
SUM(-m.mntmvtttc)::float AS ca_ttc
|
||||
FROM mvtart m
|
||||
JOIN articles a ON a.no_id = m.artnoid
|
||||
LEFT JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||
FROM artfou1
|
||||
WHERE preference = 1
|
||||
ORDER BY art_no_id, code
|
||||
) af ON af.art_no_id = a.no_id
|
||||
LEFT JOIN fouident fi ON fi.code = af.code
|
||||
WHERE (TO_CHAR(m.datmvt, 'YYYY-MM') = ${mois} OR TO_CHAR(m.datmvt, 'YYYY-MM') = ${moisN1})
|
||||
AND m.site IN ('292', '579')
|
||||
@@ -703,6 +712,7 @@ export async function pgGetCaByFournisseur(
|
||||
|
||||
/**
|
||||
* Retourne le CA TTC par nomenclature et site pour deux mois donnés.
|
||||
* CA NET : SUM(-mntmvtttc) — retours clients déduits (convention commune).
|
||||
*/
|
||||
export async function pgGetCaByNomenclature(
|
||||
mois: string,
|
||||
@@ -714,7 +724,7 @@ export async function pgGetCaByNomenclature(
|
||||
COALESCE(n.libelle, 'Sans nomenclature')::text AS libelle,
|
||||
m.site,
|
||||
TO_CHAR(m.datmvt, 'YYYY-MM') AS mois,
|
||||
ABS(SUM(m.mntmvtttc))::float AS ca_ttc
|
||||
SUM(-m.mntmvtttc)::float AS ca_ttc
|
||||
FROM mvtart m
|
||||
JOIN articles a ON a.no_id = m.artnoid
|
||||
LEFT JOIN nomenclature n ON n.no_id = a.nom_no_id
|
||||
@@ -750,48 +760,77 @@ export interface HitParadeRow {
|
||||
|
||||
/**
|
||||
* Retourne les ventes produit sur une période, avec fournisseur, qté, CA TTC, marge et stock par site.
|
||||
* Le stock est intégré via un LEFT JOIN — pas de requête séparée, pas de tableau de paramètres.
|
||||
*
|
||||
* L'agrégation des ventes se fait UNIQUEMENT sur mvtart × articles (jointure 1:1),
|
||||
* exactement comme pgGetMensuelByFournisseur (le calcul canonique de la Grille) :
|
||||
* qte = SUM(-qtemvt), ca = SUM(-mntmvtttc), marge = SUM(margemvt) pour genremvt = 3
|
||||
* (ventes stockées en négatif — vérifié sur les mouvements réels de l'API).
|
||||
* Les attributs (libellé, fournisseur préféré, nomenclature) et le stock sont
|
||||
* joints APRÈS l'agrégation, par codein : aucune jointure ne peut dupliquer les
|
||||
* mouvements et fausser les sommes, et le LATERAL fournisseur ne s'exécute que
|
||||
* par article vendu (et non par ligne de mouvement).
|
||||
*
|
||||
* Filtre mntmvtttc <> 0 : mvtart contient des lignes genremvt=3 de quantité
|
||||
* sans montant (corrections/régularisations, ex: codein 487673 site 579 —
|
||||
* 38 affiché au lieu de 24 réels, CA exact). Une vente réelle a toujours un
|
||||
* montant TTC non nul ; les endpoints officiels de l'API les excluent aussi.
|
||||
*/
|
||||
export async function pgGetHitParade(dateDebut: string, dateFin: string): Promise<HitParadeRow[]> {
|
||||
const result = await pgNoParallel(sql`
|
||||
WITH ventes AS (
|
||||
SELECT
|
||||
a.no_id AS art_no_id,
|
||||
a.codein::text AS codein,
|
||||
a.libelle1::text AS libelle,
|
||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
||||
COALESCE(n.code, '')::text AS nomenclature_code,
|
||||
COALESCE(n.libelle, 'Sans nomenclature')::text AS nomenclature,
|
||||
TRIM(a.codein::text) AS codein,
|
||||
m.site,
|
||||
SUM(ABS(m.qtemvt))::float AS qte_vendue,
|
||||
ABS(SUM(m.mntmvtttc))::float AS ca_ttc,
|
||||
SUM(-m.qtemvt)::float AS qte_vendue,
|
||||
SUM(-m.mntmvtttc)::float AS ca_ttc,
|
||||
SUM(m.margemvt)::float AS marge
|
||||
FROM mvtart m
|
||||
JOIN articles a ON a.no_id = m.artnoid
|
||||
LEFT JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
|
||||
LEFT JOIN fouident fi ON fi.code = af.code
|
||||
LEFT JOIN nomenclature n ON n.no_id = a.nom_no_id
|
||||
JOIN articles a ON a.no_id = m.artnoid
|
||||
WHERE m.datmvt BETWEEN ${dateDebut}::date AND ${dateFin}::date
|
||||
AND m.site IN ('292', '579')
|
||||
AND m.genremvt = 3
|
||||
GROUP BY a.no_id, a.codein, a.libelle1, fi.nom, af.code, n.code, n.libelle, m.site
|
||||
AND m.mntmvtttc IS NOT NULL
|
||||
AND m.mntmvtttc <> 0
|
||||
AND a.codein IS NOT NULL
|
||||
GROUP BY TRIM(a.codein::text), m.site
|
||||
),
|
||||
attrs AS (
|
||||
SELECT DISTINCT ON (TRIM(a.codein::text))
|
||||
TRIM(a.codein::text) AS codein,
|
||||
a.libelle1::text AS libelle,
|
||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
||||
COALESCE(n.code, '')::text AS nomenclature_code,
|
||||
COALESCE(n.libelle, 'Sans nomenclature')::text AS nomenclature
|
||||
FROM articles a
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT af1.code
|
||||
FROM artfou1 af1
|
||||
WHERE af1.art_no_id = a.no_id AND af1.preference = 1
|
||||
ORDER BY af1.code
|
||||
LIMIT 1
|
||||
) af ON TRUE
|
||||
LEFT JOIN fouident fi ON fi.code = af.code
|
||||
LEFT JOIN nomenclature n ON n.no_id = a.nom_no_id
|
||||
WHERE TRIM(a.codein::text) IN (SELECT codein FROM ventes)
|
||||
ORDER BY TRIM(a.codein::text), a.no_id DESC
|
||||
),
|
||||
stock_agg AS (
|
||||
SELECT
|
||||
cs.artnoid,
|
||||
TRIM(a2.codein::text) AS codein,
|
||||
SUM(CASE WHEN cs.site = '292' THEN cs.qte ELSE 0 END)::float AS stock292,
|
||||
SUM(CASE WHEN cs.site = '579' THEN cs.qte ELSE 0 END)::float AS stock579,
|
||||
SUM(cs.qte)::float AS stockTotal
|
||||
FROM cube_stock cs
|
||||
WHERE cs.artnoid IN (SELECT DISTINCT art_no_id FROM ventes)
|
||||
GROUP BY cs.artnoid
|
||||
JOIN articles a2 ON a2.no_id = cs.artnoid
|
||||
WHERE TRIM(a2.codein::text) IN (SELECT codein FROM ventes)
|
||||
GROUP BY TRIM(a2.codein::text)
|
||||
)
|
||||
SELECT
|
||||
v.codein,
|
||||
v.libelle,
|
||||
v.fournisseur,
|
||||
v.nomenclature_code,
|
||||
v.nomenclature,
|
||||
COALESCE(ar.libelle, '') AS libelle,
|
||||
COALESCE(ar.fournisseur, 'Sans fournisseur') AS fournisseur,
|
||||
COALESCE(ar.nomenclature_code, '') AS nomenclature_code,
|
||||
COALESCE(ar.nomenclature, 'Sans nomenclature') AS nomenclature,
|
||||
v.site,
|
||||
v.qte_vendue,
|
||||
v.ca_ttc,
|
||||
@@ -800,7 +839,8 @@ export async function pgGetHitParade(dateDebut: string, dateFin: string): Promis
|
||||
COALESCE(s.stock579, 0) AS stock579,
|
||||
COALESCE(s.stockTotal, 0) AS "stockTotal"
|
||||
FROM ventes v
|
||||
LEFT JOIN stock_agg s ON s.artnoid = v.art_no_id
|
||||
LEFT JOIN attrs ar ON ar.codein = v.codein
|
||||
LEFT JOIN stock_agg s ON s.codein = v.codein
|
||||
ORDER BY v.ca_ttc DESC
|
||||
`);
|
||||
|
||||
@@ -929,7 +969,12 @@ export async function pgGetStockNegatif(site?: string): Promise<PgStockNegatifRo
|
||||
) AS derniereentree
|
||||
FROM cube_stock cs
|
||||
JOIN articles a ON a.no_id = cs.artnoid
|
||||
JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
|
||||
JOIN (
|
||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||
FROM artfou1
|
||||
WHERE preference = 1
|
||||
ORDER BY art_no_id, code
|
||||
) af ON af.art_no_id = a.no_id
|
||||
JOIN fouident f ON f.code = af.code
|
||||
WHERE cs.qte < 0
|
||||
AND cs.site IN ('292', '579')
|
||||
@@ -982,7 +1027,12 @@ export async function pgGetSansVente6Mois(site?: string): Promise<PgSansVente6Mo
|
||||
) AS derniere_entree
|
||||
FROM cube_stock cs
|
||||
JOIN articles a ON a.no_id = cs.artnoid
|
||||
JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
|
||||
JOIN (
|
||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||
FROM artfou1
|
||||
WHERE preference = 1
|
||||
ORDER BY art_no_id, code
|
||||
) af ON af.art_no_id = a.no_id
|
||||
JOIN fouident f ON f.code = af.code
|
||||
WHERE cs.site IN ('292', '579')
|
||||
AND cs.qte > 0
|
||||
|
||||
Reference in new issue
Block a user