Files
Cleo/pub/res/js/jdevis.js
Pierre 443b0509df feat(v2.0.2): Corrections de sécurité critiques et fonctionnalité de réactivation des devis
- Correction de 14 vulnérabilités SQL (8 critiques, 6 moyennes)
- Suppression de la fonction autocomplete non utilisée
- Migration complète vers PDO avec requêtes préparées
- Ajout du bouton 'Réactiver' pour les devis archivés (statut 20 → 1)
- Conversion des appels $.ajax en fetch API (vanilla JS)
- Correction des erreurs JavaScript empêchant l'attachement d'événements
- Mise à jour de la documentation (README.md et TODO.md)

Sécurité: Utilisation systématique de intval() et requêtes préparées PDO
UI: Nouveau bouton vert dans la grille 2x2 des actions sur devis archivés
Historique: Traçabilité dans devis_histo lors de la réactivation
2025-09-12 20:25:48 +02:00

3401 lines
139 KiB
JavaScript

//! 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 elBtnReactiverDevis = document.getElementsByClassName('btnReactiverDevis')
console.log('Nombre de boutons btnReactiverDevis trouvés:', elBtnReactiverDevis.length)
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 = '<label for >Validation du devis</label>'
let celComment = newRowComment.insertCell(1)
celComment.classList.add('w-60')
celComment.innerHTML =
'<input type="txt" class="form-control w-100" id="inpCommentValidatDevis" name="commentValidatDevis" value="' +
data.comment_validat +
'" />'
let celBtnValid = newRowComment.insertCell(2)
celBtnValid.classList.add('w-40')
celBtnValid.innerHTML =
'<div class="btn-group"><button type="button" class="btn btn-success mr-1" id="btnValidatDevis" name="btnValidatDevis">Valider ce devis</button>'
celBtnValid.innerHTML +=
'<button type="button" class="btn btn-danger" id="btnRefusDevis" name="btnRefusDevis">Refuser ce devis</button></div>'
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 =
'<span data-after-text="Sp" data-after-type="blue circle">Modifier les produits spéciaux </span>'
btnSpeciaux.classList.remove('btn-warning')
btnSpeciaux.classList.add('btn-info')
} else {
btnSpeciaux.innerHTML =
'<span data-after-text="Sp" data-after-type="orange circle">Ajouter des produits spéciaux </span>'
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 =
'<input type="checkbox" class="chkBox" name="chkProdSelect_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-code="' +
val['code'] +
'" data-libelle="' +
val['libelle'] +
'" value="' +
val['fk_produit'] +
'"/>'
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 =
'<svg id="commentProd_' +
val['fk_produit'] +
'" class="clickable" data-rid="' +
val['fk_produit'] +
'" data-code="' +
val['code'] +
'"><use xlink:href="pub/res/svg/icons.svg#message" style="fill: ' +
svgColor +
';"/></svg>'
let inputOrdreHidden =
'<input type="hidden" id="inpOrdre_' +
val['fk_produit'] +
'" name="inpOrdre_' +
val['fk_produit'] +
'" value="' +
val['ordre'] +
'" />'
let inputCommentHidden =
'<input type="hidden" id="inpCom_' +
val['fk_produit'] +
'" name="inpCom_' +
val['fk_produit'] +
'" value="' +
val['commentaire'] +
'" />'
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 =
'<input type="number" class="form-control numeric" id="inpQte_' +
val['fk_produit'] +
'" name="inpQte_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-achat="' +
val['prix_achat_net'] +
'" data-vente="' +
val['prix_vente'] +
'" value="' +
val['qte'] +
'" min="0" max="1000" step="1" style="width: 100px; text-align: right;"><input type="hidden" name="achat_' +
val['fk_produit'] +
'" value="' +
val['prix_achat_net'] +
'"/><input type="hidden" name="vente_' +
val['fk_produit'] +
'" value="' +
val['prix_vente'] +
'"/>'
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 =
'<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' +
val['fk_produit'] +
'" name="inpRemise_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-achat="' +
val['prix_achat_net'] +
'" data-vente="' +
val['prix_vente'] +
'" value="' +
remiseProduit +
'" min="0" max="100" step="1" style="width: 80px; text-align: right;" ' +
readonlyRemiseProduit +
'/><div class="input-group-addon">%</div></div>'
if (readonlyRemiseProduit == '') {
document.getElementById('inpRemise_' + val['fk_produit']).addEventListener('change', calculDevis)
}
// nouvelle colonne PU vente avec remise
let celPUVenteRemPro = newRowPro.insertCell(5)
celPUVenteRemPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpPUVenteRem_' +
val['fk_produit'] +
'" name="inpPUVenteRem_' +
val['fk_produit'] +
'" value="' +
formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
// Fin nouvelle colonne
let celHTPro = newRowPro.insertCell(6)
celHTPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' +
val['fk_produit'] +
'" name="inpHT_' +
val['fk_produit'] +
'" value="' +
formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
let celVariante = newRowPro.insertCell(7)
celVariante.className = 'text-center'
celVariante.innerHTML =
'<input type="checkbox" id="chkVariante_' +
val['fk_produit'] +
'" name="chkVariante_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-achat="' +
val['prix_achat_net'] +
'" data-vente="' +
val['prix_vente'] +
'" value="' +
val['chk_variante'] +
'" ' +
(val['chk_variante'] == 1 ? 'checked' : '') +
' />'
document.getElementById('chkVariante_' + val['fk_produit']).addEventListener('change', calculDevis)
let celMargePro = newRowPro.insertCell(8)
celMargePro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpMG_' +
val['fk_produit'] +
'" name="inpMG_' +
val['fk_produit'] +
'" value="' +
val['marge'] +
'" style="width: 80px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">%</div></div>'
// 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 = '<bolder>Informations du marché ' + line.libelle + '</bolder>' // 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 += ' <textalert>(PRIX NETS)</textalert>'
}
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) + '<br/>' + titreOnglet.substring(espace + 1)
onglet.innerHTML = titreOnglet
} else {
const longueur = titreOnglet.length
if (longueur < 10) {
onglet.innerHTML = titreOnglet + '<br><br>'
}
}
}
})
// 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 =
'<input type="checkbox" class="chkBox" id="chkBoxProd_' +
lineProduit.rowid +
'" name="chkBoxProd_' +
lineProduit.rowid +
'" data-rid="' +
lineProduit.rowid +
'" data-code="' +
lineProduit.code +
'" data-libelle="' +
lineProduit.libelle +
'" data-famille="' +
libIdFamille +
'" />'
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()
const formData = new FormData()
formData.append('rid', idDevis)
fetch('/jxdevis/duplic_devis', {
method: 'POST',
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error('Erreur réseau')
}
return response.text()
})
.then((data) => {
showNotification('Duplication', 'Duplication du devis effectuée avec succès', 'success')
hideLoading()
setTimeout(function () {
location.reload()
}, 2000)
})
.catch((error) => {
showNotification('Erreur', 'Erreur lors de la duplication de ce devis : ' + error.message, 'error')
hideLoading()
})
}
}
function clickReactiverDevis(e) {
console.log('clickReactiverDevis appelé')
idDevis = this.getAttribute('data-rid')
console.log('ID du devis à réactiver:', idDevis)
if (
confirm(
'Voulez-vous réactiver ce devis archivé n° ' +
idDevis +
' ?\n\nLe devis passera du statut "Archivé" au statut "En cours".'
)
) {
showLoading()
// Utilisation de fetch API (vanilla JS) au lieu de jQuery
const formData = new FormData()
formData.append('rid', idDevis)
fetch('/jxdevis/reactiver_devis', {
method: 'POST',
body: formData,
})
.then((response) => response.json())
.then((data) => {
showNotification('Réactivation', 'Le devis a été réactivé avec succès', 'success')
hideLoading()
setTimeout(function () {
location.reload()
}, 2000)
})
.catch((error) => {
showNotification('Erreur', 'Erreur lors de la réactivation de ce devis : ' + error, 'error')
hideLoading()
})
}
}
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
const form = document.getElementById('frmDevisEntete')
const formData = new FormData(form)
fetch('/jxdevis/save_devis_entete', {
method: 'POST',
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error('Erreur réseau')
}
return response.json()
})
.then((data) => {
hideLoading()
showNotification('Enregistrement', 'Enregistrement effectué avec succès', 'success')
setTimeout(function () {
location.reload()
}, 2000)
})
.catch((error) => {
showNotification('Erreur', 'Erreur lors de la sauvegarde du devis : ' + error.message, 'error')
hideLoading()
})
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
}
}
const inpSearchProduit = document.getElementById('inpSearchProduit')
if (inpSearchProduit) {
inpSearchProduit.addEventListener('keypress', function (e) {
if (e.which == 13) {
const searchValue = document.getElementById('inpSearchProduit').value
if (searchValue.length > 2) {
const formData = new FormData()
formData.append('term', searchValue)
fetch('/jxdevis/load_devis_produits_search', {
method: 'POST',
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error('Erreur réseau')
}
return response.json()
})
.then((data) => {
const tblProduits = document.getElementById('tblProduits')
const rowCount = tblProduits.rows.length
for (let i = rowCount - 1; i > 0; i--) {
tblProduits.deleteRow(i)
}
data.forEach(function (line) {
const row = tblProduits.insertRow()
row.innerHTML =
'<td class="text-center"><input type="checkbox" name="chkProdCat" data-rid="' +
line.rowid +
'" data-code="' +
line.code +
'" data-libelle="' +
line.libelle +
'" value="' +
line.rowid +
'"></td><td>' +
line.code +
'</td><td>' +
line.libelle +
'</td>'
})
})
.catch((error) => {
showNotification('Erreur', 'Erreur lors de la recherche de produits : ' + error.message, '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(
'<tr><td class="text-center"><input type="checkbox" class="chkBox" name="chkProdSelect_' +
rid +
'" data-rid="' +
rid +
'" data-code="' +
code +
'" data-libelle="' +
libelle +
'" value="' +
rid +
'"></td><td>' +
code +
'</td><td>' +
libelle +
'</td></tr>'
)
//! 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 =
'<div class="col-md-2"><label for="inpCom_' +
val['fk_produit'] +
'">Commentaire ' +
val['code'] +
': </label></div><div class="col-md-9"><input type="text" class="form-control w-75" id="inpCom_' +
val['fk_produit'] +
'" name="inpCom_' +
val['fk_produit'] +
'" value="' +
val['commentaire'] +
'" /></div>'
// 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 =
'<svg id="commentProd_' +
val['fk_produit'] +
'" class="clickable" data-rid="' +
val['fk_produit'] +
'" data-code="' +
val['code'] +
'"><use xlink:href="pub/res/svg/icons.svg#message" style="fill: ' +
svgColor +
';"/></svg>'
let inputOrdreHidden =
'<input type="hidden" id="inpOrdre_' +
val['fk_produit'] +
'" name="inpOrdre_' +
val['fk_produit'] +
'" value="' +
val['ordre'] +
'" />'
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 =
'<input type="number" class="form-control numeric" id="inpQte_' +
val['fk_produit'] +
'" name="inpQte_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-achat="' +
val['prix_achat_net'] +
'" data-vente="' +
val['prix_vente'] +
'" value="' +
val['qte'] +
'" min="0" max="1000" step="1" style="width: 100px; text-align: right;"/><input type="hidden" name="achat_' +
val['fk_produit'] +
'" value="' +
val['prix_achat_net'] +
'"/><input type="hidden" name="vente_' +
val['fk_produit'] +
'" value="' +
val['prix_vente'] +
'"/>'
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 =
'<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' +
val['fk_produit'] +
'" name="inpRemise_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-achat="' +
val['prix_achat_net'] +
'" data-vente="' +
val['prix_vente'] +
'" value="' +
remiseProduit +
'" min="0" max="100" step="1" style="width: 80px; text-align: right;" ' +
readonlyRemiseProduit +
' /><div class="input-group-addon">%</div></div>'
if (readonlyRemiseProduit == '') {
document.getElementById('inpRemise_' + val['fk_produit']).addEventListener('change', calculDevis)
}
// nouvelle colonne PU vente avec remise
let celPUVenteRemPro = newRowPro.insertCell(5)
celPUVenteRemPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpPUVenteRem_' +
val['fk_produit'] +
'" name="inpPUVenteRem_' +
val['fk_produit'] +
'" value="' +
formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
// Fin nouvelle colonne
let celHTPro = newRowPro.insertCell(6)
celHTPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' +
val['fk_produit'] +
'" name="inpHT_' +
val['fk_produit'] +
'" value="' +
formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
let celVariante = newRowPro.insertCell(7)
celVariante.className = 'text-center'
celVariante.innerHTML =
'<input type="checkbox" id="chkVariante_' +
val['fk_produit'] +
'" name="chkVariante_' +
val['fk_produit'] +
'" data-rid="' +
val['fk_produit'] +
'" data-achat="' +
val['prix_achat_net'] +
'" data-vente="' +
val['prix_vente'] +
'" value="' +
val['chk_variante'] +
'" ' +
(val['chk_variante'] == 1 ? 'checked' : '') +
' />'
document.getElementById('chkVariante_' + val['fk_produit']).addEventListener('change', calculDevis)
let celMargePro = newRowPro.insertCell(8)
celMargePro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpMG_' +
val['fk_produit'] +
'" name="inpMG_' +
val['fk_produit'] +
'" value="' +
val['marge'] +
'" style="width: 80px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">%</div></div>'
// 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 <div> 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 <div> and add it to the suggestions <div>
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 <div> 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 <div> and add it to the suggestions <div>
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 =
'<input type="checkbox" class="chkBox" id="chkBoxProd_' +
lineProduit.rowid +
'" name="chkBoxProd_' +
lineProduit.rowid +
'" data-rid="' +
lineProduit.rowid +
'" data-code="' +
lineProduit.code +
'" data-libelle="' +
lineProduit.libelle +
'" data-famille="' +
libIdFamille +
'" />'
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(elBtnReactiverDevis).forEach(function (reactiverDevis) {
console.log('Attachement événement click sur bouton réactiver:', reactiverDevis)
reactiverDevis.addEventListener('click', clickReactiverDevis)
})
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)
}
})
})