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>
115 lines
4.8 KiB
PHP
115 lines
4.8 KiB
PHP
<?php
|
|
global $Session;
|
|
|
|
$fkUser = intval($Session->_user["rowid"]); // SÉCURITÉ : Validation de l'ID utilisateur
|
|
$fkRole = intval($Session->_user["fk_role"]); // SÉCURITÉ : Validation du rôle
|
|
|
|
// SÉCURITÉ : Construction de la clause WHERE avec des paramètres sécurisés
|
|
$whereParams = [];
|
|
switch ($fkRole) {
|
|
case 1:
|
|
// DIR-CO
|
|
$where = 'd.fk_user = :fkUser OR d.fk_statut_devis >= 2';
|
|
$whereParams[':fkUser'] = $fkUser;
|
|
break;
|
|
case 2:
|
|
// DV : on récupère tous les RR de son périmètre
|
|
try {
|
|
$db = Database::getInstance();
|
|
$sql = 'SELECT rowid FROM users WHERE fk_parent = :fkParent';
|
|
$aRR = $db->fetchAll($sql, [':fkParent' => $fkUser]);
|
|
|
|
$rrIds = array_column($aRR, 'rowid');
|
|
if (!empty($rrIds)) {
|
|
// Création de placeholders pour la requête IN
|
|
$placeholders = [];
|
|
foreach ($rrIds as $index => $id) {
|
|
$placeholder = ':rr' . $index;
|
|
$placeholders[] = $placeholder;
|
|
$whereParams[$placeholder] = $id;
|
|
}
|
|
$where = 'd.fk_user = :fkUser OR (d.fk_statut_devis >= 3 AND d.fk_user IN (' . implode(',', $placeholders) . '))';
|
|
$whereParams[':fkUser'] = $fkUser;
|
|
} else {
|
|
$where = 'd.fk_user = :fkUser';
|
|
$whereParams[':fkUser'] = $fkUser;
|
|
}
|
|
} catch (Exception $e) {
|
|
error_log("Erreur récupération RR : " . $e->getMessage());
|
|
$where = 'd.fk_user = :fkUser';
|
|
$whereParams[':fkUser'] = $fkUser;
|
|
}
|
|
break;
|
|
default:
|
|
// RR
|
|
$where = 'd.fk_user = :fkUser';
|
|
$whereParams[':fkUser'] = $fkUser;
|
|
break;
|
|
}
|
|
|
|
// SÉCURITÉ : Requête avec paramètres préparés
|
|
try {
|
|
$db = Database::getInstance();
|
|
$sql = 'SELECT d.rowid, d.dossier, d.date_demande, d.date_remise, d.num_opportunite, d.fk_client, d.montant_total_ht_remise, d.marge_totale, d.commentaire, d.chk_speciaux, c.libelle, c.ville, c.cp, d.fk_statut_devis, ';
|
|
$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, d.comment_devis, d.comment_geste_comm, ';
|
|
$sql .= 'd.contact_new_nom, d.contact_new_prenom, d.new_telephone, d.new_mobile, d.new_email, d.contact_new_fonction, LEFT(u.prenom,1) AS prenom, u.libelle as nom, ';
|
|
$sql .= 'xs.libelle as lib_statut, d.chk_new_statut, m.libelle as lib_marche, d.chk_validat, d.fk_user_validat, d.date_validat ';
|
|
$sql .= 'FROM devis d LEFT JOIN clients c ON c.rowid=d.fk_client LEFT JOIN x_statuts_devis xs ON xs.rowid=d.fk_statut_devis LEFT JOIN marches m ON m.rowid=d.fk_marche LEFT JOIN users u ON u.rowid=d.fk_user ';
|
|
$sql .= 'WHERE ' . $where . ' ORDER BY d.dossier, d.date_remise DESC';
|
|
|
|
$pdo = $db->getPDO();
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($whereParams);
|
|
$aModel["devis"] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (Exception $e) {
|
|
error_log("Erreur récupération devis : " . $e->getMessage());
|
|
$aModel["devis"] = [];
|
|
}
|
|
|
|
//! on compte le nombre de devis par statut
|
|
$aModel["nb_devis"] = array();
|
|
foreach ($aModel["devis"] as $devis) {
|
|
if (!isset($aModel["nb_devis"][$devis["fk_statut_devis"]])) {
|
|
$aModel["nb_devis"][$devis["fk_statut_devis"]] = 1;
|
|
} else {
|
|
$aModel["nb_devis"][$devis["fk_statut_devis"]]++;
|
|
}
|
|
}
|
|
|
|
//! On récupère la liste des dossiers des devis
|
|
// SÉCURITÉ : Requête avec paramètres préparés
|
|
try {
|
|
$sql = 'SELECT DISTINCT d.dossier FROM devis d WHERE ' . $where . ' ORDER BY d.dossier';
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($whereParams);
|
|
$aModel["dossiers"] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (Exception $e) {
|
|
error_log("Erreur récupération dossiers : " . $e->getMessage());
|
|
$aModel["dossiers"] = [];
|
|
}
|
|
|
|
//! Tous les produits du catalogue
|
|
$sql = 'SELECT rowid, code, libelle, prix_vente, prix_achat_net FROM produits WHERE active=1;';
|
|
$aModel["produits"] = getinfos($sql, "gen");
|
|
|
|
//! toutes les familles de produits
|
|
$sql = 'SELECT rowid, libelle, ordre FROM x_familles WHERE active=1 ORDER BY ordre;';
|
|
$aModel["familles"] = getinfos($sql, "gen");
|
|
|
|
$sql = 'SELECT m.* FROM marches m WHERE m.active=1 AND m.chk_cache_commerciaux=0 ORDER BY m.libelle;';
|
|
$aModel["marches"] = getinfos($sql, "gen");
|
|
|
|
//! les types de clients
|
|
$sql = 'SELECT rowid, code, libelle FROM x_clients_types WHERE active=1 ORDER BY code;';
|
|
$aModel["types_clients"] = getinfos($sql, "gen");
|
|
|
|
//! les statuts de devis sans le 20 - Archivés
|
|
$sql = 'SELECT rowid, libelle FROM x_statuts_devis WHERE active=1 AND rowid<20 ORDER BY rowid;';
|
|
$aModel["statuts_devis"] = getinfos($sql, "gen");
|
|
|
|
// On récupère le dernier numéro de devis mis à jour de la session
|
|
if (isset($_SESSION["lastDevis"])) {
|
|
$aModel["last_devis"] = $_SESSION["lastDevis"];
|
|
} else {
|
|
$aModel["last_devis"] = 0;
|
|
} |