feat: Enhance email functionality and PDF generation for Sales Orders

- Enabled SMTP debugging in PHPMailer for better error tracking.
- Added a "Test send email" link in the Inventory Detail View for quick email testing.
- Implemented automatic PDF generation and email sending upon Sales Order creation.
- Created a new action for sending Sales Order emails with attached PDFs.
- Added a new AJAX action for testing outgoing email server configurations.
- Updated outgoing server settings to use new SMTP credentials.
- Improved email templates for better user experience.
- Added test scripts for validating PDF generation and email sending.
This commit is contained in:
BACHIR SOULDI
2026-02-17 15:59:31 +01:00
parent 2794e62571
commit 2a647b138a
46 changed files with 25100 additions and 1296 deletions

12
.gitignore vendored
View File

@@ -1,5 +1,17 @@
/test
/logs
/user_privileges
*.log
*.bak
test/templates_c/
test/templates_c/*.tpl.php
test/templates_c/v7/710e620183d7eba30794adcdaa65a6b9b5aba915.file.Footer.tpl.php
test/templates_c/v7/833e7fc50cbf769ba11253f5188fa344a967b92e.file.SidebarAppMenu.tpl.php
test/templates_c/v7/2627944103942efed50bd439d9d1dbdcc67dc480.file.ModalFooter.tpl.php
test/templates_c/v7/b62713acd22803ca6b3ad6d54ce71cdbcc4dc22a.file.Topbar.tpl.php
test/templates_c/v7/b307425d1b338e6a92e5cdf7e1d4fa12929b2d14.file.Multireference.tpl.php

View File

@@ -234,7 +234,9 @@
LEFT JOIN vtiger_accountscf ON (vtiger_accountscf.accountid =vtiger_account.accountid)
WHERE vtiger_account.industry = '{$type}' and vtiger_accountscf.cf_990 <> ''
INNER JOIN vtiger_crmentity ON vtiger_account.accountid = vtiger_crmentity.crmid
WHERE vtiger_account.industry = '{$type}' and vtiger_accountscf.cf_990 <> '' and vtiger_crmentity.deleted = 0
GROUP by vtiger_accountscf.cf_990 ORDER BY `region` ASC";

View File

@@ -1932,11 +1932,33 @@ function box(data) {
success: function (data) {
//console.log(data);
let cleanData = data.trim();
data = data.replace('{', '{');
var dataResult = JSON.parse(data);
console.log(data);
// Remove anything starting from the first <script> tag (and everything after)
const scriptIndex = cleanData.indexOf('<script>');
if (scriptIndex !== -1) {
cleanData = cleanData.substring(0, scriptIndex);
}
// Optional: remove BOM characters if present
cleanData = cleanData.replace(/^\uFEFF/, '');
// Now you can safely parse
let dataResult;
try {
dataResult = JSON.parse(cleanData);
console.log("✅ Parsed JSON:", dataResult);
} catch (e) {
console.error("❌ Invalid JSON:", cleanData);
console.error(e);
}
// var dataResult = JSON.parse(data);
box(dataResult);
@@ -2036,6 +2058,34 @@ $(document).ready(function() {
LoadALLDashBoard();
// const menuButton = document.querySelector('.app-navigator .app-icon');
// menuButton.addEventListener('click', toggleSidebar);
// function toggleSidebar() {
// console.log('clicked');
// const mainContainer = document.querySelector('.main-container');
// const sidebar = document.querySelector('.vertical-menu');
// const tabContainerD = document.querySelector('.tabContainer');
// const dashboard = document.querySelector('.dashboard');
// const isOpen = sidebar.classList.contains('open');
// if (isOpen) {
// sidebar.classList.remove('open');
// sidebar.style.width = '0px';
// if (mainContainer) mainContainer.style.marginLeft = '0px';
// if (tabContainerD) tabContainerD.style.marginLeft = '0px';
// if (dashboard) dashboard.style.marginLeft = '0px';
// } else {
// sidebar.classList.add('open');
// sidebar.style.width = '250px';
// if (mainContainer) mainContainer.style.marginLeft = '250px';
// if (tabContainerD) tabContainerD.style.marginLeft = '250px';
// if (dashboard) dashboard.style.marginLeft = '250px';
// }
// }
}
catch(err) {

View File

@@ -8,6 +8,10 @@ vimport ('includes.runtime.EntryPoint');
$roleid = $current_user->get('roleid');
$userId = $current_user->get('id');
if($roleid == 'H37')
die("Vous n'avez pas le droit d'accéder à cette page");
if(isset($_POST["Export"])){
$filename = "ETAT_DES_STOCKS".date('Ymd') . ".xls";
header("Content-Type: application/vnd.ms-excel");

View File

@@ -16,131 +16,161 @@ vimport ('includes.runtime.EntryPoint');
$current_user = Users_Record_Model::getCurrentUserModel();
$current_user = Users_Record_Model::getCurrentUserModel();
$current_user->set('activity_view', 'Today');
$current_user->set('activity_view', 'Today');
$current_user->set('submenuhide', true);
$current_user->set('submenuhide', true);
$current_user->set('leftpanelhide', true);
$current_user->set('leftpanelhide', true);
$roleid = $current_user->get('roleid');
$roleid = $current_user->get('roleid');
$_SESSION["roleid"]= $roleid ;
$viewer = new Vtiger_Viewer();
$viewer = new Vtiger_Viewer();
$viewer->assign('MODULE', 'Calendar');
$viewer->assign('MODULE', 'Calendar');
$viewer->assign('VIEW', 'Calendar');
$viewer->assign('VIEW', 'Calendar');
$viewer->assign('SELECTED_MENU_CATEGORY', 'SALES');
$viewer->assign('SELECTED_MENU_CATEGORY', 'SALES');
$viewer->assign('COMPANY_LOGO', Vtiger_CompanyDetails_Model::getInstanceById()->getLogo());
$viewer->assign('COMPANY_LOGO', Vtiger_CompanyDetails_Model::getInstanceById()->getLogo());
$viewer->assign('COMPANY_DETAILS_SETTINGS',new Settings_Vtiger_CompanyDetails_Model());
$viewer->assign('COMPANY_DETAILS_SETTINGS',new Settings_Vtiger_CompanyDetails_Model());
$viewer->assign('USER_MODEL', $current_user );
$viewer->assign('USER_MODEL', $current_user );
$viewer->assign('CURRENT_USER_MODEL', $current_user);
$viewer->assign('CURRENT_USER_MODEL', $current_user);
$viewer->assign('CURRENT_USER', $current_user);
$viewer->assign('CURRENT_USER', $current_user);
$viewer->assign('SEARCHABLE_MODULES', Vtiger_Module_Model::getSearchableModules());
$viewer->assign('SEARCHABLE_MODULES', Vtiger_Module_Model::getSearchableModules());
$viewer->assign('LANGUAGE_STRINGS', Vtiger_Language_Handler::export('Calendar', 'jsLanguageStrings'));
$viewer->assign('LANGUAGE_STRINGS', Vtiger_Language_Handler::export('Calendar', 'jsLanguageStrings'));
$detailViewModel = Vtiger_Module_Model::getInstance('Calendar');
$detailViewModel = Vtiger_Module_Model::getInstance('Calendar');
$linkParams = array('MODULE'=>'Calendar', 'ACTION'=>'Calendar');
$linkParams = array('MODULE'=>'Calendar', 'ACTION'=>'Calendar');
$linkModels = $detailViewModel->getSideBarLinks($linkParams);
$linkModels = $detailViewModel->getSideBarLinks($linkParams);
$viewer->assign('QUICK_LINKS', $linkModels);
$viewer->assign('QUICK_LINKS', $linkModels);
$viewer->assign('PAGETITLE','MA TOURNEE');
$viewer->assign('PAGETITLE','MA TOURNEE');
//$viewer->assign('IS_CREATE_PERMITTED', isPermitted('Calendar', 'CreateView'));
//$viewer->assign('IS_CREATE_PERMITTED', isPermitted('Calendar', 'CreateView'));
$viewer->view('CalendarViewPreProcess.tpl', 'Calendar');
$viewer->view('CalendarViewPreProcess.tpl', 'Calendar');
global $dbconfig;
global $dbconfig;
global $adb;
global $adb;
$query = "SHOW FULL TABLES IN {$dbconfig['db_name']} WHERE TABLE_TYPE LIKE 'VIEW';";
$query = "SHOW FULL TABLES IN {$dbconfig['db_name']} WHERE TABLE_TYPE LIKE 'VIEW';";
$sql_get_result = $adb->query($query);
$sql_get_result = $adb->query($query);
$result = array();
$result = array();
while ($recordinfo = $adb->fetch_array($sql_get_result)) {
while ($recordinfo = $adb->fetch_array($sql_get_result)) {
$result[] = $recordinfo;
}
}
if ($roleid == 'H8')
// $filter = [];
$result = [['tables_in_sophalnexscrmnew' => 'comptes_medecins' ], ['tables_in_sophalnexscrmnew' => 'comptes_pharmacies'], ['tables_in_sophalnexscrmnew' => 'full_activity']];
// $sub = (getMySubordinates($roleid));
// $flag = false;
// foreach ($sub as $key => $value) {
// array_push($filter, $value);
// };
// $visiteurs = [];
// $user_query = 'select * from comptes_medecins';
// $result2 = $adb->query($user_query);
// while ($row = $adb->fetchByAssoc($result2)) {
// if (in_array($row['visiteur1'], $filter) || in_array($row['visiteur2'], $filter) || in_array($row['visiteur3'], $filter) || in_array($row['visiteur3'], $filter)) {
// array_push($visiteurs, $row);
// }
// }
if ($roleid == 'H17' || $roleid == 'H18' || $roleid == 'H20' || $roleid == 'H21' || $roleid == 'H24' || $roleid == 'H25' || $roleid == 'H26' || $roleid == 'H39'){
// echo "<pre>";
// var_dump($filter);
// var_dump($visiteurs);
// echo "</pre>";
// die();
$result = [['tables_in_sophalnexscrmnew' => 'comptes_medecins' ],['tables_in_sophalnexscrmnew' => 'comptes_pharmacies'],['tables_in_sophalnexscrmnew' => 'full_activity']];
if ($roleid == 'H8')
}
$result = [['tables_in_sophalnexscrmnew2' => 'comptes_medecins'], ['tables_in_sophalnexscrmnew2' => 'comptes_pharmacies'], ['tables_in_sophalnexscrmnew2' => 'full_activity']];
echo '<br><br>
if ($roleid == 'H17' || $roleid == 'H18' || $roleid == 'H20' || $roleid == 'H21' || $roleid == 'H24' || $roleid == 'H25' || $roleid == 'H26' || $roleid == 'H39') {
$result = [['tables_in_sophalnexscrmnew2' => 'comptes_medecins'], ['tables_in_sophalnexscrmnew2' => 'full_activity']];
// ['tables_in_sophalnexscrmnew2' => 'comptes_pharmacies'],
}
if ($roleid == 'H34' || $roleid == 'H36' || $roleid == 'H38' || $roleid == 'H42' || $roleid == 'H44') {
$result = [['tables_in_sophalnexscrmnew2' => 'bc_produits'], ['tables_in_sophalnexscrmnew2' => 'bon_de_commande']];
}
echo '<br><br>
<form action="ExportTable.php">';
if ($roleid == 'H8')
if ($roleid == 'H8')
echo '<input type="hidden" name="xls" value=true>';
echo '<div id="btndiv">
echo '<div id="btndiv">
<select id="table" class="form-control" name="table" style="font-size: 16px; font-weight: bold; width: 200px"> ';
$rowcolumn = 'tables_in_'.$dbconfig['db_name'];
$rowcolumn = 'tables_in_'.$dbconfig['db_name'];
foreach ($result as $row) {
foreach ($result as $row) {
echo '<option value="'.$row[$rowcolumn].'">'.$row[$rowcolumn].'</option>';
}
}
echo '</select><br><br>
echo '</select><br><br>
<button id="exportbtn" type="submit" class="btn btn-default addButton"> Export</button>
@@ -150,11 +180,11 @@ vimport ('includes.runtime.EntryPoint');
$viewer->view('CustomDashboardFooter.tpl');
$viewer->view('CustomDashboardFooter.tpl');
?>
?>
@@ -168,7 +198,7 @@ vimport ('includes.runtime.EntryPoint');
:root {
:root {
--gray-color: rgb(220, 220, 220);
@@ -176,57 +206,57 @@ vimport ('includes.runtime.EntryPoint');
--my-color: rgb(20, 160, 217);
}
}
.addButton {
.addButton {
margin-left: 10px;
}
}
.addButton2 {
.addButton2 {
margin-left: 30px;
}
}
#btndiv {
#btndiv {
margin-left: 0px;
height:50px;
}
}
#maintab {
#maintab {
margin-left: 0px;
width: calc (100% - 550px) !important;
}
}
.gps-control {
.gps-control {
display: none;
}
}
#map {
#map {
@@ -236,11 +266,11 @@ vimport ('includes.runtime.EntryPoint');
background: rgba(140, 140, 140, 1);
}
}
#msg{
#msg{
top: calc(100% - 100px);
@@ -248,19 +278,19 @@ vimport ('includes.runtime.EntryPoint');
height: 100px;
}
}
.leaflet-routing-container {
.leaflet-routing-container {
display: none;
}
}
.closed {
.closed {
max-height: 0;
@@ -268,11 +298,11 @@ vimport ('includes.runtime.EntryPoint');
visibility: hidden;
}
}
.opened {
.opened {
max-height: calc(100% + 50px);
@@ -280,21 +310,21 @@ vimport ('includes.runtime.EntryPoint');
visibility: visible;
}
}
.fc-right {
.fc-right {
display: none;
}
}
/*
/*
@media all and (max-width: 800px), only screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 1024px), only screen and (min--moz-device-pixel-ratio: 2) and (max-width: 1024px), only screen and (-o-min-device-pixel-ratio: 2/1) and (max-width: 1024px), only screen and (min-device-pixel-ratio: 2) and (max-width: 1024px), only screen and (min-resolution: 192dpi) and (max-width: 1024px), only screen and (min-resolution: 2dppx) and (max-width: 1024px) {

View File

@@ -1,129 +1,273 @@
<?php
require_once 'include/utils/utils.php';
require_once 'includes/Loader.php';
vimport ('includes.runtime.EntryPoint');
require_once 'SUtiles.php';
@session_start();
require_once 'SUtiles.php';
@session_start();
// ✅ Securely initialize the current user (important for permission control)
if (isset($_SESSION['authenticated_user_id'])) {
$current_user = new Users();
$current_user->retrieveCurrentUserInfoFromFile($_SESSION['authenticated_user_id']);
$roleid = $current_user->roleid;
} else {
echo "Access denied. Not logged in.";
exit;
}
if(isset($_GET['table'])){
$table = $_GET['table'];
$current_user = new Users();
$current_user->retrieveCurrentUserInfoFromFile($_SESSION['authenticated_user_id']);
$roleid = $current_user->roleid;
$filename = "Data_".$table.".csv";
outputCsv($filename,$table,$roleid);
}
function outputCsv($fileName,$table,$roleid)
{
global $adb;
$filter = [];
$sub = (getMySubordinates($roleid));
$flag = false;
foreach($sub as $key => $value){
array_push($filter,$value);
};
$user_query = 'select * from '.$table;
if ($roleid == 'H34' || $roleid == 'H36' || $roleid == 'H38' || $roleid == 'H42' || $roleid == 'H44') {
$user_query = "SELECT * FROM `$table` WHERE parentrole LIKE '%::$roleid::%' OR parentrole = '$roleid'";
}
$result =$adb->query($user_query);
ob_clean();
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment;filename=' . $fileName);
$fp = fopen('php://output', 'w');
while ($row = $adb->fetchByAssoc($result)) {
if (!$flag) {
// display field/column names as first row
fputcsv($fp, array_keys($row),';');
$flag = true;
}
};
// if(isMedicalSuperviseur($roleid)){
// if (in_array($row['visiteur1'], $filter) || in_array($row['visiteur2'], $filter) || in_array($row['visiteur3'], $filter) || in_array($row['visiteur3'], $filter)) {
// $csv_values = array_map('decode_html', array_values($row));
// fputcsv($fp, $csv_values,';');
// }
// }
// else{
$csv_values = array_map('decode_html', array_values($row));
fputcsv($fp, $csv_values,';');
// }
}
$result =$adb->query($user_query);
ob_clean();
fclose($fp);
ob_flush();
die;
}
/* ancien code lakheder ben
if(isset($_GET['table'])){
$table = $_GET['table'];
}
$extension = ".xls";//".csv";
if(isset($_GET['xls']) && $_GET['xls'] == true)
$extension = ".xls";
global $adb;
$filename = "Data_".$table.$extension; // File Name
// Download file
header("Content-Disposition: attachment; filename=\"$filename\"");
header("Content-Type: application/vnd.ms-excel");
$user_query = 'select * from '.$table;
$result =$adb->query($user_query);
// Write data to file
$flag = false;
ob_end_clean();
ob_start();
while ($row = $adb->fetch_array($result)) {
unset($row['0']);
unset($row['1']);
unset($row['2']);
unset($row['3']);
unset($row['4']);
unset($row['5']);
unset($row['6']);
unset($row['7']);
unset($row['8']);
unset($row['9']);
unset($row['10']);
unset($row['11']);
unset($row['12']);
unset($row['13']);
unset($row['14']);
unset($row['15']);
unset($row['16']);
unset($row['17']);
unset($row['18']);
unset($row['19']);
unset($row['20']);
unset($row['21']);
if (!$flag) {
// display field/column names as first row
echo implode("\t", array_keys($row)) . "\r\n";
$flag = true;
}
$temp = implode("\t", array_values($row));
$temp = str_replace("&#039;", " ", $temp);
$temp = str_replace("&rsquo;", " ", $temp);
$temp = str_replace("&ccedil;", "c", $temp);
$temp = str_replace("&acirc;", "a", $temp);
$temp = str_replace("&egrave;", "e", $temp);
$temp = str_replace('&eacute;', 'e', $temp);
$temp = str_replace('&iuml;', 'e', $temp);
$temp = str_replace('&Iuml;', 'i', $temp);
$temp = str_replace('&Eacute;', 'E', $temp);
$temp = str_replace('&nbsp;', "", $temp);
$temp = str_replace('&ocirc;', "o", $temp);
$temp = str_replace(array("\r", "\n"), '', $temp);
//preg_replace( "/\r|\n/", "", $temp );
echo str_replace('.', ',', $temp)."\r\n";
}
*/
?>

View File

@@ -232,6 +232,8 @@ $viewer->view('CustomDashboard.tpl');
$active8 = "";
$active9 = "";
if($active == 1) $active1 = 'active';
if($active == 2) $active2 = 'active';
@@ -248,6 +250,10 @@ $viewer->view('CustomDashboard.tpl');
if($active == 8) $active8 = 'active';
if($active == 9) $active9 = 'active';
if($active == 10) $active10 = 'active';
return '<div class="dashBoardContainer clearfix">
@@ -272,6 +278,10 @@ $viewer->view('CustomDashboard.tpl');
<li class="dashboardTab font-large-2 '.$active8.'" ><a href="index.php?module=MonitoringVpObjective&view=MonitoringVpObjective"><div><strong>Objective</strong></div></a></li>
<li class="dashboardTab font-large-2 '.$active9.'" ><a href="index.php?module=PharmexObjective&view=PharmexObjective&event=Saidalya"><div><strong>Salon SIPHAL</strong></div></a></li>
<li class="dashboardTab font-large-2 '.$active10.'" ><a href="index.php?module=PharmexObjective&view=PharmexObjective&event=November"><div><strong>Offres SIPHAL</strong></div></a></li>
</ul>
</div>

View File

@@ -215,8 +215,8 @@ if(isset($_POST['comptes']) && isset($_POST['date'])) {
}
else if (isMedecin($roleid)){
$query = $query." WHERE ((e.smownerid like ? OR `vm2_id` like ? OR `vm3_id` like ?) AND e.deleted = 0 AND cf.cf_994 like ? ";
$query = $query." AND a.industry ='Medecin') or (a.industry ='Pharmacie' AND cf.cf_994 like '{$communep}')";
$query = $query." WHERE ((e.smownerid like ? OR `vm2_id` like ? OR `vm3_id` like ?)AND cf.cf_994 like ? ";
$query = $query." AND a.industry ='Medecin') or (a.industry ='Pharmacie' AND cf.cf_994 like '{$communep}') AND e.deleted = 0 ";
$result = $adb->pquery($query, array($userId, $userId, $userId, $communep));
}
@@ -231,8 +231,8 @@ if(isset($_POST['comptes']) && isset($_POST['date'])) {
else if (isMedecin($roleid)){
$query = $query." WHERE ((e.smownerid like ? OR `vm2_id` like ? OR `vm3_id` like ?) AND e.deleted = 0 AND cf.cf_996 like ? ";
$query = $query." AND a.industry ='Medecin') or (a.industry ='Pharmacie' AND cf.cf_996 like '{$brickp}')";
$query = $query." WHERE ((e.smownerid like ? OR `vm2_id` like ? OR `vm3_id` like ?) AND cf.cf_996 like ? ";
$query = $query." AND a.industry ='Medecin') or (a.industry ='Pharmacie' AND cf.cf_996 like '{$brickp}') AND e.deleted = 0 ";
$result = $adb->pquery($query, array($userId, $userId, $userId, $brickp));
}

932
PharmexObjective.php Normal file
View File

@@ -0,0 +1,932 @@
<?php
$dbvp = true;
require_once 'MonitoringVMHeader.php';
require_once 'MonitoringDBRequest.php';
?>
<?php
$userId = $current_user->get('id');
$event = $_GET["event"];
$objective = 0;
if (!isVPSuperviseur($roleid) && !isTopDG($roleid) && !isResponsableCommercial($roleid)) {
die("<div style='width:100%;height:100%;display:flex;justify-content: center;font-size: 2rem;font-style: normal;'> Vous n'êtes pas autorisé à lire cette ressource.</div>");
}
if (isTopDG($roleid)) {
$roleid = "H10";
}
// $datedeb = date("Y-m-d", strtotime("-1 month"));
// $datefin = date('Y-m-d');
$datedeb = '2026-11-04';
$datefin = '2026-11-30';
if ($event == "Saidalya") {
$datedeb = '2026-02-04';
$datefin = '2026-02-07';
$objective = 300000000.00; // Objective value (0-100)
echo getMonitoringMainBarVP(9);
} else if ($event == "November") {
$datedeb = '2026-01-13';
$datefin = '2026-02-15';
$objective = 500000000.00; // Objective value (0-100)
echo getMonitoringMainBarVP(10);
} else {
die("Unauthorized");
}
$vpFilter = "";
if ($event == "Saidalya") {
$vpFilter = " AND us.id IN (156,125,215,137,149,261,127,124,43,254,212,255,253,186,248,222)";
}
$currentValue = 0; // Current gauge value (0-100)
?>
<?php
global $adb;
$queryCA = "SELECT total_bc as bc FROM
(SELECT us.id ,CONCAT(first_name,' ', last_name) as fullname ,EXTRACT(YEAR FROM so.duedate) as YEAR,EXTRACT(MONTH FROM so.duedate) as month, sum(subtotal) as total_bc, cf_992
FROM vtiger_users us
JOIN vtiger_user2role usr ON usr.userid = us.id
JOIN vtiger_role ro ON ro.roleid = usr.roleid
JOIN vtiger_crmentity crm on crm.smownerid = us.id and crm.setype='SalesOrder' and crm.deleted <> 1
JOIN vtiger_salesorder so ON so.salesorderid = crm.crmid
JOIN vtiger_accountscf acf ON acf.accountid = so.accountid";
$queryCA = $queryCA . " WHERE so.duedate BETWEEN '" . $datedeb . "' and '" . $datefin . "' $vpFilter order by total_bc asc";
$queryCA = $queryCA . ") AS subquery order by total_bc desc; ";
$sql_get_result_ca = $adb->query($queryCA);
$result_ca = array();
while ($recordinfo = $adb->fetch_array($sql_get_result_ca)) {
$result_ca[] = $recordinfo;
}
$currentValue = $result_ca[0][0] ?? 0;
// CA Par Client
$query = "SELECT soc.cf_854 as accountname,
sum(so.subtotal) as totalmargin
FROM `vtiger_salesorder` so
JOIN vtiger_salesordercf soc on soc.salesorderid = so.salesorderid
JOIN vtiger_crmentity e on so.`salesorderid` = e.crmid and e.deleted <> 1 and e.setype='SalesOrder'
JOIN vtiger_users us on us.id = e.smownerid and us.status <> 'Inactive'
JOIN vtiger_user2role usr ON usr.userid = us.id
JOIN vtiger_role ro ON ro.roleid = usr.roleid";
$query = $query . " WHERE so.duedate BETWEEN '" . $datedeb . "' and '" . $datefin . "' $vpFilter";
$query = $query . " GROUP by accountname order by totalmargin desc";
$sql_get_result_client = $adb->query($query);
$result_client = array();
while ($recordinfo = $adb->fetch_array($sql_get_result_client)) {
$result_client[] = $recordinfo;
}
$json_data = json_encode($result_client);
// Par produit
$query_produit = "SELECT p.productname, sum(ip.quantity) as totalquantity , sum(ip.margin) as totalmargin
FROM `vtiger_salesorder` so
JOIN vtiger_inventoryproductrel ip on so.`salesorderid` = ip.id
JOIN vtiger_crmentity e on so.`salesorderid` = e.crmid and e.deleted = 0
JOIN vtiger_products p on p.productid = ip.productid
JOIN vtiger_users us on us.id = e.smownerid and us.status <> 'Inactive'
JOIN vtiger_user2role usr ON usr.userid = us.id
JOIN vtiger_role ro ON ro.roleid = usr.roleid";
$query_produit = $query_produit . " WHERE so.duedate BETWEEN '" . $datedeb . "' and '" . $datefin . "' $vpFilter";
$query_produit = $query_produit . " GROUP by p.productname order by totalmargin desc"; //, month , year";
$sql_get_result_product = $adb->query($query_produit);
$result_products_arr = array();
while ($recordinfo = $adb->fetch_array($sql_get_result_product)) {
$result_products_arr[] = $recordinfo;
}
$result_products = json_encode($result_products_arr);
// $totalMargins = array_column($result_products, 'totalmargin');
// PAR VP
$query_vp = "SELECT
fullname,
total_bc AS bc
FROM
(SELECT us.id,CONCAT(first_name, ' ', last_name) AS fullname,SUM(subtotal) AS total_bc
FROM vtiger_users us JOIN vtiger_user2role usr ON usr.userid = us.id
JOIN vtiger_crmentity crm ON crm.smownerid = us.id AND crm.setype = 'SalesOrder' AND crm.deleted <> 1
JOIN vtiger_salesorder so ON so.salesorderid = crm.crmid ";
$query_vp = $query_vp . " WHERE so.duedate BETWEEN '" . $datedeb . "' and '" . $datefin . "' $vpFilter";
$query_vp = $query_vp . " GROUP BY us.id,fullname) as s ";
$sql_get_result_vp = $adb->query($query_vp);
$result_vp = array();
while ($recordinfo = $adb->fetch_array($sql_get_result_vp)) {
$result_vp[] = $recordinfo;
}
$result_vp = json_encode($result_vp);
// echo "<pre>";
// print_r($result_products);
// echo "</pre>";
// // print_r($productNames);
// echo $result_vp;
// die();
?>
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dashboard</title>
<!-- Fonts & Icons -->
<link href="file_upload/MyFont.css" rel="stylesheet">
<!-- Chart.js and Plugins -->
<script src="file_upload/Chart.bundle.js"></script>
<script src="file_upload/chartjs-gauge.js"></script>
<script src="file_upload/chartjs-plugin-datalabels.js"></script>
<!-- DataTables CSS -->
<link rel="stylesheet" href="file_upload/dataTables.bootstrap4.min.css">
<!-- jQuery + DataTables JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="file_upload/jquery.dataTables.min.js"></script>
<script src="file_upload/dataTables.bootstrap4.min.js"></script>
<!-- Modern Styles -->
<style>
:root {
--primary: #3f51b5;
--secondary: #f5f7fa;
--text: #333;
--card-bg: #fff;
--border: #e0e0e0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--secondary);
color: var(--text);
padding: 20px;
}
h3 {
margin-bottom: 10px;
font-weight: 600;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
gap: 20px;
margin-left: 280px;
padding-top: 20px;
}
.card {
background-color: var(--card-bg);
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
padding: 40px;
border: 1px solid var(--border);
}
.chart-container {
position: relative;
width: 100%;
height: 500px;
}
.section-title {
margin-top: 40px;
margin-bottom: 15px;
font-size: 20px;
font-weight: 600;
color: var(--primary);
}
.all {
grid-column-start: 1;
grid-column-end: 3;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="dashboard">
<div class="card all">
<h3>CA % Objectif (<?php echo "$datedeb-$datefin" ?>)</h3>
<div class="chart-container">
<canvas id="chart"></canvas>
</div>
</div>
<div class="card all">
<h3>CA % Client (<?php echo "$datedeb-$datefin" ?>)</h3>
<div class="chart-container">
<canvas id="myChart"></canvas>
</div>
</div>
<!-- Par produit -->
<div class="card all">
<div class="section-title">Par Produit</div>
<div class="chart-container">
<canvas id="marginChart" width="600" height="400"></canvas>
</div>
</div>
<!-- Par produit donuts -->
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Répartition Marge Totale (Top 10 Produits)</h5>
<canvas id="productDonutChart" width="400" height="400"></canvas>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Répartition Quantité Totale (Top 10 Produits - Pie Chart)</h5>
<canvas id="productQuantityPieChart" width="400" height="400"></canvas>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Marge par client</h5>
<canvas id="myDonutChart" height="400"></canvas>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Répartition CA par VP (Top 10)</h5>
<canvas id="vpDonutChart" width="400" height="400"></canvas>
</div>
</div>
<div class="card all">
<div class="section-title">Par VP</div>
<canvas id="vpChart" width="600" height="400"></canvas>
</div>
<div class="card all">
<div class="card-body">
<h5 class="card-title">CA par Client</h5>
<div class="table-responsive">
<table id="clientMarginTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>Client</th>
<th>Marge Totale (D.A)</th>
</tr>
</thead>
<tbody>
<?php foreach ($result_client as $row): ?>
<tr>
<td><?= htmlspecialchars($row['accountname']) ?></td>
<td><?= $row['totalmargin'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mt-4 all">
<div class="card-body">
<h5 class="card-title">Produits : Quantité & Marge Totale</h5>
<div class="table-responsive">
<table id="productTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>Produit</th>
<th>Quantité Totale</th>
<th>Marge Totale (D.A)</th>
</tr>
</thead>
<tbody>
<?php foreach ($result_products_arr as $row): ?>
<tr>
<td><?= htmlspecialchars($row['productname']) ?></td>
<td><?= $row['totalquantity'] ?></td>
<td><?= $row['totalmargin'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Your Chart.js scripts here -->
<script>
const currentValue = <?php echo $currentValue; ?>;
const objective = <?php echo $objective; ?>;
const value = currentValue * 100 / objective;
const gaugeConfig = {
type: 'gauge',
data: {
// labels: ['Fail', 'Warning', 'Success'],
datasets: [{
data: [30, 80, 100],
value: value.toFixed(2),
backgroundColor: ['#f44336', '#ff9800', '#4caf50'],
borderWidth: 2
}]
},
options: {
responsive: true,
title: {
display: true,
text: 'CA Réalisé : ' + currentValue.toLocaleString() + ' D.A',
fontSize: 16
},
layout: {
padding: {
bottom: 20
}
},
needle: {
radiusPercentage: 2,
widthPercentage: 2.2,
lengthPercentage: 60,
color: 'rgba(0,0,0,1)'
},
valueLabel: {
display: true
},
plugins: {
datalabels: {
display: true,
formatter: (value, context) => context.chart.data.labels[context.dataIndex],
color: '#000',
font: {
size: 10,
weight: 'bold'
}
}
}
}
};
const ctx = document.getElementById('chart').getContext('2d');
new Chart(ctx, gaugeConfig);
//
const phpData = <?php echo $json_data; ?>;
const barLabels = phpData.map(item => item.accountname);
const barData = phpData.map(item => parseFloat(item.totalmargin));
var top10Clients = barData.sort((a, b) => parseFloat(b.totalmargin) - parseFloat(a.totalmargin)) // Descending sort
.slice(0, 10); // Take top 10
const barChart = new Chart(document.getElementById('myChart').getContext('2d'), {
type: 'bar',
data: {
labels: barLabels,
datasets: [{
label: 'Total CA par Client',
data: top10Clients,
backgroundColor: 'rgba(63, 81, 181, 0.6)',
borderColor: 'rgba(63, 81, 181, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: value => value.toLocaleString() + ' D.A'
}
}],
xAxes: [{
ticks: {
autoSkip: false,
maxRotation: 45,
minRotation: 45
}
}]
},
// ✅ Add this to show labels over bars
plugins: {
datalabels: {
anchor: 'end',
align: 'right',
color: '#000',
offset: 6,
clamp: true,
clip: false,
font: {
size: window.innerWidth < 768 ? 8 : 10,
weight: 'bold'
},
formatter: function(value, context) {
// Format with thousands separator and decimals
return Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + (context.dataset.label === 'Total Margin' ? ' D.A' : '');
}
}
},
layout: {
padding: {
left: 40
}
}
}
});
// PRODUCTS
const result_products = <?php echo $result_products; ?>;
// Step 1: Sort by totalmargin in descending order
const top10Products = result_products
.sort((a, b) => parseFloat(b.totalmargin) - parseFloat(a.totalmargin)) // Descending sort
.slice(0, 10); // Take top 10
// Step 2: Extract data for use (e.g., charts)
const productNames = top10Products.map(item => item.productname);
const totalquantity = top10Products.map(item => parseFloat(item.totalquantity));
const totalMargins = top10Products.map(item => parseFloat(item.totalmargin));
const ctxMarginChart = document.getElementById('marginChart').getContext('2d');
const marginChart = new Chart(ctxMarginChart, {
type: 'horizontalBar', // horizontal bar in Chart.js 2.8
data: {
labels: productNames,
datasets: [{
label: 'Total',
data: totalquantity,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
},
{
label: 'Total Margin',
data: totalMargins,
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
callback: function(value) {
// Format ticks with thousands separator, 2 decimals for margin, 0 for quantity
// Assuming values for margin may have decimals; quantity probably integers
return Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 0,
maximumFractionDigits: 2
});
}
}
}],
yAxes: [{
barPercentage: 0.6
}]
},
legend: {
display: true,
position: 'top'
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
const datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
const value = tooltipItem.xLabel;
// Format tooltip number with 2 decimals + thousands separator
return `${datasetLabel}: ${Number(value).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${datasetLabel === 'Total Margin' ? 'D.A' : ''}`;
}
}
},
// ✅ Add this to show labels over bars
plugins: {
datalabels: {
anchor: 'end',
align: 'right',
color: '#000',
font: {
weight: 'bold',
size: 10
},
formatter: function(value, context) {
// Format with thousands separator and decimals
return Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + (context.dataset.label === 'Total Margin' ? ' D.A' : '');
}
}
}
}
});
// Donuts Products
// 🎯 Donut chart for Top 10 Products by Total Margin
const productDonutColors = [
'#4caf50', '#ff9800', '#2196f3', '#e91e63', '#9c27b0',
'#00bcd4', '#ffc107', '#8bc34a', '#ff5722', '#3f51b5'
];
const ctxProductDonut = document.getElementById('productDonutChart').getContext('2d');
const productDonutChart = new Chart(ctxProductDonut, {
type: 'pie', // 🎯 Pie chart type
data: {
labels: productNames,
datasets: [{
data: totalMargins,
backgroundColor: productDonutColors,
borderWidth: 1
}]
},
options: {
responsive: true,
cutoutPercentage: 60,
legend: {
position: 'right',
labels: {
boxWidth: 12
}
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
const index = tooltipItem.index;
const name = data.labels[index];
const value = data.datasets[0].data[index];
return `${name}: ${Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} D.A`;
}
}
},
plugins: {
datalabels: {
color: '#000',
formatter: (value, ctx) => {
const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return percentage + '%';
},
font: {
weight: 'bold',
size: 10
}
}
}
}
});
const ctxProductQuantityPie = document.getElementById('productQuantityPieChart').getContext('2d');
const productQuantityPieChart = new Chart(ctxProductQuantityPie, {
type: 'pie',
data: {
labels: productNames,
datasets: [{
data: totalquantity,
backgroundColor: productDonutColors,
borderWidth: 1
}]
},
options: {
responsive: true,
legend: {
position: 'right',
labels: {
boxWidth: 12
}
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
const index = tooltipItem.index;
const name = data.labels[index];
const value = data.datasets[0].data[index];
return `${name}: ${Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
})}`;
}
}
},
plugins: {
datalabels: {
color: '#000',
formatter: (value, context) => {
const total = context.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return percentage + '%';
},
font: {
weight: 'bold',
size: 10
}
}
}
}
});
// VP
const result_vp = <?php echo $result_vp; ?>;
// Step 1: Sort by `bc` descending and take top 10
const top10VPs = result_vp
.sort((a, b) => parseFloat(b.bc) - parseFloat(a.bc)) // Sort high to low
.slice(0, 10); // Take top 10
// Step 2: Extract fullname and bc into arrays
const fullnames = top10VPs.map(item => item.fullname);
const bc = top10VPs.map(item => parseFloat(item.bc));
const ctxVphart = document.getElementById('vpChart').getContext('2d');
const vpChart = new Chart(ctxVphart, {
type: 'horizontalBar', // ✅ horizontal bar for Chart.js v2.8
data: {
labels: fullnames,
datasets: [{
label: 'Total',
data: bc,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}, ]
},
options: {
responsive: true,
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
callback: function(value) {
return parseFloat(value).toFixed(2).toLocaleString() + " D.A";
}
}
}],
yAxes: [{
barPercentage: 0.6
}]
},
legend: {
display: true,
position: 'top'
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
const datasetLabel = (data.datasets[tooltipItem.datasetIndex].label || '');
const value = parseFloat(tooltipItem.xLabel).toFixed(2)
return `${datasetLabel}: ${Number(value).toLocaleString()} DA`;
}
}
},
// ✅ Add this to show labels over bars
plugins: {
datalabels: {
anchor: 'end',
align: 'top',
color: '#000',
font: {
weight: 'bold',
size: 10
},
formatter: function(value, context) {
// Format with thousands separator and decimals
return Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + (context.dataset.label === 'Total Margin' ? ' D.A' : '');
}
}
}
}
});
// donuts top 10 vps
// 🎯 VP Donut Chart (Top 10)
const donutColors = [
'#3f51b5', '#e91e63', '#ff9800', '#4caf50', '#2196f3',
'#9c27b0', '#00bcd4', '#ffc107', '#8bc34a', '#ff5722'
];
const ctxVPDoughnut = document.getElementById('vpDonutChart').getContext('2d');
const vpDonutChart = new Chart(ctxVPDoughnut, {
type: 'doughnut',
data: {
labels: fullnames,
datasets: [{
data: bc,
backgroundColor: donutColors,
borderWidth: 1
}]
},
options: {
responsive: true,
cutoutPercentage: 60, // makes it a donut instead of a pie
legend: {
position: 'right',
labels: {
boxWidth: 12
}
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
const index = tooltipItem.index;
const name = data.labels[index];
const value = data.datasets[0].data[index];
return `${name}: ${Number(value).toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} D.A`;
}
}
},
plugins: {
datalabels: {
color: '#000',
formatter: (value, ctx) => {
const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return percentage + '%';
},
font: {
weight: 'bold',
size: 10
}
}
}
}
});
console.log('top10Clients', top10Clients);
const donutChart = new Chart(document.getElementById('myDonutChart').getContext('2d'), {
type: 'doughnut',
data: {
labels: barLabels,
datasets: [{
label: 'Répartition du CA par Client',
data: top10Clients,
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
'rgba(75, 192, 192, 0.6)',
'rgba(153, 102, 255, 0.6)',
'rgba(255, 159, 64, 0.6)',
'rgba(63, 81, 181, 0.6)'
],
borderColor: 'white',
borderWidth: 2
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right'
},
tooltip: {
callbacks: {
label: function(context) {
const value = context.raw;
return `${context.label} : ${value.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} D.A`;
}
}
},
datalabels: {
color: '#000',
font: {
size: 10,
weight: 'bold'
},
formatter: function(value) {
const total = top10Clients.reduce((acc, val) => acc + val, 0);
const percentage = (value / total * 100).toFixed(1);
return percentage + '%';
}
}
},
cutout: '60%' // Thickness of the donut hole
}
});
$(document).ready(function() {
$('#clientMarginTable').DataTable({
// language: {
// url: "//cdn.datatables.net/plug-ins/1.13.6/i18n/fr-FR.json"
// },
columnDefs: [{
targets: 1, // Marge totale
render: function(data, type) {
const floatVal = parseFloat(data);
if (type === 'sort' || type === 'type') {
return floatVal; // return raw number for sorting
}
return floatVal.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' D.A'; // formatted for display
}
}]
});
$('#productTable').DataTable({
language: {
// url: "//cdn.datatables.net/plug-ins/1.13.6/i18n/fr-FR.json"
},
columnDefs: [{
targets: 1, // Quantité Totale
render: function(data, type) {
const num = parseFloat(data);
if (type === 'sort' || type === 'type') {
return num;
}
return num.toLocaleString('fr-FR', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
}
},
{
targets: 2, // Marge Totale
render: function(data, type) {
const value = parseFloat(data);
if (type === 'sort' || type === 'type') {
return value;
}
return value.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' D.A';
}
}
]
});
});
</script>
</body>

View File

@@ -1,176 +1,364 @@
<?php
function isPharma($roleid) {
return ($roleid == 'H7' || $roleid == 'H13' || $roleid == 'H14' || $roleid == "H34" || $roleid == "H35" || $roleid == "H36" || $roleid == "H38" || $roleid == "H42" || $roleid == "H43" || $roleid == "H44" || $roleid=="H45");
function isPharma($roleid) {
return ($roleid == 'H7' || $roleid == 'H13' || $roleid == 'H14' || $roleid == "H34" || $roleid == "H35" || $roleid == "H36" || $roleid == "H38" || $roleid == "H42" || $roleid == "H43" || $roleid == "H44" || $roleid=="H45");
}
function isMedecin($roleid) {
return ($roleid == 'H3' || $roleid == 'H9' || $roleid == 'H15' || $roleid == 'H16' || $roleid == 'H17' || $roleid == 'H18' || $roleid == 'H19' || $roleid == 'H20' || $roleid == 'H21' || $roleid == 'H39' || $roleid == 'H22' || $roleid == 'H23' || $roleid == 'H24' || $roleid == 'H25' || $roleid == 'H26' || $roleid == 'H27' || $roleid == 'H28' || $roleid == 'H29' || $roleid == 'H30' || $roleid == 'H31');
}
}
function isGro($roleid) {
return ($roleid == 'H4' || $roleid == 'H11' || $roleid == 'H12' );
function isMedecin($roleid) {
return ($roleid == 'H3' || $roleid == 'H9' || $roleid == 'H15' || $roleid == 'H16' || $roleid == 'H17' || $roleid == 'H18' || $roleid == 'H19' || $roleid == 'H20' || $roleid == 'H21' || $roleid == 'H39' || $roleid == 'H22' || $roleid == 'H23' || $roleid == 'H24' || $roleid == 'H25' || $roleid == 'H26' || $roleid == 'H27' || $roleid == 'H28' || $roleid == 'H29' || $roleid == 'H30' || $roleid == 'H31');
}
function isDG($roleid) {
return ($roleid == 'H2' || $roleid == 'H10' || $roleid == 'H32' || $roleid == 'H37');
}
}
function isTopDG($roleid) {
return ($roleid == 'H2' || $roleid == 'H32' || $roleid == 'H37');
function isGro($roleid) {
return ($roleid == 'H4' || $roleid == 'H11' || $roleid == 'H12' );
}
function isDRMedical($roleid) {
return ($roleid == 'H3' || $roleid == 'H15' || $roleid == 'H16' || $roleid == 'H8');
}
}
function isNotSup($roleid) {
return ($roleid != "H17" && $roleid != "H18" && $roleid != "H20" && $roleid != "H21" && $roleid == 'H39' && $roleid != "H24" && $roleid != "H25" && $roleid != "H26");
function isDG($roleid) {
return ($roleid == 'H2' || $roleid == 'H10' || $roleid == 'H32' || $roleid == 'H37');
}
function isResponsable($roleid) {
return ($roleid != "H2" && $roleid != 'H32' && $roleid != 'H37' && !isDRMedical($roleid) && isNotSup($roleid));
}
}
function isVP($roleid) {
return ($roleid == 'H7' || $roleid == 'H13' || $roleid == 'H14' || $roleid == "H35" || $roleid == "H43" || $roleid=="H45");
function isTopDG($roleid) {
return ($roleid == 'H2' || $roleid == 'H32' || $roleid == 'H37');
}
function isVM($roleid) {
return ($roleid == "H19" || $roleid == "H22" || $roleid == "H23" || $roleid == "H27" || $roleid == "H28" || $roleid == "H29" || $roleid == "H30" || $roleid == "H31" || $roleid == "H9");
}
}
function isDR($roleid) {
return ($roleid == "DIRECTEUR REGIONAL OUEST" || $roleid == "DIRECTEUR REGIONAL EST" || $roleid == "DIRECTEUR REGIONAL CENTRE");
function isDRMedical($roleid) {
return ($roleid == 'H3' || $roleid == 'H15' || $roleid == 'H16' || $roleid == 'H8');
}
function isUserInRole($owuserid, $vm2, $vm3, $roleid) {
$sub = getSubordinateRoleAndUsers($roleid);
foreach($sub as $roleid=>$userids) {
foreach($userids as $roleid2=>$userids2) {
if($owuserid == $roleid2 || $vm2 == $roleid2 || $vm3 == $roleid2)
return true;
}
}
return false;
}
function isResponsable($roleid) {
}
function isByWilaya($id) {
return $id == '26' || $id == '48' || $id == '51' || $id == '125' || $id == '126' || $id == '130' || $id == '152' || $id == '46' || $id == '47' || $id == '49' || $id == '50' || $id == '52' || $id == '53' || $id == '104' || $id == '128' || $id == '131' || $id == '135' || $id == '136' || $id == '150' || $id == '153' || $id == '154' || $id == '186' || $id == '217' || $id == '44' || $id == '54' || $id == '55' || $id == '43' || $id == '124' || $id == '143' || $id == '158' || $id == '181' || $id == '254' || $id == '259' || $id == '134' || $id == '137' || $id == '149' || $id == '184' || $id == '187' || $id == '248' || $id == '253' || $id == '255' || $id == '38' || $id == '39' || $id == '40' || $id == '41' || $id == '42' || $id == '112' || $id == '113' || $id == '123' || $id == '127' || $id == '200' || $id == '201' || $id == '212' || $id == '216';
}
function isOuestVMVP($roleid) {
return ($roleid != "H2" && $roleid != 'H32' && $roleid != 'H37' && !isDRMedical($roleid) && isNotSup($roleid));
return $roleid=="H9" || $roleid=="H31" || $roleid=="H3" || $roleid=="H19" || $roleid=="H18" || $roleid=="H17" || $roleid=="H7" || $roleid=="H4" || $roleid=="H36" ; //(H7,H4,H36 VP)
}
}
function isEstVMVP($roleid) {
return $roleid=="H28" || $roleid=="H29" || $roleid=="H27" || $roleid=="H24" || $roleid=="H25" || $roleid=="H26" || $roleid=="H15" || $roleid=="H12" || $roleid=="H13" || $roleid=="H34" || $roleid=="H35" || $roleid=="H42" || $roleid == "H43"; //(H12,H13,H34,H35 VP)
function isVP($roleid) {
return ($roleid == 'H7' || $roleid == 'H13' || $roleid == 'H14' || $roleid == "H35" || $roleid == "H43" || $roleid=="H45");
}
function isCentreVMVP($roleid) {
return $roleid=="H16" || $roleid=="H20" || $roleid=="H21" || $roleid == 'H39' || $roleid=="H22" || $roleid=="H23" || $roleid=="H30" || $roleid=="H11" || $roleid=="H14" || $roleid == "H44" || $roleid=="H45"; //(H11,H14 VP)
}
}
function isSuperviseur($role) {
return $role=="SUPERVISEUR MEDICAL EST1" || $role=="SUPERVISEUR MEDICAL EST2" || $role=="SUPERVISEUR MEDICAL EST3" ||
$role=="SUPERVISEUR MEDICAL CENTRE1" || $role=="SUPERVISEUR MEDICAL CENTRE2" || $role=="SUPERVISEUR MEDICAL CENTRE3" ||
$role=="SUPERVISEUR MEDICAL OUEST1" || $role=="SUPERVISEUR MEDICAL OUEST2";
return ($roleid == "H19" || $roleid == "H22" || $roleid == "H23" || $roleid == "H27" || $roleid == "H28" || $roleid == "H29" || $roleid == "H30" || $roleid == "H31" || $roleid == "H9");
}
}
function isMedicalSuperviseur($roleid) {
return $roleid == 'H17' || $roleid == 'H18' || $roleid == 'H20' || $roleid == 'H21' || $roleid == 'H24' || $roleid == 'H25' || $roleid == 'H26' || $roleid == 'H39';
function isDR($roleid) {
}
function isDM($role) {
return $role=="DELEGUE MEDICAL EST1" || $role=="DELEGUE MEDICAL EST2" || $role=="DELEGUE MEDICAL EST3" ||
$role=="DELEGUE MEDICAL CENTRE1" || $role=="DELEGUE MEDICAL CENTRE2" || $role=="DELEGUE MEDICAL CENTRE3" ||
$role=="DELEGUE MEDICAL OUEST1" || $role=="DELEGUE MEDICAL OUEST2" || $role=="DELEGUE MEDICAL OUEST";
}
}
function isOuestVMText($role) {
return $role=="DIRECTEUR REGIONAL OUEST" || $role=="SUPERVISEUR MEDICAL OUEST1" || $role=="DELEGUE MEDICAL OUEST1"
|| $role=="SUPERVISEUR MEDICAL OUEST2" || $role=="DELEGUE MEDICAL OUEST2" || $role=="DELEGUE MEDICAL OUEST";
}
function isCentreVMText($role) {
return $role=="DIRECTEUR REGIONAL CENTRE" || $role=="SUPERVISEUR MEDICAL CENTRE1" || $role=="DELEGUE MEDICAL CENTRE1"
|| $role=="SUPERVISEUR MEDICAL CENTRE2" || $role=="DELEGUE MEDICAL CENTRE2" || $role=="DELEGUE MEDICAL CENTRE3" || $role=="SUPERVISEUR MEDICAL CENTRE3";
foreach($sub as $roleid=>$userids) {
foreach($userids as $roleid2=>$userids2) {
}
function isEstVMText($role) {
return $role=="DIRECTEUR REGIONAL EST" || $role=="SUPERVISEUR MEDICAL EST1" || $role=="DELEGUE MEDICAL EST1"
|| $role=="SUPERVISEUR MEDICAL EST2" || $role=="DELEGUE MEDICAL EST2" || $role=="SUPERVISEUR MEDICAL EST3" || $role=="DELEGUE MEDICAL EST3";
}
function isKAM($role) {
return $role=="KEY ACCOUNT MANAGER CENTRE" || $role=="KEY ACCOUNT MANAGER EST" || $role=="KEY ACCOUNT MANAGER OUEST";
}
function isVPText($role) {
return $role=="DELEGUE COMMERCIAL EST" || $role=="DELEGUE COMMERCIAL EST2" || $role=="DELEGUE COMMERCIAL CENTRE" || $role=="DELEGUE COMMERCIAL OUEST";
}
function isOuestVPText($role) {
return $role=="KEY ACCOUNT MANAGER OUEST" || $role=="DELEGUE COMMERCIAL OUEST";
}
function isCentreVPText($role) {
return $role=="KEY ACCOUNT MANAGER CENTRE" || $role=="DELEGUE COMMERCIAL CENTRE";
}
function isEstVPText($role) {
return $role=="KEY ACCOUNT MANAGER EST" || $role=="DELEGUE COMMERCIAL EST" || $role=="DELEGUE COMMERCIAL EST2";
}
function showNewDashBoard($userID) {
require_once 'include/utils/GetGroupUsers.php';
$userGroups_DB = new GetGroupUsers();
//145 id group
$userGroups_DB->getAllUsersInGroup(145);
$GetGroupUsers_DB = $userGroups_DB->group_users;
$is = in_array($userID, $GetGroupUsers_DB);
if($is == true){
return true;
}
return false;
return $roleid=="H16" || $roleid=="H20" || $roleid=="H21" || $roleid == 'H39' || $roleid=="H22" || $roleid=="H23" || $roleid=="H30" || $roleid=="H11" || $roleid=="H14" || $roleid == "H44" || $roleid=="H45"; //(H11,H14 VP)
}
function hideStock($roleid)
{
return ($roleid == 'H36' || $roleid == 'H44' || $roleid == 'H34' || $roleid == 'H42' || $roleid == 'H38' || $roleid == 'H37');
}
//note de frais admin
function isAdminNdf($userId){
global $adb;
$query = "SELECT id_validateur_2 FROM note_de_frais_parametres";
$sql_get_result = $adb->query($query);
$res= array();
while ($recordinfo = $adb->fetch_array($sql_get_result)) {
$res[] = $recordinfo;
}
if( $userId == 1 || $userId == $res[0]['id_validateur_2']){
return true;
}
return false;
function isMedicalSuperviseur($roleid) {
}
function generatePassword($user_name, $crypt_type) {
$password = makeRandomPassword();
$salt = substr($user_name, 0, 2);
$salt = '$1$' . str_pad($salt, 9, '0');
$enc_password = crypt($password, $salt);
$user_hash = strtolower(md5($password));
return [$enc_password, $user_hash, $password];
}
function isVPSuperviseur($role){
return $role=="H36" || $role=="H44" ||
$role=="H34" || $role=="H42" ||
$role=="H38" ;
}
function isResponsableCommercial($role){
return $role=="H10" ;
}
?>

View File

@@ -1,298 +1,596 @@
/*+***********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.2
* ("License.txt"); You may not use this file except in compliance with the License
* The Original Code is: Vtiger CRM Open Source
* The Initial Developer of the Original Code is Vtiger.
* Portions created by Vtiger are Copyright (C) Vtiger.
* All Rights Reserved.
*************************************************************************************/
function MainController($scope, $api, $webapp, $modal, $translate, $translatePartialLoader) {
$translatePartialLoader.addPart('login');
$translatePartialLoader.addPart('home');
$scope.loginUser = null;
$scope.modules = [];
$scope.modulesCount = 0;
$scope.language = "fr_fr";
$api.get('Portal/Ping').success(function (user) {
if (user) {
$scope.loginUser = true;
$translate.use(user.language);
$scope.language = user.language;
if (localStorage.getItem('modules') !== null && localStorage.getItem('modules') !== 'null') {
$scope.modules = JSON.parse(localStorage.getItem('modules'));
$scope.modulesCount = Object.keys($scope.modules).length;
if (localStorage.getItem('orgName') == undefined) {
$scope.companyDetails();
}
$scope.$root.$emit('LoginUser.Ready');
} else {
$scope.fecthModules($scope.language);
$scope.$root.$emit('LoginUser.Ready');
}
}
});
$scope.fecthModules = function (language) {
$api.get('/FetchModules', {language: language}).success(function (result) {
delete(result.language);
$scope.modules = result.moduleInfo;
$scope.modulesCount = Object.keys($scope.modules).length;
localStorage.setItem('modules', JSON.stringify($scope.modules));
if (result.endDate !== undefined)
localStorage.setItem('supportNotification', result.endDate)
});
}
$scope.changePassword = function () {
var modalInstance = $modal.open({
templateUrl: 'changePassword.template',
controller: Main_Setting_Component,
backdrop: 'static',
keyboard: 'false',
resolve: {
record: function () {
return {};
},
api: function () {
return $api;
},
webapp: function () {
return $webapp;
},
module: function () {
return $scope.module;
},
translatePartialLoader: function () {
return $translatePartialLoader;
}
}
});
}
$scope.forgotPassword = function () {
var modalInstance = $modal.open({
templateUrl: 'forgotPassword.template',
controller: ForgotPassword_Component,
backdrop: 'static',
keyboard: 'false',
resolve: {
api: function () {
return $api;
},
webapp: function () {
return $webapp;
},
translatePartialLoader: function () {
return $translatePartialLoader;
}
}
});
};
$scope.setLanguage = function (lang) {
$translate.use(lang);
};
$scope.logout = function () {
localStorage.clear();
window.location.href = "index.php?module=Portal&view=Logout";
}
$scope.isActive = function (module) {
var url = purl();
var routeModule = url.param('module');
if (routeModule !== undefined) {
if (module === routeModule) {
return true;
}
else {
return false;
}
}
if (module == 'Home' && routeModule === undefined) {
return true;
}
}
$scope.companyDetails = function () {
$api.get('/FetchCompanyDetails').success(function (data) {
if (data.message === undefined && data.code === undefined)
$scope.companyInfo = angular.copy(data);
else if (data.message === 'Contacts module is disabled') {
alert(data.message);
localStorage.clear();
window.location.href = 'index.php?module=Portal&view=Logout';
}
})
}
$scope.disableSearch = true;
$scope.Query = {};
$scope.searchEvent = function (Query) {
$scope.$broadcast('searchFor', $scope.Query.search);
}
$scope.makeAutoComplete = function () {
$scope.$broadcast("autofill:update");
}
$scope.login = function (validity) {
q = {};
q.username = $scope.username;
q.password = $scope.password;
q.language = $scope.language;
if (validity) {
$scope.noUserName = false;
$scope.noPassword = false;
$api.post('Portal/Login', {q: q}).success(function (response) {
if (response.success) {
window.location.href = 'index.php';
}
else {
$scope.loginFailed = true;
$scope.loginMessage = response.error.message;
}
});
}
else if (!validity) {
if (q.username === undefined && q.password === undefined) {
$scope.noUserName = true;
$scope.loginFailed = false;
return false;
}
if (q.password !== undefined && q.username === undefined) {
$scope.noUserName = true;
$scope.noPassword = false;
$scope.loginFailed = false;
return false;
}
if (q.username !== undefined && q.password === undefined) {
$scope.noPassword = true;
$scope.noUserName = false;
$scope.loginFailed = false;
return false;
}
}
}
$api.get('Portal/FetchCompanyTitle').then(function (err, data) {
if (err) {
//Error fetching company title.
$scope.companyTitle = '';
}
if (data) {
$scope.companyTitle = data.result;
}
})
}
function Main_Setting_Component($scope, $modalInstance, record, api, $webapp, module, translatePartialLoader) {
if (translatePartialLoader !== undefined) {
translatePartialLoader.addPart('home');
$scope.setLanguage = function (lang) {
$translate.use(lang);
};
$scope.logout = function () {
localStorage.clear();
window.location.href = "index.php?module=Portal&view=Logout";
}
$scope.isActive = function (module) {
var url = purl();
}
if (routeModule !== undefined) {
$scope.editRecord = angular.copy(record);
$scope.data = {'oldPassword': "", 'newPassword': "", 'confirmPassword': ""};
$scope.save = function () {
api.post('Portal/ChangePassword', {record: $scope.data}).success(function (result) {
$modalInstance.dismiss();
if (result.result.message === undefined) {
alert(result.result);
$webapp.busy(false);
localStorage.clear();
window.location.href = 'index.php?module=Portal&view=Logout';
}
else if (result.result.message !== undefined) {
alert(result.result.message)
$webapp.busy(false);
}
});
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
}
}
function ForgotPassword_Component($scope, $modalInstance, api, $webapp, translatePartialLoader) {
if (translatePartialLoader !== undefined) {
translatePartialLoader.addPart('home');
}
$scope.data = {'email': ""};
$scope.updatePassword = function () {
$webapp.busy(true);
api.post('Portal/ForgotPassword', {email: $scope.data.email}).success(function (data) {
$modalInstance.dismiss();
if (data.result.message === undefined) {
alert(data.result);
}
else if (data.result.message !== undefined) {
alert(data.result.message);
}
$webapp.busy(false);
});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
}
}
function globalSearchController($scope, $http, $timeout, $webapp, $translate) {
jQuery(".search-icon").on('click', function (e) {
jQuery(".search-box").focus();
})
$scope.search = '';
$scope.getModuleLabelClass = function (module) {
return 'label label-info';
}
$scope.searchItemSelected = function (item, $timeout) {
if (item.model.count > 0) {
var module = item.model.module.module;
var url = "index.php?module=" + module + "&view=Detail&id=" + item.model.id;
window.location.href = url;
$scope.disableSearch = true;
$scope.Query = {};
}
else {
$scope.search = '';
return false;
}
}
$scope.searchRecords = function (searchKey, $timeout) {
var params = {
"api": "SearchRecords",
"searchKey": searchKey,
"module": "Portal"
};
var result = [];
return $http.get("index.php", {params: params}).then(function (response) {
$webapp.busy();
var data = response.data.result;
angular.forEach(data, function (moduleInfo, i) {
if (angular.isObject(moduleInfo) && moduleInfo.length !== 0) {
if (i !== 'language') {
var labelField = 'label';
angular.forEach(moduleInfo, function (recordInfo) {
if (recordInfo.hasOwnProperty('id')) {
var res = {value: recordInfo[labelField], module: {"module": i, "uiLabel": moduleInfo.uiLabel}, id: recordInfo.id, count: 1};
result.push(res);
}
});
}
}
});
if (result.length < 1) {
//No results found
$scope.noMatchFound = true;
var noRecords = {value: $translate.instant('No matches found.'), module: {}, count: 0};
result.push(noRecords);
}
$webapp.busy(false);
return result;
});
}
}

View File

@@ -1,2 +1,4 @@
</BODY>
</HTML>

19288
file_upload/Chart.bundle.js Normal file

File diff suppressed because it is too large Load Diff

126
file_upload/MyFont.css Normal file
View File

@@ -0,0 +1,126 @@
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@@ -0,0 +1,363 @@
/*!
* chartjs-gauge.js v0.3.0
* https://github.com/haiiaaa/chartjs-gauge/
* (c) 2021 chartjs-gauge.js Contributors
* Released under the MIT License
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js')) :
typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
(global = global || self, global.Gauge = factory(global.Chart));
}(this, (function (Chart) { 'use strict';
Chart = Chart && Object.prototype.hasOwnProperty.call(Chart, 'default') ? Chart['default'] : Chart;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
Chart.defaults._set('gauge', {
needle: {
// Needle circle radius as the percentage of the chart area width
radiusPercentage: 2,
// Needle width as the percentage of the chart area width
widthPercentage: 3.2,
// Needle length as the percentage of the interval between inner radius (0%) and outer radius (100%) of the arc
lengthPercentage: 80,
// The color of the needle
color: 'rgba(0, 0, 0, 1)'
},
valueLabel: {
// fontSize: undefined
display: true,
formatter: null,
color: 'rgba(255, 255, 255, 1)',
backgroundColor: 'rgba(0, 0, 0, 1)',
borderRadius: 5,
padding: {
top: 5,
right: 5,
bottom: 5,
left: 5
},
bottomMarginPercentage: 5
},
animation: {
duration: 1000,
animateRotate: true,
animateScale: false
},
// The percentage of the chart that we cut out of the middle.
cutoutPercentage: 50,
// The rotation of the chart, where the first data arc begins.
rotation: -Math.PI,
// The total circumference of the chart.
circumference: Math.PI,
legend: {
display: false
},
tooltips: {
enabled: false
}
});
var GaugeController = Chart.controllers.doughnut.extend({
getValuePercent: function getValuePercent(_ref, value) {
var minValue = _ref.minValue,
data = _ref.data;
var min = minValue || 0;
var max = data[data.length - 1] || 1;
var length = max - min;
var percent = (value - min) / length;
return percent;
},
getWidth: function getWidth(chart) {
return chart.chartArea.right - chart.chartArea.left;
},
getTranslation: function getTranslation(chart) {
var chartArea = chart.chartArea,
offsetX = chart.offsetX,
offsetY = chart.offsetY;
var centerX = (chartArea.left + chartArea.right) / 2;
var centerY = (chartArea.top + chartArea.bottom) / 2;
var dx = centerX + offsetX;
var dy = centerY + offsetY;
return {
dx: dx,
dy: dy
};
},
getAngle: function getAngle(_ref2) {
var chart = _ref2.chart,
valuePercent = _ref2.valuePercent;
var _chart$options = chart.options,
rotation = _chart$options.rotation,
circumference = _chart$options.circumference;
return rotation + circumference * valuePercent;
},
/* TODO set min padding, not applied until chart.update() (also chartArea must have been set)
setBottomPadding(chart) {
const needleRadius = this.getNeedleRadius(chart);
const padding = this.chart.config.options.layout.padding;
if (needleRadius > padding.bottom) {
padding.bottom = needleRadius;
return true;
}
return false;
},
*/
drawNeedle: function drawNeedle(ease) {
if (!this.chart.animating) {
// triggered when hovering
ease = 1;
}
var _this$chart = this.chart,
ctx = _this$chart.ctx,
config = _this$chart.config,
innerRadius = _this$chart.innerRadius,
outerRadius = _this$chart.outerRadius;
var dataset = config.data.datasets[this.index];
var _this$getMeta = this.getMeta(),
previous = _this$getMeta.previous;
var _config$options$needl = config.options.needle,
radiusPercentage = _config$options$needl.radiusPercentage,
widthPercentage = _config$options$needl.widthPercentage,
lengthPercentage = _config$options$needl.lengthPercentage,
color = _config$options$needl.color;
var width = this.getWidth(this.chart);
var needleRadius = radiusPercentage / 100 * width;
var needleWidth = widthPercentage / 100 * width;
var needleLength = lengthPercentage / 100 * (outerRadius - innerRadius) + innerRadius; // center
var _this$getTranslation = this.getTranslation(this.chart),
dx = _this$getTranslation.dx,
dy = _this$getTranslation.dy; // interpolate
var origin = this.getAngle({
chart: this.chart,
valuePercent: previous.valuePercent
}); // TODO valuePercent is in current.valuePercent also
var target = this.getAngle({
chart: this.chart,
valuePercent: this.getValuePercent(dataset, dataset.value)
});
var angle = origin + (target - origin) * ease; // draw
ctx.save();
ctx.translate(dx, dy);
ctx.rotate(angle);
ctx.fillStyle = color; // draw circle
ctx.beginPath();
ctx.ellipse(0, 0, needleRadius, needleRadius, 0, 0, 2 * Math.PI);
ctx.fill(); // draw needle
ctx.beginPath();
ctx.moveTo(0, needleWidth / 2);
ctx.lineTo(needleLength, 0);
ctx.lineTo(0, -needleWidth / 2);
ctx.fill();
ctx.restore();
},
drawValueLabel: function drawValueLabel(ease) {
// eslint-disable-line no-unused-vars
if (!this.chart.config.options.valueLabel.display) {
return;
}
var _this$chart2 = this.chart,
ctx = _this$chart2.ctx,
config = _this$chart2.config;
var defaultFontFamily = config.options.defaultFontFamily;
var dataset = config.data.datasets[this.index];
var _config$options$value = config.options.valueLabel,
formatter = _config$options$value.formatter,
fontSize = _config$options$value.fontSize,
color = _config$options$value.color,
backgroundColor = _config$options$value.backgroundColor,
borderRadius = _config$options$value.borderRadius,
padding = _config$options$value.padding,
bottomMarginPercentage = _config$options$value.bottomMarginPercentage;
var width = this.getWidth(this.chart);
var bottomMargin = bottomMarginPercentage / 100 * width;
var fmt = formatter || function (value) {
return value;
};
var valueText = fmt(dataset.value).toString();
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
if (fontSize) {
ctx.font = "".concat(fontSize, "px ").concat(defaultFontFamily);
} // const { width: textWidth, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(valueText);
// const textHeight = actualBoundingBoxAscent + actualBoundingBoxDescent;
var _ctx$measureText = ctx.measureText(valueText),
textWidth = _ctx$measureText.width; // approximate height until browsers support advanced TextMetrics
var textHeight = Math.max(ctx.measureText('m').width, ctx.measureText("\uFF37").width);
var x = -(padding.left + textWidth / 2);
var y = -(padding.top + textHeight / 2);
var w = padding.left + textWidth + padding.right;
var h = padding.top + textHeight + padding.bottom; // center
var _this$getTranslation2 = this.getTranslation(this.chart),
dx = _this$getTranslation2.dx,
dy = _this$getTranslation2.dy; // add rotation
var rotation = this.chart.options.rotation % (Math.PI * 2.0);
dx += bottomMargin * Math.cos(rotation + Math.PI / 2);
dy += bottomMargin * Math.sin(rotation + Math.PI / 2); // draw
ctx.save();
ctx.translate(dx, dy); // draw background
ctx.beginPath();
Chart.helpers.canvas.roundedRect(ctx, x, y, w, h, borderRadius);
ctx.fillStyle = backgroundColor;
ctx.fill(); // draw value text
ctx.fillStyle = color || config.options.defaultFontColor;
var magicNumber = 0.075; // manual testing
ctx.fillText(valueText, 0, textHeight * magicNumber);
ctx.restore();
},
// overrides
update: function update(reset) {
var dataset = this.chart.config.data.datasets[this.index];
dataset.minValue = dataset.minValue || 0;
var meta = this.getMeta();
var initialValue = {
valuePercent: 0
}; // animations on will call update(reset) before update()
if (reset) {
meta.previous = null;
meta.current = initialValue;
} else {
dataset.data.sort(function (a, b) {
return a - b;
});
meta.previous = meta.current || initialValue;
meta.current = {
valuePercent: this.getValuePercent(dataset, dataset.value)
};
}
Chart.controllers.doughnut.prototype.update.call(this, reset);
},
updateElement: function updateElement(arc, index, reset) {
// TODO handle reset and options.animation
Chart.controllers.doughnut.prototype.updateElement.call(this, arc, index, reset);
var dataset = this.getDataset();
var data = dataset.data; // const { options } = this.chart.config;
// scale data
var previousValue = index === 0 ? dataset.minValue : data[index - 1];
var value = data[index];
var startAngle = this.getAngle({
chart: this.chart,
valuePercent: this.getValuePercent(dataset, previousValue)
});
var endAngle = this.getAngle({
chart: this.chart,
valuePercent: this.getValuePercent(dataset, value)
});
var circumference = endAngle - startAngle;
arc._model = _objectSpread2({}, arc._model, {
startAngle: startAngle,
endAngle: endAngle,
circumference: circumference
});
},
draw: function draw(ease) {
Chart.controllers.doughnut.prototype.draw.call(this, ease);
this.drawNeedle(ease);
this.drawValueLabel(ease);
}
});
/* eslint-disable max-len, func-names */
var polyfill = function polyfill() {
if (CanvasRenderingContext2D.prototype.ellipse === undefined) {
CanvasRenderingContext2D.prototype.ellipse = function (x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
this.save();
this.translate(x, y);
this.rotate(rotation);
this.scale(radiusX, radiusY);
this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
this.restore();
};
}
};
polyfill();
Chart.controllers.gauge = GaugeController;
Chart.Gauge = function (context, config) {
config.type = 'gauge';
return new Chart(context, config);
};
var index = Chart.Gauge;
return index;
})));

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
/*! DataTables Bootstrap 4 integration
* ©2011-2017 SpryMedia Ltd - datatables.net/license
*/
!function(t){var n,o;"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return t(e,window,document)}):"object"==typeof exports?(n=require("jquery"),o=function(e,a){a.fn.dataTable||require("datatables.net")(e,a)},"undefined"==typeof window?module.exports=function(e,a){return e=e||window,a=a||n(e),o(e,a),t(a,0,e.document)}:(o(window,n),module.exports=t(n,window,window.document))):t(jQuery,window,document)}(function(x,e,n,o){"use strict";var r=x.fn.dataTable;return x.extend(!0,r.defaults,{dom:"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",renderer:"bootstrap"}),x.extend(r.ext.classes,{sWrapper:"dataTables_wrapper dt-bootstrap4",sFilterInput:"form-control form-control-sm",sLengthSelect:"custom-select custom-select-sm form-control form-control-sm",sProcessing:"dataTables_processing card",sPageButton:"paginate_button page-item"}),r.ext.renderer.pageButton.bootstrap=function(i,e,d,a,l,c){function u(e,a){for(var t,n,o=function(e){e.preventDefault(),x(e.currentTarget).hasClass("disabled")||m.page()==e.data.action||m.page(e.data.action).draw("page")},r=0,s=a.length;r<s;r++)if(t=a[r],Array.isArray(t))u(e,t);else{switch(f=p="",t){case"ellipsis":p="&#x2026;",f="disabled";break;case"first":p=g.sFirst,f=t+(0<l?"":" disabled");break;case"previous":p=g.sPrevious,f=t+(0<l?"":" disabled");break;case"next":p=g.sNext,f=t+(l<c-1?"":" disabled");break;case"last":p=g.sLast,f=t+(l<c-1?"":" disabled");break;default:p=t+1,f=l===t?"active":""}p&&(n=-1!==f.indexOf("disabled"),n=x("<li>",{class:b.sPageButton+" "+f,id:0===d&&"string"==typeof t?i.sTableId+"_"+t:null}).append(x("<a>",{href:n?null:"#","aria-controls":i.sTableId,"aria-disabled":n?"true":null,"aria-label":w[t],role:"link","aria-current":"active"===f?"page":null,"data-dt-idx":t,tabindex:n?-1:i.iTabIndex,class:"page-link"}).html(p)).appendTo(e),i.oApi._fnBindAction(n,{action:t},o))}}var p,f,t,m=new r.Api(i),b=i.oClasses,g=i.oLanguage.oPaginate,w=i.oLanguage.oAria.paginate||{};try{t=x(e).find(n.activeElement).data("dt-idx")}catch(e){}u(x(e).empty().html('<ul class="pagination"/>').children("ul"),a),t!==o&&x(e).find("[data-dt-idx="+t+"]").trigger("focus")},r});

4
file_upload/jquery.dataTables.min.js vendored Normal file

File diff suppressed because one or more lines are too long

24
fix_permissions.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
function chmodRecursive($path, $filePerm = 0644, $dirPerm = 0755)
{
if (!file_exists($path)) return false;
if (is_file($path)) {
return chmod($path, $filePerm);
}
if (is_dir($path)) {
$dir = new DirectoryIterator($path);
foreach ($dir as $item) {
if ($item->isDot()) continue;
chmodRecursive($item->getPathname(), $filePerm, $dirPerm);
}
return chmod($path, $dirPerm);
}
return false;
}
$storagePath = __DIR__ . '/storage';
chmodRecursive($storagePath);
echo "✅ Permissions fixed for storage folder.";
?>

View File

@@ -2318,4 +2318,15 @@ function appendFromClauseToQuery($query,$fromClause) {
return $query;
}
function getMySubordinates($roleid){
global $adb;
$subordinates = array();
$user_query = "select * from vw_users_with_roles where parentrole like '%::$roleid::%'";
$result2 = $adb->query($user_query);
while ($row = $adb->fetchByAssoc($result2)) {
array_push($subordinates, $row['user_name']);
}
return $subordinates ;
}
?>

View File

@@ -1193,7 +1193,13 @@ class Vtiger_WebUI extends Vtiger_EntryPoint {
}
if($module == "PharmexObjective"){
include "PharmexObjective.php";
return;
}

View File

@@ -0,0 +1 @@
/*# sourceMappingURL=variables.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"variables.css"}

View File

@@ -28,6 +28,41 @@ Vtiger_Detail_Js("Inventory_Detail_Js", {
app.helper.showModal(response,data);
});
},
testSendEmailPDFClickHandler : function(url){
var params = {
module: 'Vtiger',
parent: 'Settings',
action: 'OutgoingServerAjax',
mode: 'sendTestMail'
};
app.helper.showProgress();
app.request.post({ data: params }).then(function (err, data) {
console.log("data",data);
console.log("err",err);
app.helper.hideProgress();
if (err === null) {
if (data && data.success) {
app.helper.showSuccessNotification({
message: data.message || '✅ Test mail sent successfully!'
});
} else if (data && data.error) {
app.helper.showErrorNotification({
message: data.error.message || '❌ Mailer Error.'
});
} else {
app.helper.showErrorNotification({
message: '❌ Unknown error. Check logs.'
});
}
} else {
app.helper.showErrorNotification({ message: '❌ ' + err.message });
}
});
}
},
{
showRecordPreview: function(recordId, templateId) {

View File

@@ -1,14 +1,14 @@
{*+**********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.1
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
************************************************************************************}
{* modules/Settings/Vtiger/views/OutgoingServerEdit.php *}
{*+**********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.1
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
************************************************************************************}
{* modules/Settings/Vtiger/views/OutgoingServerEdit.php *}
{strip}
{strip}
<div class="editViewPageDiv editViewContainer" id="EditViewOutgoing" style="padding-top:0px;">
<div class="col-lg-12 col-md-12 col-sm-12">
<div>
@@ -87,6 +87,7 @@
</div>
</div>
</form>
<button class="btn btn-info" id="sendTestMail">Send Test Mail</button>
</div>
</div>
{/strip}
{/strip}

View File

@@ -197,6 +197,45 @@ Vtiger.Class("Settings_Vtiger_OutgoingServer_Js",{},{
}
});
jQuery(document).ready(function () {
jQuery('#sendTestMail').on('click', function (e) {
e.preventDefault();
var params = {
module: 'Vtiger',
parent: 'Settings',
action: 'OutgoingServerAjax',
mode: 'sendTestMail'
};
app.helper.showProgress();
app.request.post({ data: params }).then(function (err, data) {
console.log("data",data);
console.log("err",err);
app.helper.hideProgress();
if (err === null) {
if (data && data.success) {
app.helper.showSuccessNotification({
message: data.message || '✅ Test mail sent successfully!'
});
} else if (data && data.error) {
app.helper.showErrorNotification({
message: data.error.message || '❌ Mailer Error.'
});
} else {
app.helper.showErrorNotification({
message: '❌ Unknown error. Check logs.'
});
}
} else {
app.helper.showErrorNotification({ message: '❌ ' + err.message });
}
});
});
});
Settings_Vtiger_OutgoingServer_Js("Settings_Vtiger_OutgoingServerEdit_Js",{},{});

View File

@@ -1,95 +1,189 @@
{*+**********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.1
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
************************************************************************************}
{* modules/Vtiger/views/DashBoard.php *}
<script>
function getSplashScreen(){
$.ajax({
type: "GET",
url: "index.php?module=SplashScreenAjax&view=SplashScreenAjax",
contentType: "application/json; charset=utf-8",
success: function(result) {
$("#SplashScreen").html(result);
}
});
}
getSplashScreen();
</script>
{strip}
{assign var="roleid" value=$USER_MODEL->get('roleid')}
<div id="SplashScreen"></div>
<div class="dashBoardContainer clearfix">
<div class="tabContainer">
<ul class="nav nav-tabs tabs sortable container-fluid">
{foreach key=index item=TAB_DATA from=$DASHBOARD_TABS}
<li class="{if $TAB_DATA["id"] eq $SELECTED_TAB}active{/if} dashboardTab" data-tabid="{$TAB_DATA["id"]}" data-tabname="{$TAB_DATA["tabname"]}">
<a data-toggle="tab" href="#tab_{$TAB_DATA["id"]}">
<div>
<span class="name textOverflowEllipsis" value="{$TAB_DATA["tabname"]}" style="width:10%">
<strong>{$TAB_DATA["tabname"]}</strong>
</span>
<span class="editTabName hide">
<input type="text" name="tabName"/>
</span>
{if $TAB_DATA["isdefault"] eq 0}
<i class="fa fa-close deleteTab"></i>
{/if}
<i class="fa fa-bars moveTab hide"></i>
</div>
</a>
</li>
{/foreach}
<!--
{if isGro($roleid) || isVP($roleid) || isDG($roleid)}
<li class="dashboardTab font-large-2" ><a href="index.php?module=CustomDashboard&view=CustomDashboard"><div><strong>Conventions</strong></div></a></li>
{/if}
-->
{if isDG($roleid) || $roleid == 'H8' || isMedecin($roleid)}
{if isVM($roleid)}
<!-- sophal --><li class="dashboardTab" style="font-size: 1.2rem"><a href="index.php?module=MonitoringVmCompte&view=MonitoringVmCompte"><div><strong>Monitoring Promotion Medicale</strong></div></a></li><!-- sophal -->
{else}
<!-- sophal --><li class="dashboardTab" style="font-size: 1.2rem"><a href="index.php?module=MonitoringVMPlanning&view=MonitoringVMPlanning"><div><strong>Monitoring Promotion Medicale</strong></div></a></li><!-- sophal -->
{/if}
{/if}
{if isGro($roleid) || isPharma($roleid) || isDG($roleid)}
<!-- sophal --><li class="dashboardTab" style="font-size: 1.2rem"><a href="index.php?module=MonitoringVpBCTotal&view=MonitoringVpBCTotal"><div><strong>Monitoring Commercial</strong></div></a></li><!-- sophal -->
{/if}
<div class="moreSettings pull-right">
<div class="dropdown dashBoardDropDown">
<button class="btn btn-default reArrangeTabs dropdown-toggle" type="button" data-toggle="dropdown">{vtranslate('LBL_MORE',$MODULE)}
&nbsp;&nbsp;<span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right moreDashBoards">
<li id="newDashBoardLi"{if count($DASHBOARD_TABS) eq $DASHBOARD_TABS_LIMIT}class="disabled"{/if}><a class = "addNewDashBoard" href="#">{vtranslate('LBL_ADD_NEW_DASHBOARD',$MODULE)}</a></li>
<li><a class = "reArrangeTabs" href="#">{vtranslate('LBL_REARRANGE_DASHBOARD_TABS',$MODULE)}</a></li>
</ul>
</div>
<button class="btn-success updateSequence pull-right hide">{vtranslate('LBL_SAVE_ORDER',$MODULE)}</button>
</div>
</ul>
<div class="tab-content">
{foreach key=index item=TAB_DATA from=$DASHBOARD_TABS}
<div id="tab_{$TAB_DATA["id"]}" data-tabid="{$TAB_DATA["id"]}" data-tabname="{$TAB_DATA["tabname"]}" class="tab-pane fade {if $TAB_DATA["id"] eq $SELECTED_TAB}in active{/if}">
{if $TAB_DATA["id"] eq $SELECTED_TAB}
{include file="dashboards/DashBoardTabContents.tpl"|vtemplate_path:$MODULE TABID=$TABID}
{/if}
</div>
{/foreach}
</div>
</div>
</div>
{/strip}

View File

@@ -327,12 +327,15 @@
<span style="font-size: 10pt" class="module-name textOverflowEllipsis">Questionnaire</span>
</a>
</li>
{if !hideStock($roleid)}
<li style=" padding: 2.5% 2% !important;">
<a style=" color:#ffffff; " href="?module=EtatStockReporting&view=EtatStockReporting">
<!--<span class="fa fa-cog module-icon"></span>-->
<span style="font-size: 10pt" class="module-name textOverflowEllipsis">Stock</span>
</a>
</li>
{/if}
{/if}
{if $roleid == 'H8'}
<li style=" padding: 2.5% 2% !important;">
@@ -348,7 +351,8 @@
</a>
</li>
{/if}
{if $roleid == 'H12' || $roleid == 'H11' || $roleid == 'H4' || $roleid == 'H34' || $roleid == 'H36' || $roleid == 'H38' || $roleid == 'H42' || $roleid == 'H44'}
{*if $roleid == 'H34' || $roleid == 'H36' || $roleid == 'H38' || $roleid == 'H42' || $roleid == 'H44' *}
{if $roleid == 'H12' || $roleid == 'H11' || $roleid == 'H4' }
<li style=" padding: 2.5% 2% !important;">
<a style=" color:#ffffff; " href="?module=EtatStockReporting&view=EtatStockReporting">
<!--<span class="fa fa-cog module-icon"></span>-->
@@ -356,7 +360,8 @@
</a>
</li>
{/if}
{if $roleid != 'H12' && $roleid != 'H11' && $roleid != 'H4' && $roleid != 'H34' && $roleid != 'H36' && $roleid != 'H38' && $roleid != 'H42' && $roleid != 'H44' }
{*&& $roleid != 'H34' && $roleid != 'H36' && $roleid != 'H38' && $roleid != 'H42' && $roleid != 'H44' *}
{if $roleid != 'H12' && $roleid != 'H11' && $roleid != 'H4' }
<li style=" padding: 2.5% 2% !important;">
<a style=" color:#ffffff; " href="?module=ExportData&view=ExportData">
<span style="font-size: 10pt" class="module-name textOverflowEllipsis">Export Data</span>

View File

@@ -1881,6 +1881,7 @@ th {
margin: 0px 1px !important;
}
.referencefield-wrapper .createReferenceRecord {
display: none !important;
float: left;
margin-left: 5px;
margin-top: 3px;

View File

@@ -13,6 +13,42 @@ Vtiger_Detail_Js("Inventory_Detail_Js",{
popupInstance.show(url,function(){}, app.vtranslate('JS_SEND_PDF_MAIL') );
}
},{
testSendEmailPDFClickHandler : function(url){
var params = {
module: 'Vtiger',
parent: 'Settings',
action: 'OutgoingServerAjax',
mode: 'sendTestMail'
};
app.helper.showProgress();
app.request.post({ data: params }).then(function (err, data) {
console.log("data",data);
console.log("err",err);
app.helper.hideProgress();
if (err === null) {
if (data && data.success) {
app.helper.showSuccessNotification({
message: data.message || '✅ Test mail sent successfully!'
});
} else if (data && data.error) {
app.helper.showErrorNotification({
message: data.error.message || '❌ Mailer Error.'
});
} else {
app.helper.showErrorNotification({
message: '❌ Unknown error. Check logs.'
});
}
} else {
app.helper.showErrorNotification({ message: '❌ ' + err.message });
}
});
}
},{
/**

View File

@@ -1,304 +1,610 @@
<?php
/*+***********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.0
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
*************************************************************************************/
require_once 'SUtiles.php';
class Accounts_ListView_Model extends Vtiger_ListView_Model {
protected $isVMPharma = true;
/**
* Function to get the list of Mass actions for the module
* @param <Array> $linkParams
* @return <Array> - Associative array of Link type to List of Vtiger_Link_Model instances for Mass Actions
*/
public function getListViewMassActions($linkParams) {
$massActionLinks = parent::getListViewMassActions($linkParams);
$currentUserModel = Users_Privileges_Model::getCurrentUserPrivilegesModel();
$emailModuleModel = Vtiger_Module_Model::getInstance('Emails');
if($currentUserModel->hasModulePermission($emailModuleModel->getId())) {
$massActionLink = array(
'linktype' => 'LISTVIEWMASSACTION',
'linklabel' => 'LBL_SEND_EMAIL',
'linkurl' => 'javascript:Vtiger_List_Js.triggerSendEmail("index.php?module='.$this->getModule()->getName().'&view=MassActionAjax&mode=showComposeEmailForm&step=step1","Emails");',
'linkicon' => ''
);
$massActionLinks['LISTVIEWMASSACTION'][] = Vtiger_Link_Model::getInstanceFromValues($massActionLink);
}
$SMSNotifierModuleModel = Vtiger_Module_Model::getInstance('SMSNotifier');
if(!empty($SMSNotifierModuleModel) && $currentUserModel->hasModulePermission($SMSNotifierModuleModel->getId())) {
$massActionLink = array(
'linktype' => 'LISTVIEWMASSACTION',
'linklabel' => 'LBL_SEND_SMS',
'linkurl' => 'javascript:Vtiger_List_Js.triggerSendSms("index.php?module='.$this->getModule()->getName().'&view=MassActionAjax&mode=showSendSMSForm","SMSNotifier");',
'linkicon' => ''
);
$massActionLinks['LISTVIEWMASSACTION'][] = Vtiger_Link_Model::getInstanceFromValues($massActionLink);
}
$currentUser = Users_Record_Model::getCurrentUserModel();
$userId = $currentUser->get('id');
if($userId == 1 || $userId == 17) {
$moduleModel = $this->getModule();
if($currentUserModel->hasModuleActionPermission($moduleModel->getId(), 'EditView')) {
$massActionLink = array(
'linktype' => 'LISTVIEWMASSACTION',
'linklabel' => 'LBL_TRANSFER_OWNERSHIP',
'linkurl' => 'javascript:Vtiger_List_Js.triggerTransferOwnership("index.php?module='.$moduleModel->getName().'&view=MassActionAjax&mode=transferOwnership")',
'linkicon' => ''
);
$massActionLinks['LISTVIEWMASSACTION'][] = Vtiger_Link_Model::getInstanceFromValues($massActionLink);
}
}
return $massActionLinks;
}
/**
* Function to get the list of listview links for the module
* @param <Array> $linkParams
* @return <Array> - Associate array of Link Type to List of Vtiger_Link_Model instances
*/
function getListViewLinks($linkParams) {
$links = parent::getListViewLinks($linkParams);
$index=0;
foreach($links['LISTVIEWBASIC'] as $link) {
if($link->linklabel == 'Send SMS') {
unset($links['LISTVIEWBASIC'][$index]);
}
$index++;
}
return $links;
}
//filter account by default
private function customSearch($roleid) {
$field_value = "all";
$fullname = "";
$aCondition = ["columnname" => "vtiger_account:account_type:accounttype:Accounts_Type:V", "comparator" => "n", "value" => $field_value];
$aCondition2 = [];
$aCondition3 = [];
$currentUser = Users_Record_Model::getCurrentUserModel();
if(isByWilaya($currentUser->get('id'))) {
$lieu = $currentUser->get('secteur1_id').','.$currentUser->get('secteur2_id').','.$currentUser->get('secteur3_id').','.$currentUser->get('secteur4_id');
$industry = "Pharmacie";
if(isMedecin($roleid))
$industry = "Medecin";
$aCondition = ["columnname" => "vtiger_account:industry:industry:Accounts_industry:V", "comparator" => "c", "value" => $industry, "column_condition" => "and"];
$aCondition2 = ["columnname" => "vtiger_accountscf:cf_992:cf_992:Accounts_Wilaya:V", "comparator" => "c", "value" => $lieu];
$asearchParams = $this->get('search_params');
if(empty($asearchParams)) {
$searchParams = [[ "columns" => [$aCondition, $aCondition2] ]];
$this->set('search_params', $searchParams);
return;
}
} else
if(isMedecin($roleid) || isPharma($roleid)) {
$field_value = "Medecin";
$fullname = $currentUser->get('first_name').' '.$currentUser->get('last_name');
$sub = getSubordinateRoleAndUsers($roleid);
foreach($sub as $roleid=>$userids) {
foreach($userids as $roleid2=>$userids2) {
$fullname = $fullname.",".$userids2;
}
}
$aCondition = ["columnname" => "vtiger_crmentity:smownerid:assigned_user_id:Accounts_Assigned_To:V", "comparator" => "c", "value" => $fullname, "column_condition" => "or"];
$aCondition2 = ["columnname" => "vtiger_account:vm2_id:vm2_id:Accounts_VM2:V", "comparator" => "c", "value" => $fullname, "column_condition" => "or"];
if($this->isVMPharma)
$aCondition3 = ["columnname" => "vtiger_account:industry:industry:Accounts_industry:V", "comparator" => "c", "value" => "Pharmacie"];
else
$aCondition3 = ["columnname" => "vtiger_account:vm3_id:vm3_id:Accounts_VM3:V", "comparator" => "c", "value" => $fullname];
} else if(isGro($roleid)) {
$field_value = "Grossiste";
$aCondition = ["columnname" => "vtiger_account:industry:industry:Accounts_industry:V", "comparator" => "c", "value" => $field_value];
}
// update search_params
$searchParams = $this->get('search_params');
if(empty($searchParams)) {
if($aCondition2 != [] && $aCondition3 != []) {
$searchParams = [[ "columns" => [$aCondition, $aCondition2, $aCondition3] ]];
} else {
$searchParams = [[ "columns" => [$aCondition] ]];
}
} else {
$exist = 0;
$index = 0;
$columns = $searchParams[0]["columns"];
foreach ($columns as $column) {
if(isByWilaya($currentUser->get('id'))) {
if($column["columnname"] == "vtiger_account:account_type:accounttype:Accounts_Type:V") {
$column["value"] = $field_value;
$exist = 1;
}
} else
if(isGro($roleid)) {
if($column["columnname"] == "vtiger_account:industry:industry:Accounts_industry:V") {
if($roleid == 'H4' && ($column["value"] == 'Pharmacie' || $column["value"] == 'Grossiste')) {
} else {
$column["value"] = $field_value;
}
$exist = 1;
}
} else if(isMedecin($roleid) || isPharma($roleid)) {
if($column["columnname"] == "vtiger_crmentity:smownerid:assigned_user_id:Accounts_Assigned_To:V" || $column["columnname"] == "vtiger_account:vm2_id:vm2_id:Accounts_VM2:V" || $column["columnname"] == "vtiger_account:vm3_id:vm3_id:Accounts_VM3:V") {
//$column["value"] = $fullname;
$fullnames = split(',', $fullname);
$acolumns = split(',', $column["value"]);
if (count(array_diff($fullnames, $acolumns)) == 0) {
$exist = 1;
}
}
} else {
if($column["columnname"] == "vtiger_account:account_type:accounttype:Accounts_Type:V") {
$column["value"] = $field_value;
$exist = 1;
}
}
if(!$exist && $index < count($columns)) {
//if($column["column_condition"] != 'or')
$column["column_condition"] = "and";
}
$columns[$index] = $column;
$index++;
}
if(!$exist) {
if(isByWilaya($currentUser->get('id'))) {
array_push($columns, $aCondition, $aCondition2);
} else
if(isMedecin($roleid) || isPharma($roleid)) {
$tempColumns = $columns;
array_push($columns, $aCondition);
foreach ($tempColumns as $column) {
array_push($columns, $column);
$asearchParams = $this->get('search_params');
if(empty($asearchParams)) {
$searchParams = [[ "columns" => [$aCondition, $aCondition2] ]];
$this->set('search_params', $searchParams);
}
array_push($columns, $aCondition2);
foreach ($tempColumns as $column) {
array_push($columns, $column);
}
array_push($columns, $aCondition3);
} else {
array_push($columns, $aCondition);
}
}
$searchParams[0]["columns"] = $columns;
}
//print_r($searchParams);
$this->set('search_params', $searchParams);
}
public function getListViewEntries($pagingModel) {
$currentUser = Users_Record_Model::getCurrentUserModel();
$roleid = $currentUser->get('roleid');
if($roleid != 'H2' && $_GET['viewname'] != 70) {
// if($roleid != 'H2' && $roleid != 'H20' && $roleid != 'H26' && $roleid != 'H39' && $_GET['viewname'] != 70) {
$this->customSearch($roleid);
// }
}
if($_GET['parent'] != '') {
$parent = $_GET['parent'];
$infousers = explode('user;', $parent);
if(count($infousers) > 1) {
$auser = $infousers[1];
$aCondition = ["columnname" => "vtiger_crmentity:smownerid:assigned_user_id:Accounts_Assigned_To:V", "comparator" => "c", "value" => $auser, "column_condition" => "and"];
$aCondition5 = ["columnname" => "vtiger_accountscf:cf_986:cf_986:Accounts_Latitude:V", "comparator" => "l", "value" => '0', "column_condition" => "or"];
$aCondition2 = ["columnname" => "vtiger_account:vm2_id:vm2_id:Accounts_VM2:V", "comparator" => "c", "value" => $auser, "column_condition" => "and"];
$aCondition6 = ["columnname" => "vtiger_accountscf:cf_986:cf_986:Accounts_Latitude:V", "comparator" => "l", "value" => '0', "column_condition" => "or"];
if($this->isVMPharma)
$aCondition3 = ["columnname" => "vtiger_account:industry:industry:Accounts_industry:V", "comparator" => "c", "value" => "Pharmacie", "column_condition" => "and"];
else
$aCondition3 = ["columnname" => "vtiger_account:vm3_id:vm3_id:Accounts_VM3:V", "comparator" => "c", "value" => $auser, "column_condition" => "and"];
$aCondition4 = ["columnname" => "vtiger_accountscf:cf_986:cf_986:Accounts_Latitude:V", "comparator" => "l", "value" => '0'];
$searchParams = [[ "columns" => [$aCondition, $aCondition5, $aCondition2, $aCondition6, $aCondition3, $aCondition4] ]];
//print_r($searchParams);
$this->set('search_params', $searchParams);
}
} else if(isGro($roleid)) {
}
return parent::getListViewEntries($pagingModel);
}
public function getListViewCount() {
$currentUser = Users_Record_Model::getCurrentUserModel();
$roleid = $currentUser->get('roleid');
if($roleid != 'H2' && $_GET['viewname'] != 70) {
// if($roleid != 'H2' && $roleid != 'H20' && $roleid != 'H26' && $roleid != 'H39' && $_GET['viewname'] != 70) {
// update search_params
$this->customSearch($roleid);
// }
}
if($_GET['parent'] != '') {
$parent = $_GET['parent'];
$infousers = explode('user;', $parent);
if(count($infousers) > 1) {
$auser = $infousers[1];
$aCondition = ["columnname" => "vtiger_crmentity:smownerid:assigned_user_id:Accounts_Assigned_To:V", "comparator" => "c", "value" => $auser, "column_condition" => "and"];
$aCondition5 = ["columnname" => "vtiger_accountscf:cf_986:cf_986:Accounts_Latitude:V", "comparator" => "l", "value" => '0', "column_condition" => "or"];
$aCondition2 = ["columnname" => "vtiger_account:vm2_id:vm2_id:Accounts_VM2:V", "comparator" => "c", "value" => $auser, "column_condition" => "and"];
$aCondition6 = ["columnname" => "vtiger_accountscf:cf_986:cf_986:Accounts_Latitude:V", "comparator" => "l", "value" => '0', "column_condition" => "or"];
if($this->isVMPharma)
$aCondition3 = ["columnname" => "vtiger_account:industry:industry:Accounts_industry:V", "comparator" => "c", "value" => "Pharmacie", "column_condition" => "and"];
else
$aCondition3 = ["columnname" => "vtiger_account:vm3_id:vm3_id:Accounts_VM3:V", "comparator" => "c", "value" => $auser, "column_condition" => "and"];
$aCondition4 = ["columnname" => "vtiger_accountscf:cf_986:cf_986:Accounts_Latitude:V", "comparator" => "l", "value" => '0'];
$searchParams = [[ "columns" => [$aCondition, $aCondition5, $aCondition2, $aCondition6, $aCondition3, $aCondition4] ]];
//print_r($searchParams);
$this->set('search_params', $searchParams);
}
}
return parent::getListViewCount();
}
}

View File

@@ -301,7 +301,7 @@ class PHPMailer {
* Sets SMTP class debugging on or off.
* @var bool
*/
public $SMTPDebug = false;
public $SMTPDebug = true;
/**
* Sets the function/method to use for debugging output.

View File

@@ -36,6 +36,15 @@ class Inventory_DetailView_Model extends Vtiger_DetailView_Model {
);
$linkModelList['DETAILVIEW'][] = Vtiger_Link_Model::getInstanceFromValues($sendEmailLink);
$testsendEmailLink = array(
'linklabel' => "Test send email",
'linkurl' => 'javascript:Inventory_Detail_Js.testSendEmailPDFClickHandler(\''.$recordModel->getSendEmailPDFUrl().'\')',
'linkicon' => ''
);
$linkModelList['DETAILVIEW'][] = Vtiger_Link_Model::getInstanceFromValues($testsendEmailLink);
}
return $linkModelList;

View File

@@ -7,19 +7,21 @@
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
************************************************************************************/
require_once dirname(__FILE__) .'/ModTracker.php';
require_once dirname(__FILE__) . '/ModTracker.php';
require_once 'data/VTEntityDelta.php';
class ModTrackerHandler extends VTEventHandler {
class ModTrackerHandler extends VTEventHandler
{
function handleEvent($eventName, $data) {
function handleEvent($eventName, $data)
{
global $adb, $current_user;
$moduleName = $data->getModuleName();
$isTrackingEnabled = ModTracker::isTrackingEnabledForModule($moduleName);
if(!$isTrackingEnabled) {
if (!$isTrackingEnabled) {
return;
}
if($eventName == 'vtiger.entity.aftersave.final') {
if ($eventName == 'vtiger.entity.aftersave.final') {
$recordId = $data->getId();
$columnFields = $data->getData();
$vtEntityDelta = new VTEntityDelta();
@@ -28,50 +30,222 @@ class ModTrackerHandler extends VTEventHandler {
$newerEntity = $vtEntityDelta->getNewEntity($moduleName, $recordId);
$newerColumnFields = $newerEntity->getData();
if(is_array($delta)) {
if ($moduleName === 'SalesOrder') {
$recordId = $data->getId();
$focus = CRMEntity::getInstance($moduleName);
$focus->retrieve_entity_info($recordId, $moduleName);
$accountId = $focus->column_fields['account_id'];
if (empty($accountId)) return;
$toEmail = "souldibachir3150@gmail.com";
// $toEmail = getSingleFieldValue('vtiger_account', 'email1', 'accountid', $accountId);
if (empty($toEmail)) return;
/** -----------------------------------------------------------
* 🔥 Secure ExportPDF with login cookie (your working version)
* ----------------------------------------------------------- */
ob_clean();
$loginUrl = "https://sophal.net/sophalcrm/index.php?module=Users&action=Login";
$exportUrl = "https://sophal.net/sophalcrm/index.php?module=SalesOrder&action=ExportPDF&record=$recordId";
// 1) Login
$post = http_build_query([
'username' => 'admin', // ⚠️ Replace later with "pdfbot"
'password' => 'Sophal@Crm@Sophal', // ⚠️ Replace later with another password
]);
$contextLogin = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $post,
]
]);
file_get_contents($loginUrl, false, $contextLogin);
// Extract session cookie
$cookies = [];
foreach ($http_response_header as $hdr) {
if (stripos($hdr, 'Set-Cookie:') === 0) {
$cookies[] = trim(substr($hdr, 11), ';');
}
}
$cookieHeader = 'Cookie: ' . implode('; ', $cookies);
// 2) Download PDF
$contextPDF = stream_context_create([
'http' => [
'method' => 'GET',
'header' => $cookieHeader
]
]);
$pdfContent = file_get_contents($exportUrl, false, $contextPDF);
// 3) Validate
if (strpos($pdfContent, '%PDF') !== 0) {
error_log("❌ ExportPDF returned invalid PDF for SalesOrder #$recordId");
return;
}
// 4) Save to storage
$pdfPath = "storage/SalesOrder_{$recordId}.pdf";
$filePath = $_SERVER['DOCUMENT_ROOT']."/sophalcrm/storage/SalesOrder_$recordId.pdf";
file_put_contents($filePath, $pdfContent);
ob_end_clean();
/** -----------------------------------------------------------
* 📧 Email body + send
* ----------------------------------------------------------- */
$subject = "Sales Order #" . $focus->column_fields['salesorder_no'];
$body = '<html>
<body style="font-family: Arial, sans-serif; background-color:#f5f5f5; margin:0; padding:0;">
<!-- HEADER -->
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#0d6efd; padding:20px 0;">
<tr>
<td align="center">
<img src="https://sophal.net/sophalcrm/layouts/v7/skins/images/favicon.ico" alt="SOPHAL SPA" style="max-height:60px;">
</td>
</tr>
<tr>
<td align="center" style="color:#ffffff; font-size:20px; padding-top:10px;">
<strong>SOPHAL SPA</strong>
</td>
</tr>
</table>
<!-- BODY -->
<table width="100%" cellpadding="0" cellspacing="0" style="padding:30px;">
<tr>
<td style="background:#ffffff; padding:25px; border-radius:8px;">
<p>Bonjour <strong>'.$accountId.'</strong>,</p>
<p>Votre <strong>bon de commande n° '.$focus->column_fields['salesorder_no'].'</strong> est maintenant disponible.</p>
<p>Vous pouvez le télécharger en cliquant sur le bouton ci-dessous :</p>
<!-- DOWNLOAD BUTTON -->
<p style="text-align:center; margin:30px 0;">
<a href="https://sophal.net/sophalcrm/'.$pdfPath.'"
style="
background-color:#0d6efd;
color:#ffffff;
padding:14px 28px;
text-decoration:none;
font-size:16px;
border-radius:6px;
display:inline-block;
font-weight:bold;
">
📄 Télécharger le Bon de Commande
</a>
</p>
<p style="color:#b00; font-size:13px; margin-top:20px;">
⚠️ Ceci est un email automatique. Merci de ne pas répondre à ce message.
</p>
<p>
Pour toute assistance, veuillez contacter notre service client :<br>
📞 +213541229487<br>
✉️ COMMERCIAL@SOPHAL.DZ
</p>
<p>Cordialement,<br>
<strong>SOPHAL SPA</strong></p>
</td>
</tr>
</table>
<!-- FOOTER -->
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f0f0f0; padding:15px 0; border-top:1px solid #ddd;">
<tr>
<td align="center" style="font-size:12px; color:#666;">
© 2025 SOPHAL SPA — Tous droits réservés.<br>
HASSI BEN OKBA ORAN
</td>
</tr>
</table>
</body>
</html>';
// Send email
$outgoingModel = Settings_Vtiger_Systems_Model::getInstanceFromServerType('email', 'OutgoingServer');
$outgoingModel->pdf_path = $pdfPath;
$outgoingModel->to_email = $toEmail;
$ajaxAction = new Settings_Vtiger_OutgoingServerAjax_Action();
$ajaxAction->sendTestMail($outgoingModel, $subject, $body, $pdfPath);
}
if (is_array($delta)) {
$inserted = false;
foreach($delta as $fieldName => $values) {
if($fieldName != 'modifiedtime') {
if(!$inserted) {
foreach ($delta as $fieldName => $values) {
if ($fieldName != 'modifiedtime') {
if (!$inserted) {
$checkRecordPresentResult = $adb->pquery('SELECT * FROM vtiger_modtracker_basic WHERE crmid = ? AND status = ?', array($recordId, ModTracker::$CREATED));
if(!$adb->num_rows($checkRecordPresentResult) && $data->isNew()) {
if (!$adb->num_rows($checkRecordPresentResult) && $data->isNew()) {
$status = ModTracker::$CREATED;
} else {
$status = ModTracker::$UPDATED;
}
$this->id = $adb->getUniqueId('vtiger_modtracker_basic');
$changedOn = $newerColumnFields['modifiedtime'];
if($moduleName == 'Users') {
if ($moduleName == 'Users') {
$date_var = date("Y-m-d H:i:s");
$changedOn = $adb->formatDate($date_var,true);
$changedOn = $adb->formatDate($date_var, true);
}
$adb->pquery('INSERT INTO vtiger_modtracker_basic(id, crmid, module, whodid, changedon, status)
VALUES(?,?,?,?,?,?)', Array($this->id, $recordId, $moduleName,
$current_user->id, $changedOn, $status));
VALUES(?,?,?,?,?,?)', array(
$this->id,
$recordId,
$moduleName,
$current_user->id,
$changedOn,
$status
));
$inserted = true;
}
$adb->pquery('INSERT INTO vtiger_modtracker_detail(id,fieldname,prevalue,postvalue) VALUES(?,?,?,?)',
Array($this->id, $fieldName, $values['oldValue'], $values['currentValue']));
}
$adb->pquery(
'INSERT INTO vtiger_modtracker_detail(id,fieldname,prevalue,postvalue) VALUES(?,?,?,?)',
array($this->id, $fieldName, $values['oldValue'], $values['currentValue'])
);
}
}
}
if($eventName == 'vtiger.entity.beforedelete') {
$recordId = $data->getId();
$columnFields = $data->getData();
$id = $adb->getUniqueId('vtiger_modtracker_basic');
$adb->pquery('INSERT INTO vtiger_modtracker_basic(id, crmid, module, whodid, changedon, status)
VALUES(?,?,?,?,?,?)', Array($id, $recordId, $moduleName, $current_user->id, date('Y-m-d H:i:s',time()), ModTracker::$DELETED));
header("Location: index.php");
exit;
}
if($eventName == 'vtiger.entity.afterrestore') {
if ($eventName == 'vtiger.entity.beforedelete') {
$recordId = $data->getId();
$columnFields = $data->getData();
$id = $adb->getUniqueId('vtiger_modtracker_basic');
$adb->pquery('INSERT INTO vtiger_modtracker_basic(id, crmid, module, whodid, changedon, status)
VALUES(?,?,?,?,?,?)', Array($id, $recordId, $moduleName, $current_user->id, date('Y-m-d H:i:s',time()), ModTracker::$RESTORED));
VALUES(?,?,?,?,?,?)', array($id, $recordId, $moduleName, $current_user->id, date('Y-m-d H:i:s', time()), ModTracker::$DELETED));
}
if ($eventName == 'vtiger.entity.afterrestore') {
$recordId = $data->getId();
$columnFields = $data->getData();
$id = $adb->getUniqueId('vtiger_modtracker_basic');
$adb->pquery('INSERT INTO vtiger_modtracker_basic(id, crmid, module, whodid, changedon, status)
VALUES(?,?,?,?,?,?)', array($id, $recordId, $moduleName, $current_user->id, date('Y-m-d H:i:s', time()), ModTracker::$RESTORED));
}
}
}
?>

View File

@@ -0,0 +1,63 @@
<?php
class SalesOrder_SendSaleOrderMailAjax_Action extends Vtiger_BasicAjax_Action
{
public function process(Vtiger_Request $request)
{
$recordId = $request->get('record');
$response = new Vtiger_Response();
try {
// Get record model
$recordModel = Vtiger_Record_Model::getInstanceById($recordId, 'SalesOrder');
// Load Outgoing Server config
$outgoingServerSettingsModel = Settings_Vtiger_Systems_Model::getInstanceFromServerType('email', 'OutgoingServer');
// Include mailer
require_once 'vtlib/Vtiger/Mailer.php';
$mailer = new Vtiger_Mailer();
$mailer->IsSMTP();
$mailer->Host = $outgoingServerSettingsModel->get('server');
$mailer->Port = 587;
$mailer->SMTPAuth = $outgoingServerSettingsModel->isSmtpAuthEnabled();
$mailer->Username = $outgoingServerSettingsModel->get('server_username');
$mailer->Password = $outgoingServerSettingsModel->get('server_password');
$mailer->SMTPSecure = 'tls';
$mailer->From = $outgoingServerSettingsModel->get('from_email_field');
$mailer->FromName = 'Vtiger CRM';
$mailer->AddAddress('souldibachir3150@gmail.com');
$mailer->Subject = 'Sales Order #' . $recordModel->get('salesorder_no');
$mailer->Body = 'Attached is your Sales Order from Vtiger CRM.';
// Attach PDF
$pdfPath = 'storage/SalesOrder_' . $recordId . '.pdf';
$this->generatePDF($recordId, $pdfPath);
$mailer->AddAttachment($pdfPath);
// Force send immediately
$sent = $mailer->Send(true);
if ($sent) {
$response->setResult(['success' => true, 'message' => '✅ Sales Order email sent successfully!']);
} else {
$response->setResult(['success' => false, 'message' => '⚠️ Failed to send Sales Order email.']);
}
} catch (Exception $e) {
$response->setError($e->getCode(), $e->getMessage());
}
$response->emit();
}
private function generatePDF($recordId, $pdfPath)
{
require_once 'modules/SalesOrder/SalesOrderPDFController.php';
$pdfController = new Vtiger_SalesOrderPDFController('SalesOrder');
$pdfController->loadRecord($recordId);
$pdfContent = $pdfController->Output('', 'S'); // Output as string
file_put_contents($pdfPath, $pdfContent);
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*+***********************************************************************************
* Custom Action for Outgoing Server Test Mail
*************************************************************************************/
class Settings_Vtiger_OutgoingServerAjax_Action extends Settings_Vtiger_Basic_Action
{
public function process(Vtiger_Request $request)
{
$outgoingServerSettingsModel = Settings_Vtiger_Systems_Model::getInstanceFromServerType('email', 'OutgoingServer');
$response = new Vtiger_Response();
try {
// Always send test mail
$sent = $this->sendTestMail($outgoingServerSettingsModel);
if ($sent) {
$data = $outgoingServerSettingsModel->getData();
$data['message'] = '✅ Configuration saved and test mail sent successfully.';
$data['success'] = true;
$response->setResult($data);
} else {
$response->setResult([
'success' => false,
'message' => '⚠️ Test mail failed to send. Check SMTP settings or logs.'
]);
}
} catch (Exception $e) {
$response->setError($e->getCode(), $e->getMessage());
}
// Important: emit and stop ALL further output
$response->emit();
exit; // 🔹 This prevents extra output after JSON
}
public function sendTestMail($model , $subject = null, $body = null,$pdf_path = null)
{
require_once 'vtlib/Vtiger/Mailer.php';
$mailer = new Vtiger_Mailer();
try {
$mailer->IsSMTP();
$mailer->Host = $model->get('server');
$mailer->Port = 587;
$mailer->SMTPAuth = $model->isSmtpAuthEnabled();
$mailer->Username = $model->get('server_username');
$mailer->Password = $model->get('server_password');
$mailer->SMTPSecure = 'tls';
$mailer->From = $model->get('from_email_field');
$mailer->FromName = 'Vtiger Test Mail';
$mailer->AddAddress($model->to_email ?? 'souldibachir3150@gmail.com');
$mailer->AddCC('andryamo2231@gmail.com');
$mailer->Subject = $subject ?? 'Test Mail from Vtiger CRM 2';
$mailer->IsHTML(true);
$mailer->Body = $body ?? 'This is a test mail sent when saving outgoing server configuration. 2'.$pdf_path;
// // Attach Sales Order PDF if available
// if (!empty($model->pdf_path) && file_exists($model->pdf_path)) {
// $mailer->AddAttachment($model->pdf_path, basename($model->pdf_path));
// }
if (!$mailer->Send(true)) {
error_log('SMTP send() failed: ' . $mailer->ErrorInfo);
return false;
}
return true;
} catch (Exception $e) {
error_log('SMTP Test Mail Error: ' . $e->getMessage());
return false;
}
}
}

View File

@@ -18,6 +18,7 @@ class Settings_Vtiger_OutgoingServerEdit_View extends Settings_Vtiger_Index_View
$viewer->assign('MODEL',$systemDetailsModel);
$viewer->assign('QUALIFIED_MODULE', $qualifiedName);
$viewer->assign('CURRENT_USER_MODEL', Users_Record_Model::getCurrentUserModel());
$viewer->assign('TEST_MAIL', true);
$viewer->view('OutgoingServerEdit.tpl',$qualifiedName);
}

View File

@@ -11,7 +11,7 @@
; run quicker, but you won't get error messages back to the calling
; application.
smtp_server=mail.sophal.dz
smtp_server=mail.sophal.net
; smtp port (normally 25)
@@ -43,8 +43,8 @@ debug_logfile=debug.log
; if your smtp server requires authentication, modify the following two lines
auth_username=crm@sophal.dz
auth_password=Sophal2019
auth_username=erp@mail.sophal.net
auth_password=Sophal@@25**
; if your smtp server uses pop3 before smtp authentication, modify the
; following three lines. do not enable unless it is required.

View File

@@ -1,4 +1,4 @@
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-26 16:22:51
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-30 08:46:30
compiled from "C:\xampp\htdocs\sophalcrm\includes\runtime/../../layouts/v7\modules\Vtiger\ModalFooter.tpl" */ ?>
<?php /*%%SmartyHeaderCode:35691707368d6bddb322281-34526287%%*/if(!defined('SMARTY_DIR')) exit('no direct access allowed');
$_valid = $_smarty_tpl->decodeProperties(array (
@@ -7,7 +7,7 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'2627944103942efed50bd439d9d1dbdcc67dc480' =>
array (
0 => 'C:\\xampp\\htdocs\\sophalcrm\\includes\\runtime/../../layouts/v7\\modules\\Vtiger\\ModalFooter.tpl',
1 => 1758796678,
1 => 1759179619,
2 => 'file',
),
),
@@ -15,6 +15,8 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'function' =>
array (
),
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bddb32583',
'variables' =>
array (
'BUTTON_NAME' => 0,
@@ -23,8 +25,6 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'BUTTON_LABEL' => 0,
),
'has_nocache_code' => false,
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bddb32583',
),false); /*/%%SmartyHeaderCode%%*/?>
<?php if ($_valid && !is_callable('content_68d6bddb32583')) {function content_68d6bddb32583($_smarty_tpl) {?>
<div class="modal-footer "><center><?php if ($_smarty_tpl->tpl_vars['BUTTON_NAME']->value!=null){?><?php $_smarty_tpl->tpl_vars['BUTTON_LABEL'] = new Smarty_variable($_smarty_tpl->tpl_vars['BUTTON_NAME']->value, null, 0);?><?php }else{ ?><?php ob_start();?><?php echo vtranslate('LBL_SAVE',$_smarty_tpl->tpl_vars['MODULE']->value);?>

View File

@@ -1,4 +1,4 @@
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-26 16:15:58
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-30 08:19:20
compiled from "C:\xampp\htdocs\sophalcrm\includes\runtime/../../layouts/v7\modules\Vtiger\Footer.tpl" */ ?>
<?php /*%%SmartyHeaderCode:147362918768d6bc3e892923-48997099%%*/if(!defined('SMARTY_DIR')) exit('no direct access allowed');
$_valid = $_smarty_tpl->decodeProperties(array (
@@ -7,7 +7,7 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'710e620183d7eba30794adcdaa65a6b9b5aba915' =>
array (
0 => 'C:\\xampp\\htdocs\\sophalcrm\\includes\\runtime/../../layouts/v7\\modules\\Vtiger\\Footer.tpl',
1 => 1758796679,
1 => 1759179585,
2 => 'file',
),
),
@@ -15,13 +15,13 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'function' =>
array (
),
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bc3e9002c',
'variables' =>
array (
'LANGUAGE_STRINGS' => 0,
),
'has_nocache_code' => false,
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bc3e9002c',
),false); /*/%%SmartyHeaderCode%%*/?>
<?php if ($_valid && !is_callable('content_68d6bc3e9002c')) {function content_68d6bc3e9002c($_smarty_tpl) {?>

View File

@@ -1,4 +1,4 @@
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-26 16:17:25
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-30 09:14:25
compiled from "C:\xampp\htdocs\sophalcrm\includes\runtime/../../layouts/v7\modules\Vtiger\partials\SidebarAppMenu.tpl" */ ?>
<?php /*%%SmartyHeaderCode:88911824368d6bc952ae0a7-57885333%%*/if(!defined('SMARTY_DIR')) exit('no direct access allowed');
$_valid = $_smarty_tpl->decodeProperties(array (
@@ -7,7 +7,7 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'833e7fc50cbf769ba11253f5188fa344a967b92e' =>
array (
0 => 'C:\\xampp\\htdocs\\sophalcrm\\includes\\runtime/../../layouts/v7\\modules\\Vtiger\\partials\\SidebarAppMenu.tpl',
1 => 1758796685,
1 => 1759223637,
2 => 'file',
),
),
@@ -15,6 +15,8 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'function' =>
array (
),
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bc952e7ed',
'variables' =>
array (
'USER_MODEL' => 0,
@@ -38,8 +40,6 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'DOCUMENTS_MODULE_MODEL' => 0,
),
'has_nocache_code' => false,
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bc952e7ed',
),false); /*/%%SmartyHeaderCode%%*/?>
<?php if ($_valid && !is_callable('content_68d6bc952e7ed')) {function content_68d6bc952e7ed($_smarty_tpl) {?>
@@ -394,12 +394,15 @@ $_smarty_tpl->tpl_vars['moduleModel']->_loop = true;
<span style="font-size: 10pt" class="module-name textOverflowEllipsis">Questionnaire</span>
</a>
</li>
<?php if (!hideStock($_smarty_tpl->tpl_vars['roleid']->value)){?>
<li style=" padding: 2.5% 2% !important;">
<a style=" color:#ffffff; " href="?module=EtatStockReporting&view=EtatStockReporting">
<!--<span class="fa fa-cog module-icon"></span>-->
<span style="font-size: 10pt" class="module-name textOverflowEllipsis">Stock</span>
</a>
</li>
<?php }?>
<?php }?>
<?php if ($_smarty_tpl->tpl_vars['roleid']->value=='H8'){?>
<li style=" padding: 2.5% 2% !important;">
@@ -415,7 +418,8 @@ $_smarty_tpl->tpl_vars['moduleModel']->_loop = true;
</a>
</li>
<?php }?>
<?php if ($_smarty_tpl->tpl_vars['roleid']->value=='H12'||$_smarty_tpl->tpl_vars['roleid']->value=='H11'||$_smarty_tpl->tpl_vars['roleid']->value=='H4'||$_smarty_tpl->tpl_vars['roleid']->value=='H34'||$_smarty_tpl->tpl_vars['roleid']->value=='H36'||$_smarty_tpl->tpl_vars['roleid']->value=='H38'||$_smarty_tpl->tpl_vars['roleid']->value=='H42'||$_smarty_tpl->tpl_vars['roleid']->value=='H44'){?>
<?php if ($_smarty_tpl->tpl_vars['roleid']->value=='H12'||$_smarty_tpl->tpl_vars['roleid']->value=='H11'||$_smarty_tpl->tpl_vars['roleid']->value=='H4'){?>
<li style=" padding: 2.5% 2% !important;">
<a style=" color:#ffffff; " href="?module=EtatStockReporting&view=EtatStockReporting">
<!--<span class="fa fa-cog module-icon"></span>-->
@@ -423,7 +427,8 @@ $_smarty_tpl->tpl_vars['moduleModel']->_loop = true;
</a>
</li>
<?php }?>
<?php if ($_smarty_tpl->tpl_vars['roleid']->value!='H12'&&$_smarty_tpl->tpl_vars['roleid']->value!='H11'&&$_smarty_tpl->tpl_vars['roleid']->value!='H4'&&$_smarty_tpl->tpl_vars['roleid']->value!='H34'&&$_smarty_tpl->tpl_vars['roleid']->value!='H36'&&$_smarty_tpl->tpl_vars['roleid']->value!='H38'&&$_smarty_tpl->tpl_vars['roleid']->value!='H42'&&$_smarty_tpl->tpl_vars['roleid']->value!='H44'){?>
<?php if ($_smarty_tpl->tpl_vars['roleid']->value!='H12'&&$_smarty_tpl->tpl_vars['roleid']->value!='H11'&&$_smarty_tpl->tpl_vars['roleid']->value!='H4'){?>
<li style=" padding: 2.5% 2% !important;">
<a style=" color:#ffffff; " href="?module=ExportData&view=ExportData">
<span style="font-size: 10pt" class="module-name textOverflowEllipsis">Export Data</span>

View File

@@ -1,4 +1,4 @@
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-27 18:02:08
<?php /* Smarty version Smarty-3.1.7, created on 2025-10-15 15:23:42
compiled from "C:\xampp\htdocs\sophalcrm\includes\runtime/../../layouts/v7\modules\Events\uitypes\Multireference.tpl" */ ?>
<?php /*%%SmartyHeaderCode:157938834968d826a00c4717-78770212%%*/if(!defined('SMARTY_DIR')) exit('no direct access allowed');
$_valid = $_smarty_tpl->decodeProperties(array (
@@ -7,7 +7,7 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'b307425d1b338e6a92e5cdf7e1d4fa12929b2d14' =>
array (
0 => 'C:\\xampp\\htdocs\\sophalcrm\\includes\\runtime/../../layouts/v7\\modules\\Events\\uitypes\\Multireference.tpl',
1 => 1758796609,
1 => 1759105402,
2 => 'file',
),
),
@@ -15,6 +15,8 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'function' =>
array (
),
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d826a01474f',
'variables' =>
array (
'FIELD_MODEL' => 0,
@@ -30,8 +32,6 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'RELATED_CONTACTS' => 0,
),
'has_nocache_code' => false,
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d826a01474f',
),false); /*/%%SmartyHeaderCode%%*/?>
<?php if ($_valid && !is_callable('content_68d826a01474f')) {function content_68d826a01474f($_smarty_tpl) {?>

View File

@@ -1,4 +1,4 @@
<?php /* Smarty version Smarty-3.1.7, created on 2025-09-26 16:17:23
<?php /* Smarty version Smarty-3.1.7, created on 2025-10-25 18:34:43
compiled from "C:\xampp\htdocs\sophalcrm\includes\runtime/../../layouts/v7\modules\Vtiger\partials\Topbar.tpl" */ ?>
<?php /*%%SmartyHeaderCode:82061580868d6bc93909675-76245406%%*/if(!defined('SMARTY_DIR')) exit('no direct access allowed');
$_valid = $_smarty_tpl->decodeProperties(array (
@@ -7,7 +7,7 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'b62713acd22803ca6b3ad6d54ce71cdbcc4dc22a' =>
array (
0 => 'C:\\xampp\\htdocs\\sophalcrm\\includes\\runtime/../../layouts/v7\\modules\\Vtiger\\partials\\Topbar.tpl',
1 => 1758796685,
1 => 1761417278,
2 => 'file',
),
),
@@ -15,6 +15,8 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'function' =>
array (
),
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bc93933c4',
'variables' =>
array (
'MODULE' => 0,
@@ -37,8 +39,6 @@ $_valid = $_smarty_tpl->decodeProperties(array (
'IMAGE_INFO' => 0,
),
'has_nocache_code' => false,
'version' => 'Smarty-3.1.7',
'unifunc' => 'content_68d6bc93933c4',
),false); /*/%%SmartyHeaderCode%%*/?>
<?php if ($_valid && !is_callable('content_68d6bc93933c4')) {function content_68d6bc93933c4($_smarty_tpl) {?>

51
test_so_pdf.php Normal file
View File

@@ -0,0 +1,51 @@
<?php
$recordId = 1020725; // the SalesOrder ID
$module = 'SalesOrder';
$loginUrl = "https://sophal.net/sophalcrm/index.php?module=Users&action=Login";
$exportUrl = "https://sophal.net/sophalcrm/index.php?module=SalesOrder&action=ExportPDF&record=$recordId";
// 1) Login
$post = http_build_query([
'username' => 'admin',
'password' => 'Sophal@Crm@Sophal',
]);
$contextLogin = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $post,
]
]);
file_get_contents($loginUrl, false, $contextLogin);
// Extract session cookie
$cookies = [];
foreach ($http_response_header as $hdr) {
if (stripos($hdr, 'Set-Cookie:') === 0) {
$cookies[] = trim(substr($hdr, 11), ';');
}
}
$cookieHeader = 'Cookie: ' . implode('; ', $cookies);
// 2) Get PDF
$contextPDF = stream_context_create([
'http' => [
'method' => 'GET',
'header' => $cookieHeader
]
]);
$pdfContent = file_get_contents($exportUrl, false, $contextPDF);
// 3) Validate
if (strpos($pdfContent, '%PDF') !== 0) {
error_log("ExportPDF returned invalid PDF for record $recordId");
return;
}
// 4) Save
$filePath = $_SERVER['DOCUMENT_ROOT']."/sophalcrm/storage/SalesOrder_$recordId.pdf";
file_put_contents($filePath, $pdfContent);

24
testmail.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
include_once 'vtlib/Vtiger/Mailer.php';
$mailer = new Vtiger_Mailer();
$mailer->IsSMTP();
$mailer->Host = 'mail.carixin.com'; // your SMTP host
$mailer->Port = 587; // or 465
$mailer->SMTPAuth = true;
$mailer->Username = 'contact@carixin.com';
$mailer->Password = 'carixin@2025';
$mailer->SMTPSecure = 'tls'; // or 'ssl'
$mailer->From = 'contact@carixin.com';
$mailer->FromName = 'ERP Test';
$mailer->AddAddress('souldibachir3150@gmail.com', 'Admin');
$mailer->Subject = 'SMTP Test from Vtiger';
$mailer->Body = 'If you see this email, SMTP works!';
$mailer->SMTPDebug = 2;
$mailer->Debugoutput = 'html';
$mailer->Send();
if ($mailer->Send()) {
echo "✅ Mail sent successfully!";
} else {
echo "❌ Mail failed: " . $mailer->ErrorInfo;
}