1 Commits

Author SHA1 Message Date
639969ca1b 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>
2026-01-27 13:44:48 +01:00
2 changed files with 533 additions and 186 deletions

204
controllers/cjxdevis.php Normal file → Executable file
View File

@@ -158,6 +158,131 @@ switch ($Route->_action) {
} }
break; break;
case "duplicate_ligne_produit":
$data = json_decode(file_get_contents("php://input"));
if (isset($data->rowid_ligne)) {
$rowidLigneSafe = intval(nettoie_input($data->rowid_ligne));
$sql = 'SELECT * FROM devis_produits WHERE rowid = ' . $rowidLigneSafe . ';';
$ligne = getinfos($sql, 'gen');
if (count($ligne) > 0) {
$l = $ligne[0];
$fk_devis = $l['fk_devis'];
// Récupérer toutes les lignes du devis pour recalculer les ordres
$sql = 'SELECT rowid, ordre FROM devis_produits WHERE fk_devis = ' . $fk_devis . ' ORDER BY rowid;';
$lignes_devis = getinfos($sql, 'gen');
// Recalculer tous les ordres si nécessaire (au cas où il y a des doublons à 0)
$ordre_actuel = 0;
$position_source = -1;
foreach ($lignes_devis as $idx => $ligne_devis) {
if ($ligne_devis['rowid'] == $rowidLigneSafe) {
$position_source = $ordre_actuel;
}
if ($ligne_devis['ordre'] != $ordre_actuel) {
$sql = 'UPDATE devis_produits SET ordre = ' . $ordre_actuel . ' WHERE rowid = ' . $ligne_devis['rowid'] . ';';
qSQL($sql, 'gen');
}
$ordre_actuel++;
}
// Le nouvel ordre sera juste après la ligne source
$nouvel_ordre = $position_source + 1;
// Décaler toutes les lignes après la position d'insertion
$sql = 'UPDATE devis_produits SET ordre = ordre + 1 WHERE fk_devis = ' . $fk_devis . ' AND ordre >= ' . $nouvel_ordre . ';';
qSQL($sql, 'gen');
$gratuite = false;
if (isset($data->gratuite) && $data->gratuite === true) {
$gratuite = true;
}
$sql = 'INSERT INTO devis_produits SET ';
$sql .= 'fk_devis = ' . $l['fk_devis'] . ', ';
$sql .= 'fk_produit = ' . $l['fk_produit'] . ', ';
$sql .= 'ordre = ' . $nouvel_ordre . ', ';
$sql .= 'code = "' . $l['code'] . '", ';
$sql .= 'libelle = "' . $l['libelle'] . '", ';
$sql .= 'qte = ' . $l['qte'] . ', ';
if ($gratuite) {
$sql .= 'prix_vente = 0, ';
$sql .= 'pu_vente_remise = 0, ';
$sql .= 'totalht = 0, ';
$sql .= 'marge = ' . (-floatval($l['prix_achat_net']) * intval($l['qte'])) . ', ';
$sql .= 'remise = 100, ';
} else {
$sql .= 'totalht = ' . $l['totalht'] . ', ';
$sql .= 'remise = ' . $l['remise'] . ', ';
$sql .= 'marge = ' . $l['marge'] . ', ';
$sql .= 'prix_vente = ' . $l['prix_vente'] . ', ';
$sql .= 'pu_vente_remise = ' . $l['pu_vente_remise'] . ', ';
}
$sql .= 'prix_achat_net = ' . $l['prix_achat_net'] . ', ';
$sql .= 'prc_discount_1 = ' . $l['prc_discount_1'] . ', ';
$sql .= 'quantite_1 = ' . $l['quantite_1'] . ', ';
$sql .= 'prc_discount_2 = ' . $l['prc_discount_2'] . ', ';
$sql .= 'quantite_2 = ' . $l['quantite_2'] . ', ';
$sql .= 'prc_discount_3 = ' . $l['prc_discount_3'] . ', ';
$sql .= 'quantite_3 = ' . $l['quantite_3'] . ', ';
$sql .= 'prc_discount_4 = ' . $l['prc_discount_4'] . ', ';
$sql .= 'quantite_4 = ' . $l['quantite_4'] . ', ';
$sql .= 'prc_discount_5 = ' . $l['prc_discount_5'] . ', ';
$sql .= 'quantite_5 = ' . $l['quantite_5'] . ', ';
$sql .= 'prc_discount_6 = ' . $l['prc_discount_6'] . ', ';
$sql .= 'quantite_6 = ' . $l['quantite_6'] . ', ';
$sql .= 'chk_variante = ' . $l['chk_variante'] . ', ';
$sql .= 'chk_prix_net = ' . $l['chk_prix_net'] . ', ';
$sql .= 'commentaire = "' . $l['commentaire'] . '";';
eLog($sql);
qSQL($sql, 'gen');
$sql = 'SELECT dp.*, pf.marge_rr, pf.marge_dv FROM devis_produits dp ';
$sql .= 'LEFT JOIN produits p ON dp.fk_produit = p.rowid ';
$sql .= 'LEFT JOIN produits_familles pf ON p.groupe = pf.groupe ';
$sql .= 'WHERE dp.fk_devis = ' . $fk_devis . ' ORDER BY dp.ordre;';
echo getinfos($sql, 'gen', 'json');
} else {
echo json_encode(['success' => false, 'msg' => 'Ligne non trouvée']);
}
} else {
echo json_encode(['success' => false, 'msg' => 'rowid_ligne manquant']);
}
break;
case "delete_ligne_produit":
$data = json_decode(file_get_contents("php://input"));
if (isset($data->rowid_ligne)) {
$rowidLigneSafe = intval(nettoie_input($data->rowid_ligne));
$sql = 'SELECT fk_devis FROM devis_produits WHERE rowid = ' . $rowidLigneSafe . ';';
$ligne = getinfos($sql, 'gen');
if (count($ligne) > 0) {
$fk_devis = $ligne[0]['fk_devis'];
$sql = 'DELETE FROM devis_produits WHERE rowid = ' . $rowidLigneSafe . ';';
eLog($sql);
qSQL($sql, 'gen');
$sql = 'SELECT dp.*, pf.marge_rr, pf.marge_dv FROM devis_produits dp ';
$sql .= 'LEFT JOIN produits p ON dp.fk_produit = p.rowid ';
$sql .= 'LEFT JOIN produits_familles pf ON p.groupe = pf.groupe ';
$sql .= 'WHERE dp.fk_devis = ' . $fk_devis . ' ORDER BY dp.ordre;';
echo getinfos($sql, 'gen', 'json');
} else {
echo json_encode(['success' => false, 'msg' => 'Ligne non trouvée']);
}
} else {
echo json_encode(['success' => false, 'msg' => 'rowid_ligne manquant']);
}
break;
case "load_clients_devis": case "load_clients_devis":
//! On récupère les infos des clients de son secteur ou de toute la France, suivant le devis chk_clients_secteur, utilisé aussi pour l'autocomplete de la recherche de clients //! On récupère les infos des clients de son secteur ou de toute la France, suivant le devis chk_clients_secteur, utilisé aussi pour l'autocomplete de la recherche de clients
$data = json_decode(file_get_contents("php://input")); $data = json_decode(file_get_contents("php://input"));
@@ -843,53 +968,54 @@ switch ($Route->_action) {
$idDevis = nettoie_input($data->inpIdDevis); $idDevis = nettoie_input($data->inpIdDevis);
eLog("save_devis final : idDevis = " . $idDevis); eLog("save_devis final : idDevis = " . $idDevis);
// TODO: enregistrer le prix d'achat et de vente de chaque produit au moment du devis $sql = 'SELECT rowid FROM devis_produits WHERE fk_devis = ' . $idDevis . ';';
$lignesProduits = getinfos($sql, 'gen');
//! loop sur les datas commençant par inpQte_ if (count($lignesProduits) > 0) {
foreach ($data as $key => $value) { foreach ($lignesProduits as $ligne) {
if (substr($key, 0, 7) == "inpQte_") { $rowidLigne = $ligne['rowid'];
//! on a une ligne de produit
$idProd = substr($key, 7);
$qte = nettoie_input($value);
$qte = "" ? 0 : $qte;
$rem = nettoie_input($data->{"inpRemise_" . $idProd});
$rem = "" ? 0 : $rem;
$ht = nettoie_input($data->{"inpHT_" . $idProd}); if (isset($data->{"inpQte_" . $rowidLigne})) {
$ht = "" ? 0 : $ht; $qte = nettoie_input($data->{"inpQte_" . $rowidLigne});
// si $ht contient un espace (délimiteur millier), on le supprime $qte = $qte == "" ? 0 : $qte;
$ht = str_replace(" ", "", $ht); $rem = nettoie_input($data->{"inpRemise_" . $rowidLigne});
$rem = $rem == "" ? 0 : $rem;
$mg = nettoie_input($data->{"inpMG_" . $idProd}); $ht = nettoie_input($data->{"inpHT_" . $rowidLigne});
$mg = "" ? 0 : $mg; $ht = $ht == "" ? 0 : $ht;
$ht = str_replace(" ", "", $ht);
$ha = nettoie_input($data->{"achat_" . $idProd}); $mg = nettoie_input($data->{"inpMG_" . $rowidLigne});
$ha = "" ? 0 : $ha; $mg = $mg == "" ? 0 : $mg;
$ha = str_replace(" ", "", $ha);
$pv = nettoie_input($data->{"vente_" . $idProd}); $ha = nettoie_input($data->{"achat_" . $rowidLigne});
$pv = "" ? 0 : $pv; $ha = $ha == "" ? 0 : $ha;
$pv = str_replace(" ", "", $pv); $ha = str_replace(" ", "", $ha);
$pu = nettoie_input($data->{"inpPUVenteRem_" . $idProd}); $pv = nettoie_input($data->{"vente_" . $rowidLigne});
$pu = "" ? 0 : $pu; $pv = $pv == "" ? 0 : $pv;
$pu = str_replace(" ", "", $pu); $pv = str_replace(" ", "", $pv);
$varOpt = 0; $pu = nettoie_input($data->{"inpPUVenteRem_" . $rowidLigne});
if (isset($data->{"chkVariante_" . $idProd})) { $pu = $pu == "" ? 0 : $pu;
$varOpt = 1; $pu = str_replace(" ", "", $pu);
$varOpt = 0;
if (isset($data->{"chkVariante_" . $rowidLigne})) {
$varOpt = 1;
}
$comment = nettoie_input($data->{"inpCom_" . $rowidLigne});
$ordre = nettoie_input($data->{"inpOrdre_" . $rowidLigne});
if ($ordre == "") {
$ordre = 0;
}
$sql = 'UPDATE devis_produits SET qte=' . $qte . ', remise=' . $rem . ', totalht=' . $ht . ', marge=' . $mg . ', prix_achat_net=' . $ha . ', ';
$sql .= 'prix_vente=' . $pv . ', pu_vente_remise=' . $pu . ', chk_variante=' . $varOpt . ', commentaire="' . $comment . '", ordre=' . $ordre . ' ';
$sql .= 'WHERE rowid = ' . $rowidLigne . ';';
eLog($sql);
qSQL($sql, "gen");
} }
$comment = nettoie_input($data->{"inpCom_" . $idProd});
$ordre = nettoie_input($data->{"inpOrdre_" . $idProd});
if ($ordre == "") {
$ordre = 0;
}
$sql = 'UPDATE devis_produits SET qte=' . $qte . ', remise=' . $rem . ', totalht=' . $ht . ', marge=' . $mg . ', prix_achat_net=' . $ha . ', ';
$sql .= 'prix_vente=' . $pv . ', pu_vente_remise=' . $pu . ', chk_variante=' . $varOpt . ', commentaire="' . $comment . '", ordre=' . $ordre . ' ';
$sql .= 'WHERE fk_devis=' . $idDevis . ' AND fk_produit=' . $idProd . ';';
eLog($sql);
qSQL($sql, "gen");
} }
} }

515
pub/res/js/jdevis.js Normal file → Executable file
View File

@@ -693,16 +693,29 @@ window.addEventListener('DOMContentLoaded', (event) => {
// au moins un produit trouvé pour ce devis // au moins un produit trouvé pour ce devis
let nbProduits = ret.length 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 // on récupère le premier rowid, pour simuler un changement sur ce produit pour recalculer les totaux en fin de boucle
const fkProduit1 = ret[0]['fk_produit'] 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) { for (let key in ret) {
if (ret.hasOwnProperty(key)) { if (ret.hasOwnProperty(key)) {
// Récupération des valeurs de la ligne const fkProduit = ret[key]['fk_produit']
let val = ret[key] 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 // Remplir le tableau tblProduitsSelect avec des produits uniques
readonlyRemiseProduit = readonlyRemise 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 // Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le fk_produit
let newRowSelect = tblBodySelect.insertRow(-1) let newRowSelect = tblBodySelect.insertRow(-1)
@@ -723,18 +736,29 @@ window.addEventListener('DOMContentLoaded', (event) => {
'"/>' '"/>'
let celCode = newRowSelect.insertCell(1) let celCode = newRowSelect.insertCell(1)
celCode.innerHTML = val['code'] celCode.innerHTML = val['code'] + badgeCount
let celLibelle = newRowSelect.insertCell(2) let celLibelle = newRowSelect.insertCell(2)
celLibelle.innerHTML = val['libelle'] 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 // Sur le tableau tblBodyPro
// Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le rowid de devis_produits // Insertion d'une nouvelle ligne et création de ses colonnes : on prend ici le rowid de devis_produits
let newRowPro = tblBodyPro.insertRow(-1) let newRowPro = tblBodyPro.insertRow(-1)
newRowPro.id = 'trPro_' + val['fk_produit'] newRowPro.id = 'trPro_' + val['rowid']
newRowPro.dataset.ordre = val['ordre'] 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.code = val['code']
newRowPro.dataset.achat = val['prix_achat_net'] newRowPro.dataset.achat = val['prix_achat_net']
newRowPro.dataset.achatdiscount = val['prix_achat_net'] newRowPro.dataset.achatdiscount = val['prix_achat_net']
@@ -758,36 +782,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
newRowPro.addEventListener('drop', handleDrop) newRowPro.addEventListener('drop', handleDrop)
let celCodePro = newRowPro.insertCell(-1) let celCodePro = newRowPro.insertCell(-1)
const svgColor = val['commentaire'] == '' ? 'lightgray' : 'red' celCodePro.style.whiteSpace = 'nowrap'
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) let celLibellePro = newRowPro.insertCell(1)
celLibellePro.innerHTML = val['libelle'] celLibellePro.innerHTML = val['libelle']
@@ -799,10 +794,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
let celQtePro = newRowPro.insertCell(3) let celQtePro = newRowPro.insertCell(3)
celQtePro.innerHTML = celQtePro.innerHTML =
'<input type="number" class="form-control numeric" id="inpQte_' + '<input type="number" class="form-control numeric" id="inpQte_' +
val['fk_produit'] + val['rowid'] +
'" name="inpQte_' + '" name="inpQte_' +
val['fk_produit'] + val['rowid'] +
'" data-rid="' + '" data-rowidligne="' +
val['rowid'] +
'" data-fkproduit="' +
val['fk_produit'] + val['fk_produit'] +
'" data-achat="' + '" data-achat="' +
val['prix_achat_net'] + val['prix_achat_net'] +
@@ -811,15 +808,15 @@ window.addEventListener('DOMContentLoaded', (event) => {
'" value="' + '" value="' +
val['qte'] + val['qte'] +
'" min="0" max="1000" step="1" style="width: 100px; text-align: right;"><input type="hidden" name="achat_' + '" min="0" max="1000" step="1" style="width: 100px; text-align: right;"><input type="hidden" name="achat_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['prix_achat_net'] + val['prix_achat_net'] +
'"/><input type="hidden" name="vente_' + '"/><input type="hidden" name="vente_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['prix_vente'] + 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) let celRemisePro = newRowPro.insertCell(4)
// Nouveau code 21/09 // Nouveau code 21/09
@@ -834,6 +831,76 @@ window.addEventListener('DOMContentLoaded', (event) => {
} }
// Fin du nouveau code du 21/09 // 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) // 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']) { if (val['chk_prix_net']) {
console.log('on a un chk_prix_net : ' + 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 = celRemisePro.innerHTML =
'<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' + '<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' +
val['fk_produit'] + val['rowid'] +
'" name="inpRemise_' + '" name="inpRemise_' +
val['fk_produit'] + val['rowid'] +
'" data-rid="' + '" data-rowidligne="' +
val['rowid'] +
'" data-fkproduit="' +
val['fk_produit'] + val['fk_produit'] +
'" data-achat="' + '" data-achat="' +
val['prix_achat_net'] + val['prix_achat_net'] +
@@ -860,16 +929,16 @@ window.addEventListener('DOMContentLoaded', (event) => {
readonlyRemiseProduit + readonlyRemiseProduit +
'/><div class="input-group-addon">%</div></div>' '/><div class="input-group-addon">%</div></div>'
if (readonlyRemiseProduit == '') { 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 // nouvelle colonne PU vente avec remise
let celPUVenteRemPro = newRowPro.insertCell(5) let celPUVenteRemPro = newRowPro.insertCell(5)
celPUVenteRemPro.innerHTML = celPUVenteRemPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpPUVenteRem_' + '<div class="input-group"><input type="text" class="form-control numeric" id="inpPUVenteRem_' +
val['fk_produit'] + val['rowid'] +
'" name="inpPUVenteRem_' + '" name="inpPUVenteRem_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
formatAmount(val['totalht']) + formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>' '" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
@@ -878,9 +947,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
let celHTPro = newRowPro.insertCell(6) let celHTPro = newRowPro.insertCell(6)
celHTPro.innerHTML = celHTPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' + '<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' +
val['fk_produit'] + val['rowid'] +
'" name="inpHT_' + '" name="inpHT_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
formatAmount(val['totalht']) + formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>' '" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
@@ -889,10 +958,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
celVariante.className = 'text-center' celVariante.className = 'text-center'
celVariante.innerHTML = celVariante.innerHTML =
'<input type="checkbox" id="chkVariante_' + '<input type="checkbox" id="chkVariante_' +
val['fk_produit'] + val['rowid'] +
'" name="chkVariante_' + '" name="chkVariante_' +
val['fk_produit'] + val['rowid'] +
'" data-rid="' + '" data-rowidligne="' +
val['rowid'] +
'" data-fkproduit="' +
val['fk_produit'] + val['fk_produit'] +
'" data-achat="' + '" data-achat="' +
val['prix_achat_net'] + val['prix_achat_net'] +
@@ -903,14 +974,14 @@ window.addEventListener('DOMContentLoaded', (event) => {
'" ' + '" ' +
(val['chk_variante'] == 1 ? 'checked' : '') + (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) let celMargePro = newRowPro.insertCell(8)
celMargePro.innerHTML = celMargePro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpMG_' + '<div class="input-group"><input type="text" class="form-control numeric" id="inpMG_' +
val['fk_produit'] + val['rowid'] +
'" name="inpMG_' + '" name="inpMG_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['marge'] + val['marge'] +
'" style="width: 80px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">%</div></div>' '" 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 document.getElementById('inp_latitudeDV').value = seuilMargeDV
// On simule le changement de quantité sur la première ligne pour recalculer les totaux // 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') const event = new Event('change')
inpQte.dispatchEvent(event) inpQte.dispatchEvent(event)
@@ -2070,8 +2141,17 @@ window.addEventListener('DOMContentLoaded', (event) => {
} }
// Fin de l'ajout du 26 juin 2024 // 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 // on récupère le premier rowid, pour simuler un changement sur ce produit pour recalculer les totaux en fin de boucle
const fkProduit1 = data[0]['fk_produit'] 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) { for (let key in data) {
if (data.hasOwnProperty(key)) { 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 // on insère la ligne pour la saisie du commentaire au-dessus de la ligne du produit
let newRowCom = tblBodyPro.insertRow(-1) let newRowCom = tblBodyPro.insertRow(-1)
newRowCom.className = 'hidden' newRowCom.className = 'hidden'
newRowCom.id = 'trCom_' + val['fk_produit'] newRowCom.id = 'trCom_' + val['rowid']
newRowCom.dataset.rid = val['fk_produit'] newRowCom.dataset.rowidligne = val['rowid']
let celCom = newRowCom.insertCell(0) let celCom = newRowCom.insertCell(0)
celCom.colSpan = 8 celCom.colSpan = 8
celCom.innerHTML = celCom.innerHTML =
'<div class="col-md-2"><label for="inpCom_' + '<div class="col-md-2"><label for="inpCom_' +
val['fk_produit'] + val['rowid'] +
'">Commentaire ' + '">Commentaire ' +
val['code'] + val['code'] +
': </label></div><div class="col-md-9"><input type="text" class="form-control w-75" id="inpCom_' + ': </label></div><div class="col-md-9"><input type="text" class="form-control w-75" id="inpCom_' +
val['fk_produit'] + val['rowid'] +
'" name="inpCom_' + '" name="inpCom_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['commentaire'] + val['commentaire'] +
'" /></div>' '" /></div>'
// Insertion d'une nouvelle ligne et création de ses colonnes // Insertion d'une nouvelle ligne et création de ses colonnes
let newRowPro = tblBodyPro.insertRow(-1) let newRowPro = tblBodyPro.insertRow(-1)
newRowPro.id = 'trPro_' + val['fk_produit'] newRowPro.id = 'trPro_' + val['rowid']
newRowPro.dataset.rid = val['fk_produit'] newRowPro.dataset.rowidligne = val['rowid']
newRowPro.dataset.fkproduit = val['fk_produit']
newRowPro.dataset.ordre = val['ordre'] newRowPro.dataset.ordre = val['ordre']
newRowPro.dataset.code = val['code'] newRowPro.dataset.code = val['code']
newRowPro.dataset.achat = val['prix_achat_net'] newRowPro.dataset.achat = val['prix_achat_net']
@@ -2124,27 +2205,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
newRowPro.dataset.quantite6 = val['quantite_6'] newRowPro.dataset.quantite6 = val['quantite_6']
let celCodePro = newRowPro.insertCell(0) let celCodePro = newRowPro.insertCell(0)
const svgColor = val['commentaire'] == '' ? 'lightgray' : 'red' celCodePro.style.whiteSpace = 'nowrap'
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) let celLibellePro = newRowPro.insertCell(1)
celLibellePro.innerHTML = val['libelle'] celLibellePro.innerHTML = val['libelle']
@@ -2156,10 +2217,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
let celQtePro = newRowPro.insertCell(3) let celQtePro = newRowPro.insertCell(3)
celQtePro.innerHTML = celQtePro.innerHTML =
'<input type="number" class="form-control numeric" id="inpQte_' + '<input type="number" class="form-control numeric" id="inpQte_' +
val['fk_produit'] + val['rowid'] +
'" name="inpQte_' + '" name="inpQte_' +
val['fk_produit'] + val['rowid'] +
'" data-rid="' + '" data-rowidligne="' +
val['rowid'] +
'" data-fkproduit="' +
val['fk_produit'] + val['fk_produit'] +
'" data-achat="' + '" data-achat="' +
val['prix_achat_net'] + val['prix_achat_net'] +
@@ -2168,15 +2231,15 @@ window.addEventListener('DOMContentLoaded', (event) => {
'" value="' + '" value="' +
val['qte'] + val['qte'] +
'" min="0" max="1000" step="1" style="width: 100px; text-align: right;"/><input type="hidden" name="achat_' + '" min="0" max="1000" step="1" style="width: 100px; text-align: right;"/><input type="hidden" name="achat_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['prix_achat_net'] + val['prix_achat_net'] +
'"/><input type="hidden" name="vente_' + '"/><input type="hidden" name="vente_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['prix_vente'] + 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) let celRemisePro = newRowPro.insertCell(4)
// Nouveau code 21/09 // Nouveau code 21/09
@@ -2191,6 +2254,68 @@ window.addEventListener('DOMContentLoaded', (event) => {
} }
// Fin du nouveau code du 21/09 // 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 // 20/02/2025
if (val['chk_prix_net']) { if (val['chk_prix_net']) {
if (val['chk_prix_net'] == 1) { if (val['chk_prix_net'] == 1) {
@@ -2201,10 +2326,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
celRemisePro.innerHTML = celRemisePro.innerHTML =
'<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' + '<div class="input-group"><input type="number" class="form-control numeric" id="inpRemise_' +
val['fk_produit'] + val['rowid'] +
'" name="inpRemise_' + '" name="inpRemise_' +
val['fk_produit'] + val['rowid'] +
'" data-rid="' + '" data-rowidligne="' +
val['rowid'] +
'" data-fkproduit="' +
val['fk_produit'] + val['fk_produit'] +
'" data-achat="' + '" data-achat="' +
val['prix_achat_net'] + val['prix_achat_net'] +
@@ -2216,16 +2343,16 @@ window.addEventListener('DOMContentLoaded', (event) => {
readonlyRemiseProduit + readonlyRemiseProduit +
' /><div class="input-group-addon">%</div></div>' ' /><div class="input-group-addon">%</div></div>'
if (readonlyRemiseProduit == '') { 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 // nouvelle colonne PU vente avec remise
let celPUVenteRemPro = newRowPro.insertCell(5) let celPUVenteRemPro = newRowPro.insertCell(5)
celPUVenteRemPro.innerHTML = celPUVenteRemPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpPUVenteRem_' + '<div class="input-group"><input type="text" class="form-control numeric" id="inpPUVenteRem_' +
val['fk_produit'] + val['rowid'] +
'" name="inpPUVenteRem_' + '" name="inpPUVenteRem_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
formatAmount(val['totalht']) + formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>' '" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
@@ -2234,9 +2361,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
let celHTPro = newRowPro.insertCell(6) let celHTPro = newRowPro.insertCell(6)
celHTPro.innerHTML = celHTPro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' + '<div class="input-group"><input type="text" class="form-control numeric" id="inpHT_' +
val['fk_produit'] + val['rowid'] +
'" name="inpHT_' + '" name="inpHT_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
formatAmount(val['totalht']) + formatAmount(val['totalht']) +
'" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>' '" style="width: 100px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">&euro;</div></div>'
@@ -2245,10 +2372,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
celVariante.className = 'text-center' celVariante.className = 'text-center'
celVariante.innerHTML = celVariante.innerHTML =
'<input type="checkbox" id="chkVariante_' + '<input type="checkbox" id="chkVariante_' +
val['fk_produit'] + val['rowid'] +
'" name="chkVariante_' + '" name="chkVariante_' +
val['fk_produit'] + val['rowid'] +
'" data-rid="' + '" data-rowidligne="' +
val['rowid'] +
'" data-fkproduit="' +
val['fk_produit'] + val['fk_produit'] +
'" data-achat="' + '" data-achat="' +
val['prix_achat_net'] + val['prix_achat_net'] +
@@ -2259,14 +2388,14 @@ window.addEventListener('DOMContentLoaded', (event) => {
'" ' + '" ' +
(val['chk_variante'] == 1 ? 'checked' : '') + (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) let celMargePro = newRowPro.insertCell(8)
celMargePro.innerHTML = celMargePro.innerHTML =
'<div class="input-group"><input type="text" class="form-control numeric" id="inpMG_' + '<div class="input-group"><input type="text" class="form-control numeric" id="inpMG_' +
val['fk_produit'] + val['rowid'] +
'" name="inpMG_' + '" name="inpMG_' +
val['fk_produit'] + val['rowid'] +
'" value="' + '" value="' +
val['marge'] + val['marge'] +
'" style="width: 80px; text-align: right;" readonly tabindex="-1" /><div class="input-group-addon">%</div></div>' '" 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 document.getElementById('inp_latitudeDV').value = seuilMargeDV
// On simule le changement de quantité sur la première ligne pour recalculer les totaux // 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') const event = new Event('change')
inpQte.dispatchEvent(event) inpQte.dispatchEvent(event)
} }
} }
let showCommentProd = function () { let showCommentProd = function () {
console.log('click sur le SVG commentProd de la ligne ' + this.dataset.rid) console.log('click sur le SVG commentProd de la ligne ' + this.dataset.rowidligne)
document.getElementById('inp_commentProdId').value = this.dataset.rid document.getElementById('inp_commentProdId').value = this.dataset.rowidligne
document.getElementById('modCommentProdTitre').innerHTML = 'Commentaire sur le produit ' + this.dataset.code document.getElementById('modCommentProdTitre').innerHTML = 'Commentaire sur le produit ' + this.dataset.code
const inpComment = document.getElementById('inp_commentProd') 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')) showModal(document.getElementById('modalCommentProd'))
inpComment.focus() inpComment.focus()
return false 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) { function controlRemisesProduits(totalHT) {
// Contrôle des remises du marché en fonction du total HT du devis // 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) { function getPrixAchatAvecDiscount(rowidLigne, qte) {
let trPro = document.getElementById('trPro_' + idProduit) let trPro = document.getElementById('trPro_' + rowidLigne)
const prixAchat = parseFloat(trPro.dataset.achat) const prixAchat = parseFloat(trPro.dataset.achat)
const qtt = parseInt(qte, 10) const qtt = parseInt(qte, 10)
// console.log("==== Début de getPrixAchatAvecDiscount pour la ref " + trPro.dataset.code + " : prix achat " + prixAchat + " et qte " + qtt); // 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 // on applique le prc_discount
prixAchatDiscount = prixAchat - (prixAchat * dscnt) / 100 prixAchatDiscount = prixAchat - (prixAchat * dscnt) / 100
console.log( console.log(
'=== idProduit : ' + '=== rowidLigne : ' +
idProduit + rowidLigne +
' (prc_discount_' + ' (prc_discount_' +
inc + inc +
' applique de ' + ' applique de ' +
@@ -2474,15 +2695,15 @@ window.addEventListener('DOMContentLoaded', (event) => {
let calculDevis = function () { let calculDevis = function () {
console.log('calculDevis...') 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 // 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 const code = trPro.dataset.code
let prixAchat = trPro.dataset.achat let prixAchat = trPro.dataset.achat
const prixVente = trPro.dataset.vente 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 qte = 0
let remise = 0 let remise = 0
@@ -2491,20 +2712,20 @@ window.addEventListener('DOMContentLoaded', (event) => {
if (this.name.indexOf('inpQte') > -1) { if (this.name.indexOf('inpQte') > -1) {
// c'est la quantité qui a changé // c'est la quantité qui a changé
qte = this.value qte = this.value
remise = document.getElementById('inpRemise_' + idProduit).value remise = document.getElementById('inpRemise_' + rowidLigne).value
variante = document.getElementById('chkVariante_' + idProduit).checked variante = document.getElementById('chkVariante_' + rowidLigne).checked
typeInput = 'qte' typeInput = 'qte'
} else if (this.name.indexOf('inpRemise') > -1) { } else if (this.name.indexOf('inpRemise') > -1) {
// c'est la remise qui a changé // c'est la remise qui a changé
qte = document.getElementById('inpQte_' + idProduit).value qte = document.getElementById('inpQte_' + rowidLigne).value
remise = this.value remise = this.value
variante = document.getElementById('chkVariante_' + idProduit).checked variante = document.getElementById('chkVariante_' + rowidLigne).checked
typeInput = 'remise' typeInput = 'remise'
chkSaisieRemise = true chkSaisieRemise = true
} else if (this.name.indexOf('chkVariante') > -1) { } else if (this.name.indexOf('chkVariante') > -1) {
// c'est la variante qui a changé // c'est la variante qui a changé
qte = document.getElementById('inpQte_' + idProduit).value qte = document.getElementById('inpQte_' + rowidLigne).value
remise = document.getElementById('inpRemise_' + idProduit).value remise = document.getElementById('inpRemise_' + rowidLigne).value
variante = this.checked variante = this.checked
typeInput = 'variante' typeInput = 'variante'
} }
@@ -2526,7 +2747,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
totalHt = (prixVente * 1 - remiseProduit * 1) * (qte * 1) 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) inpHT.value = parseFloat(totalHt).toFixed(2)
// Modif du 25/04 : on calcule la marge même si c'est une variante / option // 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 { } else {
txMarge = 0 txMarge = 0
console.log( console.log(
'ERREUR idProduit : ' + 'ERREUR rowidLigne : ' +
idProduit + rowidLigne +
', code : ' + ', code : ' +
code + code +
' - prixAchat : ' + ' - 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) inpMG.value = parseFloat(txMarge).toFixed(2)
console.log('Boucle 1 : calcul Total HT sans remise') 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 //! 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++) { for (let i = 0, elInp; (elInp = document.querySelectorAll("[name ^= 'inpQte_' ]")[i]); i++) {
const idProd = elInp.dataset.rid const rowidLigneProd = elInp.dataset.rowidligne
const ligne = document.getElementById('trPro_' + idProd) const ligne = document.getElementById('trPro_' + rowidLigneProd)
const code = ligne.dataset.code const code = ligne.dataset.code
const vente = ligne.dataset.vente * 1 const vente = ligne.dataset.vente * 1
const qte = elInp.value * 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é // 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 // Fin de la mise à jour du 09/11
const varOption = document.getElementById('chkVariante_' + idProd).checked const varOption = document.getElementById('chkVariante_' + rowidLigneProd).checked
if (!varOption) { if (!varOption) {
// calcul avec juste la quantité et le prix de vente // calcul avec juste la quantité et le prix de vente
const vente = elInp.dataset.vente * 1 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) // Mise à jour du 03/11/2023 : nouvelle colonne du Prix de Vente Unitaire (avec remise)
// On met à jour le PUVenteRem sur la ligne // 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 const remise = remProd * 1
let puVenteApresRemise = vente let puVenteApresRemise = vente
if (remise > 0) { if (remise > 0) {
puVenteApresRemise = vente - (vente * remise) / 100 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) console.log('--- 1 Produit : ' + code + ' - PUVenteApresRemise : ' + puVenteApresRemise)
// Fin de la mise à jour du 03/11/2023 // 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) 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 //! 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++) { for (let i = 0, elInp; (elInp = document.querySelectorAll("[name ^= 'inpRemise_' ]")[i]); i++) {
// calcul avec la quantité, le prix de vente et la remise // calcul avec la quantité, le prix de vente et la remise
const idProd = elInp.dataset.rid const rowidLigneProd = elInp.dataset.rowidligne
const ligne = document.getElementById('trPro_' + idProd) const ligne = document.getElementById('trPro_' + rowidLigneProd)
const vente = ligne.dataset.vente * 1 const vente = ligne.dataset.vente * 1
const achat = ligne.dataset.achatdiscount * 1 const achat = ligne.dataset.achatdiscount * 1
const rem = elInp.value * 1 const rem = elInp.value * 1
@@ -2689,9 +2910,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
// elInp.readOnly = false; // elInp.readOnly = false;
} }
const varOption = document.getElementById('chkVariante_' + idProd).checked const varOption = document.getElementById('chkVariante_' + rowidLigneProd).checked
if (!varOption) { if (!varOption) {
const inpQte = document.getElementById('inpQte_' + idProd) const inpQte = document.getElementById('inpQte_' + rowidLigneProd)
const qte = inpQte.value const qte = inpQte.value
const remiseProduit = (remise * vente) / 100 const remiseProduit = (remise * vente) / 100
@@ -2701,8 +2922,8 @@ window.addEventListener('DOMContentLoaded', (event) => {
console.log( console.log(
'--- 2 ligne code ' + '--- 2 ligne code ' +
ligne.dataset.code + ligne.dataset.code +
' = idProd : ' + ' = rowidLigneProd : ' +
idProd + rowidLigneProd +
', vente : ' + ', vente : ' +
vente + vente +
', achat : ' + ', achat : ' +
@@ -3473,7 +3694,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
inputs.forEach((input) => (input.disabled = false)) inputs.forEach((input) => (input.disabled = false))
// Ajouter un événement click à l'élément svg // 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) svgElement.addEventListener('click', showCommentProd)
addDnDHandlers(dropElem) addDnDHandlers(dropElem)
@@ -3495,9 +3716,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
const sonCode = row.dataset.code const sonCode = row.dataset.code
if (sonCode) { if (sonCode) {
row.dataset.ordre = index + 1 row.dataset.ordre = index + 1
const fkProduit = row.dataset.rid const rowidLigne = row.dataset.rowidligne
console.log('index : ' + index + ' code : ' + sonCode) console.log('index : ' + index + ' code : ' + sonCode)
document.getElementById('inpOrdre_' + fkProduit).value = index document.getElementById('inpOrdre_' + rowidLigne).value = index
} }
}) })
showNotification( showNotification(