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>
This commit is contained in:
@@ -54,7 +54,7 @@ switch ($Route->_action) {
|
||||
|
||||
//! 4. On met à jour la date_demande, date_remise, num_opportunite et fk_statut_devis du nouveau devis
|
||||
$newRowidSafe = intval($newRowid);
|
||||
$sql = 'UPDATE devis SET date_demande = "' . date("Y-m-d H:i:s") . '", date_remise = "", num_opportunite = "", fk_statut_devis = 1 WHERE rowid = ' . $newRowidSafe . ';';
|
||||
$sql = 'UPDATE devis SET date_demande = "' . date("Y-m-d H:i:s") . '", date_remise = NULL, num_opportunite = "", fk_statut_devis = 1 WHERE rowid = ' . $newRowidSafe . ';';
|
||||
eLog($sql);
|
||||
qSQL($sql, "gen");
|
||||
|
||||
@@ -124,14 +124,16 @@ switch ($Route->_action) {
|
||||
$sql = 'UPDATE devis SET chk_maj = 0 WHERE rowid = ' . $cidSafe . ';';
|
||||
qSQL($sql, "gen");
|
||||
}
|
||||
$sql = 'SELECT d.rowid, d.fk_user, d.fk_client, d.fk_marche, m.libelle AS lib_marche, d.fk_statut_devis, d.dossier, d.num_opportunite, d.montant_total_ht, ';
|
||||
$sql = 'SELECT d.rowid, d.fk_user, d.fk_client, d.fk_contact, d.fk_marche, m.libelle AS lib_marche, d.fk_statut_devis, d.dossier, d.num_opportunite, d.montant_total_ht, ';
|
||||
$sql .= 'd.date_demande, d.date_remise, d.montant_total_ht_remise, d.marge_totale, d.commentaire, d.comment_devis, d.chk_clients_secteur, d.chk_speciaux, ';
|
||||
$sql .= 'd.lib_new_client, d.type_new_client, d.adresse1_new_client, d.adresse2_new_client, d.adresse3_new_client, d.cp_new_client, d.ville_new_client,';
|
||||
$sql .= 'd.contact_new_nom, d.contact_new_prenom, d.contact_new_fonction, d.new_telephone, d.new_mobile, d.new_email, ';
|
||||
$sql .= 'd.comment_geste_comm, d.comment_validat, d.chk_validat, d.fk_user_validat, d.date_validat, ';
|
||||
$sql .= 'xs.libelle as lib_statut_devis, c.code, c.libelle, c.adresse1, c.adresse2, c.adresse3, c.cp, c.ville, c.type_client, ';
|
||||
$sql .= 'c.contact_nom, c.contact_prenom, c.contact_fonction, c.telephone, c.mobile, c.email, d.chk_devis_photos ';
|
||||
$sql .= 'FROM devis d LEFT JOIN clients c ON d.fk_client = c.rowid LEFT JOIN x_statuts_devis xs ON d.fk_statut_devis = xs.rowid ';
|
||||
$sql .= 'cc.rowid as contact_rowid, cc.nom as contact_nom, cc.prenom as contact_prenom, cc.fonction as contact_fonction, cc.telephone, cc.mobile, cc.email, d.chk_devis_photos ';
|
||||
$sql .= 'FROM devis d LEFT JOIN clients c ON d.fk_client = c.rowid ';
|
||||
$sql .= 'LEFT JOIN clients_contacts cc ON d.fk_contact = cc.rowid ';
|
||||
$sql .= 'LEFT JOIN x_statuts_devis xs ON d.fk_statut_devis = xs.rowid ';
|
||||
$sql .= 'LEFT JOIN marches m ON d.fk_marche = m.rowid ';
|
||||
$sql .= 'WHERE d.rowid = ' . $cidSafe . ';';
|
||||
echo getinfos($sql, "gen", "json");
|
||||
@@ -162,7 +164,7 @@ switch ($Route->_action) {
|
||||
if (isset($data->secteur)) {
|
||||
$chkSecteur = nettoie_input($data->secteur);
|
||||
$fkUser = nettoie_input($data->user);
|
||||
$sql = 'SELECT rowid, libelle, CONCAT(libelle, ", ", adresse1, ", ", cp, " ", ville) AS rech, adresse1, adresse2, adresse3, cp, ville, contact_nom, contact_prenom, contact_fonction, telephone, mobile, email, type_client FROM clients WHERE active=1 ';
|
||||
$sql = 'SELECT rowid, code, libelle, CONCAT(libelle, ", ", adresse1, ", ", cp, " ", ville) AS rech, adresse1, adresse2, adresse3, cp, ville, contact_nom, contact_prenom, contact_fonction, telephone, mobile, email, type_client FROM clients WHERE active=1 ';
|
||||
if ($chkSecteur == "1") {
|
||||
//! on ne prend que les clients du secteur de l'utilisateur
|
||||
$fkUserSafe = intval($fkUser);
|
||||
@@ -197,6 +199,32 @@ switch ($Route->_action) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "load_produits_mercurial":
|
||||
//! Charge les produits du marché hybride pour l'onglet Mercurial
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
if (isset($data->fk_marche)) {
|
||||
$fkMarche = nettoie_input($data->fk_marche);
|
||||
$fkMarcheSafe = intval($fkMarche);
|
||||
|
||||
// Vérifier que le marché est bien hybride
|
||||
$sql = 'SELECT chk_marche_hybride FROM marches WHERE rowid = ' . $fkMarcheSafe . ';';
|
||||
$retSql = getinfos($sql, "gen");
|
||||
|
||||
if (count($retSql) == 1 && $retSql[0]["chk_marche_hybride"] == 1) {
|
||||
// Le marché est hybride, on charge tous les produits de ce marché (hors 999)
|
||||
$sql = 'SELECT p.*, CONCAT(p.code, " - ", p.libelle) AS rech, pf.fk_famille, xf.libelle AS lib_famille ';
|
||||
$sql .= 'FROM produits p LEFT JOIN produits_familles pf ON p.groupe=pf.groupe LEFT JOIN x_familles xf on pf.fk_famille = xf.rowid ';
|
||||
$sql .= 'WHERE p.fk_marche = ' . $fkMarcheSafe . ' AND p.fk_marche != 999 AND p.active=1 ORDER BY xf.ordre, pf.ordre;';
|
||||
echo getinfos($sql, "gen", "json");
|
||||
} else {
|
||||
// Le marché n'est pas hybride, on retourne un tableau vide
|
||||
echo json_encode(array());
|
||||
}
|
||||
} else {
|
||||
echo json_encode(array());
|
||||
}
|
||||
break;
|
||||
|
||||
case "load_devis_marche_produits":
|
||||
//! Charge les produits enregistrés pour un marché
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
@@ -234,7 +262,7 @@ switch ($Route->_action) {
|
||||
|
||||
$sql = 'SELECT p.*, CONCAT(p.code, " - ", p.libelle) AS rech, pf.fk_famille, xf.libelle AS lib_famille, "0" AS chk_prix_net ';
|
||||
$sql .= 'FROM produits p LEFT JOIN produits_familles pf ON p.groupe=pf.groupe LEFT JOIN x_familles xf on pf.fk_famille = xf.rowid ';
|
||||
$sql .= 'WHERE p.fk_marche = ' . $cidSafe . ' AND p.active=1 ORDER BY xf.ordre, pf.ordre;';
|
||||
$sql .= 'WHERE p.fk_marche = ' . intval($cid) . ' AND p.active=1 ORDER BY xf.ordre, pf.ordre;';
|
||||
$upls = getinfos($sql, "gen");
|
||||
|
||||
if ($cid != "999") {
|
||||
@@ -390,31 +418,13 @@ switch ($Route->_action) {
|
||||
$commentaire = nettoie_input($_POST["commentaire"]);
|
||||
$newCommentaire = 0;
|
||||
|
||||
$contact_nom = nettoie_input($_POST["contact_nom"]);
|
||||
$contact_prenom = nettoie_input($_POST["contact_prenom"]);
|
||||
$contact_fonction = nettoie_input($_POST["contact_fonction"]);
|
||||
$email = nettoie_input($_POST["email"]);
|
||||
$telephone = formattel(nettoie_input($_POST["telephone"]));
|
||||
$mobile = formattel(nettoie_input($_POST["mobile"]));
|
||||
// Récupération du contact sélectionné
|
||||
$fk_contact = isset($_POST["fk_contact"]) ? intval($_POST["fk_contact"]) : 0;
|
||||
if ($fk_contact == 0) $fk_contact = NULL;
|
||||
|
||||
$set = 'fk_client=' . $fk_client . ', num_opportunite="' . $num_opportunite . '", date_demande="' . $date_demande . '", date_remise="' . $date_remise . '", ';
|
||||
$set = 'fk_client=' . $fk_client . ', fk_contact=' . ($fk_contact === NULL ? 'NULL' : $fk_contact) . ', num_opportunite="' . $num_opportunite . '", date_demande="' . $date_demande . '", date_remise="' . $date_remise . '", ';
|
||||
$set .= 'fk_user=' . $fk_user . ', fk_marche=' . $fk_marche . ', commentaire="' . $commentaire . '", chk_devis_photos=' . $chk_devis_photos . ', ';
|
||||
|
||||
if ($fk_client == 0) {
|
||||
//! C'est un nouveau client : on enregistre ces infos et celle du contact dans le devis et non au niveau de la table clients
|
||||
$libNewClient = nettoie_input($_POST["lib_client"]);
|
||||
$typNewClient = nettoie_input($_POST["type_client"]);
|
||||
$adr1NewClient = nettoie_input($_POST["adresse1"]);
|
||||
$adr2NewClient = nettoie_input($_POST["adresse2"]);
|
||||
$adr3NewClient = nettoie_input($_POST["adresse3"]);
|
||||
$cpNewClient = nettoie_input($_POST["cp"]);
|
||||
// Si le CP a une longueur de 4, on rajoute un 0 devant
|
||||
if (strlen($cpNewClient) == 4) $cpNewClient = "0" . $cpNewClient;
|
||||
$villeNewClient = nettoie_input($_POST["ville"]);
|
||||
$set .= 'lib_new_client="' . $libNewClient . '", type_new_client="' . $typNewClient . '", adresse1_new_client="' . $adr1NewClient . '", adresse2_new_client="' . $adr2NewClient . '", adresse3_new_client="' . $adr3NewClient . '", cp_new_client="' . $cpNewClient . '", ville_new_client="' . $villeNewClient . '", ';
|
||||
$set .= 'contact_new_nom="' . $contact_nom . '", contact_new_prenom="' . $contact_prenom . '", contact_new_fonction="' . $contact_fonction . '", new_email="' . $email . '", new_telephone="' . $telephone . '", new_mobile="' . $mobile . '", ';
|
||||
}
|
||||
|
||||
if ($_POST["rowid"] == 0) {
|
||||
//! C'est un nouveau devis
|
||||
//! On le range dans un dossier
|
||||
@@ -465,14 +475,8 @@ switch ($Route->_action) {
|
||||
}
|
||||
eLog('Entete Devis Save : ' . $sql);
|
||||
|
||||
if ($fk_client != "0") {
|
||||
//! On sauvegarde aussi les infos complémentaires du client qui peuvent ête mises à jour
|
||||
$sql = 'UPDATE clients SET contact_nom="' . $contact_nom . '", contact_prenom="' . $contact_prenom . '", contact_fonction="' . $contact_fonction . '", ';
|
||||
$fkClientSafe = intval($fk_client);
|
||||
$sql .= 'email="' . $email . '", telephone="' . $telephone . '", mobile="' . $mobile . '" WHERE rowid=' . $fkClientSafe . ';';
|
||||
eLog('Entete Devis Save infos client : ' . $sql);
|
||||
qSQL($sql, "gen");
|
||||
}
|
||||
// NOTE: Les contacts sont maintenant gérés via la table clients_contacts
|
||||
// et non plus directement dans la table clients
|
||||
|
||||
// On inscrit l'enregistrement dans le journal si il y a eu un changement de commentaire ou bien si c'est une création avec commentaire
|
||||
if ($newCommentaire > 0) {
|
||||
@@ -651,11 +655,8 @@ switch ($Route->_action) {
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$sql = 'SELECT rowid, code, libelle, prix_vente FROM produits WHERE active=1 AND (code LIKE :term OR libelle LIKE :term) ORDER BY code';
|
||||
$stmt = $db->prepare($sql);
|
||||
$termParam = '%' . $term . '%';
|
||||
$stmt->bindParam(':term', $termParam, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$upls = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$upls = $db->fetchAll($sql, [':term' => $termParam]);
|
||||
} catch (Exception $e) {
|
||||
error_log("Erreur recherche produits : " . $e->getMessage());
|
||||
$upls = [];
|
||||
@@ -1303,5 +1304,69 @@ switch ($Route->_action) {
|
||||
echo json_encode(array("success" => "true", "message" => "Devis refusé avec succès"));
|
||||
}
|
||||
break;
|
||||
|
||||
case "save_new_client":
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
if (isset($data->libelle)) {
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
$libelle = nettoie_input($data->libelle);
|
||||
$typeClient = nettoie_input($data->type_client);
|
||||
$adresse1 = nettoie_input($data->adresse1);
|
||||
$adresse2 = nettoie_input($data->adresse2);
|
||||
$adresse3 = nettoie_input($data->adresse3);
|
||||
$cp = nettoie_input($data->cp);
|
||||
if (strlen($cp) == 4) $cp = "0" . $cp;
|
||||
$ville = nettoie_input($data->ville);
|
||||
$fkUserSafe = intval($fk_user);
|
||||
|
||||
$sqlMaxCode = 'SELECT MAX(code) as max_code FROM clients';
|
||||
$resultCode = $db->fetchOne($sqlMaxCode);
|
||||
$newCode = ($resultCode && $resultCode['max_code']) ? intval($resultCode['max_code']) + 1 : 1;
|
||||
|
||||
$sql = 'INSERT INTO clients SET code = :code, libelle = :libelle, type_client = :type_client, adresse1 = :adresse1, adresse2 = :adresse2, adresse3 = :adresse3, ';
|
||||
$sql .= 'cp = :cp, ville = :ville, fk_user_creat = :fk_user_creat, date_creat = NOW(), active = 1';
|
||||
|
||||
$params = [
|
||||
':code' => $newCode,
|
||||
':libelle' => $libelle,
|
||||
':type_client' => $typeClient,
|
||||
':adresse1' => $adresse1,
|
||||
':adresse2' => $adresse2,
|
||||
':adresse3' => $adresse3,
|
||||
':cp' => $cp,
|
||||
':ville' => $ville,
|
||||
':fk_user_creat' => $fkUserSafe
|
||||
];
|
||||
|
||||
$db->query($sql, $params);
|
||||
$newClientId = $db->lastInsertId();
|
||||
|
||||
if ($newClientId > 0) {
|
||||
$sql = 'INSERT INTO clients_contacts SET fk_client = :fk_client, nom = :nom, prenom = :prenom, principal = 1, active = 1, date_creat = NOW()';
|
||||
|
||||
$params = [
|
||||
':fk_client' => $newCode,
|
||||
':nom' => 'À compléter',
|
||||
':prenom' => ''
|
||||
];
|
||||
|
||||
$db->query($sql, $params);
|
||||
|
||||
eLog("Nouveau client créé : ID=" . $newClientId . ", code=" . $newCode);
|
||||
echo json_encode(array("success" => true, "rowid" => $newClientId, "code" => $newCode, "message" => "Client créé avec succès"));
|
||||
} else {
|
||||
eLog("save_new_client ERREUR: newClientId = 0");
|
||||
echo json_encode(array("success" => false, "message" => "Erreur lors de la création du client - ID non récupéré"));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errorMsg = $e->getMessage();
|
||||
error_log("Erreur création client : " . $errorMsg);
|
||||
eLog("save_new_client EXCEPTION: " . $errorMsg);
|
||||
echo json_encode(array("success" => false, "message" => "Erreur : " . $errorMsg));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
exit();
|
||||
|
||||
Reference in New Issue
Block a user