//! jdevis.js let chkPageLoad = true; // indique que la page vient d'être chargée pour la première fois let idDevis = 0; let fkUser = 0; let fkRole = 0; let devIp = '0'; let fkUserDevis = 0; let fkStatutDevis = 0; let chkValidat = 0; let oldColorLn; let oldIdLn; let chkChange = 0; let idMarche = ''; let idNewMarche = ''; // dans le cas on l'utilisateur change de marché sur un devis déjà créé let chkClientsSecteur; let chkShowDevisArchives = false; // indique si on affiche les devis archivés ou non let chkCreateClient = false; // On charge les produits du marché du devis en cours dans un tableau pour ne pas avoir à les recharger à chaque fois let dataProduitsMarche = []; //! Pour ne charger les clients du secteur ou de toute la France qu'en cas de changement de la valeur du chkbox let oldChkClientsSecteur = 2; let clients = []; let chkPrixNets = false; // le marché du devis en cours est-il en prix nets ou pas let remiseMarcheDeBase = 0; // la remise de base du marché let chkSaisieRemise = false; let chkRemisesMarche = true; // indique si toutes les lignes du devis appliquent la remise du marché ou pas let aRemisesMarches = []; let devisTotalHT = 0; let devisTotalRemHT = 0; let devisTotalMarge = 0; let chkRegleSeuilsMarge = false; // indique si le marché sélectionné prend en compte les seuils de marge fixés dans les familles de produits let seuilMargeRR = 30; // le seuil de marge du RR sur ce devis, par défaut à 30 % let seuilMargeDV = 20; // le seuil de marge du DV sur ce devis, par défaut à 20 % let intervalRefresh; let nbCommentChat = 0; let draggedElement = null; // l'élément qui est en train d'être déplacé (la ligne du produit du devis lors d'un drag and drop) window.addEventListener('DOMContentLoaded', (event) => { console.log('#'); // Initialisation des éléments utilisés let elCelDevis = document.getElementsByClassName('celDevis'); let elCelArchives = document.getElementsByClassName('celArchives'); let elBtnDupDevis = document.getElementsByClassName('btnDupDevis'); let elBtnSupprDevis = document.getElementsByClassName('btnSupprDevis'); let elBtnExpExcelDevis = document.getElementsByClassName('btnExpExcelDevis'); let elBtnValDevis = document.getElementsByClassName('btnValDevis'); let elBtnPdfDevis = document.getElementsByClassName('btnPdfDevis'); let elBtnValidationRR = document.getElementById('btnValidationRR'); let elBtnRefusRR = document.getElementById('btnRefusRR'); let elBtnCloseRR = document.getElementById('btnCloseRR'); let elBtnClosePDF = document.getElementById('btnClosePDF'); let elBtnDevisArchives = document.getElementById('btnDevisArchives'); let elBtnCreateDevis = document.getElementById('btnCreateDevis'); let elBtnCreateClient = document.getElementById('btnCreateClient'); let elBtnCancelCreateClient = document.getElementById('btnCancelCreateClient'); let elBtnSaveCreateClient = document.getElementById('btnSaveCreateClient'); let elBtnSpeciaux = document.getElementById('btnSpeciaux'); let elBtnCancelSpeciaux = document.getElementById('btnCancelSpeciaux'); let elBtnSaveSpeciaux = document.getElementById('btnSaveSpeciaux'); let elBtnSaveEnTete = document.getElementById('btnSaveEnTete'); let elBtnSaveSelProduits = document.getElementById('btnSaveSelProduits'); let elBtnSaveDevis = document.getElementById('btnSaveDevis'); let elBtnSaveDevisAndSend = document.getElementById('btnSaveDevisAndSend'); let elChkClientsSecteur = document.getElementById('inp_chk_clients_secteur'); let elInputSearchProducts = document.querySelectorAll("input[id^='inpSearchProduct_']"); let elInputQtes = document.querySelectorAll("input[name^='inpQte_']"); let elInputRemises = document.querySelectorAll("input[name^='inpRemise_']"); let elChkVariantes = document.querySelectorAll("input[type='checkbox'][name^='chkVariante_']"); let elInputDateDemande = document.getElementById('inp_date_demande'); let elInputDateRemise = document.getElementById('inp_date_remise'); let elListOngletsProduits = document.getElementById('listOngletsProduits'); let elOngletsProduits = document.querySelectorAll('[id^="onglet_"]'); let elProdSelect = document.querySelectorAll('input[type="checkbox"][name^="chkBoxProd_"]'); let elInpCommentGesteComm = document.getElementById('inpCommentGesteComm'); let elChatBtnSend = document.getElementById('chatBtnSend'); let elBtnCancelCommentProd = document.getElementById('btnCancelCommentProd'); let elBtnSaveCommentProd = document.getElementById('btnSaveCommentProd'); //! Au chargement de la page on affiche le menu vertical de choix du devis et on cache les 3 onglets const elDivDevis = document.getElementById('divDevis'); const elDossStatuts = document.getElementById('vb-dossiers-statuts'); const elDossArchives = document.getElementById('vb-dossiers-archives'); // Par défaut on n'affiche pas le chat document.getElementById('chat-container').style.display = 'none'; const elVerticalBar = document.getElementById('verticalBar'); const elBtnSideBarDevis = document.getElementById('btnSideBarDevis'); // par défaut on affiche les dossiers par statuts de devis elDossStatuts.classList.remove('hidden'); elDossArchives.classList.add('hidden'); elVerticalBar.style.width = '1100px'; elDivDevis.style.display = 'none'; //! On récupère les données contextuelles propres à l'utilisateur fetch('/jxpost/get_context', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { const ret = response.json(); ret.then(function (data) { const user = data.user; fkUser = user.rowid; fkRole = user.fk_role; devIp = data.devip; const session = data.session; }); }); let clickDevisArchives = function () { // click sur le bouton de la sidebar pour afficher les devis archivés ou revenir sur les devis en cours elDossStatuts.classList.toggle('hidden'); idDevis = 0; const archivesHidden = elDossArchives.classList.toggle('hidden'); if (archivesHidden) { this.innerHTML = 'Mes devis archivés'; chkShowDevisArchives = false; document.getElementById('chat-message-input').classList.remove('hidden'); // Il faut afficher tous les boutons d'enregistrement elBtnSaveEnTete.classList.remove('hidden'); elBtnSaveSelProduits.classList.remove('hidden'); elBtnSaveDevis.classList.remove('hidden'); elBtnSaveDevisAndSend.classList.remove('hidden'); elBtnSaveSpeciaux.classList.remove('hidden'); } else { this.innerHTML = 'Mes devis en cours'; chkShowDevisArchives = true; document.getElementById('chat-message-input').classList.add('hidden'); // Il faut cacher tous les boutons d'enregistrement elBtnSaveEnTete.classList.add('hidden'); elBtnSaveSelProduits.classList.add('hidden'); elBtnSaveDevis.classList.add('hidden'); elBtnSaveDevisAndSend.classList.add('hidden'); elBtnSaveSpeciaux.classList.add('hidden'); } document.getElementById('chat-container').style.display = 'none'; elDivDevis.style.display = 'none'; return false; }; let clickLigDevis = function () { //! L'utilisateur vient de cliquer sur un devis dans la liste de gauche //! On ne fait rien si l'utilisateur clique sur le même devis if (this.getAttribute('data-rid') != idDevis) { if (chkChange == 1) { if (confirm('Attention, vous avez des modifications non enregistrées sur ce devis. Voulez-vous continuer ?')) { chkChange = 0; } else { return false; } } idDevis = this.getAttribute('data-rid'); showLoading(); // on met à jour l'input caché contenant l'id du devis sélectionné document.getElementById('inpIdDevis').value = idDevis; let dataFamilles; //! on charge les familles de groupes de produits pour mettre à jour le tableau de chaque onglet fetch('/jxdevis/load_familles', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des familles de produits n'a pas abouti", 'error'); } else { const ret = response.json(); ret.then(function (data) { dataFamilles = data; }); } }); // effectue la requête ajax fetch pour charger les produits du marché fetch('/jxdevis/load_devis', { method: 'POST', body: JSON.stringify({ cid: idDevis }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }) .then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des infos de l'en-tête de ce devis n'a pas abouti", 'error'); } else { const ret = response.json(); ret .then(function (data) { fkUserDevis = data[0].fk_user; fkStatutDevis = data[0].fk_statut_devis; chkValidat = data[0].chk_validat; idMarche = data[0].fk_marche; idNewMarche = data[0].fk_marche; // par défaut le nouveau marché est le même que le marché en cours sur ce devis chkClientsSecteur = data[0].chk_clients_secteur; showDevisEnTete(data); updateBtnSpeciaux(data[0].chk_speciaux); showDevisTotaux(data); }) .then(function () { //! Une fois le marché trouvé, on charge les infos du marché préchargé dans l'en-tête du devis fetch('/jxdevis/load_devis_marche_infos', { method: 'POST', body: JSON.stringify({ cid: idMarche }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des infos du marché n'a pas abouti", 'error'); } else { const ret = response.json(); //! Boucle sur le résultat de la requête ajax ret.then(function (data) { showDevisMarcheInfos(data); chkRegleSeuilsMarge = data[0].chk_regle_seuils_marge; //! On charge ensuite les produits du marché de ce devis fetch('/jxdevis/load_devis_marche_produits', { method: 'POST', body: JSON.stringify({ cid: idMarche }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des produits du marché n'a pas abouti", 'error'); } else { const ret = response.json(); ret.then(function (data) { dataProduitsMarche = data; showDevisMarcheProduits(dataFamilles, data); // on charge les produits enregistrés pour ce devis dans 2 tableaux distincts tblProduitsSelect (2ème onglet) et tblDevisPro (3ème onglet) fetch('/jxdevis/load_devis_produits', { method: 'POST', body: JSON.stringify({ cid: idDevis }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des produits de ce devis n'a pas abouti", 'error'); } else { const ret = response.json(); //! Boucle sur le résultat de la requête ajax ret.then(function (data) { showDevisProduits(data); }); } }); }); } }); }); } }); }); } }) .catch((error) => { showNotification('Erreur', "Le chargement des infos de l'en-tête de ce devis n'a pas abouti", 'error'); }); hideLoading(); chkPageLoad = false; elDivDevis.style.display = 'block'; refreshChat(); //! On met enfin en évidence la ligne cliquée Array.from(elCelDevis).forEach(function (ligDevis) { if (ligDevis.getAttribute('data-rid') == oldIdLn) { ligDevis.style.backgroundColor = oldColorLn; } else if (ligDevis.getAttribute('data-rid') == idDevis) { oldColorLn = ligDevis.style.backgroundColor; ligDevis.style.backgroundColor = '#9bbce7'; } }); oldIdLn = idDevis; chkChange = 0; } }; let clickLigArchives = function () { //! L'utilisateur vient de cliquer sur un devis archivé dans la liste de gauche //! On ne fait rien si l'utilisateur clique sur le même devis if (this.getAttribute('data-rid') != idDevis) { if (chkChange == 1) { if (confirm('Attention, vous avez des modifications non enregistrées sur le devis en cours. Voulez-vous continuer ?')) { chkChange = 0; } else { return false; } } idDevis = this.getAttribute('data-rid'); refreshChat(); //! On met enfin en évidence la ligne cliquée Array.from(elCelArchives).forEach(function (ligArchive) { if (ligArchive.getAttribute('data-rid') == oldIdLn) { ligArchive.style.backgroundColor = oldColorLn; } else if (ligArchive.getAttribute('data-rid') == idDevis) { oldColorLn = ligArchive.style.backgroundColor; ligArchive.style.backgroundColor = '#9bbce7'; } }); oldIdLn = idDevis; chkChange = 0; } }; function showDevisEnTete(ret) { // Affiche les données de l'en-tête du devis const data = ret[0]; document.getElementById('inp_rowid').value = data.rowid; document.getElementById('inp_num_opportunite').value = data.num_opportunite; document.getElementById('inp_date_demande').value = data.date_demande; document.getElementById('inp_date_remise').value = data.date_remise; document.getElementById('inp_fk_user').value = data.fk_user; document.getElementById('inp_fk_marche').value = data.fk_marche; // On surveille un changement dans le champ fk_marche, ce qui peut provoquer la suppression des produits du devis s'il enregistre ce changement document.getElementById('inp_fk_marche').addEventListener('change', function () { idNewMarche = this.value; console.log('idNewMarche :' + idNewMarche); }); if (data.chk_clients_secteur == '1') { document.getElementById('inp_chk_clients_secteur').checked = true; } else { document.getElementById('inp_chk_clients_secteur').checked = false; } if (data.chk_clients_secteur != oldChkClientsSecteur) { // la valeur du chk_clients_secteur est différente de l'actuelle, on charge les clients du commercial sur son secteur ou sur toute la France changeClientsSecteur(); oldChkClientsSecteur == 2 ? (chkChange = 0) : (chkChange = 1); oldChkClientsSecteur = data.chk_clients_secteur; } document.getElementById('inp_fk_client').value = data.fk_client; console.log('fk_type_new :' + data.type_new_client); if (data.fk_client == 0) { document.getElementById('inp_lib_client').value = data.lib_new_client; document.getElementById('inp_adresse1').value = data.adresse1_new_client; document.getElementById('inp_adresse2').value = data.adresse2_new_client; document.getElementById('inp_adresse3').value = data.adresse3_new_client; document.getElementById('inp_cp').value = data.cp_new_client; document.getElementById('inp_ville').value = data.ville_new_client; document.getElementById('inp_contact_nom').value = data.contact_new_nom; document.getElementById('inp_contact_prenom').value = data.contact_new_prenom; document.getElementById('inp_contact_fonction').value = data.contact_new_fonction; document.getElementById('inp_email').value = data.new_email; document.getElementById('inp_telephone').value = data.new_telephone; document.getElementById('inp_mobile').value = data.new_mobile; document.getElementById('selTypeEtab').value = data.type_new_client; elBtnCreateClient.innerHTML = 'Modifier ce nouveau client'; if (elBtnCreateClient.classList.contains('btn-primary')) { elBtnCreateClient.classList.remove('btn-primary'); elBtnCreateClient.classList.add('btn-info'); } } else { document.getElementById('inp_lib_client').value = data.libelle; document.getElementById('inp_adresse1').value = data.adresse1; document.getElementById('inp_adresse2').value = data.adresse2; document.getElementById('inp_adresse3').value = data.adresse3; document.getElementById('inp_cp').value = data.cp; document.getElementById('inp_ville').value = data.ville; document.getElementById('inp_contact_nom').value = data.contact_nom; document.getElementById('inp_contact_prenom').value = data.contact_prenom; document.getElementById('inp_contact_fonction').value = data.contact_fonction; document.getElementById('inp_email').value = data.email; document.getElementById('inp_telephone').value = data.telephone; document.getElementById('inp_mobile').value = data.mobile; document.getElementById('selTypeEtab').value = data.type_client; elBtnCreateClient.innerHTML = 'Créer un nouveau client'; if (elBtnCreateClient.classList.contains('btn-info')) { elBtnCreateClient.classList.remove('btn-info'); elBtnCreateClient.classList.add('btn-primary'); } } if (data.chk_devis_photos == '1') { document.getElementById('inp_chk_devis_photos').checked = true; } else { document.getElementById('inp_chk_devis_photos').checked = false; } // Gestion et affichage des commentaires document.getElementById('inp_commentaire').value = data.commentaire; document.getElementById('inpCommentDevis').value = data.comment_devis; elInpCommentGesteComm.value = data.comment_geste_comm; // On supprime systématiquement la ligne de validation du devis let rowCommentValidatDevis = document.getElementById('rowCommentValidatDevis'); if (rowCommentValidatDevis !== null) { rowCommentValidatDevis.remove(); } if (fkUserDevis != fkUser && fkRole < 3) { // Le user actuel n'est pas le créateur du devis, et son rôle est le DIR-CO ou un DV const tblBodyComment = document.getElementById('tblCommentDevis').getElementsByTagName('tbody')[0]; // Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le fk_produit let newRowComment = tblBodyComment.insertRow(0); newRowComment.id = 'rowCommentValidatDevis'; let celLabel = newRowComment.insertCell(0); celLabel.innerHTML = ''; let celComment = newRowComment.insertCell(1); celComment.classList.add('w-60'); celComment.innerHTML = ''; let celBtnValid = newRowComment.insertCell(2); celBtnValid.classList.add('w-40'); celBtnValid.innerHTML = '
'; celBtnValid.innerHTML += '
'; document.getElementById('btnValidatDevis').addEventListener('click', clickValidatDevis); document.getElementById('btnRefusDevis').addEventListener('click', clickRefusDevis); } } function updateBtnSpeciaux(chkSpeciaux) { // Met à jour en fonction le bouton btnSpeciaux console.log('chkSpeciaux = ' + chkSpeciaux); const btnSpeciaux = document.getElementById('btnSpeciaux'); if (chkSpeciaux == '1') { btnSpeciaux.innerHTML = 'Modifier les produits spéciaux '; btnSpeciaux.classList.remove('btn-warning'); btnSpeciaux.classList.add('btn-info'); } else { btnSpeciaux.innerHTML = 'Ajouter des produits spéciaux '; btnSpeciaux.classList.remove('btn-info'); btnSpeciaux.classList.add('btn-warning'); } } function showDevisTotaux(ret) { // Affiche les totaux du devis const data = ret[0]; document.getElementById('inpTotalHT').value = formatAmount(data.montant_total_ht); document.getElementById('inpTotalRemHT').value = formatAmount(data.montant_total_ht_remise); document.getElementById('inpTotalMarge').value = data.marge_totale; // on renseigne les valeurs globales de ces 3 données devisTotalHT = data.montant_total_ht; devisTotalRemHT = data.montant_total_ht_remise; devisTotalMarge = data.marge_totale; // on met à jour le bouton de sauvegarde du devis updateBtnSaveDevisAndSend(); } function updateBtnSaveDevisAndSend() { // Si la marge Totale est inférieure au seuil de latitude, on change le bouton en orange ou rouge let btn = document.getElementById('btnSaveDevisAndSend'); let typRole = 'DV'; if (fkRole == 3 || fkRole > 19) { typRole = 'RR'; } console.log('updateBtnSaveDevisAndSend : chkRemisesMarche = ' + chkRemisesMarche + ' & typRole = ' + typRole); // if ((chkPrixNets || chkRemisesMarche) && typRole == "RR") { if (chkPrixNets || chkRemisesMarche) { // Modif du 10/04/2024 : dans tous les rôles si le devis est en prix nets ou en remises marchés on passe directement le devis à l'ADV/SAP // Si le marché est en prix nets, ou si les lignes produits sont en remises marchés, // on ne peut pas modifier les remises donc on envoie le devis directement à l'ADV/SAP if (elInpCommentGesteComm.value != '') { btn.classList.add('btn-warning'); btn.classList.remove('btn-success'); btn.classList.remove('btn-danger'); btn.innerHTML = 'Demander Accord DV/DCG'; btn.dataset.statut = '3'; } else { btn.classList.add('btn-primary'); btn.classList.remove('btn-danger'); btn.classList.remove('btn-warning'); btn.innerHTML = 'Demander Traitement SAP'; btn.dataset.statut = '4'; } } else { const margeTotale = parseFloat(document.getElementById('inpTotalMarge').value); const latitudeRR = parseFloat(document.getElementById('inp_latitudeRR').value); const latitudeDV = parseFloat(document.getElementById('inp_latitudeDV').value); console.log('btnSaveDevisAndSend : Marge totale =' + margeTotale + ' vs DV ' + latitudeDV + ' & RR ' + latitudeRR); if ((typRole == 'RR' && margeTotale < latitudeRR) || (fkRole > 2 && elInpCommentGesteComm.value != '')) { // si on est un RR commercial et que la marge totale est inférieure à la latitude RR 30%, on demande l'accord DV // ou si on est un DV ou RR commercial et qu'on a saisi un geste commercial btn.classList.add('btn-warning'); btn.classList.remove('btn-success'); btn.classList.remove('btn-danger'); btn.innerHTML = 'Demander Accord DV/DCG'; btn.dataset.statut = '3'; } else { if ((fkRole == 2 && margeTotale < latitudeDV) || (fkRole == 2 && fkUserDevis == fkUser && elInpCommentGesteComm.value != '')) { // si on est un DV et que la marge totale est inférieure à la latitude DV 20%, on demande l'accord DIR-CO // ou si on est un DV et que le devis est le sien et qu'on a saisi un geste commercial btn.classList.add('btn-danger'); btn.classList.remove('btn-success'); btn.classList.remove('btn-warning'); btn.innerHTML = 'Demander Accord DIR-CO'; btn.dataset.statut = '2'; } else { // sinon on envoie le devis directement à l'ADV/SAP btn.classList.add('btn-primary'); btn.classList.remove('btn-danger'); btn.classList.remove('btn-warning'); btn.innerHTML = 'Demander Traitement SAP'; btn.dataset.statut = '4'; } } } } function showDevisProduits(ret) { //! On affiche les produits du devis dans les 2 tableaux // tblProduitsSelect (2ème onglet : le tableau des produits sélectionnés) // tblDevisPro (3ème onglet : le tableau de saisie des qté) // On vide le tableau tblProduitsSelect let tblBodySelect = document.getElementById('tblProduitsSelect').getElementsByTagName('tbody')[0]; tblBodySelect.innerHTML = ''; // On vide le tableau tblDevisPro let tblBodyPro = document.getElementById('tblDevisPro').getElementsByTagName('tbody')[0]; tblBodyPro.innerHTML = ''; // Si le marché est un marché de prix nets, on met les champs de saisie des remises en readonly let readonlyRemise = chkPrixNets ? 'readonly="readonly"' : ''; // Ajout du 20/02/2025 : si marché hybride, les produits de ce marché sont en prix nets let readonlyRemiseProduit = ''; // Ajout du 26 juin 2024 : si l'utilisateur est le DC ou DV ou DGC, ils peuvent modifier les remises dans tous les cas if (fkRole < 3 || fkRole == 5) { readonlyRemise = ''; } // Fin de l'ajout du 26 juin 2024 if (ret.length > 0) { // au moins un produit trouvé pour ce devis let nbProduits = ret.length; // on récupère le premier fk_produit, pour simuler un changement sur ce produit pour recalculer les totaux en fin de boucle const fkProduit1 = ret[0]['fk_produit']; for (let key in ret) { if (ret.hasOwnProperty(key)) { // Récupération des valeurs de la ligne let val = ret[key]; // On initialise le readonlyremise par produit pour gérer les cas de marché hybride où leurs produits sont en Prix Nets readonlyRemiseProduit = readonlyRemise; // Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le fk_produit let newRowSelect = tblBodySelect.insertRow(-1); let celChkBox = newRowSelect.insertCell(0); celChkBox.className = 'text-center'; celChkBox.innerHTML = ''; let celCode = newRowSelect.insertCell(1); celCode.innerHTML = val['code']; let celLibelle = newRowSelect.insertCell(2); celLibelle.innerHTML = val['libelle']; // Sur le tableau tblBodyPro // Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le rowid de devis_produits let newRowPro = tblBodyPro.insertRow(-1); newRowPro.id = 'trPro_' + val['fk_produit']; newRowPro.dataset.ordre = val['ordre']; newRowPro.dataset.rid = val['fk_produit']; newRowPro.dataset.code = val['code']; newRowPro.dataset.achat = val['prix_achat_net']; newRowPro.dataset.achatdiscount = val['prix_achat_net']; newRowPro.dataset.vente = val['prix_vente']; newRowPro.dataset.discount1 = val['prc_discount_1']; newRowPro.dataset.quantite1 = val['quantite_1']; newRowPro.dataset.discount2 = val['prc_discount_2']; newRowPro.dataset.quantite2 = val['quantite_2']; newRowPro.dataset.discount3 = val['prc_discount_3']; newRowPro.dataset.quantite3 = val['quantite_3']; newRowPro.dataset.discount4 = val['prc_discount_4']; newRowPro.dataset.quantite4 = val['quantite_4']; newRowPro.dataset.discount5 = val['prc_discount_5']; newRowPro.dataset.quantite5 = val['quantite_5']; newRowPro.dataset.discount6 = val['prc_discount_6']; newRowPro.dataset.quantite6 = val['quantite_6']; newRowPro.setAttribute('draggable', 'true'); newRowPro.addEventListener('dragstart', handleDragStart); newRowPro.addEventListener('dragover', handleDragOver); newRowPro.addEventListener('drop', handleDrop); let celCodePro = newRowPro.insertCell(-1); const svgColor = val['commentaire'] == '' ? 'lightgray' : 'red'; const svgComment = ''; let inputOrdreHidden = ''; let inputCommentHidden = ''; celCodePro.innerHTML = val['code'] + ' ' + svgComment + inputOrdreHidden + inputCommentHidden; document.getElementById('commentProd_' + val['fk_produit']).addEventListener('click', showCommentProd); let celLibellePro = newRowPro.insertCell(1); celLibellePro.innerHTML = val['libelle']; let celPrixVentePro = newRowPro.insertCell(2); celPrixVentePro.className = 'text-right'; celPrixVentePro.innerHTML = formatAmount(val['prix_vente']) + ' €'; let celQtePro = newRowPro.insertCell(3); celQtePro.innerHTML = ''; document.getElementById('inpQte_' + val['fk_produit']).addEventListener('change', calculDevis); let celRemisePro = newRowPro.insertCell(4); // Nouveau code 21/09 // S'il y a une remise de base sur le marché, on vérifie chaque remise produit pour l'aligner à cette remise de base si elle est supérieure let remiseProduit = val['remise']; console.log('Remise de base : ' + remiseMarcheDeBase + ' vs remise sur le produit : ' + val['remise']); if (remiseMarcheDeBase > 0) { if (val['remise'] < remiseMarcheDeBase) { console.log('La remise du produit est inférieure à la remise de base, on la force à la remise de base'); remiseProduit = remiseMarcheDeBase; } } // Fin du nouveau code du 21/09 // AJOUT DU 20/02/25 : on regarde si ce produit a un chk_prix_net et s'il est à 1 (marché hybride) if (val['chk_prix_net']) { console.log('on a un chk_prix_net : ' + val['chk_prix_net']); if (val['chk_prix_net'] == '1') { console.log('Le produit ' + val['code'] + ' est sur un marché hybride donc chk_prix_net=1'); readonlyRemiseProduit = 'readonly="readonly"'; } } celRemisePro.innerHTML = '
%
'; if (readonlyRemiseProduit == '') { document.getElementById('inpRemise_' + val['fk_produit']).addEventListener('change', calculDevis); } // nouvelle colonne PU vente avec remise let celPUVenteRemPro = newRowPro.insertCell(5); celPUVenteRemPro.innerHTML = '
'; // Fin nouvelle colonne let celHTPro = newRowPro.insertCell(6); celHTPro.innerHTML = '
'; let celVariante = newRowPro.insertCell(7); celVariante.className = 'text-center'; celVariante.innerHTML = ''; document.getElementById('chkVariante_' + val['fk_produit']).addEventListener('change', calculDevis); let celMargePro = newRowPro.insertCell(8); celMargePro.innerHTML = '
%
'; // on calcule et enregistre le prix d'achat discount du produit si on a un prc_discount let chkDiscount = false; if (val['prc_discount_6'] > 0 && val['quantite_6'] > 0) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_6'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_6'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_6 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_5'] > 0 && val['quantite_5'] > 0 && !chkDiscount) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_5'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_5'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_5 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_4'] > 0 && val['quantite_4'] > 0 && !chkDiscount) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_4'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_4'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_4 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_3'] > 0 && val['quantite_3'] > 0 && !chkDiscount) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_3'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_3'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_3 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_2'] > 0 && val['quantite_2'] > 0 && !chkDiscount) { if (parseInt(val['qte']) >= parseInt(val['quantite_2'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_2'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_2 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_1'] > 0 && val['quantite_1'] > 0 && !chkDiscount) { if (parseInt(val['qte']) >= parseInt(val['quantite_1'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_1'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_1 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (chkRegleSeuilsMarge == 1) { // Le marché demande la prise en compte des seuils de marge RR et DV paramétrés dans la table produits_familles if (val['marge_rr'] > seuilMargeRR) seuilMargeRR = val['marge_rr']; if (val['marge_dv'] > seuilMargeDV) seuilMargeDV = val['marge_dv']; } } } // On met à jour les seuils de marge RR et DV en fonction du marché et des produits document.getElementById('inp_latitudeRR').value = seuilMargeRR; document.getElementById('inp_latitudeDV').value = seuilMargeDV; // On simule le changement de quantité sur la première ligne pour recalculer les totaux const inpQte = document.getElementById('inpQte_' + fkProduit1); const event = new Event('change'); inpQte.dispatchEvent(event); chkChange = 0; } } function showDevisMarcheInfos(ret) { // On affiche les infos du marché if (ret.length == 1) { let line = ret[0]; chkPrixNets = line.chk_prix_nets == 1 ? true : false; document.getElementById('inp_latitudeRR').value = seuilMargeRR; document.getElementById('inp_latitudeDV').value = seuilMargeDV; document.getElementById('titleMarche').innerHTML = 'Informations du marché ' + line.libelle + ''; // le titre du panel des infos marché dans l'onglet 3. Devis $('#tdTxRemiseTrim').text(line.taux_remise_trimestrielle + ' %'); $('#tdTxRemiseSeme').text(line.taux_remise_semestrielle + ' %'); $('#tdTxRemiseAnnu').text(line.taux_remise_annuelle + ' %'); document.getElementById('tdDebutFin').innerHTML = convertMySQLDateToFrenchDate(line.date_debut) + ' - ' + convertMySQLDateToFrenchDate(line.date_fin); // vérifie la date de validité du prix du marché est inférieure à la date du jour const today = new Date(); if (line.date_validite_prix < today) { document.getElementById('tdDateValiditePrix').style.color = 'red'; } else { // vérifie que cette date est encore valable dans 2 mois const today2mois = new Date(); today2mois.setMonth(today2mois.getMonth() + 2); if (line.date_validite_prix < today2mois) { document.getElementById('tdDateValiditePrix').style.color = 'orange'; } else { document.getElementById('tdDateValiditePrix').style.color = 'green'; } } document.getElementById('tdDateValiditePrix').innerHTML = convertMySQLDateToFrenchDate(line.date_validite_prix); $('#tdGarantie').text(line.garantie); $('#tdRemisesCo').text(line.remises_commerciales); // On affiche les remises de marché if (line.remise_palier_1 == 0 && line.remise_taux_1 == 0 && line.remise_palier_2 == 0 && line.remise_palier_3 == 0 && !chkPrixNets) { document.getElementById('trRemisesMarche').style.display = 'none'; } else { let remisesMarche = ''; if (line.remise_palier_1 > 0) { remisesMarche += line.remise_taux_1 + '% à partir de ' + line.remise_palier_1 + 'k€'; } else { remisesMarche += line.remise_taux_1 + '% de base'; remiseMarcheDeBase = line.remise_taux_1; } if (line.remise_palier_2 > 0) { remisesMarche += ', ' + line.remise_taux_2 + '% à partir de ' + line.remise_palier_2 + 'k€'; } if (line.remise_palier_3 > 0) { remisesMarche += ', ' + line.remise_taux_3 + '% à partir de ' + line.remise_palier_3 + 'k€'; } if (line.remise_palier_4 > 0) { remisesMarche += ', ' + line.remise_taux_4 + '% à partir de ' + line.remise_palier_4 + 'k€'; } if (chkPrixNets) { remisesMarche += ' (PRIX NETS)'; } document.getElementById('tdRemisesMarche').innerHTML = remisesMarche; document.getElementById('trRemisesMarche').style.display = 'block'; // on ajoute le readonly à tous les champs de saisie des remises de marché si le marché est en prix nets et que c'est un RR if (chkPrixNets && (fkRole == 3 || fkRole == 4 || fkRole > 5)) { // on boucle sur tous les inputs inpRemise_* console.log('Prix Nets et RR : on boucle sur tous les inputs inpRemise_* pour les mettre en readonly'); let inputs = document.getElementsByTagName('input'); for (let i = 0; i < inputs.length; i++) { if (inputs[i].id.substr(0, 10) == 'inpRemise_') { inputs[i].readOnly = true; } } } } // on sauvegarde les remises de marché dans le array aRemisesMarches aRemisesMarches = [line.remise_palier_1, line.remise_taux_1, line.remise_palier_2, line.remise_taux_2, line.remise_palier_3, line.remise_taux_3, line.remise_palier_4, line.remise_taux_4]; // on met à jour le bouton de sauvegarde du devis updateBtnSaveDevisAndSend(); } else { $('#tdTxRemiseTrim').text('-'); $('#tdTxRemiseSeme').text('-'); $('#tdTxRemiseAnnu').text('-'); $('#tdDebutFin').text('-'); $('#tdDateValiditePrix').text('-'); $('#tdGarantie').text('-'); $('#tdRemisesCo').text('-'); } } function showDevisMarcheProduits(dFamilles, dProduits) { // on met à jour les onglets de familles de groupes de produits showLoading(); let famillesChargees = []; dFamilles.forEach(function (lineFamille) { let libIdFamille = lineFamille.libelle.replace(/ /g, '_'); let idFamille = lineFamille.rowid; let dataProduitsFamille = []; let tblBodyProduits = document.getElementById('tblProduits_' + libIdFamille).getElementsByTagName('tbody')[0]; tblBodyProduits.innerHTML = ''; // on charge les produits de cette famille dProduits.forEach(function (lineProduit) { if (lineProduit.fk_famille == idFamille) { // on renseigne cet idFamille dans le tableau des familles chargées si ce n'est pas déjà fait (pour éviter les doublons) if (famillesChargees.indexOf(libIdFamille) == -1) { famillesChargees.push(libIdFamille); } // on enregistre les données de ce produit dans le tableau dataProduitsFamille dataProduitsFamille.push(lineProduit); } }); // Une fois que tous les produits de cette famille sont chargés, on les affiche showProduitsFamille(dataProduitsFamille, libIdFamille); // puis on affecte les données de ce tableau dataProduitsFamille à l'autocomplete de l'input de recherche de produits de cette famille autocompleteProduitsFamille(document.getElementById('inpSearchProduct_' + libIdFamille), dataProduitsFamille, libIdFamille, idFamille); }); // Enfin, on affiche que les onglets des familles de produits chargées // 1. On trie les onglets par ordre croissant de leur id_ordre let sortedOnglets = Array.from(elOngletsProduits).sort(function (a, b) { const aIndex = parseInt(a.id.split('_')[1]); const bIndex = parseInt(b.id.split('_')[1]); if (aIndex < bIndex) { return -1; } else if (aIndex > bIndex) { return 1; } else { return 0; } }); // 2. On boucle sur ces onglets triés en ordre décroissant et on les affiche dans le bon ordre sortedOnglets.forEach(function (onglet) { elListOngletsProduits.appendChild(onglet.parentNode); onglet.classList.remove('hidden'); }); // 3. On cache maintenant les onglets des familles de produits non chargées, et on les pousse à la fin de la liste let nbOnglets = elOngletsProduits.length; Array.from(elOngletsProduits).forEach(function (onglet) { const libIdFamille = onglet.getAttribute('data-famille'); if (famillesChargees.indexOf(libIdFamille) == -1) { onglet.classList.add('hidden'); nbOnglets--; // et on pousse cet onglet à la fin de la liste elListOngletsProduits.appendChild(onglet.parentNode); } }); console.log('nbOnglets : ' + nbOnglets); Array.from(elOngletsProduits).forEach(function (onglet) { const libIdFamille = onglet.getAttribute('data-famille'); if (famillesChargees.indexOf(libIdFamille) > -1) { // on récupère le titre de l'onglet, on calcule sa longueur, let titreOnglet = onglet.innerText.trim(); const espace = titreOnglet.indexOf(' '); if (espace > 0) { titreOnglet = titreOnglet.substring(0, espace) + '
' + titreOnglet.substring(espace + 1); onglet.innerHTML = titreOnglet; } else { const longueur = titreOnglet.length; if (longueur < 10) { onglet.innerHTML = titreOnglet + '

'; } } } }); // 4. Enfin, on force le nav-justified à se réorganiser elListOngletsProduits.classList.remove('nav-justified'); elListOngletsProduits.classList.add('nav-justified'); hideLoading(); } function showProduitsFamille(dProduits, libIdFamille) { // Affiche tous les produits d'une famille dans le 2ème onglet Produits // dProduits ne contient que les produits de cette famille let tblBody = document.getElementById('tblProduits_' + libIdFamille).getElementsByTagName('tbody')[0]; tblBody.innerHTML = ''; // on charge les produits de cette famille dProduits.forEach(function (lineProduit) { // Insertion d'une nouvelle ligne et création de ses colonnes showLineProduitFamille(tblBody, lineProduit, libIdFamille); }); } function showLineProduitFamille(tblBody, lineProduit, libIdFamille) { let newRow = tblBody.insertRow(0); newRow.className = 'ligProduit_' + libIdFamille; newRow.id = 'ligProduit_' + libIdFamille + '_' + lineProduit.rowid; newRow.setAttribute('data-rid', lineProduit.rowid); let celChkBox = newRow.insertCell(0); celChkBox.className = 'chkBox_' + libIdFamille + ' text-center'; celChkBox.setAttribute('data-rid', lineProduit.rowid); celChkBox.innerHTML = ''; let celCode = newRow.insertCell(1); celCode.innerHTML = lineProduit.code; let celLibelle = newRow.insertCell(2); celLibelle.innerHTML = lineProduit.libelle; let celFamille = newRow.insertCell(3); celFamille.innerHTML = lineProduit.lib_famille; } $('a[data-toggle="tab"]').on('show.bs.tab', function (e) { if (idDevis == 0) { if ($(this).attr('href') == '#tabproduits' || $(this).attr('href') == '#tabdevis') { showNotification('Erreur', "Vous devez d'abord sélectionner un devis dans la liste de vos devis à gauche", 'warning'); return false; } } else { if (chkChange == 1) { //! il y a un changement en cours... if ($(this).attr('href') == '#tabentete' || $(this).attr('href') == '#tabproduits' || $(this).attr('href') == '#tabdevis') { if (confirm("Attention, vous avez fait des modifications non enregistrées sur cette page du devis. Vous allez perdre d'éventuelles modifications importantes. Voulez-vous continuer ?")) { chkChange = 0; } else { return false; } } } } }); function changeClientsSecteur() { // en cas de changement de secteur, on recharge les clients du commercial sur ce secteur ou sur toute la France chkClientsSecteur = document.getElementById('inp_chk_clients_secteur').checked ? 1 : 0; console.log('changement de secteur clients : ' + chkClientsSecteur); if (fkUser > 0) { fetch('/jxdevis/load_clients_devis', { method: 'POST', body: JSON.stringify({ user: fkUser, secteur: chkClientsSecteur.toString(), }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des clients n'a pas abouti", 'error'); } else { const retClients = response.json(); retClients.then(function (dataClients) { clients = dataClients; // on charge les clients du commercial sur son secteur ou sur toute la France dans l'autocomplete autocompleteClient(document.getElementById('inp_lib_client'), dataClients); }); } }); } } function clickDupDevis() { idDevis = this.getAttribute('data-rid'); if (confirm('Confirmez-vous la duplication de ce devis n° ' + idDevis + ' ?')) { showLoading(); $.ajax({ url: '/jxdevis/duplic_devis', type: 'POST', async: false, cache: false, data: 'rid=' + idDevis, success: function (data) { showNotification('Duplication', 'Duplication du devis effectuée avec succès', 'success'); hideLoading(); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes return false; }, error: function (jqXHR, textStatus, errorThrown) { showNotification('Erreur', 'Erreur lors de la duplication de ce devis : ' + textStatus, 'error'); hideLoading(); return false; }, }); } } function clickExpExcelDevis(e) { const idDevis = this.dataset.rid; if (confirm("Confirmez l'exportation de ce devis #" + idDevis + ' en Excel ?')) { const url = '/expxls/export_sap_devis/' + idDevis; window.open(url); return false; } } let clickSupprDevis = function (e) { const idDevis = this.dataset.rid; if (confirm('Confirmez la suppression définitive de ce devis #' + idDevis + ' ?')) { showLoading(); fetch('/jxdevis/delete_devis', { method: 'POST', body: JSON.stringify({ cid: idDevis }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "La suppression du devis n'a pas abouti", 'error'); } else { const retDevis = response.json(); retDevis.then(function (dataDevis) { // On supprime la ligne du tableau qui contenait ce bouton let trDevis = document.getElementById('tr_' + idDevis); trDevis.parentNode.removeChild(trDevis); showNotification('Suppression', 'Suppression du devis effectuée avec succès', 'success'); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes }); } }); hideLoading(); } return false; }; let clickCreateClient = function () { if (this.innerHTML == 'Créer un nouveau client') { if (confirm('Voulez-vous créer un nouveau client pour ce devis ?')) { document.getElementById('frmCreateClient').reset(); showModal(document.getElementById('modalCreateClient')); document.getElementById('inp_create_libelle').focus(); } } else { document.getElementById('inp_create_libelle').value = document.getElementById('inp_lib_client').value; document.getElementById('inp_create_type_client').value = document.getElementById('selTypeEtab').value; document.getElementById('inp_create_adresse1').value = document.getElementById('inp_adresse1').value; document.getElementById('inp_create_adresse2').value = document.getElementById('inp_adresse2').value; document.getElementById('inp_create_adresse3').value = document.getElementById('inp_adresse3').value; document.getElementById('inp_create_cp').value = document.getElementById('inp_cp').value; document.getElementById('inp_create_ville').value = document.getElementById('inp_ville').value; showModal(document.getElementById('modalCreateClient')); document.getElementById('inp_create_libelle').focus(); } }; let clickCancelCreateClient = function () { hideModal(document.getElementById('modalCreateClient')); }; let clickSaveCreateClient = function () { // on regarde si c'est une création de devis ou une modification // on enregistre le fait que ça soit un nouveau client // on met à jour les champs du devis avec les infos du nouveau client // et quand on enregistre le devis on enregistre le nouveau client dans le devis document.getElementById('inp_fk_client').value = '0'; document.getElementById('inp_lib_client').value = document.getElementById('inp_create_libelle').value; document.getElementById('selTypeEtab').value = document.getElementById('inp_create_type_client').value; document.getElementById('inp_type_client').value = document.getElementById('inp_create_type_client').value; document.getElementById('inp_adresse1').value = document.getElementById('inp_create_adresse1').value; document.getElementById('inp_adresse2').value = document.getElementById('inp_create_adresse2').value; document.getElementById('inp_adresse3').value = document.getElementById('inp_create_adresse3').value; document.getElementById('inp_cp').value = document.getElementById('inp_create_cp').value; document.getElementById('inp_ville').value = document.getElementById('inp_create_ville').value; console.log('nouveau client créé : ' + document.getElementById('inp_create_type_client').value + ' -> ' + document.getElementById('selTypeEtab').value); hideModal(document.getElementById('modalCreateClient')); // on change le texte et la couleur du bouton de nouveau client document.getElementById('btnCreateClient').innerHTML = 'Modifier le nouveau client'; document.getElementById('btnCreateClient').classList.remove('btn-primary'); document.getElementById('btnCreateClient').classList.add('btn-info'); chkCreateClient = true; }; let clickSpeciaux = function () { showLoading(); // On cherche dans la table devis_speciaux s'il y a une ligne pour ce devis fetch('/jxdevis/load_devis_speciaux', { method: 'POST', body: JSON.stringify({ cid: idDevis }), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { showNotification('Erreur', "Le chargement des produits spéciaux n'a pas abouti", 'error'); } else { const retSpeciaux = response.json(); retSpeciaux.then(function (dataSpeciaux) { // on vide les 5 lignes de produits spéciaux pour éviter de reprendre des données d'un autre devis for (i = 1; i <= 5; i++) { document.getElementById('inp_specialFkProduit_' + i).value = ''; document.getElementById('inp_specialCode_' + i).value = ''; document.getElementById('inp_specialLibe_' + i).value = ''; document.getElementById('inp_specialQte_' + i).value = ''; document.getElementById('inp_specialCout_' + i).value = ''; document.getElementById('inp_chk_specialEchantillon_' + i).checked = false; document.getElementById('inp_specialDate_' + i).value = ''; document.getElementById('inp_specialConcurrent_' + i).value = ''; document.getElementById('inp_specialDescription_' + i).value = ''; } if (dataSpeciaux.length > 0) { // on a trouvé une ligne dans la table devis_speciaux const data = dataSpeciaux[0]; // on charge les données dans le formulaire document.getElementById('inp_idDevis_speciaux').value = data.fk_devis; if (data.chk_livr_multi == '1') { document.getElementById('inp_chk_livr_multi').checked = true; } else { document.getElementById('inp_chk_livr_multi').checked = false; } document.getElementById('inp_nb_livr').value = data.nb_livr; document.getElementById('inp_date_livr_1').value = data.date_livr_1; for (i = 1; i <= 5; i++) { document.getElementById('inp_specialFkProduit_' + i).value = data[`fk_produit_${i}`]; document.getElementById('inp_specialCode_' + i).value = data[`code_produit_${i}`]; document.getElementById('inp_specialLibe_' + i).value = data[`lib_produit_${i}`]; document.getElementById('inp_specialQte_' + i).value = data[`qte_${i}`]; document.getElementById('inp_specialCout_' + i).value = data[`surcout_${i}`]; if (data[`chk_echantillon_${i}`] == '1') { document.getElementById('inp_chk_specialEchantillon_' + i).checked = true; } else { document.getElementById('inp_chk_specialEchantillon_' + i).checked = false; } if (data[`date_echantillon_${i}`] != '0000-00-00') { document.getElementById('inp_specialDate_' + i).value = data[`date_echantillon_${i}`]; } document.getElementById('inp_specialConcurrent_' + i).value = data[`lib_concurrent_${i}`]; document.getElementById('inp_specialDescription_' + i).value = data[`description_${i}`]; } document.getElementById('inp_specialEmail').value = data.email; if (data.chk_email == 1) { document.getElementById('inp_specialEmail').style.backgroundColor = 'lightgreen'; } else { document.getElementById('inp_specialEmail').style.backgroundColor = 'white'; } } else { // on n'a pas trouvé de ligne dans la table devis_speciaux // on vide les champs du formulaire document.getElementById('inp_idDevis_speciaux').value = idDevis; document.getElementById('frmSpeciaux').reset(); } autocompleteProdSpecial(document.getElementById('inp_specialCode_1'), '1', dataProduitsMarche); autocompleteProdSpecial(document.getElementById('inp_specialCode_2'), '2', dataProduitsMarche); autocompleteProdSpecial(document.getElementById('inp_specialCode_3'), '3', dataProduitsMarche); autocompleteProdSpecial(document.getElementById('inp_specialCode_4'), '4', dataProduitsMarche); autocompleteProdSpecial(document.getElementById('inp_specialCode_5'), '5', dataProduitsMarche); }); } }); hideLoading(); showModal(document.getElementById('modalSpeciaux')); }; let clickCancelSpeciaux = function () { hideModal(document.getElementById('modalSpeciaux')); }; let clickSaveSpeciaux = function () { let frmData = new FormData(document.getElementById('frmSpeciaux')); let objData = {}; frmData.forEach(function (value, key) { objData[key] = value; }); console.log(objData); showLoading(); fetch('/jxdevis/save_devis_speciaux', { method: 'POST', body: JSON.stringify(objData), headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, }).then((response) => { if (!response.ok) { hideLoading(); showNotification('Erreur', "L'enregistrement des produits spéciaux de ce devis n'a pas abouti", 'error'); } else { const ret = response.json(); ret.then(function (data) { hideLoading(); showNotification('Succès', 'Enregistrement des produits spéciaux de ce devis effectué', 'success'); }); } }); hideLoading(); hideModal(document.getElementById('modalSpeciaux')); chkChange = 0; return false; }; let clickSaveEnTete = function () { if (document.getElementById('inp_lib_client').value == '') { showNotification('Erreur', 'Enregistrement impossible : vous devez sélectionner ou créer un client', 'error'); document.getElementById('inp_lib_client').focus(); return false; } if (document.getElementById('inp_num_opportunite').value == '') { showNotification('Erreur', "Enregistrement impossible : vous devez saisir un N° d'opportunité", 'error'); document.getElementById('inp_num_opportunite').focus(); return false; } if (document.getElementById('inp_contact_nom').value == '') { showNotification('Erreur', 'Enregistrement impossible : vous devez renseigner le nom et prénom du contact', 'error'); document.getElementById('inp_contact_nom').focus(); return false; } if (document.getElementById('inp_contact_prenom').value == '') { showNotification('Erreur', 'Enregistrement impossible : vous devez renseigner le nom et prénom du contact', 'error'); document.getElementById('inp_contact_prenom').focus(); return false; } if (document.getElementById('inp_contact_fonction').value == '') { showNotification('Erreur', 'Enregistrement impossible : vous devez renseigner la fonction du contact', 'error'); document.getElementById('inp_contact_fonction').focus(); return false; } if (document.getElementById('inp_email').value == '') { showNotification('Erreur', "Enregistrement impossible : vous devez renseigner l'email du contact", 'error'); document.getElementById('inp_email').focus(); return false; } if (document.getElementById('inp_telephone').value == '' && document.getElementById('inp_mobile').value == '') { showNotification('Erreur', 'Enregistrement impossible : vous devez renseigner au moins un numéro de téléphone du contact (fixe ou mobile)', 'error'); document.getElementById('inp_telephone').focus(); return false; } if (document.getElementById('inp_fk_marche').value == '0') { showNotification('Erreur', 'Enregistrement impossible : vous devez sélectionner un marché', 'error'); document.getElementById('inp_fk_marche').focus(); return false; } const dateDemande = document.getElementById('inp_date_demande').value; const dateRemise = document.getElementById('inp_date_remise').value; // Vérification de la validité des dates saisies if (isNaN(Date.parse(dateDemande))) { showNotification('Erreur', "Enregistrement impossible : la date de la demande n'est pas saisie ou est incorrecte", 'error'); dateDemande.focus(); return false; } if (isNaN(Date.parse(dateRemise))) { showNotification('Erreur', "Enregistrement impossible : la date de la remise n'est pas saisie ou est incorrecte", 'error'); dateRemise.focus(); return false; } if (dateDemande !== '' && dateRemise !== '') { const dateDemandeObj = new Date(Date.parse(dateDemande)); const dateRemiseObj = new Date(Date.parse(dateRemise)); if (dateRemiseObj <= dateDemandeObj) { showNotification('Erreur', 'Enregistrement impossible : la date de remise au client doit être supérieure à la date de la demande', 'error'); dateRemise.focus(); return false; // empêcher l'enregistrement du formulaire } } // Vérification du non changement du marché if (idDevis > 0) { if (idMarche != document.getElementById('inp_fk_marche').value) { if (!confirm('Vous avez changé le marché de ce devis. Confirmez-vous ce changement ? Cela va supprimer tous les produits enregistrés de ce devis.')) { return false; } } } showLoading(); //! on récupère tous les input de la form dans dataform let dataform = $('#frmDevisEntete').serialize(); //! on supprime les %2F des dates au lieu des / dataform = decodeURIComponent(dataform.replace(/%2F/g, ' ')); $.ajax({ url: '/jxdevis/save_devis_entete', type: 'POST', dataType: 'json', async: false, cache: false, data: dataform, success: function (data) { hideLoading(); showNotification('Enregistrement', 'Enregistrement effectué avec succès', 'success'); // On recharge la page en cours setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes }, error: function (jqXHR, textStatus, errorThrown) { showNotification('Erreur', 'Erreur lors de la sauvegarde du devis : ' + textStatus, 'error'); }, }); chkChange = 0; return false; }; let clickCreateDevis = function () { if (confirm('Voulez-vous créer un nouveau devis ?')) { //! On vide tous les champs de la form frmDevisEntete document.getElementById('frmDevisEntete').reset(); document.getElementById('inp_rowid').value = 0; document.getElementById('inp_fk_user').value = fkUser; document.getElementById('inp_fk_marche').value = 0; document.getElementById('inp_fk_client').value = ''; document.getElementById('inp_chk_devis_photos').checked = false; document.getElementById('inp_num_opportunite').focus(); //! On vérifie le bon chargement des clients if (oldChkClientsSecteur == 2) { // les clients n'ont pas encore été chargés document.getElementById('inp_chk_clients_secteur').checked = true; oldChkClientsSecteur = 1; changeClientsSecteur(); } chkChange = 1; elDivDevis.style.display = 'block'; chkPageLoad = false; idDevis = 0; } }; $('#inpSearchProduit').keypress(function (e) { if (e.which == 13) { if ($('#inpSearchProduit').val().length > 2) { $.ajax({ url: '/jxdevis/load_devis_produits_search', type: 'POST', dataType: 'json', async: false, cache: false, data: 'term=' + $('#inpSearchProduit').val(), success: function (data) { // on importe les produits sélectionnés dans le tableau tblProduits let rowCount = $('#tblProduits tr').length; // Pour ne garder que la 1ère ligne d'entête de la table if (rowCount > 1) { for (i = rowCount; i > 1; i--) { $('#tblProduits tr:last').remove(); } } let nbProduits = data.length; $.each(data, function (idx, line) { $('#tblProduits').append('' + line.code + '' + line.libelle + ''); }); }, error: function (jqXHR, textStatus, errorThrown) { showNotification('Erreur', 'Erreur lors de la recherche de produits : ' + textStatus, 'error'); }, }); return false; } else { showNotification('Saisie', 'Vous devez saisir au moins 3 caractères', 'warning'); return false; } } }); $(document).on('change', 'input[name^="chkBoxProd_"]', function () { // on ajoute un produit disponible dans la liste des produits sélectionnés const rid = this.dataset.rid; const code = this.dataset.code; const libelle = this.dataset.libelle; //! 1. on ajoute ce produit dans la liste des produits sélectionnés $('#tblProduitsSelect').append('' + code + '' + libelle + ''); //! 2. on cache la ligne de ce produit de la liste des produits disponibles à gauche const trProd = this.parentNode.parentNode; trProd.style.display = 'none'; chkChange = 1; return false; }); $(document).on('change', 'input[name^="chkProdSelect_"]', function () { // on supprime un produit sélectionné pour le remettre dans la liste des produits disponibles let rid = this.dataset.rid; let code = this.dataset.code; let libelle = this.dataset.libelle; //! 1. on remet ce produit dans la liste des produits du catalogue const chkBoxProd = document.querySelector('#chkBoxProd_' + rid); if (chkBoxProd) { // Si l'élément existe, on peut procéder chkBoxProd.checked = false; const trProd = chkBoxProd.parentNode.parentNode; trProd.style.display = 'table-row'; } else { console.warn(`L'élément #chkBoxProd_${rid} n'existe pas dans le DOM`); } //! 2. on supprime ce produit de la liste des produits sélectionnés const trProdSelect = this.parentNode.parentNode; trProdSelect.parentNode.removeChild(trProdSelect); chkChange = 1; return false; }); let clickSaveSelProduits = function () { //! Sauve la liste des produits sélectionnés d'un devis const tblBodySelect = document.getElementById('tblProduitsSelect').getElementsByTagName('tbody')[0]; const nbProduits = tblBodySelect.rows.length; showLoading(); let aProduits = new Array(); let lstProduits = ''; // if (nbProduits > 0) { //! On parcourt la liste des produits sélectionnés for (let i = 0, row; (row = tblBodySelect.rows[i]); i++) { const rid = row.cells[0].firstElementChild.dataset.rid; aProduits.push(rid); //! On crée une chaîne liste des produits sélectionnés avec le "s" comme séparateur lstProduits += ';' + rid; } let dataProduits = {}; dataProduits['cid'] = idDevis; dataProduits['produits'] = lstProduits; fetch('/jxdevis/save_devis_produits', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(dataProduits), }).then((response) => { if (!response.ok) { hideLoading(); showNotification('Erreur', "L'enregistrement des produits de ce devis n'a pas abouti", 'error'); } else { const ret = response.json(); ret.then(function (data) { showDevisPro(data); hideLoading(); showNotification('Succès', 'Enregistrement des ' + nbProduits + ' produits de ce devis effectué', 'success'); }); } }); chkChange = 0; return false; }; function showDevisPro(data) { //! Rafraîchit la liste des produits d'un devis dans le 3ème onglet Devis const tblBodyPro = document.getElementById('tblDevisPro').getElementsByTagName('tbody')[0]; tblBodyPro.innerHTML = ''; if (data.length > 0) { // au moins un produit trouvé pour ce devis const nbProduits = data.length; // Si le marché est un marché de prix nets, on met les champs de saisie des remises en readonly let readonlyRemise = chkPrixNets ? 'readonly="readonly"' : ''; // Ajout du 20/02/2025 : si marché hybride, les produits de ce marché sont en prix nets let readonlyRemiseProduit = ''; // Ajout du 26 juin 2024 : si l'utilisateur est le DC ou DV ou DGC, ils peuvent modifier les remises dans tous les cas if (fkRole < 3 || fkRole == 5) { readonlyRemise = ''; } // Fin de l'ajout du 26 juin 2024 // on récupère le premier fk_produit, pour simuler un changement sur ce produit pour recalculer les totaux en fin de boucle const fkProduit1 = data[0]['fk_produit']; for (let key in data) { if (data.hasOwnProperty(key)) { // Récupération des valeurs de la ligne let val = data[key]; // 20/02/2025 : On initialise le readonlyremise par produit pour gérer les cas de marché hybride où leurs produits sont en Prix Nets readonlyRemiseProduit = readonlyRemise; // on insère la ligne pour la saisie du commentaire au-dessus de la ligne du produit let newRowCom = tblBodyPro.insertRow(-1); newRowCom.className = 'hidden'; newRowCom.id = 'trCom_' + val['fk_produit']; newRowCom.dataset.rid = val['fk_produit']; let celCom = newRowCom.insertCell(0); celCom.colSpan = 8; celCom.innerHTML = '
'; // Insertion d'une nouvelle ligne et création de ses colonnes let newRowPro = tblBodyPro.insertRow(-1); newRowPro.id = 'trPro_' + val['fk_produit']; newRowPro.dataset.rid = val['fk_produit']; newRowPro.dataset.ordre = val['ordre']; newRowPro.dataset.code = val['code']; newRowPro.dataset.achat = val['prix_achat_net']; newRowPro.dataset.achatdiscount = val['prix_achat_net']; newRowPro.dataset.vente = val['prix_vente']; newRowPro.dataset.discount1 = val['prc_discount_1']; newRowPro.dataset.quantite1 = val['quantite_1']; newRowPro.dataset.discount2 = val['prc_discount_2']; newRowPro.dataset.quantite2 = val['quantite_2']; newRowPro.dataset.discount3 = val['prc_discount_3']; newRowPro.dataset.quantite3 = val['quantite_3']; newRowPro.dataset.discount4 = val['prc_discount_4']; newRowPro.dataset.quantite4 = val['quantite_4']; newRowPro.dataset.discount5 = val['prc_discount_5']; newRowPro.dataset.quantite5 = val['quantite_5']; newRowPro.dataset.discount6 = val['prc_discount_6']; newRowPro.dataset.quantite6 = val['quantite_6']; let celCodePro = newRowPro.insertCell(0); const svgColor = val['commentaire'] == '' ? 'lightgray' : 'red'; const svgComment = ''; let inputOrdreHidden = ''; celCodePro.innerHTML = val['code'] + ' ' + svgComment + inputOrdreHidden; document.getElementById('commentProd_' + val['fk_produit']).addEventListener('click', showCommentProd); let celLibellePro = newRowPro.insertCell(1); celLibellePro.innerHTML = val['libelle']; let celPrixVentePro = newRowPro.insertCell(2); celPrixVentePro.className = 'text-right'; celPrixVentePro.innerHTML = formatAmount(val['prix_vente']) + ' €'; let celQtePro = newRowPro.insertCell(3); celQtePro.innerHTML = ''; document.getElementById('inpQte_' + val['fk_produit']).addEventListener('change', calculDevis); let celRemisePro = newRowPro.insertCell(4); // Nouveau code 21/09 // S'il y a une remise de base sur le marché, on vérifie chaque remise produit pour l'aligner à cette remise de base si elle est supérieure let remiseProduit = val['remise']; console.log('Remise de base : ' + remiseMarcheDeBase + ' vs remise sur le produit : ' + val['remise']); if (remiseMarcheDeBase > 0) { if (val['remise'] < remiseMarcheDeBase) { console.log('La remise du produit est inférieure à la remise de base, on la force à la remise de base'); remiseProduit = remiseMarcheDeBase; } } // Fin du nouveau code du 21/09 // 20/02/2025 if (val['chk_prix_net']) { if (val['chk_prix_net'] == 1) { console.log('Le produit ' + val['code'] + ' est sur un marché hybride donc chk_prix_net=1'); readonlyRemiseProduit = 'readonly=readonly'; } } celRemisePro.innerHTML = '
%
'; if (readonlyRemiseProduit == '') { document.getElementById('inpRemise_' + val['fk_produit']).addEventListener('change', calculDevis); } // nouvelle colonne PU vente avec remise let celPUVenteRemPro = newRowPro.insertCell(5); celPUVenteRemPro.innerHTML = '
'; // Fin nouvelle colonne let celHTPro = newRowPro.insertCell(6); celHTPro.innerHTML = '
'; let celVariante = newRowPro.insertCell(7); celVariante.className = 'text-center'; celVariante.innerHTML = ''; document.getElementById('chkVariante_' + val['fk_produit']).addEventListener('change', calculDevis); let celMargePro = newRowPro.insertCell(8); celMargePro.innerHTML = '
%
'; // on calcule et enregistre le prix d'achat discount du produit si on a un prc_discount let chkDiscount = false; if (val['prc_discount_6'] > 0 && val['quantite_6'] > 0) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_6'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_6'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_6 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_5'] > 0 && val['quantite_5'] > 0 && !chkDiscount) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_5'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_5'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_5 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_4'] > 0 && val['quantite_4'] > 0 && !chkDiscount) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_4'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_4'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_4 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_3'] > 0 && val['quantite_3'] > 0 && !chkDiscount) { // il y a un prc_discount sur ce produit if (parseInt(val['qte']) >= parseInt(val['quantite_3'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_3'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_3 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_2'] > 0 && val['quantite_2'] > 0 && !chkDiscount) { if (parseInt(val['qte']) >= parseInt(val['quantite_2'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_2'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_2 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (val['prc_discount_1'] > 0 && val['quantite_1'] > 0 && !chkDiscount) { if (parseInt(val['qte']) >= parseInt(val['quantite_1'])) { // on applique le prc_discount const prixAchat = (val['prix_achat_net'] * 1 * (val['prc_discount_1'] * 1)) / 100; // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne newRowPro.dataset.achatdiscount = prixAchat; chkDiscount = true; console.log('showDevisPro prc_discount_1 appliqué pour la ref ' + val['code'] + ' : ' + prixAchat); } } if (chkRegleSeuilsMarge == 1) { // Le marché demande la prise en compte des seuils de marge RR et DV paramétrés dans la table produits_familles if (val['marge_rr'] != seuilMargeRR) seuilMargeRR = val['marge_rr']; if (val['marge_dv'] != seuilMargeDV) seuilMargeDV = val['marge_dv']; console.log("C'est un marché qui prend en compte les seuils de marge RR et DV : " + seuilMargeRR + ' / ' + seuilMargeDV); } } } // On met à jour les seuils de marge RR et DV en fonction du marché et des produits document.getElementById('inp_latitudeRR').value = seuilMargeRR; document.getElementById('inp_latitudeDV').value = seuilMargeDV; // On simule le changement de quantité sur la première ligne pour recalculer les totaux const inpQte = document.getElementById('inpQte_' + fkProduit1); const event = new Event('change'); inpQte.dispatchEvent(event); } } let showCommentProd = function () { console.log('click sur le SVG commentProd de la ligne ' + this.dataset.rid); document.getElementById('inp_commentProdId').value = this.dataset.rid; document.getElementById('modCommentProdTitre').innerHTML = 'Commentaire sur le produit ' + this.dataset.code; const inpComment = document.getElementById('inp_commentProd'); inpComment.value = document.getElementById('inpCom_' + this.dataset.rid).value; showModal(document.getElementById('modalCommentProd')); inpComment.focus(); return false; }; function controlRemisesProduits(totalHT) { // Contrôle des remises du marché en fonction du total HT du devis // on arrondit le total HT à l'entier supérieur const totHT = Math.ceil(totalHT); // la remise calculée sur ce devis et à appliquer let txRemiseAppliquee = 0.0; // aRemisesMarches = [line.remise_palier_1, line.remise_taux_1, line.remise_palier_2, line.remise_taux_2, line.remise_palier_3, line.remise_taux_3, line.remise_palier_4, line.remise_taux_4]; const totPalier1 = aRemisesMarches[0] * 1000; const txPalier1 = aRemisesMarches[1]; const totPalier2 = aRemisesMarches[2] * 1000; const txPalier2 = aRemisesMarches[3]; const totPalier3 = aRemisesMarches[4] * 1000; const txPalier3 = aRemisesMarches[5]; const totPalier4 = aRemisesMarches[6] * 1000; const txPalier4 = aRemisesMarches[7]; console.log('controlRemisesProduits totHT : ' + totHT + ' totPalier1 : ' + totPalier1 + ' txPalier1 : ' + txPalier1); if (txPalier1 == 0 && txPalier2 == 0 && txPalier3 == 0 && txPalier4 == 0) { // pas de remise sur ce marché return 0; } else { if (totPalier1 >= 0 && txPalier1 > 0) { // il y a une remise sur ce marché à partir de ce total HT, si ce total est atteint on applique la remise console.log('controlRemisesProduits totHT : ' + totHT + ' totPalier1 : ' + totPalier1 + ' txPalier1 : ' + txPalier1); if (totHT >= totPalier1) txRemiseAppliquee = txPalier1; } if (totPalier2 > 0 && txPalier2 > 0) { // il y a une remise sur ce marché à partir de ce total HT, si ce total est atteint on applique la remise if (totHT >= totPalier2) txRemiseAppliquee = txPalier2; } if (totPalier3 > 0 && txPalier3 > 0) { // il y a une remise sur ce marché à partir de ce total HT, si ce total est atteint on applique la remise if (totHT >= totPalier3) txRemiseAppliquee = txPalier3; } if (totPalier4 > 0 && txPalier4 > 0) { // il y a une remise sur ce marché à partir de ce total HT, si ce total est atteint on applique la remise if (totHT >= totPalier4) txRemiseAppliquee = txPalier4; } console.log('controlRemisesProduits totalHT : ' + totHT + ' txRemiseAppliquee : ' + txRemiseAppliquee); return txRemiseAppliquee; } } function getPrixAchatAvecDiscount(idProduit, qte) { let trPro = document.getElementById('trPro_' + idProduit); const prixAchat = parseFloat(trPro.dataset.achat); const qtt = parseInt(qte, 10); // console.log("==== Début de getPrixAchatAvecDiscount pour la ref " + trPro.dataset.code + " : prix achat " + prixAchat + " et qte " + qtt); let prixAchatDiscount = prixAchat; let discount = []; discount[1] = Array(parseFloat(trPro.dataset.discount1), parseInt(trPro.dataset.quantite1, 10)); discount[2] = Array(parseFloat(trPro.dataset.discount2), parseInt(trPro.dataset.quantite2, 10)); discount[3] = Array(parseFloat(trPro.dataset.discount3), parseInt(trPro.dataset.quantite3, 10)); discount[4] = Array(parseFloat(trPro.dataset.discount4), parseInt(trPro.dataset.quantite4, 10)); discount[5] = Array(parseFloat(trPro.dataset.discount5), parseInt(trPro.dataset.quantite5, 10)); discount[6] = Array(parseFloat(trPro.dataset.discount6), parseInt(trPro.dataset.quantite6, 10)); for (let inc = 6; inc > 0; inc--) { const dscnt = discount[inc][0]; const qntt = discount[inc][1]; if (dscnt > 0 && qntt > 0) { if (qtt >= qntt) { // on applique le prc_discount prixAchatDiscount = prixAchat - (prixAchat * dscnt) / 100; console.log('=== idProduit : ' + idProduit + ' (prc_discount_' + inc + ' applique de ' + dscnt + '%, qte : ' + qtt + ' pour une quantité mini de ' + qntt + ') = ' + prixAchatDiscount); break; } } } // On met à jour le nouveau prix achat de ce produit dans le dataset achatdiscount de sa ligne trPro.dataset.achatdiscount = prixAchatDiscount.toFixed(2); // console.log("==== Fin de getPrixAchatAvecDiscount pour la ref " + trPro.dataset.code); return prixAchatDiscount; } let calculDevis = function () { console.log('calculDevis...'); const idProduit = this.dataset.rid; // On récupère toutes les infos de ce produit au niveau de sa ligne trPro_XX stockées en dataset let trPro = document.getElementById('trPro_' + idProduit); const code = trPro.dataset.code; let prixAchat = trPro.dataset.achat; const prixVente = trPro.dataset.vente; // console.log("idProduit: " + idProduit + ", code : " + code + ", prixAchat: " + prixAchat + ", prixVente: " + prixVente); let qte = 0; let remise = 0; let variante = 0; let typeInput = ''; // qte, remise, variante if (this.name.indexOf('inpQte') > -1) { // c'est la quantité qui a changé qte = this.value; remise = document.getElementById('inpRemise_' + idProduit).value; variante = document.getElementById('chkVariante_' + idProduit).checked; typeInput = 'qte'; } else if (this.name.indexOf('inpRemise') > -1) { // c'est la remise qui a changé qte = document.getElementById('inpQte_' + idProduit).value; remise = this.value; variante = document.getElementById('chkVariante_' + idProduit).checked; typeInput = 'remise'; chkSaisieRemise = true; } else if (this.name.indexOf('chkVariante') > -1) { // c'est la variante qui a changé qte = document.getElementById('inpQte_' + idProduit).value; remise = document.getElementById('inpRemise_' + idProduit).value; variante = this.checked; typeInput = 'variante'; } let totalHt = 0; let totalDevisHt = 0; let totalDevisHtRemise = 0; let txMarge = 0; let coutTotalAchat = 0; let margeTotale = 0; // on calcule le total HT de cette ligne let remiseProduit = 0; if (variante) { remiseProduit = 0; totalHt = 0; } else { remiseProduit = (remise * 1 * (prixVente * 1)) / 100; totalHt = (prixVente * 1 - remiseProduit * 1) * (qte * 1); } let inpHT = document.getElementById('inpHT_' + idProduit); inpHT.value = parseFloat(totalHt).toFixed(2); // Modif du 25/04 : on calcule la marge même si c'est une variante / option //if (variante) { txMarge = 0; //} else { if (prixAchat !== '0.00' && prixVente !== '0.00' && qte > 0) { let prixVenteApresRemise = prixVente; if (remise > 0) { prixVenteApresRemise = prixVente - (prixVente * 1 * (remise * 1)) / 100; } console.log('Marge sur code : ' + code + ' - prixAchat : ' + prixAchat + ' - prixVente : ' + prixVente + ' - prixVenteApresRemise : ' + prixVenteApresRemise); txMarge = ((prixVenteApresRemise * 1 - prixAchat * 1) / prixVenteApresRemise) * 100; } else { txMarge = 0; console.log('ERREUR idProduit : ' + idProduit + ', code : ' + code + ' - prixAchat : ' + prixAchat + ' - prixVente : ' + prixVente); if (qte > 0) showNotification('Info', "Le prix d'achat et/ou le prix de vente n'est pas renseigné pour ce produit, la marge ne peut pas être calculée.", 'info'); } //} let inpMG = document.getElementById('inpMG_' + idProduit); inpMG.value = parseFloat(txMarge).toFixed(2); console.log('Boucle 1 : calcul Total HT sans remise'); //! on boucle sur tous les éléments dont le name commence par inpQte_ pour calculer le total HT sans remise for (let i = 0, elInp; (elInp = document.querySelectorAll("[name ^= 'inpQte_' ]")[i]); i++) { const idProd = elInp.dataset.rid; const ligne = document.getElementById('trPro_' + idProd); const code = ligne.dataset.code; const vente = ligne.dataset.vente * 1; const qte = elInp.value * 1; // Mise à jour du 09/11 : on calcule le prix d'achat du produit avec éventuel discount suivant sa qté const achat = getPrixAchatAvecDiscount(idProd, elInp.value); // Fin de la mise à jour du 09/11 const varOption = document.getElementById('chkVariante_' + idProd).checked; if (!varOption) { // calcul avec juste la quantité et le prix de vente const vente = elInp.dataset.vente * 1; totalDevisHt += qte * vente; } // Mise à jour du 03/11/2023 : nouvelle colonne du Prix de Vente Unitaire (avec remise) // On met à jour le PUVenteRem sur la ligne const remProd = document.getElementById('inpRemise_' + idProd).value; const remise = remProd * 1; let puVenteApresRemise = vente; if (remise > 0) { puVenteApresRemise = vente - (vente * remise) / 100; } document.getElementById('inpPUVenteRem_' + idProd).value = puVenteApresRemise.toFixed(2); console.log('--- 1 Produit : ' + code + ' - PUVenteApresRemise : ' + puVenteApresRemise); // Fin de la mise à jour du 03/11/2023 // Modif du 25/04 : on calcule la marge même si c'est une variante / option //if (variante) { txMarge = 0; //} else { if (achat > 0 && vente > 0 && qte > 0) { let venteApresRemise = vente; if (remise > 0) { venteApresRemise = vente - (vente * remise) / 100; } console.log('--- 1 Marge sur : ' + code + ' - achat : ' + achat + ' - vente : ' + vente + ' - venteApresRemise : ' + venteApresRemise); // Ajout du 0&/12/2023 pour éviter le -infinity if (venteApresRemise > 0) txMarge = ((venteApresRemise - achat) / venteApresRemise) * 100; } else { txMarge = 0; console.log('--- 1 ERREUR : ' + code + ' - achat : ' + achat + ' - vente : ' + vente); if (qte > 0) showNotification('Info', "Le prix d'achat et/ou le prix de vente n'est pas renseigné pour ce produit " + code + ', la marge ne peut pas être calculée.', 'info'); } //} let inpMG = document.getElementById('inpMG_' + idProd); inpMG.value = parseFloat(txMarge).toFixed(2); } // on met à jour le total HT du devis avant remise let inpTotalHT = document.getElementById('inpTotalHT'); inpTotalHT.value = totalDevisHt.toFixed(2); // le total HT du devis a été recalculé, on contrôle les remises sur les produits du devis let txRemiseAppliquee = 0; txRemiseAppliquee = controlRemisesProduits(totalDevisHt); // On réinitialise le flag chkRemisesMarche if (txRemiseAppliquee > 0) { // on a une remise de base sur ce devis, par défaut toutes les lignes produits respectent cette remise chkRemisesMarche = true; } else { chkRemisesMarche = false; } console.log('calculDevis txRemiseAppliquee : ' + txRemiseAppliquee + ' & chkRemisesMarche : ' + chkRemisesMarche); console.log('Boucle 2 : calcul du total HT du devis apres remise'); //! on boucle sur tous les éléments dont le name commence par inpRemise_ pour calculer le total HT avec remise for (let i = 0, elInp; (elInp = document.querySelectorAll("[name ^= 'inpRemise_' ]")[i]); i++) { // calcul avec la quantité, le prix de vente et la remise const idProd = elInp.dataset.rid; const ligne = document.getElementById('trPro_' + idProd); const vente = ligne.dataset.vente * 1; const achat = ligne.dataset.achatdiscount * 1; const rem = elInp.value * 1; let remise = 0; if (txRemiseAppliquee > 0) { if (rem == txRemiseAppliquee) { // cette ligne produit a un taux de remise identique à la remise de base du devis remise = txRemiseAppliquee * 1; } else { // cette ligne produit a un taux de remise différent de la remise de base du devis, on remet la remise de base du devis // elInp.value = txRemiseAppliquee; // remise = txRemiseAppliquee * 1; // cette remise est différente de la remise de base du devis, le devis ne respecte pas la remise de base, // donc on peut envoyer le devis à validation si la marge n'est pas bonne chkRemisesMarche = false; remise = elInp.value * 1; } // elInp.readOnly = true; } else { remise = elInp.value * 1; // elInp.readOnly = false; } const varOption = document.getElementById('chkVariante_' + idProd).checked; if (!varOption) { const inpQte = document.getElementById('inpQte_' + idProd); const qte = inpQte.value; const remiseProduit = (remise * vente) / 100; console.log('--- 2 remiseProduit : ' + remise + ' * ' + vente + ' / 100 = ' + remiseProduit); totalDevisHtRemise += (vente - remiseProduit) * (qte * 1); coutTotalAchat += achat * 1 * (qte * 1); console.log('--- 2 ligne code ' + ligne.dataset.code + ' = idProd : ' + idProd + ', vente : ' + vente + ', achat : ' + achat + ', qté : ' + qte + ', remise : ' + remise + ', remiseProduit : ' + remiseProduit); } } let inpTotalRemHT = document.getElementById('inpTotalRemHT'); inpTotalRemHT.value = formatAmount(totalDevisHtRemise); // on met à jour la marge totale let totalRFA = 0; // on prend le total HT après remise // on recherche une RFA sur ce marché // console.log("RFA TRIM : " + document.getElementById("tdTxRemiseTrim").textContent); const RFAtrimestrielle = parseFloatFromPercentageString(document.getElementById('tdTxRemiseTrim').textContent); if (RFAtrimestrielle > 0) { totalRFA = totalDevisHtRemise * (RFAtrimestrielle / 100); } else { // console.log("RFA SEME : " + document.getElementById("tdTxRemiseSeme").textContent); const RFAsemestrielle = parseFloatFromPercentageString(document.getElementById('tdTxRemiseSeme').textContent); if (RFAsemestrielle > 0) { totalRFA = totalDevisHtRemise * (RFAsemestrielle / 100); } else { // console.log("RFA ANNU : " + document.getElementById("tdTxRemiseAnnu").textContent); const RFAannuelle = parseFloatFromPercentageString(document.getElementById('tdTxRemiseAnnu').textContent); if (RFAannuelle > 0) { // console.log("On prend en compte la RFAannuelle : " + RFAannuelle + " & totalDevisHtRemise : " + totalDevisHtRemise); totalRFA = totalDevisHtRemise * (RFAannuelle / 100); } } } console.log('CoutTotalAchat affiché : ' + coutTotalAchat + ', totalRFA : ' + totalRFA); // on ajoute le coût total de la RFA au total Achat coutTotalAchat += totalRFA; // et on calcule la marge totale if (totalDevisHtRemise > 0) { margeTotale = ((totalDevisHtRemise - coutTotalAchat) / totalDevisHtRemise) * 100; } else { margeTotale = 0; } let inpTotalMG = document.getElementById('inpTotalMarge'); inpTotalMG.value = margeTotale.toFixed(2); console.log('margeTotale : ' + margeTotale + ', soit (totalDevisHtRemise : ' + totalDevisHtRemise + ' - coutTotalAchat : ' + coutTotalAchat + ') / totalDevisHtRemise = ' + (totalDevisHtRemise - coutTotalAchat) / totalDevisHtRemise); if (devIp == '1') { let inpCoutTotalAchat = document.getElementById('inpCoutTotalAchat'); inpCoutTotalAchat.value = coutTotalAchat.toFixed(2); } // on renseigne les valeurs globales de ces 3 données mises à jour devisTotalHT = totalDevisHt; devisTotalRemHT = totalDevisHtRemise; devisTotalMarge = margeTotale; // Si la marge Totale est inférieure au seuil de latitude, on change le bouton en orange ou rouge const latitudeRR = document.getElementById('inp_latitudeRR').value; const latitudeDV = document.getElementById('inp_latitudeDV').value; let btn = document.getElementById('btnSaveDevisAndSend'); // enfin, on met à jour le bouton de sauvegarde du devis updateBtnSaveDevisAndSend(); chkChange = 1; }; let clickSaveDevis = function () { showLoading(); let frmData = new FormData(document.getElementById('frmDevis')); let objData = {}; frmData.forEach(function (value, key) { objData[key] = value; }); fetch('/jxdevis/save_devis', { method: 'POST', body: JSON.stringify(objData), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }).then(function (response) { if (!response.ok) { hideLoading(); showNotification('Erreur', "L'enregistrement du devis n'a pas abouti", 'error'); } else { const ret = response.json(); ret.then(function (data) { hideLoading(); // On met à jour la ligne du tableau des devis let elLigDevis = document.getElementById('tr_' + data.rid); elLigDevis.cells[7].innerHTML = data.totalremht + ' €'; elLigDevis.cells[8].innerHTML = data.totalmarge + ' %'; showNotification('Devis enregistré', 'Le devis a bien été enregistré', 'success'); }); } }); chkChange = 0; return false; }; let clickSaveDevisAndSend = function () { const btnLibelle = this.innerHTML; // si le btnLibelle contient "SAP", on envoie le devis à SAP let confLibelle = ''; if (btnLibelle.indexOf('SAP') > -1) { confLibelle = 'traitement SAP ?'; //! On controle que ce devis ne soit pas en cours de validation et qu'il n'ait pas été validé ! if (fkRole < 3) { // Uniquement pour le DIR-CO et les DV if (fkStatutDevis == 2 || fkStatutDevis == 3) { if (chkValidat == 0) { showNotification('Erreur', "Ce devis est en cours de validation et n'a pas encore été validé. Vous devez d'abord saisir un commentaire de validation et cliquer sur le bouton 'Valider ce devis'", 'error'); return false; } } } } else if (btnLibelle.indexOf('DIR-CO') > -1) { confLibelle = 'accord DIR-CO ?'; } else if (btnLibelle.indexOf('DV/DCG') > -1) { confLibelle = 'accord DV/DCG ?'; } if (confirm('Voulez-vous enregistrer et envoyer ce devis pour ' + confLibelle)) { clickSaveDevis(); //! on modifie le statut du devis pour le passer à "2 : en cours de validation" ou en "3: validé" let data = {}; data['cid'] = idDevis; data['statut'] = this.getAttribute('data-statut'); data['commentaire'] = 'Devis enregistré et transmis'; fetch('/jxdevis/statut_devis', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); showNotification('Devis enregistré', 'Le devis a bien été enregistré et transmis', 'success'); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes } return false; }; let clickValDevis = function () { // Le RR visualise le PDF SAP pour le valider ou non idDevis = this.getAttribute('data-rid'); fetch('/jximport/get_files', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, body: JSON.stringify({ cid: idDevis, sup: 'devis_pdf_sap', }), }).then(function (response) { if (response.ok) { const ret = response.json(); ret.then(function (data) { if (data.length > 0) { for (let key in data) { if (data.hasOwnProperty(key)) { // Récupération des valeurs de la ligne let val = data[key]; // On ajoute le lien vers le fichier const leFichier = val['dir0'] + val['fichier']; const elLien = document.getElementById('embPdfSAP'); elLien.setAttribute('src', leFichier); // On affiche le formulaire de validation frmValidationRR document.getElementById('frmValidationRR').style.display = 'block'; document.getElementById('btnClosePDF').style.display = 'none'; showModal(document.getElementById('modalPDFSAP')); break; } } } else { showNotification('Erreur', "Aucun fichier PDF SAP n'a été trouvé", 'error'); } }); } }); return false; }; let clickPdfDevis = function () { idDevis = this.getAttribute('data-rid'); fetch('/jximport/get_files', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, body: JSON.stringify({ cid: idDevis, sup: 'devis_pdf_sap', }), }).then(function (response) { if (response.ok) { const ret = response.json(); ret.then(function (data) { if (data.length > 0) { for (let key in data) { if (data.hasOwnProperty(key)) { // Récupération des valeurs de la ligne let val = data[key]; // On ajoute le lien vers le fichier const leFichier = val['dir0'] + val['fichier']; const elLien = document.getElementById('embPdfSAP'); elLien.setAttribute('src', leFichier); // On cache le formulaire de validation frmValidationRR document.getElementById('frmValidationRR').style.display = 'none'; document.getElementById('btnClosePDF').style.display = 'block'; showModal(document.getElementById('modalPDFSAP')); break; } } } else { showNotification('Erreur', "Aucun fichier PDF SAP n'a été trouvé", 'error'); } }); } }); return false; }; let clickValidationRR = function () { const inpCommentPDFSAP = document.getElementById('inpCommentPDFSAP'); // if (inpCommentPDFSAP.value == "") { // showNotification("Validation impossible", "Vous devez saisir un commentaire", "error"); // return false; // } if (confirm('Confirmez-vous la validation de ce document et du devis ?')) { let data = {}; data['cid'] = idDevis; data['statut'] = 7; // 7 = devis validé par le RR, il est à envoyer au client par SAP data['commentaire'] = inpCommentPDFSAP.value ? inpCommentPDFSAP.value : 'Devis validé par le RR'; fetch('/jxdevis/statut_devis', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); showNotification('Devis validé', 'Le devis a bien été validé', 'success'); hideModal(document.getElementById('modalPDFSAP')); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes } }; let clickRefusRR = function () { const inpCommentPDFSAP = document.getElementById('inpCommentPDFSAP'); if (inpCommentPDFSAP.value == '') { showNotification('Refus impossible', 'Vous devez saisir un commentaire expliquant votre refus.', 'error'); return false; } if (confirm('Confirmez-vous le refus de ce document ?')) { let data = {}; data['cid'] = idDevis; data['statut'] = 4; // 4 = ddocument refusé par le RR, il revient à 4 à traiter par SAP data['commentaire'] = inpCommentPDFSAP.value; fetch('/jxdevis/statut_devis', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); showNotification('Document refusé', 'Refus enregistré, le devis revient à ADV', 'success'); hideModal(document.getElementById('modalPDFSAP')); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes } }; let clickCloseRR = function () { if (confirm('Voulez-vous fermer ce document PDF sans y répondre ?')) { hideModal(document.getElementById('modalPDFSAP')); } return false; }; let clickClosePDF = function () { hideModal(document.getElementById('modalPDFSAP')); return false; }; let clickValidatDevis = function () { // Un DV ou le DIR-CO valide le devis const inpCommentValidatDevis = document.getElementById('inpCommentValidatDevis'); // if (inpCommentValidatDevis.value == "") { // showNotification("Validation impossible", "Vous devez saisir un commentaire", "error"); // return false; // } if (fkRole == 2) { const libBtnSave = document.getElementById('btnSaveDevisAndSend').innerHTML; if (libBtnSave.indexOf('DIR-CO') > -1) { // Le DV veut valider un devis qui demande l'accord du DIR-CO showNotification('Validation impossible', "Vous devez demander l'accord du DIR-CO", 'error'); return false; } } if (confirm("Confirmez-vous la validation de ce devis ? Le devis sera transmis à l'ADV pour traitement SAP.")) { let data = {}; data['cid'] = idDevis; data['chk_validat'] = 1; data['commentaire'] = inpCommentValidatDevis.value ? inpCommentValidatDevis.value : '-'; fetch('/jxdevis/validat_devis', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); showNotification('Devis validé', 'Le devis a bien été validé.', 'success'); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes } }; let clickRefusDevis = function () { // Un DV ou le DIR-CO refuse le devis const inpCommentValidatDevis = document.getElementById('inpCommentValidatDevis'); if (inpCommentValidatDevis.value == '') { showNotification('Refus impossible', 'Vous devez saisir un commentaire expliquant votre refus', 'error'); return false; } if (confirm('Confirmez-vous le refus de ce devis ?')) { let data = {}; data['cid'] = idDevis; data['chk_validat'] = 0; data['commentaire'] = inpCommentValidatDevis.value; fetch('/jxdevis/validat_devis', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); showNotification('Devis refusé', 'Le devis a bien été refusé.', 'success'); setTimeout(function () { location.reload(); }, 2000); // 2000 millisecondes = 2 secondes } }; function autocompleteClient(input, list) { //! Autocomplete pour la recherche de client //Add an event listener to compare the input value with list items input.addEventListener('input', function () { //Close the existing list if it is open closeList(); //If the input is empty, exit the function if (!this.value) return false; //Create a suggestions
and add it to the element containing the input field suggestions = document.createElement('div'); suggestions.setAttribute('id', 'suggestionsClients'); suggestions.setAttribute('class', 'autocomplete-items'); this.parentNode.appendChild(suggestions); //Iterate through all entries in the list and find matches (15 max) let nbSuggestionsFound = 0; for (let i = 0; i < list.length; i++) { if (list[i]['rech'].toUpperCase().includes(this.value.toUpperCase())) { //If a match is found, create a suggestion
and add it to the suggestions
suggestion = document.createElement('div'); suggestion.innerHTML = list[i]['rech']; suggestion.addEventListener('click', function () { // on a cliqué sur une suggestion, on met à jour les champs du formulaire avec les infos du client input.value = list[i]['libelle']; // this.innerHTML; document.getElementById('inp_fk_client').value = list[i]['rowid']; document.getElementById('inp_adresse1').value = list[i]['adresse1']; document.getElementById('inp_adresse2').value = list[i]['adresse2']; document.getElementById('inp_adresse3').value = list[i]['adresse3']; document.getElementById('inp_cp').value = list[i]['cp']; document.getElementById('inp_ville').value = list[i]['ville']; document.getElementById('inp_contact_nom').value = list[i]['contact_nom']; document.getElementById('inp_contact_prenom').value = list[i]['contact_prenom']; document.getElementById('inp_contact_fonction').value = list[i]['contact_fonction']; document.getElementById('inp_telephone').value = list[i]['telephone']; document.getElementById('inp_email').value = list[i]['email']; document.getElementById('inp_mobile').value = list[i]['mobile']; document.getElementById('selTypeEtab').value = list[i]['type_client']; // on ferme la liste des suggestions closeList(); }); suggestion.style.cursor = 'pointer'; suggestion.style.backgroundColor = 'lightyellow'; suggestions.appendChild(suggestion); nbSuggestionsFound++; if (nbSuggestionsFound > 15) break; } } }); function closeList() { let suggestions = document.getElementById('suggestionsClients'); if (suggestions) suggestions.parentNode.removeChild(suggestions); } } function autocompleteProdSpecial(input, num, list) { //! Autocomplete pour la recherche de produit spécial //Add an event listener to compare the input value with list items input.addEventListener('input', function () { //Close the existing list if it is open closeList(); //If the input is empty, exit the function if (!this.value) return false; //Create a suggestions
and add it to the element containing the input field suggestions = document.createElement('div'); suggestions.setAttribute('id', 'suggestionsProdSpecial'); suggestions.setAttribute('class', 'autocomplete-items'); this.parentNode.appendChild(suggestions); //Iterate through all entries in the list and find matches (15 max) let nbSuggestionsFound = 0; for (let i = 0; i < list.length; i++) { if (list[i]['code'].toUpperCase().includes(this.value.toUpperCase())) { //If a match is found, create a suggestion
and add it to the suggestions
suggestion = document.createElement('div'); suggestion.innerHTML = list[i]['rech']; suggestion.addEventListener('click', function () { // on a cliqué sur une suggestion, on met à jour les champs du formulaire avec les infos du client input.value = list[i]['code']; // this.innerHTML; document.getElementById('inp_specialFkProduit_' + num).value = list[i]['rowid']; document.getElementById('inp_specialLibe_' + num).value = list[i]['libelle']; // on ferme la liste des suggestions closeList(); }); suggestion.style.cursor = 'pointer'; suggestion.style.backgroundColor = 'lightyellow'; suggestions.appendChild(suggestion); nbSuggestionsFound++; if (nbSuggestionsFound > 15) break; } } }); function closeList() { let suggestions = document.getElementById('suggestionsProdSpecial'); if (suggestions) suggestions.parentNode.removeChild(suggestions); } } let searchProducts = function (el) { //! L'utilisateur vient de taper au clavier dans un champ de recherche de produit if (el.keyCode === 13) { showLoading(); const searchTerm = this.value; const libIdFamille = this.id.substring(this.id.indexOf('_') + 1); const idFamille = this.getAttribute('data-idFamille'); fetchSearchProducts(searchTerm, libIdFamille, idFamille); hideLoading(); return false; } }; let autocompleteProduitsFamille = function (input, list, libIdFamille, idFamille) { //! Autocomplete pour la recherche de produits d'une famille input.addEventListener('input', function () { // si l'input est vide, on sort if (!this.value) { showProduitsFamille(list, libIdFamille); return false; } // ou si sa longueur est inférieure à 2 caractères if (this.value.length < 2) return false; let tblBody = document.getElementById('tblProduits_' + libIdFamille).getElementsByTagName('tbody')[0]; tblBody.innerHTML = ''; let nbSuggestionsFound = 0; for (let key in list) { if (list.hasOwnProperty(key)) { // Récupération des valeurs de la ligne let val = list[key]; if (val['rech'].toUpperCase().includes(this.value.toUpperCase())) { // On affiche la ligne showLineProduitFamille(tblBody, val, libIdFamille); } } } }); return false; }; // Use a MutationObserver to monitor the DOM for changes let observerInputSearchProducts = new MutationObserver(function (mutationsList) { for (var mutation of mutationsList) { if (mutation.type === 'childList') { // If new nodes have been added to the DOM, attach event listeners to any new input elements var addedNodes = mutation.addedNodes; for (var node of addedNodes) { if (node instanceof HTMLElement) { var newInputs = node.querySelectorAll("input[id^='inpSearchProduct_']"); newInputs.forEach(function (newInput) { newInput.addEventListener('keyup', function () { console.log('keyup sur input de recherche de produit'); if (event.keyCode === 13) { let searchTerm = newInput.value; let libIdFamille = newInput.id.split('_')[1]; let idFamille = newInput.getAttribute('data-idFamille'); fetchSearchProducts(searchTerm, libIdFamille, idFamille); } }); }); } } } } }); function fetchSearchProducts(searchTerm, libIdFamille, idFamille) { // On cherche les produits correspondant au terme de recherche dans dataProduitsMarche qui contient la liste des produits du marché par famille // On nettoie la liste des produits de la famille let tblBodyProduits = document.getElementById('tblProduits_' + libIdFamille).getElementsByTagName('tbody')[0]; tblBodyProduits.innerHTML = ''; // on boucle sur la liste des produits du marché en cours sur ce devis, et on charge les produits de cette famille qui correspondent au terme de recherche dataProduitsMarche.forEach(function (lineProduit) { if (lineProduit.fk_famille == idFamille && (lineProduit.libelle.toUpperCase().includes(searchTerm.toUpperCase()) || lineProduit.code.toUpperCase().includes(searchTerm.toUpperCase()))) { // TODO : il faut aussi vérifier que le produit n'est pas déjà dans la liste des produits sélectionnés // on vérifie que le produit n'est pas déjà dans la liste des produits sélectionnés let isProductAlreadySelected = false; const tblBodyProductsSelect = document.getElementById('tblProduitsSelect').getElementsByTagName('tbody')[0]; const rowsProductsSelect = tblBodyProductsSelect.getElementsByTagName('tr'); for (let i = 0; i < rowsProductsSelect.length; i++) { let rowProductSelect = rowsProductsSelect[i]; if (rowProductSelect.getAttribute('data-rid') == lineProduit.rowid) { isProductAlreadySelected = true; break; } } if (!isProductAlreadySelected) { // Insertion d'une nouvelle ligne et création de ses colonnes let newRow = tblBodyProduits.insertRow(0); newRow.className = 'ligProduit_' + libIdFamille; newRow.id = 'ligProduit_' + libIdFamille + '_' + lineProduit.rowid; newRow.setAttribute('data-rid', lineProduit.rowid); let celChkBox = newRow.insertCell(0); celChkBox.className = 'chkBox_' + libIdFamille + ' text-center'; celChkBox.setAttribute('data-rid', lineProduit.rowid); celChkBox.innerHTML = ''; let celCode = newRow.insertCell(1); celCode.innerHTML = lineProduit.code; let celLibelle = newRow.insertCell(2); celLibelle.innerHTML = lineProduit.libelle; let celFamille = newRow.insertCell(3); celFamille.innerHTML = lineProduit.lib_famille; } } }); } // Start observing the DOM for changes observerInputSearchProducts.observe(document.body, { childList: true, subtree: true, }); elInpCommentGesteComm.addEventListener('input', function () { updateBtnSaveDevisAndSend(); }); function refreshChat() { if (idDevis > 0) { //! On récupère les données de devis_histo fetch('/jxchat/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, body: JSON.stringify({ cid: idDevis, }), }).then((response) => { const ret = response.json(); ret.then(function (data) { // on vérifie si le nombre de commentaires a changé if (nbCommentChat != data.length) { // Si c'est le cas on rafraîchit tous les commentaires // On supprime tous les commentaires const chatContainer = document.getElementById('chat-bubbles'); const chatBubbles = chatContainer.getElementsByClassName('chat-bubble'); while (chatBubbles.length > 0) { chatBubbles[0].parentNode.removeChild(chatBubbles[0]); } // On ajoute tous les commentaires for (let i = 0; i < data.length; i++) { const chatBubble = document.createElement('div'); chatBubble.classList.add('chat-bubble'); if (data[i].fk_user == fkUser) { chatBubble.classList.add('right-chat-bubble'); } else { chatBubble.classList.add('left-chat-bubble'); } const initiales = data[i].prenom.substring(0, 1) + data[i].libelle.substring(0, 1); const userInfo = document.createElement('div'); userInfo.classList.add('user-info'); const userInitials = document.createElement('div'); userInitials.classList.add('user-initials'); userInitials.innerHTML = initiales; userInfo.appendChild(userInitials); const usernameDate = document.createElement('div'); usernameDate.classList.add('username-date'); const username = document.createElement('span'); username.classList.add('username'); username.innerHTML = data[i].prenom + ' ' + data[i].libelle; usernameDate.appendChild(username); const date = document.createElement('span'); date.classList.add('date'); date.innerHTML = data[i].date_histo; usernameDate.appendChild(date); userInfo.appendChild(usernameDate); chatBubble.appendChild(userInfo); const message = document.createElement('div'); message.classList.add('message'); message.innerHTML = data[i].commentaire; chatBubble.appendChild(message); chatContainer.appendChild(chatBubble); } nbCommentChat = data.length; // et on vide le champ de saisie s'il n'a pas le focus if (!document.getElementById('chatInputMessage').hasFocus) { document.getElementById('chatInputMessage').value = ''; } } const chatCont = document.getElementById('chat-container'); chatCont.style.display = 'block'; }); }); } } let chatSendMessage = function () { // On récupère le message à envoyer const message = document.getElementById('chatInputMessage').value; // On récupère l'id du devis // On envoie le message fetch('/jxchat/save_message', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', Accept: 'application/json;charset=utf-8', }, body: JSON.stringify({ cid: idDevis, message: message, fkuser: fkUser, }), }).then((response) => { const ret = response.json(); ret.then(function (data) { refreshChat(); }); }); }; const elSelectTypeClient = document.getElementById('selTypeEtab'); elSelectTypeClient.addEventListener('change', function () { document.getElementById('inp_type_client').value = this.value; }); function handleDragStart(e) { draggedElement = this; // on enregistre l'élément draggé this.style.opacity = '0.4'; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/html', this.outerHTML); console.log('DragStart : ' + this.dataset.code); } function handleDragOver(e) { if (e.preventDefault) { e.preventDefault(); } e.dataTransfer.dropEffect = 'move'; console.log('DragOver au-dessus de : ' + this.dataset.code); return false; } function handleDrop(e) { if (e.stopPropagation) { e.stopPropagation(); } console.log('Drop sur : ' + this.dataset.code); if (this !== e.target && this === e.target.parentNode) { console.log('Drop dedans'); const dropHTML = e.dataTransfer.getData('text/html'); this.insertAdjacentHTML('beforebegin', dropHTML); const dropElem = this.previousSibling; // Supprimer l'élément original draggedElement.remove(); // Update draggedElement to point to the new element draggedElement = dropElem; // Reset opacity and input states draggedElement.style.opacity = '1.0'; const inputs = draggedElement.querySelectorAll('input'); inputs.forEach((input) => (input.disabled = false)); // Ajouter un événement click à l'élément svg const svgElement = dropElem.querySelector('#commentProd_' + dropElem.dataset.rid); svgElement.addEventListener('click', showCommentProd); addDnDHandlers(dropElem); updateOrder(); } return false; } function addDnDHandlers(elem) { elem.addEventListener('dragstart', handleDragStart); elem.addEventListener('dragover', handleDragOver); elem.addEventListener('drop', handleDrop); } function updateOrder() { // Met à jour l'ordre des lignes dans le tableau des produits du devis var rows = document.querySelectorAll('#tblDevisPro tr'); rows.forEach((row, index) => { const sonCode = row.dataset.code; if (sonCode) { row.dataset.ordre = index + 1; const fkProduit = row.dataset.rid; console.log('index : ' + index + ' code : ' + sonCode); document.getElementById('inpOrdre_' + fkProduit).value = index; } }); showNotification('Ordre', "Ordre des lignes produits mis à jour. Pensez à enregistrer le devis pour que l'ordre soit bien pris en compte.", 'success'); } let clickCancelCommentProd = function () { hideModal(document.getElementById('modalCommentProd')); }; let clickSaveCommentProd = function () { // Sauvegarde du commentaire produit const fkProd = document.getElementById('inp_commentProdId').value; const commentaire = document.getElementById('inp_commentProd').value; document.getElementById('inpCom_' + fkProd).value = commentaire; const svgElement = document.getElementById('commentProd_' + fkProd); const svgNewColor = commentaire == '' ? 'lightgray' : 'red'; svgElement.querySelector('use').style.fill = svgNewColor; showNotification('Commentaire', 'Votre commentaire a bien été enregistré dans le tableau. Enregistrez votre devis pour sauvegarder définitivement le commentaire.', 'success'); hideModal(document.getElementById('modalCommentProd')); }; //! Configuration des événements //! Sur chaque cellule du tableau des devis ayant la classe celDevis, on affecte un événement click qui appelle la fonction clickLigDevis() Array.from(elCelDevis).forEach(function (lnDevis) { lnDevis.addEventListener('click', clickLigDevis); }); Array.from(elCelArchives).forEach(function (lnArchives) { lnArchives.addEventListener('click', clickLigArchives); }); //! Sur chaque bouton de modification du tableau des marchés ayant la classe btnModMarche, on affecte un événement click qui appelle la fonction clickModMarche() Array.from(elBtnDupDevis).forEach(function (dupDevis) { dupDevis.addEventListener('click', clickDupDevis); }); Array.from(elBtnExpExcelDevis).forEach(function (expExcelDevis) { expExcelDevis.addEventListener('click', clickExpExcelDevis); }); Array.from(elBtnValDevis).forEach(function (valDevis) { valDevis.addEventListener('click', clickValDevis); }); Array.from(elBtnPdfDevis).forEach(function (pdfDevis) { pdfDevis.addEventListener('click', clickPdfDevis); }); Array.from(elBtnSupprDevis).forEach(function (supprDevis) { supprDevis.addEventListener('click', clickSupprDevis); }); elBtnDevisArchives.addEventListener('click', clickDevisArchives); elBtnCreateDevis.addEventListener('click', clickCreateDevis); elBtnValidationRR.addEventListener('click', clickValidationRR); elBtnRefusRR.addEventListener('click', clickRefusRR); elBtnCloseRR.addEventListener('click', clickCloseRR); elBtnClosePDF.addEventListener('click', clickClosePDF); elBtnCreateClient.addEventListener('click', clickCreateClient); elBtnCancelCreateClient.addEventListener('click', clickCancelCreateClient); elBtnSaveCreateClient.addEventListener('click', clickSaveCreateClient); elBtnSaveEnTete.addEventListener('click', clickSaveEnTete); elBtnSpeciaux.addEventListener('click', clickSpeciaux); elBtnCancelSpeciaux.addEventListener('click', clickCancelSpeciaux); elBtnSaveSpeciaux.addEventListener('click', clickSaveSpeciaux); elBtnSaveSelProduits.addEventListener('click', clickSaveSelProduits); elBtnSaveDevis.addEventListener('click', clickSaveDevis); elBtnSaveDevisAndSend.addEventListener('click', clickSaveDevisAndSend); elChkClientsSecteur.addEventListener('change', changeClientsSecteur); elChatBtnSend.addEventListener('click', chatSendMessage); elBtnCancelCommentProd.addEventListener('click', clickCancelCommentProd); elBtnSaveCommentProd.addEventListener('click', clickSaveCommentProd); Array.from(elInputSearchProducts).forEach(function (inpSearch) { inpSearch.addEventListener('keyup', searchProducts); }); Array.from(elInputQtes).forEach(function (inpQte) { inpQte.addEventListener('change', calculDevis); }); Array.from(elInputRemises).forEach(function (inpRemise) { inpRemise.addEventListener('change', calculDevis); }); Array.from(elChkVariantes).forEach(function (chkVariante) { chkVariante.addEventListener('change', calculDevis); }); elBtnSideBarDevis.addEventListener('click', function () { if (elVerticalBar.style.width == '10px') { elVerticalBar.style.width = '1100px'; // Largeur de la barre lorsqu'elle est ouverte // et son contenu est affiché document.getElementById('verticalBarContent').style.display = 'block'; intervalRefresh = setInterval(refreshChat, 6000); // Refresh every 6 seconds (1000 ms = 1 second) } else { elVerticalBar.style.width = '10px'; // Largeur de la barre lorsqu'elle est fermée // et son contenu est caché document.getElementById('verticalBarContent').style.display = 'none'; setTimeout(function () { clearInterval(intervalRefresh); }, 1000); } }); });