278 lines
6.9 KiB
JavaScript
278 lines
6.9 KiB
JavaScript
// 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,
|
||
}; |