document.addEventListener('DOMContentLoaded', function () { if (window.location.pathname !== '/advalo') return; const token = localStorage.getItem('jwtToken'); const pageSize = 20; const state = { historique: { page: 1, totalPages: 1 }, reporting: { page: 1, totalPages: 1 }, cumul: { page: 1, totalPages: 1 }, loaded: { historique: false, reporting: false, cumul: false }, confirmation: { demandId: '', summary: '' }, batchConfirmation: { batchId: '', summary: '' }, facturationRows: [], removedFacturationIds: new Set(), openHistoriqueId: null }; const requestControllers = {}; const loadingState = {}; const TRANSPORT_MODES = ['Terrestre', 'Aérien', 'Fluvial', 'Maritime', 'Postal']; const toast = (message, classes = 'red') => M.toast({ html: message, classes }); const authHeaders = () => { if (!token) throw new Error('Session expirée.'); return { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }; }; const authOnlyHeaders = () => { if (!token) throw new Error('Session expirée.'); return { Authorization: `Bearer ${token}` }; }; const isAbortError = (error) => error && (error.name === 'AbortError' || String(error.message || '').toLowerCase().includes('aborted')); const api = async (url, options = {}) => { const res = await fetch(url, { ...options, headers: options.headers || authHeaders() }); const contentType = res.headers.get('content-type') || ''; if (contentType.includes('text/csv')) { if (!res.ok) throw new Error('Erreur export CSV'); return res.text(); } const data = await res.json().catch(() => ({})); if (!res.ok || data.valid === false) throw new Error(data.message || 'Erreur serveur'); return data; }; const apiLatest = async (key, url, options = {}) => { if (requestControllers[key]) requestControllers[key].abort(); const controller = new AbortController(); requestControllers[key] = controller; try { return await api(url, { ...options, signal: controller.signal }); } finally { if (requestControllers[key] === controller) delete requestControllers[key]; } }; const abortDataRequests = () => { ['historique', 'reporting', 'cumul', 'facturation'].forEach((k) => { if (requestControllers[k]) requestControllers[k].abort(); }); }; const parseAmount = (value) => { const n = Number(String(value || '').replace(',', '.').replace(/\s/g, '')); return Number.isFinite(n) ? n : 0; }; const fmt = (n) => Number(n || 0).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const sourceLabel = (source) => (source === 'deleguee' ? 'Grille déléguée' : 'Hors grille'); const setIndicator = (id, page, totalPages, totalRows) => { const el = document.getElementById(id); if (!el) return; const pages = Math.max(1, Number(totalPages || 1)); const current = Math.min(Math.max(1, Number(page || 1)), pages); el.textContent = `Page ${current} / ${pages} - ${Number(totalRows || 0)} lignes`; }; const setLoading = (section, isLoading) => { const el = document.getElementById(`advalo-loading-${section}`); if (!el) return; if (isLoading) { const marker = Symbol(section); loadingState[section] = { marker, startedAt: Date.now() }; el.style.display = 'block'; return; } const current = loadingState[section]; if (!current) { el.style.display = 'none'; return; } const elapsed = Date.now() - current.startedAt; setTimeout(() => { const latest = loadingState[section]; if (latest && latest.marker === current.marker) { el.style.display = 'none'; delete loadingState[section]; } }, Math.max(0, 280 - elapsed)); }; const syncTextFields = (scope = document) => { if (window.M && typeof window.M.updateTextFields === 'function') window.M.updateTextFields(); const root = scope && scope.querySelectorAll ? scope : document; const fields = root.querySelectorAll('.advalo-panel .input-field input:not([type="hidden"]), .advalo-panel .input-field textarea'); fields.forEach((field) => { if (!field.id) return; const label = document.querySelector(`label[for="${field.id}"]`); if (!label) return; const shouldFloat = String(field.value || '').trim().length > 0 || document.activeElement === field; label.classList.toggle('active', shouldFloat); }); }; const refreshTextFields = (scope = document) => { syncTextFields(scope); requestAnimationFrame(() => syncTextFields(scope)); setTimeout(() => syncTextFields(scope), 60); }; const initSelects = (scope = document) => { const selects = scope.querySelectorAll('select'); selects.forEach((select) => { const instance = M.FormSelect.getInstance(select); if (instance) instance.destroy(); M.FormSelect.init(select); }); }; const formatDateInput = (raw) => { const digits = String(raw || '').replace(/\D/g, '').slice(0, 8); if (digits.length <= 2) return digits; if (digits.length <= 4) return `${digits.slice(0, 2)}/${digits.slice(2)}`; return `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4)}`; }; const initDateFields = () => { document.querySelectorAll('input.advalo-date').forEach((input) => { if (input.dataset.dateMaskBound === '1') return; input.addEventListener('input', () => { const formatted = formatDateInput(input.value); if (formatted !== input.value) input.value = formatted; refreshTextFields(input.closest('.advalo-panel') || document); }); input.dataset.dateMaskBound = '1'; }); }; const downloadDocx = async (url, options = {}, fallbackName = 'document_advalo.docx') => { const res = await fetch(url, { ...options, headers: { ...authOnlyHeaders(), ...(options.headers || {}) } }); if (!res.ok) { const payload = await res.json().catch(() => ({})); throw new Error(payload.message || 'Erreur génération document'); } const disposition = res.headers.get('content-disposition') || ''; const filenameMatch = disposition.match(/filename=\"?([^\";]+)\"?/i); const filename = filenameMatch ? filenameMatch[1] : fallbackName; const blob = await res.blob(); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href); }; const ensureContract16 = (value, { required = false } = {}) => { const digits = String(value || '').replace(/\D/g, ''); if (!digits) return required ? null : ''; if (digits.length !== 16) return null; return digits; }; const validateContractField = (id, required = false) => { const input = document.getElementById(id); if (!input) return true; const v = ensureContract16(input.value, { required }); if (v === null) { input.classList.add('invalid'); return false; } if (v) input.value = v; input.classList.remove('invalid'); return true; }; const requireFields = (ids) => { let ok = true; ids.forEach((id) => { const input = document.getElementById(id); if (!input) return; if (!String(input.value || '').trim()) { input.classList.add('invalid'); ok = false; } else { input.classList.remove('invalid'); } }); return ok; }; const applyNumericGuards = () => { const integerOnlyIds = ['p-numContrat', 'p-numClient', 'p-numAgent', 'h-numClient', 'h-numContrat', 'c-numContrat', 'c-numClient', 'f-numContrat', 'r-numClient', 'r-numContrat', 'r-actorMatricule']; integerOnlyIds.forEach((id) => { const input = document.getElementById(id); if (!input) return; input.addEventListener('input', () => { const cleaned = String(input.value || '').replace(/\D/g, ''); if (cleaned !== input.value) input.value = cleaned; }); }); const decimalIds = ['p-capital', 'p-taux', 'p-primeMin', 'p-coutActe', 'p-cotisationHT', 'p-cotisationTTC']; decimalIds.forEach((id) => { const input = document.getElementById(id); if (!input) return; input.addEventListener('input', () => { let cleaned = String(input.value || '').replace(/[^0-9.,]/g, ''); const comma = cleaned.indexOf(','); const dot = cleaned.indexOf('.'); const splitAt = comma >= 0 ? comma : dot; if (splitAt >= 0) cleaned = cleaned.slice(0, splitAt + 1) + cleaned.slice(splitAt + 1).replace(/[.,]/g, ''); if (cleaned !== input.value) input.value = cleaned; }); }); }; const bindFloatingLabels = () => { const inputs = document.querySelectorAll('#advalo-tab-ponctuel input, #advalo-tab-facturation input, #advalo-tab-historique input, #advalo-tab-cumul input, #advalo-tab-reporting input'); inputs.forEach((input) => { if (input.dataset.floatBound === '1') return; const resync = () => requestAnimationFrame(() => refreshTextFields(input.closest('.advalo-panel') || document)); input.addEventListener('focus', resync); input.addEventListener('input', resync); input.addEventListener('change', resync); input.addEventListener('blur', resync); input.dataset.floatBound = '1'; }); }; const navLinks = document.querySelectorAll('#advaloNavSelect a[data-target]'); const panels = document.querySelectorAll('.advalo-panel'); const confirmModal = M.Modal.init(document.getElementById('advalo-confirm-modal'), { dismissible: true }); const batchModal = M.Modal.init(document.getElementById('advalo-batch-modal'), { dismissible: true }); const getSelectedModes = () => [...document.querySelectorAll('.p-mode-check:checked')].map((el) => el.value).filter((v) => TRANSPORT_MODES.includes(v)); const syncModes = () => { const modes = getSelectedModes(); document.getElementById('p-mode').value = modes.join(', '); }; const recalcPonctuelPricing = () => { const capital = parseAmount(document.getElementById('p-capital').value); const taux = parseAmount(document.getElementById('p-taux').value); const primeMin = parseAmount(document.getElementById('p-primeMin').value); const coutActe = parseAmount(document.getElementById('p-coutActe').value); if (!capital && !taux && !primeMin) return; const cotisationHT = Math.max((capital * taux) / 100, primeMin); const cotisationTTC = cotisationHT + coutActe; document.getElementById('p-cotisationHT').value = cotisationHT.toFixed(2); document.getElementById('p-cotisationTTC').value = cotisationTTC.toFixed(2); document.getElementById('p-tarif').value = cotisationTTC.toFixed(2); refreshTextFields(); }; const fillContractInfo = (info) => { document.getElementById('p-numContrat').value = info.numContrat || document.getElementById('p-numContrat').value; document.getElementById('p-numClient').value = info.numClient || ''; document.getElementById('p-nomClient').value = info.nomClient || ''; document.getElementById('p-numAgent').value = info.numAgent || ''; document.getElementById('p-nomAgent').value = info.nomAgent || ''; refreshTextFields(); }; const lookupContract = async () => { if (!validateContractField('p-numContrat', true)) { toast('N° contrat invalide: 16 chiffres requis.'); return; } const contract = document.getElementById('p-numContrat').value.trim(); const params = new URLSearchParams({ numContrat: contract }); const data = await api(`/advalo/lookup-contract?${params.toString()}`, { method: 'GET' }); fillContractInfo(data.info || {}); if (data.info?.source && data.info.source !== 'none') toast(`Informations chargées (${data.info.source}).`, 'green'); else toast('Aucune donnée trouvée pour ce contrat. Saisie manuelle possible.', 'orange'); }; const renderHistoriqueDetail = (typedId, row, details) => { const detailId = `h-detail-${typedId.replace(/[^a-zA-Z0-9_-]/g, '-')}`; return `
Marchandise: ${details.marchandise || '-'}
Modes: ${details.mode || '-'}
Départ: ${details.depart || '-'}
Arrivée: ${details.arrivee || '-'}
Valeur assurée: ${details.valeurAssuree || '-'}
Taux: ${details.taux || '-'}
Prime mini: ${details.primeMinimum || '-'}
Cotisation HT: ${details.cotisationHT || '-'}
Coût acte: ${details.coutActe || '-'}
Cotisation TTC: ${details.cotisationTTC || '-'}
Acteur: ${(details.actorPrenom || '')} ${(details.actorNom || '')} ${details.actorMatricule ? `(${details.actorMatricule})` : ''}
${row.source === 'hors_grille' && String(row.statutFacturation || '').toLowerCase().includes('non') ? `` : ''}
`; }; const loadHistorique = async () => { setLoading('historique', true); try { const statut = document.getElementById('h-statut').value || 'all'; const params = new URLSearchParams({ numClient: document.getElementById('h-numClient').value || '', numContrat: document.getElementById('h-numContrat').value || '', dateDebut: document.getElementById('h-dateDebut').value || '', dateFin: document.getElementById('h-dateFin').value || '', sourceType: document.getElementById('h-sourceType').value || 'all', statutFacturation: statut === 'all' ? '' : statut, page: String(state.historique.page), pageSize: String(pageSize), sort: 'dateDebutIso', order: 'desc' }); const data = await apiLatest('historique', `/advalo/historique?${params.toString()}`, { method: 'GET' }); const body = document.getElementById('historique-body'); body.innerHTML = ''; (data.rows || []).forEach((row) => { const typedId = `${row.source === 'deleguee' ? 'g' : 'd'}:${row.id}`; body.insertAdjacentHTML('beforeend', ` ${sourceLabel(row.source)} ${row.numDemande || ''} ${row.numClient || ''} ${row.numContrat || ''} ${row.dateDebut || ''} ${row.dateFin || ''} ${row.tarif || ''} ${row.statutFacturation || ''} `); }); document.querySelectorAll('.advalo-h-expand').forEach((btn) => { btn.addEventListener('click', async () => { const typedId = btn.dataset.id; const existing = document.getElementById(`h-detail-${typedId.replace(/[^a-zA-Z0-9_-]/g, '-')}`); if (existing) { existing.remove(); state.openHistoriqueId = null; btn.textContent = '▸'; return; } if (state.openHistoriqueId) { const old = document.getElementById(`h-detail-${state.openHistoriqueId.replace(/[^a-zA-Z0-9_-]/g, '-')}`); if (old) old.remove(); const oldBtn = document.querySelector(`.advalo-h-expand[data-id="${state.openHistoriqueId}"]`); if (oldBtn) oldBtn.textContent = '▸'; } try { const detailData = await api(`/advalo/historique/${encodeURIComponent(typedId)}`, { method: 'GET' }); const row = (data.rows || []).find((r) => `${r.source === 'deleguee' ? 'g' : 'd'}:${r.id}` === typedId); const details = detailData.row?.details || row?.details || {}; const currentRow = btn.closest('tr'); currentRow.insertAdjacentHTML('afterend', renderHistoriqueDetail(typedId, row || {}, details)); state.openHistoriqueId = typedId; btn.textContent = '▾'; document.querySelectorAll('.advalo-h-delete').forEach((deleteBtn) => { deleteBtn.addEventListener('click', async () => { if (!confirm('Supprimer cette demande hors grille ?')) return; try { await api(`/advalo/demande/${encodeURIComponent(deleteBtn.dataset.id)}`, { method: 'DELETE' }); toast('Demande supprimée.', 'green'); await loadHistorique(); } catch (error) { toast(error.message); } }); }); } catch (error) { toast(error.message); } }); }); document.querySelectorAll('.advalo-doc-avenant').forEach((button) => { button.addEventListener('click', async () => { try { await downloadDocx(`/advalo/demande/${encodeURIComponent(button.dataset.id)}/avenant`, { method: 'POST' }, 'Avenant_Advalo.docx'); toast('Avenant généré.', 'green'); } catch (error) { toast(error.message); } }); }); document.querySelectorAll('.advalo-doc-attestation').forEach((button) => { button.addEventListener('click', async () => { try { await downloadDocx(`/advalo/demande/${encodeURIComponent(button.dataset.id)}/attestation`, { method: 'POST' }, 'Attestation_Advalo.docx'); toast('Attestation générée.', 'green'); } catch (error) { toast(error.message); } }); }); state.historique.totalPages = Number(data.meta?.totalPages || 1); state.historique.page = Number(data.meta?.page || 1); setIndicator('h-page-indicator', state.historique.page, state.historique.totalPages, data.meta?.totalRows || 0); refreshTextFields(); } catch (error) { if (isAbortError(error)) return; throw error; } finally { setLoading('historique', false); } }; const updateFacturationInfo = () => { const selected = [...document.querySelectorAll('.advalo-fact-check')].filter((input) => input.checked).length; const total = [...document.querySelectorAll('.advalo-fact-check')].length; document.getElementById('f-selection-info').textContent = `${selected} / ${total} ligne(s) sélectionnée(s)`; }; const renderFacturationRows = () => { const body = document.getElementById('facturation-body'); body.innerHTML = ''; state.facturationRows.forEach((row) => { const typedId = `${row.source === 'deleguee' ? 'g' : 'd'}:${row.id}`; body.insertAdjacentHTML('beforeend', ` ${sourceLabel(row.source)} ${row.numDemande || ''} ${row.numClient || ''} ${row.numContrat || ''} ${row.dateDebut || ''} ${row.tarif || ''} ${row.statutFacturation || ''} `); }); document.querySelectorAll('.advalo-fact-check').forEach((input) => input.addEventListener('change', updateFacturationInfo)); updateFacturationInfo(); const first = state.facturationRows[0]; document.getElementById('facturation-client-agent-row').style.display = first ? 'block' : 'none'; document.getElementById('f-client-recap').textContent = first ? `${first.nomClient || '-'} (${first.numClient || '-'})` : '-'; document.getElementById('f-agent-recap').textContent = first ? (first.souscripteur || '-') : '-'; }; const loadFacturationCandidates = async () => { if (!validateContractField('f-numContrat', false)) { toast('N° contrat invalide: 16 chiffres requis.'); return; } setLoading('facturation', true); try { const mode = document.getElementById('f-sourceMode').value || 'hors_grille'; const params = new URLSearchParams({ numContrat: document.getElementById('f-numContrat').value || '', dateDebut: document.getElementById('f-dateDebut').value || '', dateFin: document.getElementById('f-dateFin').value || '', facture: 'false', nonFacture: 'true', deleguee: mode === 'mixte' ? 'true' : 'false', nonDeleguee: 'true', sourceType: mode === 'mixte' ? 'all' : 'hors_grille', page: '1', pageSize: '200', sort: 'dateDebutIso', order: 'desc' }); const data = await apiLatest('facturation', `/advalo/historique?${params.toString()}`, { method: 'GET' }); state.facturationRows = (data.rows || []); state.removedFacturationIds = new Set(); renderFacturationRows(); if ((data.meta?.totalRows || 0) === 0) toast('Aucune ligne non facturée sur la période/contrat.', 'orange'); else toast(`${data.meta?.totalRows || 0} ligne(s) facturable(s).`, 'green'); } catch (error) { if (isAbortError(error)) return; throw error; } finally { setLoading('facturation', false); } }; const loadCumul = async () => { setLoading('cumul', true); try { if (!validateContractField('c-numContrat', false)) { toast('N° contrat invalide: 16 chiffres requis.'); return; } const params = new URLSearchParams({ numContrat: document.getElementById('c-numContrat').value || '', numClient: document.getElementById('c-numClient').value || '', dateDebut: document.getElementById('c-dateDebut').value || '', dateFin: document.getElementById('c-dateFin').value || '', page: String(state.cumul.page), pageSize: String(pageSize), sort: 'totalNonFacture', order: 'desc' }); const data = await apiLatest('cumul', `/advalo/cumul?${params.toString()}`, { method: 'GET' }); document.getElementById('k-total-advalo').textContent = fmt(data.totalAdvalo); document.getElementById('k-total-facture').textContent = fmt(data.totalFacture); document.getElementById('k-total-nonfacture').textContent = fmt(data.totalNonFacture); document.getElementById('k-total-lignes').textContent = String(data.totalLignes || 0); const body = document.getElementById('cumul-body'); body.innerHTML = ''; (data.rows || []).forEach((row) => { body.insertAdjacentHTML('beforeend', ` ${row.numContrat || ''} ${row.nomClient || ''} ${row.region || ''} ${row.dpt || ''} ${row.souscripteur || ''} ${fmt(row.totalAdvalo)} ${fmt(row.totalFacture)} ${fmt(row.totalNonFacture)} `); }); state.cumul.totalPages = Number(data.meta?.totalPages || 1); state.cumul.page = Number(data.meta?.page || 1); setIndicator('c-page-indicator', state.cumul.page, state.cumul.totalPages, data.meta?.totalRows || 0); document.querySelectorAll('.advalo-cumul-hist').forEach((btn) => { btn.addEventListener('click', async () => { document.getElementById('h-numContrat').value = btn.dataset.contrat || ''; state.historique.page = 1; state.loaded.historique = true; await activatePanel('advalo-tab-historique'); await loadHistorique(); }); }); document.querySelectorAll('.advalo-cumul-fact').forEach((btn) => { btn.addEventListener('click', async () => { document.getElementById('f-numContrat').value = btn.dataset.contrat || ''; await activatePanel('advalo-tab-facturation'); refreshTextFields(); }); }); refreshTextFields(); } catch (error) { if (isAbortError(error)) return; throw error; } finally { setLoading('cumul', false); } }; const loadReporting = async () => { setLoading('reporting', true); try { if (!validateContractField('r-numContrat', false)) { toast('N° contrat invalide: 16 chiffres requis.'); return; } const statut = document.getElementById('r-statut').value || 'all'; const params = new URLSearchParams({ numClient: document.getElementById('r-numClient').value || '', numContrat: document.getElementById('r-numContrat').value || '', souscripteur: document.getElementById('r-souscripteur').value || '', region: document.getElementById('r-region').value || '', dateDebut: document.getElementById('r-dateDebut').value || '', dateFin: document.getElementById('r-dateFin').value || '', actorMatricule: document.getElementById('r-actorMatricule').value || '', actorNom: document.getElementById('r-actorNom').value || '', actionType: document.getElementById('r-actionType').value || '', sourceType: document.getElementById('r-sourceType').value || 'all', statutFacturation: statut === 'all' ? '' : statut, sort: document.getElementById('r-sort').value || 'totalAdvalo', order: document.getElementById('r-order').value || 'desc', page: String(state.reporting.page), pageSize: String(pageSize) }); const data = await apiLatest('reporting', `/advalo/reporting?${params.toString()}`, { method: 'GET' }); document.getElementById('r-total-advalo').textContent = fmt(data.totaux?.totalAdvalo || 0); document.getElementById('r-total-facture').textContent = fmt(data.totaux?.totalFacture || 0); document.getElementById('r-total-nonfacture').textContent = fmt(data.totaux?.totalNonFacture || 0); document.getElementById('r-total-lignes').textContent = String(data.totaux?.totalLignes || 0); const body = document.getElementById('reporting-body'); body.innerHTML = ''; (data.rows || []).forEach((row) => { body.insertAdjacentHTML('beforeend', ` ${row.numContrat || ''} ${row.nomClient || ''} ${row.region || ''} ${row.souscripteur || ''} ${fmt(row.totalAdvalo)} ${fmt(row.totalFacture)} ${fmt(row.totalNonFacture)} ${row.totalLignes || 0} `); }); const actorBody = document.getElementById('reporting-actors-body'); actorBody.innerHTML = ''; (data.actorStats || []).forEach((a) => { actorBody.insertAdjacentHTML('beforeend', ` ${a.actorMatricule || ''} ${a.actorName || ''} ${a.actionType || ''} ${a.actionsCount || 0} `); }); state.reporting.totalPages = Number(data.meta?.totalPages || 1); state.reporting.page = Number(data.meta?.page || 1); setIndicator('r-page-indicator', state.reporting.page, state.reporting.totalPages, data.meta?.totalRows || 0); refreshTextFields(); } catch (error) { if (isAbortError(error)) return; throw error; } finally { setLoading('reporting', false); } }; const activatePanel = async (targetId) => { abortDataRequests(); panels.forEach((panel) => { panel.style.display = panel.id === targetId ? 'block' : 'none'; }); navLinks.forEach((link) => { const li = link.parentElement; if (!li) return; if (link.dataset.target === targetId) li.classList.add('active'); else li.classList.remove('active'); }); initDateFields(); refreshTextFields(); if (targetId === 'advalo-tab-historique' && !state.loaded.historique) { state.historique.page = 1; await loadHistorique(); state.loaded.historique = true; } if (targetId === 'advalo-tab-reporting' && !state.loaded.reporting) { state.reporting.page = 1; await loadReporting(); state.loaded.reporting = true; } if (targetId === 'advalo-tab-cumul' && !state.loaded.cumul) { state.cumul.page = 1; await loadCumul(); state.loaded.cumul = true; } }; navLinks.forEach((link) => { link.addEventListener('click', async (event) => { event.preventDefault(); try { await activatePanel(link.dataset.target); } catch (error) { toast(error.message); } }); }); document.querySelectorAll('.p-mode-check').forEach((input) => input.addEventListener('change', () => { syncModes(); refreshTextFields(); })); ['p-capital', 'p-taux', 'p-primeMin', 'p-coutActe'].forEach((id) => { const input = document.getElementById(id); if (input) input.addEventListener('input', recalcPonctuelPricing); }); document.getElementById('p-numContrat').addEventListener('blur', async () => { const value = String(document.getElementById('p-numContrat').value || '').trim(); if (value.length === 16) { try { await lookupContract(); } catch (error) { toast(error.message); } } }); document.getElementById('p-numContrat').addEventListener('keydown', async (e) => { if (e.key !== 'Enter') return; e.preventDefault(); try { await lookupContract(); } catch (error) { toast(error.message); } }); document.getElementById('btn-load-contract').addEventListener('click', async (event) => { event.preventDefault(); try { await lookupContract(); } catch (error) { toast(error.message); } }); document.getElementById('advalo-ponctuel-form').addEventListener('submit', async (event) => { event.preventDefault(); const errorSlot = document.getElementById('p-form-error'); if (errorSlot) errorSlot.textContent = ''; syncModes(); const required = ['p-numContrat', 'p-marchandise', 'p-depart', 'p-arrivee', 'p-dateDebut', 'p-dateFin', 'p-capital', 'p-taux', 'p-primeMin', 'p-cotisationHT', 'p-cotisationTTC']; if (!validateContractField('p-numContrat', true) || !requireFields(required)) { if (errorSlot) errorSlot.textContent = 'Complète les champs obligatoires (contrat 16 chiffres).'; toast('Complète les champs obligatoires (contrat 16 chiffres).'); return; } if (!document.getElementById('p-mode').value.trim()) { if (errorSlot) errorSlot.textContent = 'Sélectionne au moins un mode de transport.'; toast('Sélectionne au moins un mode de transport.'); return; } try { const payload = { typeFacturation: document.querySelector('input[name="p-typeFacturation"]:checked')?.value || 'ponctuel', numClient: document.getElementById('p-numClient').value.trim(), nomClient: document.getElementById('p-nomClient').value.trim(), numContrat: document.getElementById('p-numContrat').value.trim(), marchandise: document.getElementById('p-marchandise').value.trim(), mode: document.getElementById('p-mode').value.trim(), capital: document.getElementById('p-capital').value.trim(), depart: document.getElementById('p-depart').value.trim(), arrivee: document.getElementById('p-arrivee').value.trim(), dateDebut: document.getElementById('p-dateDebut').value.trim(), dateFin: document.getElementById('p-dateFin').value.trim(), taux: document.getElementById('p-taux').value.trim(), primeMinimum: document.getElementById('p-primeMin').value.trim(), coutActe: document.getElementById('p-coutActe').value.trim(), cotisationHT: document.getElementById('p-cotisationHT').value.trim(), cotisationTTC: document.getElementById('p-cotisationTTC').value.trim(), tarif: document.getElementById('p-cotisationTTC').value.trim(), facturer: document.getElementById('p-facturer').checked }; const data = await api('/advalo/ponctuel', { method: 'POST', body: JSON.stringify(payload) }); toast(`Demande créée: ${data.row.numDemande || data.row.id}`, 'green'); state.confirmation.demandId = `d:${data.row.id}`; state.confirmation.summary = `Demande ${data.row.numDemande || data.row.id} - Contrat ${data.row.numContrat || ''}`; const summary = document.getElementById('advalo-confirm-summary'); if (summary) summary.textContent = state.confirmation.summary; confirmModal.open(); event.target.reset(); document.querySelectorAll('.p-mode-check').forEach((i) => { i.checked = false; }); document.getElementById('p-coutActe').value = '36'; document.getElementById('p-taux').value = '0.3'; document.getElementById('p-primeMin').value = '15'; document.querySelector('input[name="p-typeFacturation"][value="ponctuel"]').checked = true; recalcPonctuelPricing(); refreshTextFields(); state.loaded.historique = false; state.loaded.reporting = false; state.loaded.cumul = false; } catch (error) { if (errorSlot) errorSlot.textContent = error.message; toast(error.message); } }); document.getElementById('btn-f-load').addEventListener('click', async (event) => { event.preventDefault(); try { await loadFacturationCandidates(); } catch (error) { toast(error.message); } }); document.getElementById('btn-f-remove').addEventListener('click', (event) => { event.preventDefault(); const selected = [...document.querySelectorAll('.advalo-fact-check:checked')].map((i) => i.value); if (!selected.length) { toast('Sélectionne au moins une ligne à retirer.'); return; } selected.forEach((id) => state.removedFacturationIds.add(id)); state.facturationRows = state.facturationRows.filter((row) => { const typedId = `${row.source === 'deleguee' ? 'g' : 'd'}:${row.id}`; return !state.removedFacturationIds.has(typedId); }); renderFacturationRows(); toast('Lignes retirées de la liste.', 'green'); }); document.getElementById('btn-facturer').addEventListener('click', async (event) => { event.preventDefault(); const ids = [...document.querySelectorAll('.advalo-fact-check:checked')].map((i) => i.value).filter(Boolean); if (!ids.length) { toast('Sélectionne au moins une ligne.'); return; } try { const sourceMode = document.getElementById('f-sourceMode').value || 'hors_grille'; const includeTransportDetails = document.querySelector('input[name="f-include-details"]:checked')?.value !== 'false'; const data = await api('/advalo/facturation/batch', { method: 'POST', body: JSON.stringify({ demandeIds: ids, sourceMode, includeTransportDetails, removedDemandeIds: [...state.removedFacturationIds] }) }); if (data.idempotent) toast(`Lot déjà traité: ${data.batch.id}`, 'blue'); else toast(`Facturation OK: lot ${data.batch.id}`, 'green'); state.batchConfirmation.batchId = data.batch.id; state.batchConfirmation.summary = `Lot ${data.batch.id} - Contrat ${data.batch.numContrat || ''} - ${data.batch.dateDebut || ''} / ${data.batch.dateFin || ''}`; const batchSummary = document.getElementById('advalo-batch-summary'); if (batchSummary) batchSummary.textContent = state.batchConfirmation.summary; batchModal.open(); await loadFacturationCandidates(); state.loaded.reporting = false; state.loaded.cumul = false; state.loaded.historique = false; } catch (error) { toast(error.message); } }); document.getElementById('btn-h-search').addEventListener('click', async () => { if (!validateContractField('h-numContrat', false)) { toast('N° contrat invalide: 16 chiffres requis.'); return; } state.historique.page = 1; try { await loadHistorique(); toast('Historique chargé.', 'green'); } catch (error) { toast(error.message); } }); document.getElementById('btn-h-export').addEventListener('click', async () => { if (!validateContractField('h-numContrat', false)) { toast('N° contrat invalide: 16 chiffres requis.'); return; } try { const statut = document.getElementById('h-statut').value || 'all'; const params = new URLSearchParams({ numClient: document.getElementById('h-numClient').value || '', numContrat: document.getElementById('h-numContrat').value || '', dateDebut: document.getElementById('h-dateDebut').value || '', dateFin: document.getElementById('h-dateFin').value || '', sourceType: document.getElementById('h-sourceType').value || 'all', statutFacturation: statut === 'all' ? '' : statut, sort: 'dateDebutIso', order: 'desc' }); const csv = await api(`/advalo/export?${params.toString()}`, { method: 'GET', headers: { Authorization: `Bearer ${token}` } }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'advalo_export.csv'; link.click(); URL.revokeObjectURL(url); toast('Export CSV généré.', 'green'); } catch (error) { toast(error.message); } }); document.getElementById('btn-h-prev').addEventListener('click', async () => { if (state.historique.page <= 1) return; state.historique.page -= 1; try { await loadHistorique(); } catch (error) { toast(error.message); } }); document.getElementById('btn-h-next').addEventListener('click', async () => { if (state.historique.page >= state.historique.totalPages) return; state.historique.page += 1; try { await loadHistorique(); } catch (error) { toast(error.message); } }); document.getElementById('btn-cumul').addEventListener('click', async (event) => { event.preventDefault(); state.cumul.page = 1; try { await loadCumul(); toast('Cumul calculé.', 'green'); } catch (error) { toast(error.message); } }); document.getElementById('btn-c-prev').addEventListener('click', async () => { if (state.cumul.page <= 1) return; state.cumul.page -= 1; try { await loadCumul(); } catch (error) { toast(error.message); } }); document.getElementById('btn-c-next').addEventListener('click', async () => { if (state.cumul.page >= state.cumul.totalPages) return; state.cumul.page += 1; try { await loadCumul(); } catch (error) { toast(error.message); } }); document.getElementById('btn-reporting').addEventListener('click', async (event) => { event.preventDefault(); state.reporting.page = 1; try { await loadReporting(); toast('Reporting chargé.', 'green'); } catch (error) { toast(error.message); } }); document.getElementById('btn-r-prev').addEventListener('click', async () => { if (state.reporting.page <= 1) return; state.reporting.page -= 1; try { await loadReporting(); } catch (error) { toast(error.message); } }); document.getElementById('btn-r-next').addEventListener('click', async () => { if (state.reporting.page >= state.reporting.totalPages) return; state.reporting.page += 1; try { await loadReporting(); } catch (error) { toast(error.message); } }); document.getElementById('advalo-confirm-avenant').addEventListener('click', async () => { if (!state.confirmation.demandId) return; try { await downloadDocx(`/advalo/demande/${encodeURIComponent(state.confirmation.demandId)}/avenant`, { method: 'POST' }, 'Avenant_Advalo.docx'); toast('Avenant généré.', 'green'); } catch (error) { toast(error.message); } }); document.getElementById('advalo-confirm-attestation').addEventListener('click', async () => { if (!state.confirmation.demandId) return; try { await downloadDocx(`/advalo/demande/${encodeURIComponent(state.confirmation.demandId)}/attestation`, { method: 'POST' }, 'Attestation_Advalo.docx'); toast('Attestation générée.', 'green'); } catch (error) { toast(error.message); } }); document.getElementById('advalo-batch-avenant').addEventListener('click', async () => { if (!state.batchConfirmation.batchId) return; try { await downloadDocx(`/advalo/facturation/batch/${encodeURIComponent(state.batchConfirmation.batchId)}/avenant`, { method: 'POST' }, 'Avenant_Periodique_Advalo.docx'); toast('Avenant périodique généré.', 'green'); } catch (error) { toast(error.message); } }); applyNumericGuards(); bindFloatingLabels(); initDateFields(); initSelects(document); syncModes(); recalcPonctuelPricing(); refreshTextFields(); activatePanel('advalo-tab-accueil').catch((error) => toast(error.message)); });