RC: stabilise projet validation and reorganize product scripts

This commit is contained in:
Alexis Burnaz 2026-04-24 17:38:30 +02:00
parent 349c379dc7
commit 7b484f34c9
20 changed files with 1200 additions and 307 deletions

View File

@ -37,7 +37,7 @@ document.addEventListener('DOMContentLoaded', function() {
const parcours = JSON.parse(sessionStorage.getItem('parcours'));
const contrat = JSON.parse(sessionStorage.getItem('contrat'));
let produit = parcours["@expand"].contrat.produit
let produit = String(parcours?.["@expand"]?.contrat?.produit || '').toLowerCase();
const produitObj = contrat?.["@expand"]?.enCours || null;
@ -60,10 +60,10 @@ document.addEventListener('DOMContentLoaded', function() {
if (submenu === "projet") {
fetchUrl = `/navParcours/${submenu}${produit}?numParcours=${numParcours}`;
scriptSrc = `/js/projet-form-${produit}.js`;
scriptSrc = `/js/${produit}/projet-form-${produit}.js`;
} else if (submenu === "tarif") {
fetchUrl = `/navParcours/${submenu}${produit}?numParcours=${numParcours}`;
scriptSrc = `/js/tarif-form-${produit}.js`;
scriptSrc = `/js/${produit}/tarif-form-${produit}.js`;
} else {
fetchUrl = `/navParcours/${submenu}?numParcours=${numParcours}`;
scriptSrc = `/js/${submenu}-form.js`;

View File

@ -1,9 +1,15 @@
(function () {
/**
* Normalise numeric input.
*/
function normalizeNumericInput(raw) {
if (raw == null) return '';
return String(raw).trim().replace(/\s+/g, '');
}
/**
* Parse loose number.
*/
function parseLooseNumber(raw) {
const normalized = normalizeNumericInput(raw).replace(',', '.');
if (!normalized) return NaN;
@ -11,6 +17,9 @@
return Number.isFinite(parsed) ? parsed : NaN;
}
/**
* Formate french amount.
*/
function formatFrenchAmount(value, digits) {
const number = Number(value);
if (!Number.isFinite(number)) return '0.00';
@ -20,6 +29,9 @@
});
}
/**
* Synchronise floating labels.
*/
function syncFloatingLabels(root) {
const scope = root && root.querySelectorAll ? root : document;
const labels = scope.querySelectorAll('.rc-field-label[for]');
@ -64,6 +76,9 @@
}
}
/**
* Securise error slot.
*/
function ensureErrorSlot(field, customErrorId) {
if (!field) return null;
@ -92,6 +107,9 @@
return sibling;
}
/**
* Gere pick field label.
*/
function pickFieldLabel(field, fallbackLabel) {
if (fallbackLabel) return fallbackLabel;
if (!field) return 'Champ';
@ -110,6 +128,9 @@
return field.name || field.id || 'Champ';
}
/**
* Met a jour field error.
*/
function setFieldError(field, errorSlot, message) {
if (!errorSlot) return;
errorSlot.textContent = message || '';
@ -124,10 +145,16 @@
}
}
/**
* Verifie forbidden numeric chars.
*/
function hasForbiddenNumericChars(value) {
return /[A-Za-z€$£¥]/.test(value);
}
/**
* Gere validate numeric.
*/
function validateNumeric(value, options) {
const raw = normalizeNumericInput(value);
const decimals = Number.isInteger(options.decimals) ? options.decimals : 2;
@ -182,6 +209,9 @@
return { valid: true, normalized: raw.replace(',', '.') };
}
/**
* Gere validate integer.
*/
function validateInteger(value, options) {
const raw = normalizeNumericInput(value);
const label = options.label || 'Champ';
@ -209,6 +239,9 @@
return { valid: true };
}
/**
* Gere validate immat.
*/
function validateImmat(value, options) {
const raw = String(value || '').trim().toUpperCase();
const label = options.label || 'Immatriculation';
@ -227,6 +260,9 @@
return { valid: true, normalized: raw };
}
/**
* Gere validate text safe.
*/
function validateTextSafe(value, options) {
const raw = String(value || '').trim();
const label = options.label || 'Champ';
@ -250,6 +286,9 @@
return { valid: true };
}
/**
* Gere validate number or consult.
*/
function validateNumberOrConsult(raw, options) {
const value = String(raw || '').trim();
const lower = value.toLowerCase();
@ -268,6 +307,9 @@
return validateNumeric(value, options);
}
/**
* Gere evaluate rule.
*/
function evaluateRule(field, config) {
const profile = config.profile;
const label = pickFieldLabel(field, config.label);
@ -331,11 +373,17 @@
};
}
/**
* Gere create summary element.
*/
function createSummaryElement(summaryId) {
if (!summaryId) return null;
return document.getElementById(summaryId);
}
/**
* Gere create guard.
*/
function createGuard(options) {
const summary = createSummaryElement(options.summaryId);
const summaryTitle = String(options.summaryTitle || 'Impossible de continuer car :');
@ -357,6 +405,9 @@
});
});
/**
* Gere apply summary.
*/
function applySummary(messages) {
if (!summary) return;
if (!messages.length) {
@ -379,6 +430,9 @@
summary.style.display = 'block';
}
/**
* Gere apply blocking.
*/
function applyBlocking(hasErrors) {
state.targetButtons.forEach(function (button) {
if (!button) return;
@ -395,6 +449,9 @@
});
}
/**
* Gere recompute.
*/
function recompute() {
const messages = [];
@ -405,10 +462,15 @@
});
state.externalChecks.forEach(function (entry, key) {
const checkResult = typeof entry.fn === 'function' ? entry.fn() : { valid: true };
if (!checkResult || checkResult.valid === false) {
const message = checkResult && checkResult.message ? checkResult.message : entry.message || key;
messages.push(message);
try {
const checkResult = typeof entry.fn === 'function' ? entry.fn() : { valid: true };
if (!checkResult || checkResult.valid === false) {
const message = checkResult && checkResult.message ? checkResult.message : entry.message || key;
messages.push(message);
}
} catch (error) {
console.warn('[RC guard] Check externe en erreur:', key, error);
messages.push(entry.message || ('Validation en erreur: ' + key));
}
});
@ -422,46 +484,65 @@
return messages;
}
/**
* Gere validate field.
*/
function validateField(field) {
const config = state.fieldRules.get(field);
if (!config) return;
try {
const config = state.fieldRules.get(field);
if (!config) return;
if (!field || !field.isConnected || field.disabled) {
if (config.errorSlot) setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
if (typeof config.activeWhen === 'function' && !config.activeWhen(field)) {
setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
// Sécurité défaut: sans activeWhen explicite, on ignore un champ masqué.
if (typeof config.activeWhen !== 'function' && field.type !== 'hidden' && field.offsetParent === null) {
setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
const result = evaluateRule(field, config);
setFieldError(field, config.errorSlot, result.valid ? '' : result.message);
if (result.valid) {
state.fieldErrors.delete(config.key);
} else {
state.fieldErrors.set(config.key, {
message: result.message
});
}
if (!field || !field.isConnected || field.disabled) {
if (config.errorSlot) setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
if (typeof config.activeWhen === 'function' && !config.activeWhen(field)) {
setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
// Sécurité défaut: sans activeWhen explicite, on ignore un champ masqué.
if (typeof config.activeWhen !== 'function' && field.type !== 'hidden' && field.offsetParent === null) {
setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
const result = evaluateRule(field, config);
setFieldError(field, config.errorSlot, result.valid ? '' : result.message);
if (result.valid) {
state.fieldErrors.delete(config.key);
} else {
state.fieldErrors.set(config.key, {
message: result.message
} catch (error) {
const config = state.fieldRules.get(field);
const key = config && config.key ? config.key : 'champ-inconnu';
console.warn('[RC guard] Validation en erreur:', key, error);
if (config && config.errorSlot) {
setFieldError(field, config.errorSlot, 'Erreur de validation, rechargez la page.');
}
state.fieldErrors.set(key, {
message: 'Erreur de validation sur un champ du formulaire.'
});
recompute();
}
recompute();
}
/**
* Gere attach field.
*/
function attachField(field, config) {
if (!field) return;
@ -489,6 +570,9 @@
validateField(field);
}
/**
* Enregistre field.
*/
function registerField(selectorOrElement, config) {
if (!selectorOrElement || !config || !config.profile) return;
if (typeof selectorOrElement === 'string') {
@ -500,6 +584,9 @@
attachField(selectorOrElement, config);
}
/**
* Gere observe.
*/
function observe(selector, config) {
if (!selector || !config || !config.profile) return;
@ -517,6 +604,9 @@
return observer;
}
/**
* Enregistre external.
*/
function registerExternal(key, fn, fallbackMessage) {
if (!key || typeof fn !== 'function') return;
state.externalChecks.set(key, {
@ -526,6 +616,9 @@
recompute();
}
/**
* Gere refresh.
*/
function refresh() {
state.fieldRules.forEach(function (_cfg, field) {
validateField(field);

View File

@ -1,8 +1,14 @@
/**
* Initialise submenu form.
*/
function initSubmenuForm() {
// Accéder aux informations stockées du parcours
const parcours = JSON.parse(sessionStorage.getItem('parcours'));
}
/**
* Convertit number.
*/
function toNumber(x) {
if (x == null) return 0;
@ -45,6 +51,9 @@ function toNumber(x) {
}
// Fonction pour formater un nombre avec X décimales max (sans décimales si entier)
/**
* Formate number.
*/
function formatNumber(num, decimals = 2) {
if (!num || isNaN(num)) return '0.' + '0'.repeat(decimals);
const factor = Math.pow(10, decimals);
@ -56,6 +65,9 @@ function formatNumber(num, decimals = 2) {
return rounded.toFixed(decimals);
}
/**
* Recupere element by id flexible.
*/
function getElementByIdFlexible(id) {
if (!id) return null;
const direct = document.getElementById(id);
@ -78,12 +90,18 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
let rcTarifGuard = null;
let rcTarifHasErrors = false;
/**
* Synchronise rcfloating labels.
*/
function syncRCFloatingLabels() {
if (window.RCValidationUtils && typeof window.RCValidationUtils.syncFloatingLabels === 'function') {
window.RCValidationUtils.syncFloatingLabels(document);
}
}
/**
* Met a jour tarifettes visibility.
*/
function updateTarifettesVisibility() {
const tarifettesContainer = document.getElementById('tarifettesContainer');
if (!tarifettesContainer) return;
@ -95,6 +113,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
// ═══════════════════════════════════════════════════════════════
// Fonction helper : trouver la tranche la plus proche
/**
* Trouve closest tranche.
*/
function findClosestTranche(val, tranches) {
if (val <= tranches[0]) return tranches[0];
if (val >= tranches[tranches.length - 1]) return tranches[tranches.length - 1];
@ -107,6 +128,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return tranches[tranches.length - 1];
}
/**
* Met a jour tarif choice buttons state.
*/
function updateTarifChoiceButtonsState() {
const buttons = document.querySelectorAll('.franchise-card button[name]');
buttons.forEach((button) => {
@ -122,6 +146,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
updateTarifettesVisibility();
}
/**
* Configure rcsafe validation.
*/
function setupRCSafeValidation() {
if (!window.RCValidationUtils || typeof window.RCValidationUtils.createGuard !== 'function') {
return;
@ -130,7 +157,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
rcTarifGuard = window.RCValidationUtils.createGuard({
summaryId: 'rcTarifBlockingSummary',
summaryTitle: 'Impossible de choisir un tarif ou de continuer car :',
blockTargets: ['#generateDeclinaison', '#generateProject', '#comm-OK'],
blockTargets: ['#generateDeclinaison', '#generateProject'],
onChange: function (messages) {
rcTarifHasErrors = messages.length > 0;
updateTarifChoiceButtonsState();
@ -267,30 +294,6 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
});
rcTarifGuard.registerField('#tarifCom', {
profile: 'numeric',
label: 'Tarif commercial',
min: 0,
positive: true,
requiredWhen: function () {
return Boolean(document.getElementById('modalTarifCom')?.classList.contains('open'));
},
activeWhen: function () {
return Boolean(document.getElementById('modalTarifCom')?.classList.contains('open'));
}
});
rcTarifGuard.registerField('#commentaire', {
profile: 'text',
label: 'Commentaire explicatif',
requiredWhen: function () {
return document.getElementById('tarifCom-error')?.style.display !== 'none';
},
activeWhen: function () {
return document.getElementById('col-commentaire')?.style.display !== 'none';
}
});
rcTarifGuard.observe('#tabAdvaloTerrestre input[type="text"], #tabAdvaloMultimodal input[type="text"], #tabAdvaloAerien input[type="text"]', {
profile: 'number_or_consulter',
label: 'Valeur ad valorem',
@ -328,6 +331,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour afficher un avertissement visuel de dépassement %
/**
* Affiche percentage warning.
*/
function showPercentageWarning(excess) {
let warningDiv = document.getElementById('percentageWarning');
if (!warningDiv) {
@ -357,6 +363,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour masquer toutes les primes (quand % invalide)
/**
* Masque all primes.
*/
function hideAllPrimes() {
const primeElements = [
'primeChapActRCC', 'primeChapActRCE',
@ -376,6 +385,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour mettre à jour l'indicateur visuel du total des pourcentages
/**
* Met a jour percentage indicator.
*/
function updatePercentageIndicator(total) {
const indicator = document.getElementById('pourcentageTotal');
if (!indicator) return;
@ -405,6 +417,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
// ═══════════════════════════════════════════════════════════════
// Initialisation du formulaire et des données
/**
* Initialise la logique de cette fonction.
*/
function init() {
// Materialize init select
var select = document.querySelectorAll('select');
@ -467,6 +482,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Configuration des listeners d'événements
/**
* Configure event listeners.
*/
function setupEventListeners() {
// Empêcher la soumission du formulaire avec la touche Enter
const form = document.getElementById('projetForm');
@ -1181,6 +1199,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
// Le bouton Annuler ferme automatiquement le modal grâce à la classe "modal-close"
}
/**
* Parse prefill array.
*/
function parsePrefillArray(value) {
if (Array.isArray(value)) return value;
if (typeof value === 'string') {
@ -1194,6 +1215,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return [];
}
/**
* Normalise prefill value.
*/
function normalizePrefillValue(value) {
if (Array.isArray(value)) {
return value
@ -1223,6 +1247,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return value;
}
/**
* Gere build projet prefill delta.
*/
function buildProjetPrefillDelta(snapshot, baseline) {
if (!snapshot || typeof snapshot !== 'object') return null;
if (!baseline || typeof baseline !== 'object') return snapshot;
@ -1240,6 +1267,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return Object.keys(delta).length > 0 ? delta : null;
}
/**
* Gere apply projet data to tarif.
*/
function applyProjetDataToTarif(projetSource) {
if (!projetSource || typeof projetSource !== 'object') return;
@ -1503,6 +1533,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Peupler le formulaire avec les données
/**
* Gere populate form data.
*/
function populateFormData() {
//Poupulate select historique
if (!contrat.historique) {
@ -1877,6 +1910,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}, 500);
}
/**
* Calcule mod ca.
*/
function calcModCA(caRaw){
const ca = Number(String(caRaw || '').replace(/\s/g,'').replace(',','.')) || 0;
const thresholds = Object.keys(modRCCA).map(n => Number(n)).sort((a,b)=>a-b);
@ -1884,6 +1920,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return modRCCA[thresholds.at(-1)] ?? 1;
}
/**
* Calcule mod marchandises.
*/
function calcModMarchandises(data, activityName, type, cot) {
let m = 1;
@ -1927,6 +1966,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return m;
}
/**
* Recupere zone mods.
*/
function getZoneMods(data) {
let mRCC = 1;
let mRCE = 1;
@ -1958,6 +2000,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return { mRCC, mRCE };
}
/**
* Calcule mod act compl.
*/
function calcModActCompl(data, activityName, type, cot) {
let m = 1;
const current = getSelectedActivities().find(a => a.typeActivite === activityName);
@ -1970,12 +2015,18 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return m;
}
/**
* Calcule engag compl.
*/
function calcEngagCompl(data, primeRCC) {
let prime = primeRCC;
let surPrime = 0;
let modDommImmat = 1;
let infos = [];
/**
* Gere write to card.
*/
function writeToCard(inputId, message) {
const inputEl = document.getElementById(inputId);
if (!inputEl) return;
@ -2078,6 +2129,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction helper : trouver la tranche la plus proche
/**
* Trouve closest tranche.
*/
function findClosestTranche(val, tranches) {
if (val <= tranches[0]) return tranches[0];
if (val >= tranches[tranches.length - 1]) return tranches[tranches.length - 1];
@ -2092,6 +2146,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour afficher un avertissement visuel de dépassement %
/**
* Affiche percentage warning.
*/
function showPercentageWarning(excess) {
// Créer ou récupérer le div d'avertissement
let warningDiv = document.getElementById('percentageWarning');
@ -2124,6 +2181,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour masquer toutes les primes (quand % invalide)
/**
* Masque all primes.
*/
function hideAllPrimes() {
const primeElements = [
'primeChapActRCC', 'primeChapActRCE',
@ -2143,10 +2203,16 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour afficher toutes les primes (quand % valide)
/**
* Affiche all primes.
*/
function showAllPrimes() {
}
// Fonction pour mettre à jour l'indicateur visuel du total des pourcentages
/**
* Met a jour percentage indicator.
*/
function updatePercentageIndicator(total) {
const indicator = document.getElementById('pourcentageTotal');
if (!indicator) return;
@ -2182,6 +2248,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
}
/**
* Calcule gar add.
*/
function calcGarAdd(data, primeRCC, primeRCE) {
let primeRCCres = Number(primeRCC) || 0;
let primeRCEres = Number(primeRCE) || 0;
@ -2189,6 +2258,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
const infosRCC = [];
const infosRCE = [];
/**
* Gere write to card.
*/
function writeToCard(inputId, message) {
const inputEl = document.getElementById(inputId);
if (!inputEl) return;
@ -2276,6 +2348,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
/**
* Calcule sinistre.
*/
function calcSinistre(data, primeRCC, primeRCE) {
console.log('[calcSinistre] start', { primeRCC, primeRCE });
@ -2327,6 +2402,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
// --- Fonctions utilitaires ---
/**
* Recupere max mini rcc.
*/
function getMaxMiniRCC(activites, cot) {
let maxMini = 0;
activites.forEach(act => {
@ -2355,6 +2433,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return maxMini;
}
/**
* Recupere max mini rce.
*/
function getMaxMiniRCE(activites, cot) {
let maxMini = 0;
activites.forEach(act => {
@ -2384,6 +2465,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
/**
* Calcule tarif rcc.
*/
function calcTarifRCC({
CA,
primeRCCbase,
@ -2449,6 +2533,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
/**
* Calcule tarifettes.
*/
function calcTarifettes(primePJ, CA, activites, capitalTPPC, nbVehicules, coefTPPC, primeRCCbase, primeRCEbase) {
const cards = [
{ key: "250", priceId: "priceFr250", rccId: "rccFr250", rceId: "rceFr250", pjId: "pjFr250", txRccId: "tauxRccFr250", txRceId: "tauxRceFr250", txGlobId: "tauxGlobalFr250" },
@ -2573,6 +2660,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour calculer la PJ selon la prime totale
/**
* Calcule pj.
*/
function calcPJ(primeTotale) {
let primePJ = 0;
const tablePJ = modRCGarAdd?.modRCC?.["Protection juridique"];
@ -2592,6 +2682,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
/**
* Calcule global.
*/
function calcGlobal() {
// Vérification des pourcentages AVANT calcul
const allPourcentInputs = Array.from(document.querySelectorAll('.input-pourcent')).filter(p => p.offsetParent != null);
@ -2631,6 +2724,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
updateTarifChoiceButtonsState();
}
/**
* Calcule revisable.
*/
function calcRevisable() {
// ========= Variables centrales (déclarées au début) =========
const capitalTPPCEl = document.getElementById("capital_TPPC");
@ -2856,6 +2952,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
"Autres activites": "Autres activites"
};
/**
* Recupere selected activites.
*/
function getSelectedActivites() {
const activites = [];
document.querySelectorAll('label input[type="checkbox"]:checked').forEach(cb => {
@ -2885,6 +2984,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
};
}
/**
* Calcule forfaitaire.
*/
function calcForfaitaire() {
// ========= Variables centrales =========
let primeBaseRCC = 0;
@ -3139,6 +3241,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
"Autres activites": "Autres activites"
};
/**
* Recupere selected activites.
*/
function getSelectedActivites() {
const activites = [];
document.querySelectorAll('label input[type="checkbox"]:checked').forEach(cb => {
@ -3167,6 +3272,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
};
}
/**
* Recupere zone mods.
*/
function getZoneMods(modRCZone) {
const zones = [
{ id: 'zone1', label: 'France Métropolitaine et pays limitrophes' },
@ -3191,6 +3299,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
/**
* Gere load historique btn.
*/
function handleLoadHistoriqueBtn() {
var selectedId = document.getElementById('idSelect').value;
@ -3213,6 +3324,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
//Appel pour recevoir les constantes
/**
* Gere constants json.
*/
async function constantsJSON() {
try {
const responsesJSON = await Promise.all([
@ -3249,6 +3363,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
}
/**
* Gere reset inputs.
*/
function resetInputs() {
// Sélection des éléments input pourcentage
const pourcentInputs = document.querySelectorAll('.input-pourcent');
@ -3280,6 +3397,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
}
/**
* Recupere taux base rcc.
*/
function get_taux_base_RCC(grille_mod_RCC, cot) {
const tauxOptimaux = [];
const selections = getSelectedActivities();
@ -3340,6 +3460,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
/**
* Recupere taux base rce.
*/
function get_taux_base_RCE(grille_mod_RCE, cot) {
const tauxOptimaux = [];
const selections = getSelectedActivities();
@ -3372,6 +3495,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
return tauxOptimaux;
}
/**
* Recupere selected activities.
*/
function getSelectedActivities() {
const selections = [];
@ -3503,6 +3629,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour sauvegarder les données tarifRC dans la base
/**
* Sauvegarde tarif rc.
*/
async function saveTarifRC() {
if (!rc || !contrat) {
console.error('Données manquantes pour sauvegarder le tarif RC');
@ -3741,6 +3870,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction helper pour collecter les activités complémentaires
/**
* Collecte activites compl json.
*/
function collectActivitesComplJSON(typeActivite) {
let name;
switch(typeActivite.toLowerCase()) {
@ -3770,6 +3902,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction helper pour collecter les marchandises
/**
* Collecte marchandises json.
*/
function collectMarchandisesJSON(typeActivite) {
let name;
switch(typeActivite.toLowerCase()) {
@ -3805,6 +3940,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour gérer les clics sur les boutons de tariffettes
/**
* Configure tarifette buttons.
*/
function setupTarifetteButtons() {
const tarifetteButtons = document.querySelectorAll('.franchise-card button[name]');
tarifetteButtons.forEach(button => {
@ -3827,6 +3965,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour configurer les alertes de marchandises
/**
* Configure marchandise alerts.
*/
function setupMarchandiseAlerts() {
// Alerte pour Animaux Vivants
document.querySelectorAll('[name^="mar"] input[type="checkbox"]').forEach(cb => {
@ -3843,6 +3984,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}
// Fonction pour configurer les alertes d'activités
/**
* Configure activite alerts.
*/
function setupActiviteAlerts() {
// Alerte pour Autocariste + RCE
const checkAutocariste = document.getElementById('checkAutocariste');
@ -3862,6 +4006,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
const seuil = 5;
// Fonction pour ouvrir le modal de tarif commercial
/**
* Ouvre modal tarif com.
*/
function openModalTarifCom(franchiseValue) {
// Récupérer le tarif de référence selon la franchise
let tarifRef = 0;
@ -3877,7 +4024,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
document.getElementById('tarifRefText').innerText = `Tarif de Référence : ${formatNumber(tarifRef, 2)}`;
document.getElementById('tarifCom').value = '';
document.getElementById('commentaire').value = '';
document.getElementById('comm-OK').disabled = false;
document.getElementById('comm-OK').disabled = true;
document.getElementById('tarifCom-error').style.display = 'none';
document.getElementById('col-commentaire').style.display = 'none';
document.getElementById('qualiteDiv').style.display = 'none';
@ -3896,15 +4043,23 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
// Ouvrir le modal
window.modalTarifCom.open();
if (rcTarifGuard) rcTarifGuard.refresh();
}
// Fonction pour gérer l'input du tarif commercial
/**
* Gere tarif com input.
*/
function handleTarifComInput(e) {
const tarifCom = parseFloat(e.target.value);
const tarifRef = parseFloat(document.getElementById('comm-OK').getAttribute('data-tarif-ref'));
if (!tarifCom || tarifCom <= 0) {
document.getElementById('qualiteDiv').style.display = 'none';
document.getElementById('tarifCom-error').style.display = 'none';
document.getElementById('col-commentaire').style.display = 'none';
document.getElementById('comm-OK').disabled = true;
if (rcTarifGuard) rcTarifGuard.refresh();
return;
}
@ -3952,19 +4107,32 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
document.getElementById('tarifCom-error').style.display = 'none';
document.getElementById('col-commentaire').style.display = 'none';
}
if (rcTarifGuard) rcTarifGuard.refresh();
}
// Fonction pour gérer l'input du commentaire
/**
* Gere commentaire input.
*/
function handleCommentaireInput(e) {
const commentaire = e.target.value.trim();
const tarifCom = parseFloat(document.getElementById('tarifCom').value);
const tarifComError = document.getElementById('tarifCom-error').style.display !== 'none';
if (tarifComError) {
document.getElementById('comm-OK').disabled = !commentaire;
document.getElementById('comm-OK').disabled = !commentaire || !tarifCom || tarifCom <= 0;
} else {
document.getElementById('comm-OK').disabled = !tarifCom || tarifCom <= 0;
}
if (rcTarifGuard) rcTarifGuard.refresh();
}
// Fonction pour gérer la validation du tarif commercial
/**
* Gere validate tarif com.
*/
async function handleValidateTarifCom() {
const tarifCom = parseFloat(document.getElementById('tarifCom').value);
const commentaire = document.getElementById('commentaire').value.trim();

View File

@ -4,161 +4,187 @@ function isValidEmail(email) {
}
function validateField(fieldId, showErrors = true) {
const inputElement = document.getElementById(fieldId);
try {
const inputElement = document.getElementById(fieldId);
if (!inputElement) {
return true;
}
if (!inputElement) {
return true;
}
const value = document.getElementById(fieldId).value.trim();
const rule = validationRules[fieldId]; // Importé de js/json/json-verif-form.js
const errorElement = document.getElementById(`${fieldId}-error`);
const rawValue = inputElement.value;
const value = typeof rawValue === 'string' ? rawValue.trim() : String(rawValue ?? '').trim();
const hasValidationRules = typeof validationRules !== 'undefined' && validationRules;
const rule = hasValidationRules ? validationRules[fieldId] : null; // Importé de js/json/json-verif-form.js
const errorElement = document.getElementById(`${fieldId}-error`);
let isValid = true;
let errorMessage = "";
if (!rule) {
if (showErrors && errorElement) {
errorElement.textContent = "";
errorElement.style.display = 'none';
}
return true;
}
// Vérifie si le champ est requis et vide
let isValid = true;
let errorMessage = "";
if (rule.required && (!value || value == '')) {
errorMessage = "Ce champ est obligatoire.";
isValid = false;
}
// Vérifie la longueur de la saisie si spécifié et nécessaire
if (isValid && value && rule.length && value.length !== rule.length) {
errorMessage = `Veuillez saisir ${rule.length} caractères, il y en a actuellement ${value.length}.`;
isValid = false;
}
// Vérifie si la saisie est un nombre sans décimale ou avec un maximum de deux chiffres après la virgule
if (isValid && value && rule.numberFormat) {
const isNumberFormatValid = /^(?:\d+|\d+\.\d{1,2})$/.test(value);
if (!isNumberFormatValid) {
errorMessage = "Le champ doit être nombre de deux décimal maximum '00.00' ou entier '00'";
// Vérifie si le champ est requis et vide
if (rule.required && (!value || value == '')) {
errorMessage = "Ce champ est obligatoire.";
isValid = false;
}
}
// Vérifie si la saisie est un taux sans décimale ou avec un maximum de trois chiffres après la virgule
if (isValid && value && rule.tauxFormat) {
const isTauxFormatValid = /^(?:\d+|\d+\.\d{1,3})$/.test(value);
if (!isTauxFormatValid) {
errorMessage = "Le champ doit être un nombre avec un maximum de trois décimales '00.000' ou entier '00'";
// Vérifie la longueur de la saisie si spécifié et nécessaire
if (isValid && value && rule.length && value.length !== rule.length) {
errorMessage = `Veuillez saisir ${rule.length} caractères, il y en a actuellement ${value.length}.`;
isValid = false;
}
}
// Vérifie si le champ doit être compris dans un certain intervalle
if (isValid && value && rule.range) {
const numberValue = parseFloat(value);
// Vérifie si la saisie est un nombre sans décimale ou avec un maximum de deux chiffres après la virgule
if (isValid && value && rule.numberFormat) {
const isNumberFormatValid = /^(?:\d+|\d+\.\d{1,2})$/.test(value);
if (isNaN(numberValue) || numberValue < rule.range.min || numberValue > rule.range.max) {
errorMessage = rule.errorMsg;
if (!isNumberFormatValid) {
errorMessage = "Le champ doit être nombre de deux décimal maximum '00.00' ou entier '00'";
isValid = false;
}
}
// Vérifie si la saisie est un taux sans décimale ou avec un maximum de trois chiffres après la virgule
if (isValid && value && rule.tauxFormat) {
const isTauxFormatValid = /^(?:\d+|\d+\.\d{1,3})$/.test(value);
if (!isTauxFormatValid) {
errorMessage = "Le champ doit être un nombre avec un maximum de trois décimales '00.000' ou entier '00'";
isValid = false;
}
}
// Vérifie si le champ doit être compris dans un certain intervalle
if (isValid && value && rule.range) {
const numberValue = parseFloat(value);
if (isNaN(numberValue) || numberValue < rule.range.min || numberValue > rule.range.max) {
errorMessage = rule.errorMsg;
isValid = false;
}
}
// Valide l'adresse e-mail seulement si le champ n'est pas vide
if (isValid && value && rule.email) {
if (!isValidEmail(value)) {
errorMessage = rule.errorMsg;
isValid = false;
}
}
// Vérifie la présence de caractères potentiellement problématiques
const specialCharsPattern = /[;">&<]/g;
let invalidChars = value.match(specialCharsPattern);
if (isValid && value && invalidChars) {
invalidChars = [...new Set(invalidChars)];
errorMessage = `Caractère(s) invalide(s) détecté(s) [ ${invalidChars.join(" ")} ].`;
isValid = false;
}
}
// Valide l'adresse e-mail seulement si le champ n'est pas vide
if (isValid && value && rule.email) {
if (!isValidEmail(value)) {
errorMessage = rule.errorMsg;
// Vérifie si le champ commence par le caractère spécifié
if (isValid && value && rule.startsWith && !value.startsWith(rule.startsWith)) {
errorMessage = `Le champ doit commencer par '${rule.startsWith}' majuscule.`;
isValid = false;
}
}
// Vérifie la présence de caractères potentiellement problématiques
const specialCharsPattern = /[;">&<]/g;
let invalidChars = value.match(specialCharsPattern);
// Vérifie si le champ date est correct
if (isValid && value && rule.dateFormat) {
const isDateFormatValid = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$|^00\/00\/0000$/.test(value);
if (isValid && value && invalidChars) {
invalidChars = [...new Set(invalidChars)];
errorMessage = `Caractère(s) invalide(s) détecté(s) [ ${invalidChars.join(" ")} ].`;
isValid = false;
}
// Vérifie si le champ commence par le caractère spécifié
if (isValid && value && rule.startsWith && !value.startsWith(rule.startsWith)) {
errorMessage = `Le champ doit commencer par '${rule.startsWith}' majuscule.`;
isValid = false;
}
// Vérifie si le champ date est correct
if (isValid && value && rule.dateFormat) {
const isDateFormatValid = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$|^00\/00\/0000$/.test(value);
if (!isDateFormatValid) {
errorMessage = "Le champ doit être au format JJ//MM/AAAA ou 00/00/0000.";
isValid = false;
if (!isDateFormatValid) {
errorMessage = "Le champ doit être au format JJ//MM/AAAA ou 00/00/0000.";
isValid = false;
}
}
}
// Vérifie si la saisie ne contient pas d'espace
if (isValid && value && rule.noSpace) {
const isNoSpaceValid = /^\S*$/.test(value);
// Vérifie si la saisie ne contient pas d'espace
if (isValid && value && rule.noSpace) {
const isNoSpaceValid = /^\S*$/.test(value);
if (!isNoSpaceValid) {
errorMessage = "La saisie ne doit pas contenir d'espaces.";
isValid = false;
if (!isNoSpaceValid) {
errorMessage = "La saisie ne doit pas contenir d'espaces.";
isValid = false;
}
}
}
// Vérifie si le champ date est correct au format JJ/MM
if (isValid && value && rule.dateFormatShort) {
const isDateFormatShortValid = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])$|^00\/00$/.test(value);
// Vérifie si le champ date est correct au format JJ/MM
if (isValid && value && rule.dateFormatShort) {
const isDateFormatShortValid = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])$|^00\/00$/.test(value);
if (!isDateFormatShortValid) {
errorMessage = "Le champ doit être au format JJ/MM ou 00/00.";
isValid = false;
if (!isDateFormatShortValid) {
errorMessage = "Le champ doit être au format JJ/MM ou 00/00.";
isValid = false;
}
}
}
// Vérifie la casse des caractères si spécifié
if (isValid && value && rule.case) {
if (rule.case === "upper" && value !== value.toUpperCase()) {
errorMessage = "Le champ doit être en majuscules.";
isValid = false;
} else if (rule.case === "lower" && value !== value.toLowerCase()) {
errorMessage = "Le champ doit être en minuscules.";
isValid = false;
// Vérifie la casse des caractères si spécifié
if (isValid && value && rule.case) {
if (rule.case === "upper" && value !== value.toUpperCase()) {
errorMessage = "Le champ doit être en majuscules.";
isValid = false;
} else if (rule.case === "lower" && value !== value.toLowerCase()) {
errorMessage = "Le champ doit être en minuscules.";
isValid = false;
}
}
}
// Vérifie si le champ doit être numérique
if (isValid && value && rule.digit) {
const isNumeric = /^\d+$/.test(value); // Vérifie si la chaîne est un nombre entier
// Vérifie si le champ doit être numérique
if (isValid && value && rule.digit) {
const isNumeric = /^\d+$/.test(value); // Vérifie si la chaîne est un nombre entier
if (!isNumeric) {
errorMessage = "Le champ doit contenir uniquement des chiffres.";
isValid = false;
if (!isNumeric) {
errorMessage = "Le champ doit contenir uniquement des chiffres.";
isValid = false;
}
}
}
// Affiche ou cache le message d'erreur selon la validité du champ
if (showErrors) {
errorElement.textContent = errorMessage;
errorElement.style.display = isValid ? 'none' : 'block';
}
// Affiche ou cache le message d'erreur selon la validité du champ
if (showErrors && errorElement) {
errorElement.textContent = errorMessage;
errorElement.style.display = isValid ? 'none' : 'block';
}
return isValid;
return isValid;
} catch (error) {
console.warn('Validation interrompue pour le champ:', fieldId, error);
return false;
}
}
function updateSubmitButtonState(formId) {
let allFieldsValid = true;
const form = document.querySelector(`#${formId}`);
const hasValidationRules = typeof validationRules !== 'undefined' && validationRules;
if (!form) {
console.error('Formulaire non trouvé:', formId);
return;
}
for (const fieldId in validationRules) {
const inputElement = form.querySelector(`#${fieldId}`);
if (!hasValidationRules) {
return;
}
if (inputElement && !validateField(fieldId, false)) {
allFieldsValid = false;
for (const fieldId in validationRules) {
const inputElement = document.getElementById(fieldId);
if (inputElement && form.contains(inputElement)) {
try {
if (!validateField(fieldId, false)) {
allFieldsValid = false;
}
} catch (error) {
console.warn('Erreur pendant la validation du champ:', fieldId, error);
allFieldsValid = false;
}
}
}
@ -169,4 +195,4 @@ function updateSubmitButtonState(formId) {
button.disabled = !allFieldsValid;
})
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -60,17 +60,17 @@
<!-- Feuille de script pour les donnée static JSON de saisie formulaire -->
<script src="/js/json/json-verif-form.js"></script>
<!-- Utilitaires de validation et blocage RC -->
<script src="/js/rc-validation-utils.js"></script>
<script src="/js/rc/rc-validation-utils.js"></script>
<!-- Feuille de script pour le bon fonctionnement du loader -->
<script src="/js/loader.js"></script>
<!-- Script pour la navigation AJAX -->
<script src="/js/navigation.js"></script>
<!-- Utilitaires de synchronisation RC -->
<script src="/js/rc-sync-utils.js"></script>
<script src="/js/rc/rc-sync-utils.js"></script>
<!-- Gestionnaire de données RC -->
<script src="/js/rc-data-manager.js"></script>
<script src="/js/rc/rc-data-manager.js"></script>
<!-- Orchestrateur de synchronisation RC -->
<script src="/js/rc-orchestrator.js"></script>
<script src="/js/rc/rc-orchestrator.js"></script>
</main>
</body>

View File

@ -1,5 +1,4 @@
<form id="projetForm">
<div id="rcProjetBlockingSummary" class="rc-blocking-summary"></div>
<!-- Historique de projet -->
<div class="row" id="historiqueDiv">
<div class="col s12">
@ -777,8 +776,7 @@
</div>
<div class="input-field">
<i class="material-icons prefix">trending_up</i>
<label class="rc-field-label" for="CA">Montant en euros</label>
<input id="CA" type="text" placeholder="Ex: 1234567">
<input id="CA" type="text" placeholder="100000">
<span class="helper-text">Chiffre d'affaires</span>
<span id="CA-error" class="helper-text red-text"></span>
</div>
@ -792,8 +790,7 @@
</div>
<div class="input-field">
<i class="material-icons prefix">euro_symbol</i>
<label class="rc-field-label" for="cotisationIrreductible">Montant en euros</label>
<input id="cotisationIrreductible" type="text" placeholder="Ex: 1200">
<input id="cotisationIrreductible" type="text" placeholder="200">
<span class="helper-text">Cotisation minimale irréductible</span>
<span id="cotisationIrreductible-error" class="helper-text red-text"></span>
</div>
@ -959,20 +956,20 @@
<tr>
<td>Frais de répertoire</td>
<td>
<input type="text" name="cotFraisHT" id="cotFraisHT" value="36.00" />
<input type="text" name="cotFraisHT" id="cotFraisHT" placeholder="Ex: 36.00" />
<span id="cotFraisHT-error" class="helper-text red-text"></span>
</td>
<td><input type="text" name="cotFraisTaux" id="cotFraisTaux" value="Sans taxe" disabled />
</td>
<td>
<input type="text" name="cotFraisTTC" id="cotFraisTTC" value="36.00" />
<input type="text" name="cotFraisTTC" id="cotFraisTTC" placeholder="Ex: 36.00" />
<span id="cotFraisTTC-error" class="helper-text red-text"></span>
</td>
</tr>
<tr id="primeRefRow">
<td>Prime HT de référence (tarif choisi)</td>
<td>
<input type="text" name="primeRefHT" id="primeRefHT" readonly />
<input type="text" name="primeRefHT" id="primeRefHT" disabled />
<span id="primeRefHT-error" class="helper-text red-text"></span>
</td>
<td>
@ -982,8 +979,20 @@
<input type="text" name="primeRefTTC" id="primeRefTTC" value="//" disabled />
</td>
</tr>
<tr id="tarifCommercialRow">
<td>Tarif commercial saisi (étape Tarif)</td>
<td>
<input type="text" name="tarifComSaisiHT" id="tarifComSaisiHT" disabled />
</td>
<td>
<input type="text" name="tarifComSaisiTaxe" id="tarifComSaisiTaxe" value="Tarif saisi" disabled />
</td>
<td>
<input type="text" name="tarifComSaisiTTC" id="tarifComSaisiTTC" value="//" disabled />
</td>
</tr>
<tr>
<td>Total</td>
<td>Total (retenu)</td>
<td>
<input type="text" name="cotTotalHT" id="cotTotalHT" />
<span id="cotTotalHT-error" class="helper-text red-text"></span>
@ -1016,6 +1025,7 @@
<h6 class="red-text text-darken-4">Veuillez enregistrer toutes modifications sur le formulaire :</h6>
</div>
</div>
<div id="rcProjetBlockingSummary" class="rc-blocking-summary"></div>
<button class="btn" type="submit" id="projetFormBtn">Enregistrer et poursuivre le parcours</button>
</div>
</form>

View File

@ -1248,7 +1248,7 @@
</div>
</div>
<div class="modal-footer">
<button id="comm-OK" class="waves-effect waves-light btn indigo darken-4">Valider</button>
<button id="comm-cancel" class="modal-close waves-effect waves-light btn red darken-1">Annuler</button>
<button id="comm-OK" type="button" class="waves-effect waves-light btn indigo darken-4">Valider</button>
<button id="comm-cancel" type="button" class="modal-close waves-effect waves-light btn red darken-1">Annuler</button>
</div>
</div>