Implementation de la notion d'espace d'investissement
This commit is contained in:
1 parent
6b6cad7cd6
commit
4b7df66862
54 files changed
+6782
-288
No files matched your search
@@ -110,6 +110,8 @@ const Schema = z.object({
|
||||
// Nouvelles listes gérées globalement (IDs)
|
||||
categories_inv_ids: z.array(z.number().int()).optional().default([]),
|
||||
secteurs_inv_ids: z.array(z.number().int()).optional().default([]),
|
||||
// Espaces de travail auxquels ce référentiel est affecté (au moins un requis)
|
||||
workspace_ids: z.array(z.number().int()).min(1, 'Au moins un espace de travail doit être sélectionné'),
|
||||
// Notation : tableau de critères
|
||||
notation: z.array(z.object({
|
||||
nom: z.string().min(1),
|
||||
@@ -181,6 +183,25 @@ function attachSecteursInv(rows) {
|
||||
return rows.map(r => ({ ...r, secteurs_inv: map[r.id] || [] }));
|
||||
}
|
||||
|
||||
/** Espaces de travail auxquels le référentiel est affecté */
|
||||
function attachWorkspaces(rows) {
|
||||
if (rows.length === 0) return rows;
|
||||
const ids = rows.map(r => r.id);
|
||||
const links = db.prepare(`
|
||||
SELECT rw.referentiel_id, w.id AS workspace_id, w.slug, w.nom
|
||||
FROM referentiel_workspaces rw
|
||||
JOIN workspaces w ON w.id = rw.workspace_id
|
||||
WHERE rw.referentiel_id IN (${ids.map(() => '?').join(',')})
|
||||
ORDER BY w.ordre, w.id
|
||||
`).all(...ids);
|
||||
const map = {};
|
||||
for (const l of links) {
|
||||
if (!map[l.referentiel_id]) map[l.referentiel_id] = [];
|
||||
map[l.referentiel_id].push({ id: l.workspace_id, slug: l.slug, nom: l.nom });
|
||||
}
|
||||
return rows.map(r => ({ ...r, workspaces: map[r.id] || [] }));
|
||||
}
|
||||
|
||||
/** Critères de notation du référentiel */
|
||||
function attachNotation(rows) {
|
||||
if (rows.length === 0) return rows;
|
||||
@@ -213,7 +234,7 @@ function parsePaysOp(rows) {
|
||||
}
|
||||
|
||||
/** Enregistre catégories, secteurs et notation pour un référentiel */
|
||||
function saveRelations(tx, refId, categories, notation, categoriesInvIds, secteursInvIds) {
|
||||
function saveRelations(tx, refId, categories, notation, categoriesInvIds, secteursInvIds, workspaceIds) {
|
||||
tx(() => {
|
||||
// Legacy string categories
|
||||
db.prepare('DELETE FROM referentiel_categories WHERE referentiel_id = ?').run(refId);
|
||||
@@ -238,6 +259,13 @@ function saveRelations(tx, refId, categories, notation, categoriesInvIds, secteu
|
||||
for (const id of secteursInvIds) ins.run(refId, id);
|
||||
}
|
||||
|
||||
// Espaces de travail (IDs) — au moins un requis, déjà validé par le schéma Zod
|
||||
db.prepare('DELETE FROM referentiel_workspaces WHERE referentiel_id = ?').run(refId);
|
||||
if (workspaceIds && workspaceIds.length > 0) {
|
||||
const ins = db.prepare('INSERT OR IGNORE INTO referentiel_workspaces (referentiel_id, workspace_id) VALUES (?,?)');
|
||||
for (const id of workspaceIds) ins.run(refId, id);
|
||||
}
|
||||
|
||||
db.prepare('DELETE FROM referentiel_notation WHERE referentiel_id = ?').run(refId);
|
||||
if (notation.length > 0) {
|
||||
const ins = db.prepare(`
|
||||
@@ -267,6 +295,7 @@ router.get('/', (_req, res) => {
|
||||
rows = attachCats(rows);
|
||||
rows = attachCatsInv(rows);
|
||||
rows = attachSecteursInv(rows);
|
||||
rows = attachWorkspaces(rows);
|
||||
rows = attachNotation(rows);
|
||||
rows = parsePaysOp(rows);
|
||||
res.json(rows);
|
||||
@@ -288,6 +317,7 @@ router.get('/export', (req, res, next) => {
|
||||
let rows = db.prepare('SELECT * FROM plateformes_referentiel ORDER BY nom').all();
|
||||
rows = attachCatsInv(rows);
|
||||
rows = attachSecteursInv(rows);
|
||||
rows = attachWorkspaces(rows);
|
||||
rows = parsePaysOp(rows);
|
||||
|
||||
const entries = [];
|
||||
@@ -353,6 +383,7 @@ router.get('/export', (req, res, next) => {
|
||||
icone_filename: r.icone_filename,
|
||||
categories_inv: (r.categories_inv || []).map(c => c.nom),
|
||||
secteurs_inv: (r.secteurs_inv || []).map(s => s.nom),
|
||||
workspace_slugs: (r.workspaces || []).map(w => w.slug),
|
||||
notation: notationByRef[r.id] || [],
|
||||
}));
|
||||
|
||||
@@ -388,7 +419,7 @@ router.get('/:id/export', (req, res, next) => {
|
||||
try {
|
||||
const ref = db.prepare('SELECT * FROM plateformes_referentiel WHERE id = ?').get(req.params.id);
|
||||
if (!ref) throw new HttpError(404, 'Référentiel introuvable');
|
||||
const [enriched] = parsePaysOp(attachSecteursInv(attachCatsInv([ref])));
|
||||
const [enriched] = parsePaysOp(attachWorkspaces(attachSecteursInv(attachCatsInv([ref]))));
|
||||
|
||||
const entries = [];
|
||||
const manifest = {
|
||||
@@ -450,6 +481,7 @@ router.get('/:id/export', (req, res, next) => {
|
||||
icone_filename: enriched.icone_filename,
|
||||
categories_inv: (enriched.categories_inv || []).map(c => c.nom),
|
||||
secteurs_inv: (enriched.secteurs_inv || []).map(s => s.nom),
|
||||
workspace_slugs: (enriched.workspaces || []).map(w => w.slug),
|
||||
notation,
|
||||
};
|
||||
|
||||
@@ -506,6 +538,25 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
||||
return ids;
|
||||
}
|
||||
|
||||
// Résout des slugs de workspaces vers leurs IDs DANS CE catalogue —
|
||||
// contrairement aux catégories/secteurs, les workspaces sont un
|
||||
// catalogue géré par l'admin, jamais créés automatiquement à l'import.
|
||||
// Si aucun slug ne correspond (vieux ZIP sans workspace_slugs, ou
|
||||
// catalogue cible différent), on retombe sur "crowdlending" pour ne
|
||||
// jamais importer une entrée référentiel sans aucune affectation.
|
||||
function resolveWorkspaceIds(slugs) {
|
||||
const ids = [];
|
||||
for (const slug of (slugs || [])) {
|
||||
const row = db.prepare('SELECT id FROM workspaces WHERE slug = ?').get(slug);
|
||||
if (row) ids.push(row.id);
|
||||
}
|
||||
if (ids.length === 0) {
|
||||
const cl = db.prepare("SELECT id FROM workspaces WHERE slug = 'crowdlending'").get();
|
||||
if (cl) ids.push(cl.id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
// Build a map of images from the ZIP
|
||||
const imageMap = {};
|
||||
for (const e of zipEntries) {
|
||||
@@ -522,6 +573,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
||||
|
||||
const catsIds = resolveTagIds(p.categories_inv, 'categories_inv');
|
||||
const sectsIds = resolveTagIds(p.secteurs_inv, 'secteurs_inv');
|
||||
const wsIds = resolveWorkspaceIds(p.workspace_slugs);
|
||||
const paysOp = Array.isArray(p.pays_operation) && p.pays_operation.length ? JSON.stringify(p.pays_operation) : null;
|
||||
|
||||
const existing = db.prepare('SELECT id FROM plateformes_referentiel WHERE nom = ?').get(p.nom);
|
||||
@@ -590,6 +642,11 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
||||
for (const id of sectsIds) {
|
||||
db.prepare('INSERT OR IGNORE INTO referentiel_secteurs_inv (referentiel_id, secteur_id) VALUES (?,?)').run(refId, id);
|
||||
}
|
||||
// Sync referentiel_workspaces
|
||||
db.prepare('DELETE FROM referentiel_workspaces WHERE referentiel_id = ?').run(refId);
|
||||
for (const id of wsIds) {
|
||||
db.prepare('INSERT OR IGNORE INTO referentiel_workspaces (referentiel_id, workspace_id) VALUES (?,?)').run(refId, id);
|
||||
}
|
||||
|
||||
// Sync referentiel_notation
|
||||
if (Array.isArray(p.notation) && p.notation.length) {
|
||||
@@ -659,7 +716,7 @@ router.get('/:id', (req, res, next) => {
|
||||
try {
|
||||
const row = db.prepare('SELECT * FROM plateformes_referentiel WHERE id = ?').get(req.params.id);
|
||||
if (!row) throw new HttpError(404, 'Référentiel introuvable');
|
||||
const [enriched] = parsePaysOp(attachNotation(attachSecteursInv(attachCatsInv(attachCats([row])))));
|
||||
const [enriched] = parsePaysOp(attachWorkspaces(attachNotation(attachSecteursInv(attachCatsInv(attachCats([row]))))));
|
||||
res.json(enriched);
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
@@ -701,10 +758,10 @@ router.post('/', (req, res, next) => {
|
||||
|
||||
const refId = r.lastInsertRowid;
|
||||
const tx = db.transaction(fn => fn());
|
||||
saveRelations(tx, refId, data.categories, data.notation, data.categories_inv_ids, data.secteurs_inv_ids);
|
||||
saveRelations(tx, refId, data.categories, data.notation, data.categories_inv_ids, data.secteurs_inv_ids, data.workspace_ids);
|
||||
|
||||
const row = db.prepare('SELECT * FROM plateformes_referentiel WHERE id = ?').get(refId);
|
||||
const [enriched] = parsePaysOp(attachNotation(attachSecteursInv(attachCatsInv(attachCats([row])))));
|
||||
const [enriched] = parsePaysOp(attachWorkspaces(attachNotation(attachSecteursInv(attachCatsInv(attachCats([row]))))));
|
||||
enriched.nb_plateformes_liees = 0;
|
||||
res.status(201).json(enriched);
|
||||
} catch (e) { next(e); }
|
||||
@@ -755,11 +812,11 @@ router.put('/:id', (req, res, next) => {
|
||||
);
|
||||
|
||||
const tx = db.transaction(fn => fn());
|
||||
saveRelations(tx, row.id, data.categories, data.notation, data.categories_inv_ids, data.secteurs_inv_ids);
|
||||
saveRelations(tx, row.id, data.categories, data.notation, data.categories_inv_ids, data.secteurs_inv_ids, data.workspace_ids);
|
||||
|
||||
const updated = db.prepare('SELECT * FROM plateformes_referentiel WHERE id = ?').get(row.id);
|
||||
const nb = db.prepare('SELECT COUNT(*) AS n FROM plateformes WHERE referentiel_id = ?').get(row.id).n;
|
||||
const [enriched] = parsePaysOp(attachNotation(attachSecteursInv(attachCatsInv(attachCats([updated])))));
|
||||
const [enriched] = parsePaysOp(attachWorkspaces(attachNotation(attachSecteursInv(attachCatsInv(attachCats([updated]))))));
|
||||
enriched.nb_plateformes_liees = nb;
|
||||
res.json(enriched);
|
||||
} catch (e) { next(e); }
|
||||
|
||||
Reference in new issue
Block a user