feat(v2.0.6): Fonctionnalité de duplication de lignes produits avec gratuité
Implémentation complète de la duplication de lignes produits dans les devis : Backend (controllers/cjxdevis.php): - Ajout endpoint duplicate_ligne_produit avec paramètre gratuite - Recalcul automatique des ordres lors de la duplication - Suppression du warning "Undefined array key user" - Gestion correcte de l'ordre des lignes (fix ordre=0) Frontend (pub/res/js/jdevis.js): - Bouton ➕ pour dupliquer une ligne produit - Duplication directe en gratuité (remise 100%) sans confirmation - Bouton 🗑️ pour supprimer les lignes à 100% de remise - Colorisation violet clair (rgba(138, 43, 226, 0.2)) des lignes gratuites - Limitation à 2 occurrences max par produit (➕ disparaît après) - Badge (x2) dans l'onglet Sélection pour les produits dupliqués - Déduplication de la liste de sélection (1 produit = 1 ligne) - Première colonne sans retour à la ligne (white-space: nowrap) - Fix: loadProduitsDevis n'existe pas → showDevisProduits - Fix: ReferenceError remiseProduit avant initialisation Bugs corrigés: - Bug #1: Warning PHP "Undefined array key user" (ligne 165) - Bug #2: Ligne dupliquée ne s'affiche pas (ordre=0) - Bug #3: ReferenceError loadProduitsDevis non définie - Bug #4: ReferenceError remiseProduit avant initialisation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
515
pub/res/js/jdevis.js
Normal file → Executable file
515
pub/res/js/jdevis.js
Normal file → Executable file
@@ -693,16 +693,29 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
// 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']
|
||||
// on récupère le premier rowid, pour simuler un changement sur ce produit pour recalculer les totaux en fin de boucle
|
||||
const rowidLigne1 = ret[0]['rowid']
|
||||
|
||||
// Compter les occurrences de chaque produit pour masquer le + si >= 2
|
||||
const produitsCount = {}
|
||||
const produitsUniques = {}
|
||||
for (let key in ret) {
|
||||
if (ret.hasOwnProperty(key)) {
|
||||
// Récupération des valeurs de la ligne
|
||||
let val = ret[key]
|
||||
const fkProduit = ret[key]['fk_produit']
|
||||
produitsCount[fkProduit] = (produitsCount[fkProduit] || 0) + 1
|
||||
// Garder la première occurrence de chaque produit
|
||||
if (!produitsUniques[fkProduit]) {
|
||||
produitsUniques[fkProduit] = 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
|
||||
// Remplir le tableau tblProduitsSelect avec des produits uniques
|
||||
for (let fkProduit in produitsUniques) {
|
||||
if (produitsUniques.hasOwnProperty(fkProduit)) {
|
||||
let val = produitsUniques[fkProduit]
|
||||
const count = produitsCount[fkProduit]
|
||||
const badgeCount = count > 1 ? ' <span style="color: #8a2be2; font-weight: bold;">(x' + count + ')</span>' : ''
|
||||
|
||||
// Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le fk_produit
|
||||
let newRowSelect = tblBodySelect.insertRow(-1)
|
||||
@@ -723,18 +736,29 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
'"/>'
|
||||
|
||||
let celCode = newRowSelect.insertCell(1)
|
||||
celCode.innerHTML = val['code']
|
||||
celCode.innerHTML = val['code'] + badgeCount
|
||||
|
||||
let celLibelle = newRowSelect.insertCell(2)
|
||||
celLibelle.innerHTML = val['libelle']
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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.id = 'trPro_' + val['rowid']
|
||||
newRowPro.dataset.ordre = val['ordre']
|
||||
newRowPro.dataset.rid = val['fk_produit']
|
||||
newRowPro.dataset.rowidligne = val['rowid']
|
||||
newRowPro.dataset.fkproduit = val['fk_produit']
|
||||
newRowPro.dataset.code = val['code']
|
||||
newRowPro.dataset.achat = val['prix_achat_net']
|
||||
newRowPro.dataset.achatdiscount = val['prix_achat_net']
|
||||
@@ -758,36 +782,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
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)
|
||||
celCodePro.style.whiteSpace = 'nowrap'
|
||||
|
||||
let celLibellePro = newRowPro.insertCell(1)
|
||||
celLibellePro.innerHTML = val['libelle']
|
||||
@@ -799,10 +794,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
let celQtePro = newRowPro.insertCell(3)
|
||||
celQtePro.innerHTML =
|
||||
'<input type="number" class="form-control numeric" id="inpQte_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpQte_' +
|
||||
val['fk_produit'] +
|
||||
'" data-rid="' +
|
||||
val['rowid'] +
|
||||
'" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-fkproduit="' +
|
||||
val['fk_produit'] +
|
||||
'" data-achat="' +
|
||||
val['prix_achat_net'] +
|
||||
@@ -811,15 +808,15 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
'" value="' +
|
||||
val['qte'] +
|
||||
'" min="0" max="1000" step="1" style="width: 100px; text-align: right;"><input type="hidden" name="achat_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['prix_achat_net'] +
|
||||
'"/><input type="hidden" name="vente_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['prix_vente'] +
|
||||
'"/>'
|
||||
document.getElementById('inpQte_' + val['fk_produit']).addEventListener('change', calculDevis)
|
||||
document.getElementById('inpQte_' + val['rowid']).addEventListener('change', calculDevis)
|
||||
|
||||
let celRemisePro = newRowPro.insertCell(4)
|
||||
// Nouveau code 21/09
|
||||
@@ -834,6 +831,76 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
}
|
||||
// Fin du nouveau code du 21/09
|
||||
|
||||
// Colorisation si remise à 100% (gratuité)
|
||||
if (parseFloat(remiseProduit) === 100) {
|
||||
newRowPro.style.backgroundColor = 'rgba(138, 43, 226, 0.2)'
|
||||
}
|
||||
|
||||
// Remplissage de la cellule code produit (on le fait ici car on a besoin de remiseProduit)
|
||||
const svgColor = val['commentaire'] == '' ? 'lightgray' : 'red'
|
||||
const svgComment =
|
||||
'<svg id="commentProd_' +
|
||||
val['rowid'] +
|
||||
'" class="clickable" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-code="' +
|
||||
val['code'] +
|
||||
'"><use xlink:href="pub/res/svg/icons.svg#message" style="fill: ' +
|
||||
svgColor +
|
||||
';"/></svg>'
|
||||
|
||||
// N'afficher le + que si le produit apparaît moins de 2 fois dans le devis
|
||||
let svgDuplicate = ''
|
||||
if (produitsCount[val['fk_produit']] < 2) {
|
||||
svgDuplicate =
|
||||
'<svg id="duplicateProd_' +
|
||||
val['rowid'] +
|
||||
'" class="clickable" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-code="' +
|
||||
val['code'] +
|
||||
'" title="Dupliquer cette ligne"><use xlink:href="pub/res/svg/icons.svg#add" style="fill: green;"/></svg>'
|
||||
}
|
||||
|
||||
// N'afficher la trash que pour les produits dupliqués avec remise à 100%
|
||||
let svgDelete = ''
|
||||
if (parseFloat(remiseProduit) === 100) {
|
||||
svgDelete =
|
||||
'<svg id="deleteProd_' +
|
||||
val['rowid'] +
|
||||
'" class="clickable" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-code="' +
|
||||
val['code'] +
|
||||
'" title="Supprimer cette ligne"><use xlink:href="pub/res/svg/icons.svg#trash" style="fill: red;"/></svg>'
|
||||
}
|
||||
|
||||
let inputOrdreHidden =
|
||||
'<input type="hidden" id="inpOrdre_' +
|
||||
val['rowid'] +
|
||||
'" name="inpOrdre_' +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['ordre'] +
|
||||
'" />'
|
||||
let inputCommentHidden =
|
||||
'<input type="hidden" id="inpCom_' +
|
||||
val['rowid'] +
|
||||
'" name="inpCom_' +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['commentaire'] +
|
||||
'" />'
|
||||
celCodePro.innerHTML = val['code'] + ' ' + svgComment + ' ' + svgDuplicate + ' ' + svgDelete + inputOrdreHidden + inputCommentHidden
|
||||
|
||||
document.getElementById('commentProd_' + val['rowid']).addEventListener('click', showCommentProd)
|
||||
if (produitsCount[val['fk_produit']] < 2) {
|
||||
document.getElementById('duplicateProd_' + val['rowid']).addEventListener('click', duplicateLigneProduit)
|
||||
}
|
||||
if (parseFloat(remiseProduit) === 100) {
|
||||
document.getElementById('deleteProd_' + val['rowid']).addEventListener('click', deleteLigneProduit)
|
||||
}
|
||||
|
||||
// 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'])
|
||||
@@ -845,10 +912,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
|
||||
celRemisePro.innerHTML =
|
||||
'<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpRemise_' +
|
||||
val['fk_produit'] +
|
||||
'" data-rid="' +
|
||||
val['rowid'] +
|
||||
'" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-fkproduit="' +
|
||||
val['fk_produit'] +
|
||||
'" data-achat="' +
|
||||
val['prix_achat_net'] +
|
||||
@@ -860,16 +929,16 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
readonlyRemiseProduit +
|
||||
'/><div class="input-group-addon">%</div></div>'
|
||||
if (readonlyRemiseProduit == '') {
|
||||
document.getElementById('inpRemise_' + val['fk_produit']).addEventListener('change', calculDevis)
|
||||
document.getElementById('inpRemise_' + val['rowid']).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'] +
|
||||
val['rowid'] +
|
||||
'" name="inpPUVenteRem_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
formatAmount(val['totalht']) +
|
||||
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">€</div></div>'
|
||||
@@ -878,9 +947,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
let celHTPro = newRowPro.insertCell(6)
|
||||
celHTPro.innerHTML =
|
||||
'<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpHT_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
formatAmount(val['totalht']) +
|
||||
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">€</div></div>'
|
||||
@@ -889,10 +958,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
celVariante.className = 'text-center'
|
||||
celVariante.innerHTML =
|
||||
'<input type="checkbox" id="chkVariante_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="chkVariante_' +
|
||||
val['fk_produit'] +
|
||||
'" data-rid="' +
|
||||
val['rowid'] +
|
||||
'" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-fkproduit="' +
|
||||
val['fk_produit'] +
|
||||
'" data-achat="' +
|
||||
val['prix_achat_net'] +
|
||||
@@ -903,14 +974,14 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
'" ' +
|
||||
(val['chk_variante'] == 1 ? 'checked' : '') +
|
||||
' />'
|
||||
document.getElementById('chkVariante_' + val['fk_produit']).addEventListener('change', calculDevis)
|
||||
document.getElementById('chkVariante_' + val['rowid']).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'] +
|
||||
val['rowid'] +
|
||||
'" name="inpMG_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['marge'] +
|
||||
'" style="width: 80px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">%</div></div>'
|
||||
@@ -996,7 +1067,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
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 inpQte = document.getElementById('inpQte_' + rowidLigne1)
|
||||
const event = new Event('change')
|
||||
inpQte.dispatchEvent(event)
|
||||
|
||||
@@ -2070,8 +2141,17 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
}
|
||||
// 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']
|
||||
// on récupère le premier rowid, pour simuler un changement sur ce produit pour recalculer les totaux en fin de boucle
|
||||
const rowidLigne1 = data[0]['rowid']
|
||||
|
||||
// Compter les occurrences de chaque produit pour masquer le + si >= 2
|
||||
const produitsCount = {}
|
||||
for (let key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
const fkProduit = data[key]['fk_produit']
|
||||
produitsCount[fkProduit] = (produitsCount[fkProduit] || 0) + 1
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
@@ -2084,27 +2164,28 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
// 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']
|
||||
newRowCom.id = 'trCom_' + val['rowid']
|
||||
newRowCom.dataset.rowidligne = val['rowid']
|
||||
let celCom = newRowCom.insertCell(0)
|
||||
celCom.colSpan = 8
|
||||
celCom.innerHTML =
|
||||
'<div class="col-md-2"><label for="inpCom_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'">Commentaire ' +
|
||||
val['code'] +
|
||||
': </label></div><div class="col-md-9"><input type="text" class="form-control w-75" id="inpCom_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpCom_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" 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.id = 'trPro_' + val['rowid']
|
||||
newRowPro.dataset.rowidligne = val['rowid']
|
||||
newRowPro.dataset.fkproduit = val['fk_produit']
|
||||
newRowPro.dataset.ordre = val['ordre']
|
||||
newRowPro.dataset.code = val['code']
|
||||
newRowPro.dataset.achat = val['prix_achat_net']
|
||||
@@ -2124,27 +2205,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
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)
|
||||
celCodePro.style.whiteSpace = 'nowrap'
|
||||
|
||||
let celLibellePro = newRowPro.insertCell(1)
|
||||
celLibellePro.innerHTML = val['libelle']
|
||||
@@ -2156,10 +2217,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
let celQtePro = newRowPro.insertCell(3)
|
||||
celQtePro.innerHTML =
|
||||
'<input type="number" class="form-control numeric" id="inpQte_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpQte_' +
|
||||
val['fk_produit'] +
|
||||
'" data-rid="' +
|
||||
val['rowid'] +
|
||||
'" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-fkproduit="' +
|
||||
val['fk_produit'] +
|
||||
'" data-achat="' +
|
||||
val['prix_achat_net'] +
|
||||
@@ -2168,15 +2231,15 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
'" value="' +
|
||||
val['qte'] +
|
||||
'" min="0" max="1000" step="1" style="width: 100px; text-align: right;"/><input type="hidden" name="achat_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['prix_achat_net'] +
|
||||
'"/><input type="hidden" name="vente_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['prix_vente'] +
|
||||
'"/>'
|
||||
document.getElementById('inpQte_' + val['fk_produit']).addEventListener('change', calculDevis)
|
||||
document.getElementById('inpQte_' + val['rowid']).addEventListener('change', calculDevis)
|
||||
|
||||
let celRemisePro = newRowPro.insertCell(4)
|
||||
// Nouveau code 21/09
|
||||
@@ -2191,6 +2254,68 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
}
|
||||
// Fin du nouveau code du 21/09
|
||||
|
||||
// Colorisation si remise à 100% (gratuité)
|
||||
if (parseFloat(remiseProduit) === 100) {
|
||||
newRowPro.style.backgroundColor = 'rgba(138, 43, 226, 0.2)'
|
||||
}
|
||||
|
||||
// Remplissage de la cellule code produit (on le fait ici car on a besoin de remiseProduit)
|
||||
const svgColor = val['commentaire'] == '' ? 'lightgray' : 'red'
|
||||
const svgComment =
|
||||
'<svg id="commentProd_' +
|
||||
val['rowid'] +
|
||||
'" class="clickable" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-code="' +
|
||||
val['code'] +
|
||||
'"><use xlink:href="pub/res/svg/icons.svg#message" style="fill: ' +
|
||||
svgColor +
|
||||
';"/></svg>'
|
||||
|
||||
// N'afficher le + que si le produit apparaît moins de 2 fois dans le devis
|
||||
let svgDuplicate = ''
|
||||
if (produitsCount[val['fk_produit']] < 2) {
|
||||
svgDuplicate =
|
||||
'<svg id="duplicateProd_' +
|
||||
val['rowid'] +
|
||||
'" class="clickable" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-code="' +
|
||||
val['code'] +
|
||||
'" title="Dupliquer cette ligne"><use xlink:href="pub/res/svg/icons.svg#add" style="fill: green;"/></svg>'
|
||||
}
|
||||
|
||||
// N'afficher la trash que pour les produits dupliqués avec remise à 100%
|
||||
let svgDelete = ''
|
||||
if (parseFloat(remiseProduit) === 100) {
|
||||
svgDelete =
|
||||
'<svg id="deleteProd_' +
|
||||
val['rowid'] +
|
||||
'" class="clickable" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-code="' +
|
||||
val['code'] +
|
||||
'" title="Supprimer cette ligne"><use xlink:href="pub/res/svg/icons.svg#trash" style="fill: red;"/></svg>'
|
||||
}
|
||||
|
||||
let inputOrdreHidden =
|
||||
'<input type="hidden" id="inpOrdre_' +
|
||||
val['rowid'] +
|
||||
'" name="inpOrdre_' +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['ordre'] +
|
||||
'" />'
|
||||
celCodePro.innerHTML = val['code'] + ' ' + svgComment + ' ' + svgDuplicate + ' ' + svgDelete + inputOrdreHidden
|
||||
|
||||
document.getElementById('commentProd_' + val['rowid']).addEventListener('click', showCommentProd)
|
||||
if (produitsCount[val['fk_produit']] < 2) {
|
||||
document.getElementById('duplicateProd_' + val['rowid']).addEventListener('click', duplicateLigneProduit)
|
||||
}
|
||||
if (parseFloat(remiseProduit) === 100) {
|
||||
document.getElementById('deleteProd_' + val['rowid']).addEventListener('click', deleteLigneProduit)
|
||||
}
|
||||
|
||||
// 20/02/2025
|
||||
if (val['chk_prix_net']) {
|
||||
if (val['chk_prix_net'] == 1) {
|
||||
@@ -2201,10 +2326,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
|
||||
celRemisePro.innerHTML =
|
||||
'<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpRemise_' +
|
||||
val['fk_produit'] +
|
||||
'" data-rid="' +
|
||||
val['rowid'] +
|
||||
'" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-fkproduit="' +
|
||||
val['fk_produit'] +
|
||||
'" data-achat="' +
|
||||
val['prix_achat_net'] +
|
||||
@@ -2216,16 +2343,16 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
readonlyRemiseProduit +
|
||||
' /><div class="input-group-addon">%</div></div>'
|
||||
if (readonlyRemiseProduit == '') {
|
||||
document.getElementById('inpRemise_' + val['fk_produit']).addEventListener('change', calculDevis)
|
||||
document.getElementById('inpRemise_' + val['rowid']).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'] +
|
||||
val['rowid'] +
|
||||
'" name="inpPUVenteRem_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
formatAmount(val['totalht']) +
|
||||
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">€</div></div>'
|
||||
@@ -2234,9 +2361,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
let celHTPro = newRowPro.insertCell(6)
|
||||
celHTPro.innerHTML =
|
||||
'<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="inpHT_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
formatAmount(val['totalht']) +
|
||||
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">€</div></div>'
|
||||
@@ -2245,10 +2372,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
celVariante.className = 'text-center'
|
||||
celVariante.innerHTML =
|
||||
'<input type="checkbox" id="chkVariante_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" name="chkVariante_' +
|
||||
val['fk_produit'] +
|
||||
'" data-rid="' +
|
||||
val['rowid'] +
|
||||
'" data-rowidligne="' +
|
||||
val['rowid'] +
|
||||
'" data-fkproduit="' +
|
||||
val['fk_produit'] +
|
||||
'" data-achat="' +
|
||||
val['prix_achat_net'] +
|
||||
@@ -2259,14 +2388,14 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
'" ' +
|
||||
(val['chk_variante'] == 1 ? 'checked' : '') +
|
||||
' />'
|
||||
document.getElementById('chkVariante_' + val['fk_produit']).addEventListener('change', calculDevis)
|
||||
document.getElementById('chkVariante_' + val['rowid']).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'] +
|
||||
val['rowid'] +
|
||||
'" name="inpMG_' +
|
||||
val['fk_produit'] +
|
||||
val['rowid'] +
|
||||
'" value="' +
|
||||
val['marge'] +
|
||||
'" style="width: 80px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">%</div></div>'
|
||||
@@ -2358,23 +2487,115 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
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 inpQte = document.getElementById('inpQte_' + rowidLigne1)
|
||||
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
|
||||
console.log('click sur le SVG commentProd de la ligne ' + this.dataset.rowidligne)
|
||||
document.getElementById('inp_commentProdId').value = this.dataset.rowidligne
|
||||
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
|
||||
inpComment.value = document.getElementById('inpCom_' + this.dataset.rowidligne).value
|
||||
showModal(document.getElementById('modalCommentProd'))
|
||||
inpComment.focus()
|
||||
return false
|
||||
}
|
||||
|
||||
let duplicateLigneProduit = function () {
|
||||
const rowidLigne = this.dataset.rowidligne
|
||||
const codeProduit = this.dataset.code
|
||||
console.log('Duplication de la ligne ' + rowidLigne + ' - produit ' + codeProduit)
|
||||
|
||||
// Duplication directe en gratuité (remise 100%)
|
||||
duplicateLigne(rowidLigne, true)
|
||||
return false
|
||||
}
|
||||
|
||||
function duplicateLigne(rowidLigne, gratuite) {
|
||||
showLoading()
|
||||
|
||||
const data = {
|
||||
rowid_ligne: rowidLigne,
|
||||
gratuite: gratuite
|
||||
}
|
||||
|
||||
fetch('/jxdevis/duplicate_ligne_produit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
hideLoading()
|
||||
showNotification('Erreur', 'La duplication de la ligne a échoué', 'error')
|
||||
throw new Error('Erreur lors de la duplication')
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then((ret) => {
|
||||
hideLoading()
|
||||
const message = gratuite ? 'Ligne dupliquée en GRATUITÉ' : 'Ligne dupliquée avec succès'
|
||||
showNotification('Succès', message, 'success')
|
||||
showDevisProduits(ret)
|
||||
chkChange = 1
|
||||
})
|
||||
.catch((error) => {
|
||||
hideLoading()
|
||||
console.error('Erreur:', error)
|
||||
showNotification('Erreur', 'Une erreur est survenue lors de la duplication', 'error')
|
||||
})
|
||||
}
|
||||
|
||||
let deleteLigneProduit = function () {
|
||||
const rowidLigne = this.dataset.rowidligne
|
||||
const codeProduit = this.dataset.code
|
||||
console.log('Suppression de la ligne ' + rowidLigne + ' - produit ' + codeProduit)
|
||||
|
||||
if (!confirm('Êtes-vous sûr de vouloir supprimer cette ligne ?\n\nProduit : ' + codeProduit)) {
|
||||
return false
|
||||
}
|
||||
|
||||
showLoading()
|
||||
|
||||
const data = {
|
||||
rowid_ligne: rowidLigne
|
||||
}
|
||||
|
||||
fetch('/jxdevis/delete_ligne_produit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
hideLoading()
|
||||
showNotification('Erreur', 'La suppression de la ligne a échoué', 'error')
|
||||
throw new Error('Erreur lors de la suppression')
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then((ret) => {
|
||||
hideLoading()
|
||||
showNotification('Succès', 'Ligne supprimée avec succès', 'success')
|
||||
showDevisProduits(ret)
|
||||
chkChange = 1
|
||||
})
|
||||
.catch((error) => {
|
||||
hideLoading()
|
||||
console.error('Erreur:', error)
|
||||
showNotification('Erreur', 'Une erreur est survenue lors de la suppression', 'error')
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function controlRemisesProduits(totalHT) {
|
||||
// Contrôle des remises du marché en fonction du total HT du devis
|
||||
|
||||
@@ -2424,8 +2645,8 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
function getPrixAchatAvecDiscount(idProduit, qte) {
|
||||
let trPro = document.getElementById('trPro_' + idProduit)
|
||||
function getPrixAchatAvecDiscount(rowidLigne, qte) {
|
||||
let trPro = document.getElementById('trPro_' + rowidLigne)
|
||||
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);
|
||||
@@ -2448,8 +2669,8 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
// on applique le prc_discount
|
||||
prixAchatDiscount = prixAchat - (prixAchat * dscnt) / 100
|
||||
console.log(
|
||||
'=== idProduit : ' +
|
||||
idProduit +
|
||||
'=== rowidLigne : ' +
|
||||
rowidLigne +
|
||||
' (prc_discount_' +
|
||||
inc +
|
||||
' applique de ' +
|
||||
@@ -2474,15 +2695,15 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
|
||||
let calculDevis = function () {
|
||||
console.log('calculDevis...')
|
||||
const idProduit = this.dataset.rid
|
||||
const rowidLigne = this.dataset.rowidligne
|
||||
|
||||
// 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)
|
||||
let trPro = document.getElementById('trPro_' + rowidLigne)
|
||||
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);
|
||||
// console.log("rowidLigne: " + rowidLigne + ", code : " + code + ", prixAchat: " + prixAchat + ", prixVente: " + prixVente);
|
||||
|
||||
let qte = 0
|
||||
let remise = 0
|
||||
@@ -2491,20 +2712,20 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
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
|
||||
remise = document.getElementById('inpRemise_' + rowidLigne).value
|
||||
variante = document.getElementById('chkVariante_' + rowidLigne).checked
|
||||
typeInput = 'qte'
|
||||
} else if (this.name.indexOf('inpRemise') > -1) {
|
||||
// c'est la remise qui a changé
|
||||
qte = document.getElementById('inpQte_' + idProduit).value
|
||||
qte = document.getElementById('inpQte_' + rowidLigne).value
|
||||
remise = this.value
|
||||
variante = document.getElementById('chkVariante_' + idProduit).checked
|
||||
variante = document.getElementById('chkVariante_' + rowidLigne).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
|
||||
qte = document.getElementById('inpQte_' + rowidLigne).value
|
||||
remise = document.getElementById('inpRemise_' + rowidLigne).value
|
||||
variante = this.checked
|
||||
typeInput = 'variante'
|
||||
}
|
||||
@@ -2526,7 +2747,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
totalHt = (prixVente * 1 - remiseProduit * 1) * (qte * 1)
|
||||
}
|
||||
|
||||
let inpHT = document.getElementById('inpHT_' + idProduit)
|
||||
let inpHT = document.getElementById('inpHT_' + rowidLigne)
|
||||
inpHT.value = parseFloat(totalHt).toFixed(2)
|
||||
|
||||
// Modif du 25/04 : on calcule la marge même si c'est une variante / option
|
||||
@@ -2552,8 +2773,8 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
} else {
|
||||
txMarge = 0
|
||||
console.log(
|
||||
'ERREUR idProduit : ' +
|
||||
idProduit +
|
||||
'ERREUR rowidLigne : ' +
|
||||
rowidLigne +
|
||||
', code : ' +
|
||||
code +
|
||||
' - prixAchat : ' +
|
||||
@@ -2569,22 +2790,22 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
)
|
||||
}
|
||||
//}
|
||||
let inpMG = document.getElementById('inpMG_' + idProduit)
|
||||
let inpMG = document.getElementById('inpMG_' + rowidLigne)
|
||||
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 rowidLigneProd = elInp.dataset.rowidligne
|
||||
const ligne = document.getElementById('trPro_' + rowidLigneProd)
|
||||
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)
|
||||
const achat = getPrixAchatAvecDiscount(rowidLigneProd, elInp.value)
|
||||
// Fin de la mise à jour du 09/11
|
||||
|
||||
const varOption = document.getElementById('chkVariante_' + idProd).checked
|
||||
const varOption = document.getElementById('chkVariante_' + rowidLigneProd).checked
|
||||
if (!varOption) {
|
||||
// calcul avec juste la quantité et le prix de vente
|
||||
const vente = elInp.dataset.vente * 1
|
||||
@@ -2593,14 +2814,14 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
|
||||
// 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 remProd = document.getElementById('inpRemise_' + rowidLigneProd).value
|
||||
const remise = remProd * 1
|
||||
|
||||
let puVenteApresRemise = vente
|
||||
if (remise > 0) {
|
||||
puVenteApresRemise = vente - (vente * remise) / 100
|
||||
}
|
||||
document.getElementById('inpPUVenteRem_' + idProd).value = puVenteApresRemise.toFixed(2)
|
||||
document.getElementById('inpPUVenteRem_' + rowidLigneProd).value = puVenteApresRemise.toFixed(2)
|
||||
console.log('--- 1 Produit : ' + code + ' - PUVenteApresRemise : ' + puVenteApresRemise)
|
||||
// Fin de la mise à jour du 03/11/2023
|
||||
|
||||
@@ -2638,7 +2859,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
)
|
||||
}
|
||||
//}
|
||||
let inpMG = document.getElementById('inpMG_' + idProd)
|
||||
let inpMG = document.getElementById('inpMG_' + rowidLigneProd)
|
||||
inpMG.value = parseFloat(txMarge).toFixed(2)
|
||||
}
|
||||
|
||||
@@ -2663,8 +2884,8 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
//! 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 rowidLigneProd = elInp.dataset.rowidligne
|
||||
const ligne = document.getElementById('trPro_' + rowidLigneProd)
|
||||
const vente = ligne.dataset.vente * 1
|
||||
const achat = ligne.dataset.achatdiscount * 1
|
||||
const rem = elInp.value * 1
|
||||
@@ -2689,9 +2910,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
// elInp.readOnly = false;
|
||||
}
|
||||
|
||||
const varOption = document.getElementById('chkVariante_' + idProd).checked
|
||||
const varOption = document.getElementById('chkVariante_' + rowidLigneProd).checked
|
||||
if (!varOption) {
|
||||
const inpQte = document.getElementById('inpQte_' + idProd)
|
||||
const inpQte = document.getElementById('inpQte_' + rowidLigneProd)
|
||||
const qte = inpQte.value
|
||||
const remiseProduit = (remise * vente) / 100
|
||||
|
||||
@@ -2701,8 +2922,8 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
console.log(
|
||||
'--- 2 ligne code ' +
|
||||
ligne.dataset.code +
|
||||
' = idProd : ' +
|
||||
idProd +
|
||||
' = rowidLigneProd : ' +
|
||||
rowidLigneProd +
|
||||
', vente : ' +
|
||||
vente +
|
||||
', achat : ' +
|
||||
@@ -3473,7 +3694,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
inputs.forEach((input) => (input.disabled = false))
|
||||
|
||||
// Ajouter un événement click à l'élément svg
|
||||
const svgElement = dropElem.querySelector('#commentProd_' + dropElem.dataset.rid)
|
||||
const svgElement = dropElem.querySelector('#commentProd_' + dropElem.dataset.rowidligne)
|
||||
svgElement.addEventListener('click', showCommentProd)
|
||||
|
||||
addDnDHandlers(dropElem)
|
||||
@@ -3495,9 +3716,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
const sonCode = row.dataset.code
|
||||
if (sonCode) {
|
||||
row.dataset.ordre = index + 1
|
||||
const fkProduit = row.dataset.rid
|
||||
const rowidLigne = row.dataset.rowidligne
|
||||
console.log('index : ' + index + ' code : ' + sonCode)
|
||||
document.getElementById('inpOrdre_' + fkProduit).value = index
|
||||
document.getElementById('inpOrdre_' + rowidLigne).value = index
|
||||
}
|
||||
})
|
||||
showNotification(
|
||||
|
||||
Reference in New Issue
Block a user