mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 13:08:11 +02:00
feat: Add Drizzle ORM database setup, product data fetching and processing with scoring, and a diagnostic API endpoint.
This commit is contained in:
1 parent
1e5bb74344
commit
5eb3f6732e
3 files changed
+81
-84
No files matched your search
@@ -0,0 +1,69 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { db } from "@/db";
|
||||||
|
import { ventesProduits } from "@/db/schema";
|
||||||
|
import { sql, eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const { searchParams } = new URL(req.url);
|
||||||
|
const codeFournisseur = searchParams.get("fournisseur");
|
||||||
|
|
||||||
|
if (!codeFournisseur) {
|
||||||
|
return NextResponse.json({
|
||||||
|
error: "Paramètre 'fournisseur' manquant.",
|
||||||
|
usage: "/api/diag?fournisseur=VOTRE_CODE"
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`\n--- [DIAG API] Starting check for supplier: ${codeFournisseur} ---`);
|
||||||
|
|
||||||
|
// 1. Raw Count
|
||||||
|
const countAll = await db.execute(sql`
|
||||||
|
SELECT COUNT(*) as count FROM ventes_produits
|
||||||
|
WHERE code_fournisseur = ${codeFournisseur}
|
||||||
|
`);
|
||||||
|
const totalRows = Number(countAll.rows[0].count);
|
||||||
|
|
||||||
|
// 2. Distinct Products
|
||||||
|
const countDistinctTotal = await db.execute(sql`
|
||||||
|
SELECT COUNT(DISTINCT codein) as count FROM ventes_produits
|
||||||
|
WHERE code_fournisseur = ${codeFournisseur}
|
||||||
|
`);
|
||||||
|
const totalDistinct = Number(countDistinctTotal.rows[0].count);
|
||||||
|
|
||||||
|
// 3. Sample of data (to see if it's truncated by DB query)
|
||||||
|
const sample = await db.execute(sql`
|
||||||
|
SELECT codein, libelle1, magasin, periode FROM ventes_produits
|
||||||
|
WHERE code_fournisseur = ${codeFournisseur}
|
||||||
|
LIMIT 5
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log(`- DB sees ${totalRows} total rows.`);
|
||||||
|
console.log(`- DB sees ${totalDistinct} distinct products.`);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
supplier: codeFournisseur,
|
||||||
|
database: {
|
||||||
|
totalRowsInTable: totalRows,
|
||||||
|
distinctProducts: totalDistinct,
|
||||||
|
sampleData: sample.rows
|
||||||
|
},
|
||||||
|
serverConfig: {
|
||||||
|
nextVersion: "16.1.6",
|
||||||
|
nodeEnv: process.env.NODE_ENV,
|
||||||
|
bodySizeLimit: "50mb"
|
||||||
|
},
|
||||||
|
message: totalDistinct > 1000
|
||||||
|
? "La base de données contient plus de 1000 produits. Si la grille en affiche 999, le problème est dans le transport (Application/RSC)."
|
||||||
|
: "La base de données renvoie moins de 1000 produits uniques. Le problème vient de l'import ou du filtrage SQL."
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("[DIAG API] Error:", error);
|
||||||
|
return NextResponse.json({
|
||||||
|
error: "Failed to fetch diagnostic data",
|
||||||
|
details: error.message
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,8 +46,18 @@ export function getDb() {
|
|||||||
console.error("[DB] CRITICAL: No database connection string available!");
|
console.error("[DB] CRITICAL: No database connection string available!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectionHostname = connectionString.match(/@([^:/]+)/)?.[1] || "unknown";
|
||||||
|
console.log(`[DB] Target Hostname: ${connectionHostname}`);
|
||||||
|
|
||||||
pool = new Pool({
|
pool = new Pool({
|
||||||
connectionString,
|
connectionString,
|
||||||
|
max: 20,
|
||||||
|
idleTimeoutMillis: 30000,
|
||||||
|
connectionTimeoutMillis: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
pool.on('error', (err) => {
|
||||||
|
console.error('[DB] Unexpected error on idle client', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
currentDb = drizzle(pool, { schema });
|
currentDb = drizzle(pool, { schema });
|
||||||
|
|||||||
@@ -17,90 +17,8 @@ interface GetProductRowsInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductRows(input: GetProductRowsInput): Promise<ProductRow[]> {
|
export async function getProductRows(input: GetProductRowsInput): Promise<ProductRow[]> {
|
||||||
const { codeFournisseur, magasin = "TOTAL", filters } = input;
|
// MODEL DIAGNOSTIC (Clean version)
|
||||||
|
console.log(`[getProductRows] Fetching for ${codeFournisseur} (${magasin}). Found ${rows.length} rows.`);
|
||||||
// FILE LOGGING (portainer might be filtering console.log)
|
|
||||||
try {
|
|
||||||
const logPath = "/app/data/debug_check.txt";
|
|
||||||
const entry = `\n[${new Date().toISOString()}] getProductRows called for ${codeFournisseur}\n`;
|
|
||||||
fs.appendFileSync(logPath, entry);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
console.log(`\n>>> [getProductRows] RECEIVED CALL for supplier: ${codeFournisseur}, magasin: ${magasin}`);
|
|
||||||
console.log(`>>> [getProductRows] filters:`, JSON.stringify(filters));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Self-healing: Ensure codeGammeInit column exists and is populated
|
|
||||||
if (!isSchemaInitialized) {
|
|
||||||
try {
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE ventes_produits
|
|
||||||
ADD COLUMN IF NOT EXISTS code_gamme_init VARCHAR(20)
|
|
||||||
`);
|
|
||||||
await db.execute(sql`
|
|
||||||
UPDATE ventes_produits
|
|
||||||
SET code_gamme_init = code_gamme
|
|
||||||
WHERE code_gamme_init IS NULL
|
|
||||||
`);
|
|
||||||
isSchemaInitialized = true;
|
|
||||||
console.log("[Schema] codeGammeInit initialized successfully.");
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Schema] Failed to initialize codeGammeInit:", err);
|
|
||||||
// We continue anyway, the mapping fallback will handle it for the UI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... (allowedPeriods calculation remains the same)
|
|
||||||
const now = new Date();
|
|
||||||
const allowedPeriods = new Set<string>();
|
|
||||||
for (let i = 12; i >= 0; i--) { // i >= 0 au lieu de 1 pour inclure le mois en cours
|
|
||||||
const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
|
||||||
allowedPeriods.add(`${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build conditions
|
|
||||||
const conditions = [
|
|
||||||
eq(ventesProduits.codeFournisseur, codeFournisseur),
|
|
||||||
ne(ventesProduits.periode, "TOTAL"),
|
|
||||||
magasin !== "TOTAL" ? eq(ventesProduits.magasin, magasin) : ne(ventesProduits.magasin, "TOTAL"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Hierarchical filtering
|
|
||||||
if (filters?.code3) {
|
|
||||||
conditions.push(eq(ventesProduits.code3, filters.code3));
|
|
||||||
} else if (filters?.code2) {
|
|
||||||
conditions.push(like(ventesProduits.code3, `${filters.code2}%`));
|
|
||||||
} else if (filters?.code1) {
|
|
||||||
conditions.push(like(ventesProduits.code3, `${filters.code1}%`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all rows for this supplier
|
|
||||||
const rows = await db
|
|
||||||
.select()
|
|
||||||
.from(ventesProduits)
|
|
||||||
.where(and(...conditions));
|
|
||||||
|
|
||||||
// Group by codein and aggregate monthly sales + track store participation
|
|
||||||
const productMap = new Map<string, ProductRow>();
|
|
||||||
|
|
||||||
// EXTRA VERBOSE DIAGNOSTIC
|
|
||||||
const countAll = await db.execute(sql`SELECT COUNT(*) FROM ventes_produits WHERE code_fournisseur = ${codeFournisseur}`);
|
|
||||||
const countDistinctTotal = await db.execute(sql`SELECT COUNT(DISTINCT codein) FROM ventes_produits WHERE code_fournisseur = ${codeFournisseur}`);
|
|
||||||
const countNoTotal = await db.execute(sql`SELECT COUNT(DISTINCT codein) FROM ventes_produits WHERE code_fournisseur = ${codeFournisseur} AND magasin != 'TOTAL' AND periode != 'TOTAL'`);
|
|
||||||
|
|
||||||
const diagOutput = `
|
|
||||||
--- [getProductRows] ULTRA-DIAGNOSTIC for Supplier ${codeFournisseur} ---
|
|
||||||
- Gross total rows in DB: ${countAll.rows[0].count}
|
|
||||||
- Total distinct products (any store/period): ${countDistinctTotal.rows[0].count}
|
|
||||||
- Distinct products excluding TOTAL store/period: ${countNoTotal.rows[0].count}
|
|
||||||
- Current Filters rows retrieved: ${rows.length}
|
|
||||||
- Distinct products in currently fetched rows: ${new Set(rows.map(r => r.codein)).size}
|
|
||||||
`;
|
|
||||||
try {
|
|
||||||
fs.appendFileSync("/app/data/debug_check.txt", diagOutput);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
console.log(diagOutput);
|
|
||||||
|
|
||||||
const storeParticipationMap = new Map<string, Map<string, Set<string>>>(); // codein -> magasin -> Set of periods
|
const storeParticipationMap = new Map<string, Map<string, Set<string>>>(); // codein -> magasin -> Set of periods
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user