- 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
3401 lines
139 KiB
JavaScript
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">€</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">€</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">€</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">€</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)
|
|
}
|
|
})
|
|
})
|