Files
Cleo/controllers/cjxpost.php
pierre a4d1c22a93 feat(v2.0.3): Marchés hybrides et améliorations multiples
Fonctionnalités principales :

1. Marchés hybrides - Onglet Mercurial
   - Ajout onglet Mercurial avec style distinct (vert, gras, blanc)
   - Affichage des produits mercuriaux pour marchés hybrides
   - Filtrage automatique des produits "Hors Marché 999"
   - Documentation Phase 2 avec CAS 1 et CAS 2 de marchés hybrides
   - Règles métier pour validation différenciée (devis 100% mercurial vs mixte)

2. Corrections bugs
   - Fix flag chkChange sur onglet "Sélection Produits" (callback asynchrone)
   - Plus d'alerte intempestive après sauvegarde des produits

3. Outils de déploiement
   - Nouveau script deploy-file.sh pour déploiement unitaire (DEV/PROD)
   - Amélioration deploy-cleo.sh

4. Gestion multi-contacts (v2.0.3)
   - Contrôleur AJAX cjxcontacts.php
   - Script migration clients_contacts
   - Documentation complète

5. Documentation
   - Mise à jour TODO.md avec Phase 2 marchés hybrides
   - Mise à jour README.md v2.0.3
   - Ajout RULES.md
   - Ajout migration_clients_contacts.sql

6. Nettoyage
   - Suppression fichiers obsolètes (conf_new.php, conf_old.php, uof_linet_20250911.sql)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 15:40:06 +01:00

894 lines
35 KiB
PHP

<?php
global $Session;
global $Conf;
global $Route;
//! on va chercher la data de la Session au format tableau
$session_data = $Session->get_data();
//! où on récupère le fk_tiers sur lequel l'utilisateur travaille
if (isset($session_data["tiers"])) {
$fk_tiers = $session_data["tiers"];
} else {
$fk_tiers = 0;
}
$fk_user = $Session->_user["rowid"];
eLog("jxpost action : " . $Route->_action);
function cleanData(&$str)
{
// Fonction de nettoyage des données pour l'export Excel
if ($str == 't') $str = 'TRUE';
if ($str == 'f') $str = 'FALSE';
if (preg_match("/^0/", $str) || preg_match("/^\+?\d{8,}$/", $str) || preg_match("/^\d{4}.\d{1,2}.\d{1,2}/", $str)) {
$str = "'$str";
}
if (strstr($str, '"')) $str = '"' . str_replace('"', '""', $str) . '"';
$str = mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
}
function filterData(&$str)
{
$str = preg_replace("/\t/", "\\t", $str);
$str = preg_replace("/\r?\n/", "\\n", $str);
if (strstr($str, '"')) $str = '"' . str_replace('"', '""', $str) . '"';
}
switch ($Route->_action) {
case "filter":
$idfilter = $_POST["idfilter"];
switch ($idfilter) {
case "filtertiers":
$Session->set_data($idfilter . "_search", $_POST["filter_search"]);
$Session->set_data($idfilter . "_contact", $_POST["filter_contact"]);
$Session->set_data($idfilter . "_ape", $_POST["filter_ape"]);
if (isset($_POST["filter_recent"])) {
$recent = 1;
} else {
$recent = 0;
}
$Session->set_data($idfilter . "_recent", $recent);
if (isset($_POST["filter_agenda"])) {
$agenda = 1;
} else {
$agenda = 0;
}
$Session->set_data($idfilter . "_agenda", $agenda);
if (isset($_POST["filter_archive"])) {
$archive = 1;
} else {
$archive = 0;
}
$Session->set_data($idfilter . "_archive", $archive);
$Session->set_data($idfilter . "_type_tiers", $_POST["filter_type_tiers"]);
$Session->set_data($idfilter . "_ville", $_POST["filter_villeA"]); //! filter_villeA parce que c'est un autocomplete : retourne le rowid qui est en fait la ville !
break;
}
break;
case "upfind":
$tab = $_POST["win"];
$lid = $Route->_param1;
if ($Conf::erp) {
$sql = 'SELECT * FROM medias WHERE dir2="' . $tab . '" AND support_rowid=' . $lid . ';';
} else {
$sql = 'SELECT * FROM medias WHERE support="' . $tab . '" AND support_rowid=' . $lid . ';';
}
$upls = array();
$upls = getinfos($sql, "groupe");
echo json_encode($upls);
break;
case "updelete":
updelete($_POST);
break;
case "upload":
if ($Conf::erp) {
upload($_POST);
} else {
upload_old($_POST);
}
break;
case "medias":
$user = $Session->_user["rowid"];
$type = $Route->_param1; //! facture par exemple
if (isset($_POST["rowid"])) {
$rowid = $_POST["rowid"]; //! c'est le rowid de la facture par exemple
if ($type == "facture" || $type == "devis") {
//! on doit aller chercher le num_facture pour avoir le nom du fichier
if ($type == "facture") {
$num_facture = getdata("devis", $rowid, "num_facture");
} else {
$num_facture = getdata("devis", $rowid, "num_devis");
}
$fk_tiers = getdata("devis", $rowid, "fk_soc");
$nom_tiers = str_normalize(getdata("clients", $fk_tiers, "libelle", "groupe"));
if ($Conf->_entite["raz_num_devis"]) {
$num_facture = substr('000' . $num_facture, -3);
} else {
$num_facture = substr('0000' . $num_facture, -4);
}
$support = $fk_tiers;
$filename = $fk_tiers . "_" . $type . "_" . $num_facture . ".pdf";
if (isset($_POST["relance"])) {
$filename = $fk_tiers . "_" . $type . "_" . $num_facture . "_relance_" . date("Ymd") . ".pdf";
}
$typ = "pdf";
$dir0 = "tiers";
$dir1 = $nom_tiers;
} else {
$support = $type;
$filename = $rowid . ".pdf";
$typ = "pdf";
$rep = $type . DS . $rowid . DS . "pdf";
$dir0 = "tiers";
$dir1 = "dir1";
}
$dir2 = $type;
$des = "";
$pos = "L";
$hau = 0;
$lar = 0;
//! On vérifie d'abord qu'il n'y ait pas un média existant pour le même support, le même support_rowid et le même nom de fichier : doublon !
$sql = 'SELECT * FROM medias WHERE dir0="' . $dir0 . '" AND dir1="' . $dir1 . '" AND dir2="' . $dir2 . '" AND fichier="' . $filename . '";';
$doublon = getinfos($sql, "groupe");
if (count($doublon) == 0) {
//! il n'y a pas de doublon, on peut créer l'enregistrement
$sql = 'INSERT INTO medias SET dir0="' . $dir0 . '", dir1="' . $dir1 . '", dir2="' . $dir2 . '", support_rowid=' . $rowid . ', fichier="' . $filename . '", fk_user_creat=' . $user . ', ';
$sql .= 'type_fichier="' . $typ . '", date_creat="' . date("Y-m-d H:i:s") . '", description="' . $des . '", position="' . $pos . '", hauteur=' . $hau . ', largeur=' . $lar . ';';
} else {
//! l'enregistrement existe déjà : on met à jour date_modif et fk_user_modif
$sql = 'UPDATE medias SET date_modif="' . date("Y-m-d H:i:s") . '", fk_user_modif=' . $user . ' WHERE dir0="' . $dir0 . '" AND dir1="' . $dir1 . '" AND dir2="' . $dir2 . '" AND fichier="' . $filename . '";';
}
eLog("jxpost medias : " . $sql);
qSQL($sql, "groupe");
}
break;
case "refresh":
$win = $Route->_param1;
if ($win != "") {
if (isset($_POST["input"])) {
$input = $_POST["input"];
switch ($win) {
case "winaction":
//! un refresh dans la fenêtre modale winaction
if ($input == "fk_contact") {
$sql = "SELECT rowid, CONCAT(firstname, ' ', name) AS libelle FROM contacts WHERE fk_soc=" . $fk_tiers . " ORDER BY libelle;";
$res = qSQL($sql, "groupe");
$arr = array();
if ($res instanceof PDOStatement) {
while ($rec = $res->fetch(PDO::FETCH_ASSOC)) {
$arr[] = $rec;
}
}
$jsonresult = json_encode($arr);
$lignes = $jsonresult;
echo $lignes;
}
break;
}
}
}
break;
case "mediafind":
$tab = $_POST["win"];
$lid = $Route->_param1;
if ($Conf::erp) {
$sql = 'SELECT * FROM medias WHERE dir2="' . $tab . '" AND support_rowid=' . $lid . ';';
} else {
$sql = 'SELECT * FROM medias WHERE support="' . $tab . '" AND support_rowid=' . $lid . ';';
}
$upls = array();
$upls = getinfos($sql, "groupe");
$upls = $upls[0];
echo json_encode($upls);
break;
case "getdata":
$chp = $_POST["chp"];
$typ = $Route->_param1;
$upls = array();
switch ($typ) {
case "tiers":
// SÉCURITÉ : Liste blanche des colonnes autorisées
$allowedColumns = ['code', 'libelle', 'adresse1', 'adresse2', 'adresse3', 'cp', 'ville',
'contact_nom', 'contact_prenom', 'contact_fonction', 'telephone', 'mobile', 'email'];
if (!in_array($chp, $allowedColumns)) {
echo json_encode(array('error' => 'Colonne non autorisée'));
break;
}
try {
$db = Database::getInstance();
// SÉCURITÉ : Utilisation de requête préparée pour l'ID
$sql = "SELECT `$chp` AS data FROM clients WHERE rowid = :id";
$result = $db->fetchOne($sql, [':id' => intval($fk_tiers)]);
if ($result) {
$upls = $result;
}
} catch (Exception $e) {
error_log("Erreur getdata : " . $e->getMessage());
}
break;
}
echo json_encode($upls);
break;
case "setsession":
if (isset($_POST["key"]) && isset($_POST["val"])) {
$Session->set_data($_POST["key"], $_POST["val"]);
}
break;
// case "autocomplete" supprimé car non utilisé dans l'application
// L'autocomplétion est gérée côté client JavaScript
case "get_context":
//! Renvoie le contexte de l'utilisateur
$ajson = array();
$ajson["user"] = $Session->_user;
$ajson["session"] = $Session->get_data();
$ajson["devip"] = $Conf->_devIp ? "1" : "0";
echo json_encode($ajson);
break;
case "load_client":
//! Charge les infos d'un client
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
// SÉCURITÉ : Validation de l'ID et requête préparée
$cid = intval($data->cid);
if ($cid <= 0) {
echo json_encode(array('error' => 'ID client invalide'));
break;
}
try {
$db = Database::getInstance();
$result = $db->getById('clients', $cid);
echo json_encode($result ?: array());
} catch (Exception $e) {
error_log("Erreur load_client : " . $e->getMessage());
echo json_encode(array('error' => 'Erreur lors du chargement du client'));
}
} else {
echo json_encode(array('error' => 'Pas de client spécifié'));
}
break;
case "search_clients":
//! Cherche les clients correspondant à un libellé, une adresse, un code postal, une ville, un nom, un prénom, une fonction ou un email
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->search)) {
// SÉCURITÉ : Utilisation de requêtes préparées pour la recherche
$search = trim($data->search);
try {
$db = Database::getInstance();
$sql = 'SELECT c.rowid, c.libelle, c.type_client, c.adresse1, c.cp, c.ville FROM clients c
WHERE c.libelle LIKE :search
OR c.adresse1 LIKE :search
OR c.cp LIKE :search
OR c.ville LIKE :search
OR c.contact_nom LIKE :search
OR c.contact_prenom LIKE :search
OR c.contact_fonction LIKE :search
OR c.email LIKE :search
ORDER BY c.libelle';
$searchParam = '%' . $search . '%';
$results = $db->fetchAll($sql, [':search' => $searchParam]);
echo json_encode($results);
} catch (Exception $e) {
error_log("Erreur search_clients : " . $e->getMessage());
echo json_encode(array('error' => 'Erreur lors de la recherche'));
}
} else {
echo json_encode(array('ret' => "ko"));
}
break;
case "load_marches":
//! charge des infos de tous les marchés
$sql = 'SELECT m.rowid, m.libelle FROM marches m ORDER BY m.libelle DESC;';
echo getinfos($sql, "gen", "json");
break;
case "load_marche":
//! Charge les infos d'un marché
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
$cid = nettoie_input($data->cid);
$sql = 'SELECT m.* FROM marches m WHERE m.rowid=' . $cid . ';';
echo getinfos($sql, "gen", "json");
} else {
$ret = array('ret' => "ko");
echo json_encode($ret);
}
break;
case "search_produits":
//! Cherche les produits correspondant à un code ou à un libellé
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->search)) {
$search = nettoie_input($data->search);
$sql = 'SELECT p.*, pf.famille FROM produits p LEFT JOIN produits_familles pf ON p.fk_famille_produit=pf.rowid ';
$sql .= 'WHERE (p.code LIKE "%' . $search . '%" OR p.libelle LIKE "%' . $search . '%" OR p.groupe LIKE "%' . $search . '%" OR pf.famille LIKE "%' . $search . '%") AND p.prix_vente>0 AND p.active=1 ';
$sql .= 'ORDER BY p.libelle;';
echo getinfos($sql, "gen", "json");
} else {
$ret = array('ret' => "ko");
echo json_encode($ret);
}
break;
case "load_familles":
//! Charge les familles de produits existantes
$sql = 'SELECT xf.* FROM x_familles xf ORDER BY xf.ordre;';
echo getinfos($sql, "gen", "json");
break;
case "load_familles_groupes":
//! Charge les familles par groupes de produits existantes
$sql = 'SELECT pf.* FROM produits_familles pf ORDER BY pf.ordre, pf.groupe;';
echo getinfos($sql, "gen", "json");
break;
case "save_familles":
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
//! on récupère les familles existantes
$sql = 'SELECT pf.rowid FROM produits_familles pf ORDER BY pf.rowid;';
$pf = getinfos($sql, "gen");
//! on les désactive toutes
$sql = 'UPDATE produits_familles SET active=0;';
qSQL($sql);
//! on boucle sur les familles reçues
foreach ($data as $row) {
$rowid = nettoie_input($row->id);
$set = 'groupe="' . nettoie_input(trim($row->groupe)) . '", ';
$set .= 'ordre="' . nettoie_input(trim($row->ordre)) . '", ';
$set .= 'fk_famille="' . nettoie_input(trim($row->famille)) . '", ';
$set .= 'code_maintenance="' . nettoie_input(trim($row->maintenance)) . '", ';
$set .= 'marge_rr="' . nettoie_input(trim($row->margerr)) . '", ';
$set .= 'marge_dv="' . nettoie_input(trim($row->margedv)) . '", ';
$set .= 'active=1 ';
//! on recherche si la famille existe déjà
$found = false;
foreach ($pf as $p) {
if ($p["rowid"] == $rowid) {
$found = true;
break;
}
}
if (!$found) {
//! Si la famille n'existe pas, on la crée
$sql = 'INSERT INTO produits_familles SET ' . $set . ';';
} else {
//! Sinon on la met à jour
$sql = 'UPDATE produits_familles SET ' . $set . ' ';
$sql .= 'WHERE rowid=' . $rowid . ';';
}
eLog($sql);
qSQL($sql, "gen");
}
//! enfin, on supprime toutes les familles qui n'ont pas été reçues, active=0
$sql = 'DELETE FROM produits_familles WHERE active=0;';
eLog($sql);
qSQL($sql, "gen");
$ret = array('ret' => "ok", 'msg' => "Enregistrement des familles effectué");
echo json_encode($ret);
break;
case "save_marche":
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->rowid)) {
$cid = nettoie_input($data->rowid);
$act = nettoie_input($data->act);
$set = 'SET libelle="' . nettoie_input(trim($data->libelle)) . '"';
$set .= ', numero="' . nettoie_input($data->numero) . '"';
$set .= ', nom="' . nettoie_input($data->nom) . '"';
$set .= isset($data->chk_remise_sur_tg) ? ', chk_remise_sur_tg=1' : ', chk_remise_sur_tg=0';
$set .= isset($data->chk_prix_nets) ? ', chk_prix_nets=1' : ', chk_prix_nets=0';
$set .= isset($data->chk_marche_public) ? ', chk_marche_public=1' : ', chk_marche_public=0';
if ($data->taux_remise_trimestrielle == "") {
$set .= ', taux_remise_trimestrielle=0';
} else {
$set .= ', taux_remise_trimestrielle=' . $data->taux_remise_trimestrielle;
}
if ($data->taux_remise_semestrielle == "") {
$set .= ', taux_remise_semestrielle=0';
} else {
$set .= ', taux_remise_semestrielle=' . $data->taux_remise_semestrielle;
}
if ($data->taux_remise_annuelle == "") {
$set .= ', taux_remise_annuelle=0';
} else {
$set .= ', taux_remise_annuelle=' . $data->taux_remise_annuelle;
}
$set .= ', date_debut="' . d6GetDate($data->date_debut, "FM") . '"';
$set .= ', date_fin="' . d6GetDate($data->date_fin, "FM") . '"';
$set .= ', date_validite_prix="' . d6GetDate($data->date_validite_prix, "FM") . '"';
$set .= ', franco_de_port="' . nettoie_input(trim($data->franco_de_port)) . '"';
$set .= ', garantie="' . nettoie_input(trim($data->garantie)) . '"';
$set .= ', delai_de_livraison="' . nettoie_input(trim($data->delai_de_livraison)) . '"';
$set .= ', remises_commerciales="' . nettoie_input(trim($data->remises_commerciales)) . '"';
if ($data->remise_palier_1 == "") {
$set .= ', remise_palier_1=0';
} else {
$set .= ', remise_palier_1=' . $data->remise_palier_1;
}
if ($data->remise_taux_1 == "") {
$set .= ', remise_taux_1=0';
} else {
$set .= ', remise_taux_1=' . $data->remise_taux_1;
}
if ($data->remise_palier_2 == "") {
$set .= ', remise_palier_2=0';
} else {
$set .= ', remise_palier_2=' . $data->remise_palier_2;
}
if ($data->remise_taux_2 == "") {
$set .= ', remise_taux_2=0';
} else {
$set .= ', remise_taux_2=' . $data->remise_taux_2;
}
if ($data->remise_palier_3 == "") {
$set .= ', remise_palier_3=0';
} else {
$set .= ', remise_palier_3=' . $data->remise_palier_3;
}
if ($data->remise_taux_3 == "") {
$set .= ', remise_taux_3=0';
} else {
$set .= ', remise_taux_3=' . $data->remise_taux_3;
}
if ($data->remise_palier_4 == "") {
$set .= ', remise_palier_4=0';
} else {
$set .= ', remise_palier_4=' . $data->remise_palier_4;
}
if ($data->remise_taux_4 == "") {
$set .= ', remise_taux_4=0';
} else {
$set .= ', remise_taux_4=' . $data->remise_taux_4;
}
$set .= ', commentaire="' . nettoie_input(trim($data->commentaire)) . '"';
$set .= isset($data->chk_cache_commerciaux) ? ', chk_cache_commerciaux=1' : ', chk_cache_commerciaux=0';
$set .= isset($data->chk_marche_hybride) ? ', chk_marche_hybride=1' : ', chk_marche_hybride=0';
$set .= isset($data->chk_regle_seuils_marge) ? ', chk_regle_seuils_marge=1' : ', chk_regle_seuils_marge=0';
$set .= isset($data->active) ? ', active=1' : ', active=0';
if ($cid == 0) {
$set .= ', date_creat = "' . date("Y-m-d H:i:s") . '", fk_user_creat=' . $fk_user;
$sql = 'INSERT INTO marches ' . $set . ';';
} else {
$set .= ', date_modif = "' . date("Y-m-d H:i:s") . '", fk_user_modif=' . $fk_user;
$sql = 'UPDATE marches ' . $set . ' WHERE rowid=' . $cid . ';';
}
eLog($sql);
qSQL($sql, "gen");
$ret = array('ret' => "ok");
echo json_encode($ret);
} else {
$ret = array('ret' => "ko");
echo json_encode($ret);
}
break;
case "delete_marche":
//! Supprime un marché
//! Réception de l'id du marché à supprimer
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
// SÉCURITÉ : Validation de l'ID comme entier
$cid = intval($data->cid);
if ($cid <= 0) {
echo json_encode(array('ret' => "ko", 'msg' => 'ID invalide'));
break;
}
try {
$db = Database::getInstance();
// Utilisation de requêtes préparées pour la suppression
$sql1 = 'DELETE FROM marches WHERE rowid = :id';
$db->query($sql1, ['id' => $cid]);
$sql2 = 'DELETE FROM marches_listes WHERE fk_marche = :id';
$db->query($sql2, ['id' => $cid]);
$sql3 = 'DELETE FROM produits WHERE fk_marche = :id';
$db->query($sql3, ['id' => $cid]);
eLog("Marché supprimé : ID=$cid");
$ret = array('ret' => "ok", 'msg' => 'Marché supprimé');
echo json_encode($ret);
} catch (Exception $e) {
eLog("Erreur suppression marché : " . $e->getMessage());
echo json_encode(array('ret' => "ko", 'msg' => 'Erreur lors de la suppression'));
}
} else {
$ret = array('ret' => "ko", 'msg' => 'Marché non supprimé');
echo json_encode($ret);
}
break;
case "save_marches_listes":
//! Enregistre les mises à jour du tableau des listes tarifaires par marché
//! Réception du tableau des mots clés par marché en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->idListe)) {
if ($data->idListe == "1") {
foreach ($data as $key => $val) {
if (substr($key, 0, 7) == "motcle_") {
$fk_marche = trim(substr($key, 7));
$motCle = trim($val);
$motCleAchat = "";
$motCleVente = "";
foreach ($data as $keyAV => $valAV) {
if ($keyAV == "motcleachat_" . $fk_marche) {
$motCleAchat = trim($valAV);
} else {
if ($keyAV == "motclevente_" . $fk_marche) {
$motCleVente = trim($valAV);
}
}
}
$sql = 'SELECT l.rowid FROM marches_listes l WHERE l.fk_marche=' . $fk_marche . ';';
$retour = getinfos($sql, "gen");
if (count($retour) > 0) {
$sql = 'UPDATE marches_listes SET mot_cle="' . $motCle . '", terme_achat="' . $motCleAchat . '", terme_vente="' . $motCleVente . '" WHERE fk_marche=' . $fk_marche . ';';
qSQL($sql, "gen");
} else {
$sql = 'INSERT INTO marches_listes SET fk_marche=' . $fk_marche . ', mot_cle="' . $motCle . '", terme_achat="' . $motCleAchat . '", terme_vente="' . $motCleVente . '";';
qSQL($sql, "gen");
}
}
}
$ret = array('ret' => "ok", "msg" => "Enregistrement des données effectué");
echo json_encode($ret);
} else {
$ret = array('ret' => "ko", "msg" => "Erreur lors de la réception des données à enregistrer");
echo json_encode($ret);
}
} else {
$ret = array('ret' => "ko", "msg" => "Erreur lors de la réception des données à enregistrer");
echo json_encode($ret);
}
break;
case "load_produits_marche":
//! Charge les produits d'un marché
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
$cid = nettoie_input($data->cid);
// On récupère certaines infos du marché pour savoir si on doit appliquer des filtres
$sql = 'SELECT m.chk_remise_sur_tg FROM marches m WHERE m.rowid=' . $cid . ';';
$retour = getinfos($sql, "gen");
$chk_remise_sur_tg = $retour[0]["chk_remise_sur_tg"];
if ($cid == 999 || $chk_remise_sur_tg == 1) {
// c'est directement le TG, on ne fait rien de spécial
$sql = 'SELECT p.rowid, p.code, p.libelle, concat(p.code, " # ", p.libelle) as rech, p.groupe, p.prix_achat_net, p.prix_vente, p.prc_discount_1 ';
$sql .= 'FROM produits p WHERE p.fk_marche=' . $cid . ' AND p.active=1 ORDER BY p.code DESC;';
echo getinfos($sql, "gen", "json");
} else {
// On regarde le terme_achat du marché
$sql = 'SELECT l.terme_achat FROM marches_listes l WHERE l.fk_marche=' . $cid . ';';
$retour = getinfos($sql, "gen");
$terme_achat = $retour[0]["terme_achat"];
if ($terme_achat == "Purchasing") {
// On doit alors récupérer leur prix d'achat dans le marché TG
$sql = 'SELECT p.rowid, p.code, p.libelle, concat(p.code, " # ", p.libelle) as rech, p.groupe, p.prix_achat_net, p.prix_vente, p.prc_discount_1 ';
$sql .= 'FROM produits p WHERE p.fk_marche=' . $cid . ' AND p.active=1 ORDER BY p.code DESC;';
$retour = getinfos($sql, "gen");
if (count($retour) > 0) {
foreach ($retour as &$prod) {
$sql = 'SELECT p.prix_achat_net, p.prc_discount_1 FROM produits p WHERE p.fk_marche=999 AND p.code="' . $prod["code"] . '";';
$retour2 = getinfos($sql, "gen");
if (count($retour2) == 1) {
$prod["prix_achat_net"] = $retour2[0]["prix_achat_net"];
$prod["prc_discount_1"] = $retour2[0]["prc_discount_1"];
}
}
}
echo json_encode($retour);
} else {
$sql = 'SELECT p.rowid, p.code, p.libelle, concat(p.code, " # ", p.libelle) as rech, p.groupe, p.prix_achat_net, p.prix_vente, p.prc_discount_1 ';
$sql .= 'FROM produits p WHERE p.fk_marche=' . $cid . ' AND p.active=1 ORDER BY p.code DESC;';
echo getinfos($sql, "gen", "json");
}
}
} else {
$ret = array("ret" => "ko", "msg" => "Erreur lors de l'envoi du marché à charger");
echo json_encode($ret);
}
break;
case "load_marches_listes":
//! Charge la liste des mots clés des marchés à retrouver dans les fichiers d'importation
$sql = 'SELECT l.* FROM marches_listes l;';
echo getinfos($sql, "gen", "json");
break;
case "load_user":
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
$cid = nettoie_input($data->cid);
$sql = 'SELECT u.* FROM users u WHERE u.rowid=' . $cid . ';';
echo getinfos($sql, "gen", "json");
} else {
$ret = array("ret" => "ko", "msg" => "Erreur lors de la récupération des infos de l'utilisateur");
echo json_encode($ret);
}
break;
case "save_user":
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->rowid)) {
$cid = nettoie_input($data->rowid);
$act = nettoie_input($data->act);
$set = 'SET libelle="' . nettoie_input(trim($data->libelle)) . '"';
$set .= ', prenom="' . nettoie_input(trim($data->prenom)) . '"';
$set .= ', mobile="' . nettoie_input(trim($data->mobile)) . '"';
$set .= ', email="' . nettoie_input(trim($data->email)) . '"';
$set .= ', username="' . nettoie_input(trim($data->username)) . '"';
$set .= ', fk_role=' . nettoie_input(trim($data->fk_role));
$set .= ', fk_region=' . nettoie_input(trim($data->fk_region));
if (isset($data->fk_parent)) {
$set .= ', fk_parent=' . nettoie_input(trim($data->fk_parent));
} else {
$set .= ', fk_parent=0';
}
$set .= ', lst_depts="' . nettoie_input(trim($data->lst_depts)) . '"';
$set .= isset($data->chk_grands_comptes) ? ', chk_grands_comptes=1' : ', chk_grands_comptes=0';
$set .= isset($data->active) ? ', active=1' : ', active=0';
if ($cid == 0) {
// on lui crée un mot de passe par défaut : initiale prénom en majuscule + initiale nom Maj + 3 caractères suivants du nom en minuscules + . + mois + année
// On supprime les espaces dans le nom de l'utilisateur et on ne garde que les 3 premiers caractères
$libUser = str_replace(" ", "", nettoie_input(trim($data->libelle)));
$pwd = strtoupper(substr(nettoie_input(trim($data->prenom)), 0, 1)) . strtoupper(substr($libUser, 0, 1)) . strtolower(substr($libUser, 1, 3)) . "." . date("mY");
eLog($pwd);
$set .= ', userpswd="' . hashPsswd($pwd) . '"';
$set .= ', date_creat = "' . date("Y-m-d H:i:s") . '", fk_user_creat=' . $fk_user;
$sql = 'INSERT INTO users ' . $set . ';';
} else {
$set .= ', date_modif = "' . date("Y-m-d H:i:s") . '", fk_user_modif=' . $fk_user;
$sql = 'UPDATE users ' . $set . ' WHERE rowid=' . $cid . ';';
}
eLog($sql);
qSQL($sql, "gen");
$ret = array('ret' => "ok");
echo json_encode($ret);
} else {
$ret = array('ret' => "ko");
echo json_encode($ret);
}
break;
case "delete_user":
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
// SÉCURITÉ : Validation de l'ID comme entier
$cid = intval($data->cid);
if ($cid <= 0) {
echo json_encode(array('ret' => "ko", 'msg' => 'ID invalide'));
break;
}
try {
$db = Database::getInstance();
// TODO : Supprimer les devis créés par cet utilisateur
// Utilisation de la fonction sécurisée deleteById
$result = $db->deleteById('users', $cid);
if ($result) {
eLog("Utilisateur supprimé : ID=$cid");
echo json_encode(array('ret' => "ok"));
} else {
echo json_encode(array('ret' => "ko", 'msg' => 'Utilisateur non trouvé'));
}
} catch (Exception $e) {
eLog("Erreur suppression utilisateur : " . $e->getMessage());
echo json_encode(array('ret' => "ko", 'msg' => 'Erreur lors de la suppression'));
}
} else {
echo json_encode(array('ret' => "ko", 'msg' => 'ID manquant'));
}
break;
case "maintenance":
if ($Conf->_devIp) {
// Mise à jour des mots de passe Utilisateurs
$sql = 'SELECT u.rowid, u.prenom, u.libelle FROM users u WHERE u.active=1 AND u.rowid=39;';
$users = getinfos($sql, "gen");
foreach ($users as $user) {
$uId = $user["rowid"];
$libUser = str_replace(" ", "", nettoie_input(trim($user["libelle"])));
$pwd = strtoupper(substr(nettoie_input(trim($user["prenom"])), 0, 1)) . strtoupper(substr($libUser, 0, 1)) . strtolower(substr($libUser, 1, 3)) . "." . date("mY");
eLog($pwd);
$sql = 'UPDATE users SET userpswd="' . hashPsswd($pwd) . '" WHERE rowid=' . $uId . ';';
eLog($sql);
qSQL($sql, "gen");
}
}
break;
case "load_roles":
$sql = 'SELECT xro.rowid, xro.libelle FROM x_roles xro WHERE xro.active=1 ORDER BY xro.rowid;';
echo getinfos($sql, "gen", "json");
break;
case "load_regions":
$sql = 'SELECT xre.rowid, xre.libelle FROM x_regions xre WHERE xre.active=1 ORDER BY xre.rowid;';
echo getinfos($sql, "gen", "json");
break;
case "export_sap_devis":
$data = json_decode(file_get_contents("php://input"));
eLog("export_sap_devis: ");
if (isset($data->cid)) {
$cid = nettoie_input($data->cid);
$sql = 'SELECT d.* FROM devis d WHERE d.rowid=' . $cid . ';';
$dev = getinfos($sql, "gen");
$devis = $dev[0];
eLog("export_sap_devis: " . $cid . " & " . $devis["fk_client"]);
$filename = "devis_" . $cid . "_" . date('Y_m_d_hi') . ".csv";
$fields = array("Code", "Etablissement", "Adresse 1", "Adresse 2");
// SÉCURITÉ : Utilisation de requête préparée pour l'ID client
try {
$db = Database::getInstance();
$sql = 'SELECT c.code, c.libelle, c.adresse1, c.adresse2 FROM clients c WHERE c.rowid = :id';
$client = $db->fetchOne($sql, [':id' => intval($devis["fk_client"])]);
if (!$client) {
$client = ['code' => '', 'libelle' => '', 'adresse1' => '', 'adresse2' => ''];
}
} catch (Exception $e) {
error_log("Erreur export_sap_devis : " . $e->getMessage());
$client = ['code' => '', 'libelle' => '', 'adresse1' => '', 'adresse2' => ''];
}
$excelData = implode("\t", array_values($fields)) . "\n";
eLog($excelData);
array_walk($client, 'filterData');
$excelData .= implode("\t", array_values($client)) . "\n";
$sql = 'SELECT p.code, p.libelle, p.prix_achat_net, p.prix_vente, dp.qte, dp.remise, dp.totalht FROM devis_produits dp LEFT JOIN produits p ON dp.fk_produit=p.rowid WHERE dp.fk_devis=' . $cid . ';';
eLog($sql);
$data = getinfos($sql, "gen");
// une ligne vierge de séparation
$excelData .= "\n";
$fields = array("Code", "Désignation", "Prix Achat", "Prix Vente", "Quantité", "Remise", "Total HT");
$excelData .= implode("\t", array_values($fields)) . "\n";
foreach ($data as $row) {
array_walk($row, 'filterData');
$excelData .= implode("\t", $row) . "\n";
}
eLog($excelData);
header("Content-Type: application/vnd.ms-excel; charset=UTF-16LE");
header("Content-Disposition: attachment; filename=$filename");
header("Content-Disposition: attachment; filename=\"$filename\"");
echo $excelData;
exit;
}
break;
case "load_info":
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
$cid = nettoie_input($data->cid);
$sql = 'SELECT i.* FROM infos i WHERE i.rowid=' . $cid . ';';
echo getinfos($sql, "gen", "json");
}
break;
case "save_info":
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
$cid = nettoie_input($data->cid);
$set = 'SET date_infos="' . nettoie_input($data->cdate) . '" ';
$set .= ', titre_infos="' . nettoie_input(trim($data->ctitre)) . '" ';
$set .= ', text_infos="' . nettoie_input(trim($data->ctexte)) . '" ';
if (isset($data->cpublie)) {
if ($data->cpublie == false || $data->cpublie == 0) {
$set .= ', chk_publie=0';
} else {
$set .= ', chk_publie=1';
}
} else {
$set .= ', chk_publie=0';
}
if ($cid == 0) {
$set .= ', date_creat = "' . date("Y-m-d H:i:s") . '", fk_user_creat=' . $fk_user;
$sql = 'INSERT INTO infos ' . $set . ';';
} else {
$set .= ', date_modif = "' . date("Y-m-d H:i:s") . '", fk_user_modif=' . $fk_user;
$sql = 'UPDATE infos ' . $set . ' WHERE rowid=' . $cid . ';';
}
eLog($sql);
qSQL($sql, "gen");
$ret = array('ret' => "ok");
echo json_encode($ret);
} else {
$ret = array('ret' => "ko");
echo json_encode($ret);
}
break;
case "supp_info":
//! Réception et lecture de la demande en json
$data = json_decode(file_get_contents("php://input"));
if (isset($data->cid)) {
// SÉCURITÉ : Validation de l'ID comme entier
$cid = intval($data->cid);
if ($cid <= 0) {
echo json_encode(array('ret' => "ko", 'msg' => 'ID invalide'));
break;
}
try {
$db = Database::getInstance();
$sql = 'DELETE FROM infos WHERE rowid = :id';
$stmt = $db->query($sql, ['id' => $cid]);
$result = $stmt->rowCount() > 0;
if ($result) {
eLog("Info supprimée : ID=$cid");
echo json_encode(array('ret' => "ok"));
} else {
echo json_encode(array('ret' => "ko", 'msg' => 'Info non trouvée'));
}
} catch (Exception $e) {
eLog("Erreur suppression info : " . $e->getMessage());
echo json_encode(array('ret' => "ko", 'msg' => 'Erreur lors de la suppression'));
}
}
break;
}
exit();