// services/parcoursService.js const { db } = require("../db/db-connect"); const logger = require("../utils/logger"); const globalService = require("../services/globalService"); /** * Récupère un parcours par son numéro (avec expand utiles) */ async function getParcoursByNumParcours(numParcours) { const criteria = { filter: `numParcours='${numParcours}'`, expand: [ "dernierUtilisateur.region", "contrat", "contrat.client", "contrat.intermediaire" ].join(",") }; return globalService.fetchInfoByCriteria("parcours", criteria); } /** * Full list (batch côté PocketBase). | Fetch l'ensemble de la BD via chunk "batch" * avec getFullList(collection, batchSize, options) */ async function getParcoursFullList({ filter, sort, expand, fields, batch = 500 }) { const options = { sort: sort || "-created", }; // Ajouter expand si défini if (expand) { options.expand = expand; } // Ajouter fields si défini if (fields) { options.fields = fields; } // Ajouter filter SEULEMENT s'il n'est pas vide (Pocketbase 0.7 rejette les filtres vides) if (filter && filter.trim() !== "") { options.filter = filter; } // getFullList(collection, batchSize, options) return db.records.getFullList("parcours", batch, options); } /** * Pagination multi-régions + filtres/tri optionnels (server-side DataTables) * – Parcours une seule fois db par requête * @param {string[]} regions * @param {number} page * @param {number} perPage * @param {{filter?: string, sort?: string}} opts */ async function getParcoursByRegionsPage(regions = [], page = 1, perPage = 10, opts = {}) { try { let regFilter = ""; if (Array.isArray(regions) && regions.length > 0) { const ors = regions.map(r => `dernierUtilisateur.region.nom = "${r}"`); regFilter = `(${ors.join(" || ")})`; } const filter = [regFilter, opts.filter].filter(Boolean).join(" && "); /** * Récupération des parcours avec expands nécessaires * Note: L'expand de contrat.client ne fonctionne pas toujours, * d'où la nécessité d'un fallback dans le contrôleur */ const list = await db.records.getList("parcours", page, perPage, { sort: opts.sort || "-created", filter: filter || "", expand: [ "contrat", "contrat.client", "contrat.intermediaire", "dernierUtilisateur.region" ].join(","), }); return { page: list.page, perPage: list.perPage, totalItems: list.totalItems, totalPages: list.totalPages, items: list.items, }; } catch (error) { logger.log('error', error); throw error; } } /** * Création d'un parcours vide */ async function createNewEmptyParcours(numParcours) { try { const data = { ["numParcours"]: numParcours }; const record = await db.records.create("parcours", data); if (record) { return record.id; } else { return null; } } catch (error) { logger.log("error", error); return null; } } /** * MAJ d'un champ d'un parcours */ async function updateFieldValueParcours(id, field, value) { try { const data = { [field]: value }; const record = await db.records.update("parcours", id, data); if (record) { return record.id; } else { return null; } } catch (error) { logger.log("error", error); return null; } } /** * Génère le prochain numéro de parcours */ async function getNewParcoursNumber() { try { const list = await db.records.getList("parcours", 1, 1, { sort: "-numParcours" }); const last = list?.items?.[0]; if (!last?.numParcours) return null; const numericValue = parseInt(String(last.numParcours).substring(1), 10); if (Number.isNaN(numericValue)) return null; const next = numericValue + 1; return "P" + next.toString().padStart(9, "0"); } catch (error) { logger.log("error", error); return null; } } // --- Section détails profonds (contrat + fiche produit) --- // /** * Récupère un parcours (via numParcours) avec les expands utiles pour détails. */ async function getParcoursForDetails(numParcours) { try { const list = await db.records.getList("parcours", 1, 1, { filter: `numParcours='${numParcours}'`, expand: [ "contrat", "contrat.client", "contrat.intermediaire", "dernierUtilisateur.region" ].join(","), }); return list?.items?.[0] || null; } catch (e) { logger.log("error", e); return null; } } /** * Mappe un libellé produit vers la collection PocketBase (à ajuster si changement de parcours). */ function mapProduitToCollection(produitRaw = "") { const p = String(produitRaw || "").trim().toUpperCase(); const map = { "TPPC": "tppc", "RC": "rc", "FAC": "fac", }; return map[p] || null; } /** * Récupère la fiche produit pour un contrat donné. * On tente d'abord par relation "contrat = contratId" si elle existe, * sinon fallback par "numContrat = x" si jamais la fiche stocke le numéro. */ async function getProduitRecordForContrat(contrat, opts = {}) { try { if (!contrat) return null; const collection = mapProduitToCollection(contrat.produit); if (!collection) return null; // Tente via une relation directe "contrat" (champ le plus propre) try { const record = await db.records.getFirstListItem(collection, `contrat='${contrat.id}'`, { }); if (record) return record; } catch (_) { /* ignore, on tente le fallback */ } // Fallback if (contrat.numContrat) { try { const record = await db.records.getFirstListItem(collection, `numContrat='${contrat.numContrat}'`, {}); if (record) return record; } catch (_) { /* ignore */ } } return null; } catch (e) { logger.log("error", e); return null; } } /** * reformatage texte - a virer */ function escPB(s = "") { return String(s).replace(/"/g, '\\"'); } /** * Détails complets: parcours + contrat + fiche produit */ async function getDeepDetailsByNumParcours(numParcours) { try { const filter = `numParcours = "${escPB(numParcours)}"`; const list = await db.records.getList("parcours", 1, 1, { filter, expand: [ "contrat", "contrat.client", "contrat.intermediaire", "dernierUtilisateur.region", // produit lié "contrat.tppc", "contrat.rc", "contrat.fac", // sous-relations TPPC "contrat.tppc.tarif", "contrat.tppc.projet", ].join(","), }); return list?.items?.[0] || null; } catch (e) { logger.log("error", e); return null; } } module.exports = { getNewParcoursNumber, getParcoursByNumParcours, createNewEmptyParcours, updateFieldValueParcours, getParcoursByRegionsPage, getParcoursFullList, getParcoursForDetails, getProduitRecordForContrat, getDeepDetailsByNumParcours, mapProduitToCollection, };