compare.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <?php
  2. /* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
  3. * Copyright (C) 2021 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
  4. * Copyright (C) 2021 Greg Rastklan <greg.rastklan@atm-consulting.fr>
  5. * Copyright (C) 2021 Jean-Pascal BOUDET <jean-pascal.boudet@atm-consulting.fr>
  6. * Copyright (C) 2021 Grégory BLEMAND <gregory.blemand@atm-consulting.fr>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. * \file htdocs/hrm/compare.php
  21. * \ingroup hrm
  22. * \brief This file compares skills of user groups
  23. *
  24. * Displays a table in three parts.
  25. * 1- the left part displays the list of users of the selected group 1.
  26. *
  27. * 2- the central part displays the skills. display of the maximum score for this group and the number of occurrences.
  28. *
  29. * 3- the right part displays the members of group 2 or the job to be compared
  30. */
  31. // Load Dolibarr environment
  32. require_once '../main.inc.php';
  33. require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
  34. require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
  35. require_once DOL_DOCUMENT_ROOT . '/hrm/class/skill.class.php';
  36. require_once DOL_DOCUMENT_ROOT . '/hrm/class/job.class.php';
  37. require_once DOL_DOCUMENT_ROOT . '/hrm/class/evaluation.class.php';
  38. require_once DOL_DOCUMENT_ROOT . '/hrm/class/position.class.php';
  39. require_once DOL_DOCUMENT_ROOT . '/hrm/lib/hrm.lib.php';
  40. // Load translation files required by the page
  41. $langs->load('hrm');
  42. $job = new Job($db);
  43. // Permissions
  44. $permissiontoread = $user->rights->hrm->evaluation->read || $user->rights->hrm->compare_advance->read;
  45. $permissiontoadd = 0;
  46. if (empty($conf->hrm->enabled)) {
  47. accessforbidden();
  48. }
  49. if (!$permissiontoread || ($action === 'create' && !$permissiontoadd)) {
  50. accessforbidden();
  51. }
  52. /*
  53. * View
  54. */
  55. $css = array('/hrm/css/style.css');
  56. llxHeader('', $langs->trans('SkillComparison'), '', '', 0, 0, '', $css);
  57. $head = array();
  58. $h = 0;
  59. $head[$h][0] = $_SERVER["PHP_SELF"];
  60. $head[$h][1] = $langs->trans("SkillComparison");
  61. $head[$h][2] = 'compare';
  62. print dol_get_fiche_head($head, 'compare', '', 1);
  63. ?>
  64. <script type="text/javascript">
  65. $(document).ready(function () {
  66. $("li[fk_user]").click(function () {
  67. if ($(this).hasClass('disabled')) {
  68. $(this).removeClass('disabled');
  69. } else {
  70. $(this).addClass('disabled');
  71. }
  72. var $userl = $(this).closest('ul');
  73. var listname = $userl.attr('name');
  74. var TId = [];
  75. $userl.find('li').each(function (i, item) {
  76. if ($(item).hasClass('disabled')) {
  77. TId.push($(item).attr('fk_user'));
  78. }
  79. });
  80. $('#' + listname + '_excluded_id').val(TId.join(','));
  81. });
  82. });
  83. </script>
  84. <?php
  85. $fk_usergroup2 = 0;
  86. $fk_job = (int) GETPOST('fk_job');
  87. if ($fk_job <= 0) {
  88. $fk_usergroup2 = GETPOST('fk_usergroup2');
  89. }
  90. $fk_usergroup1 = GETPOST('fk_usergroup1');
  91. ?>
  92. <div class="fichecenter">
  93. <form action="<?php echo $_SERVER['PHP_SELF'] ?>">
  94. <div class="tabBar tabBarWithBottom">
  95. <div class="fichehalfleft">
  96. <table class="border tableforfield" width="100%">
  97. <tr>
  98. <td><?php
  99. print $langs->trans('group1ToCompare').'</td><td>';
  100. print img_picto('', 'group', 'class="pictofixedwidth"');
  101. print $form->select_dolgroups($fk_usergroup1, 'fk_usergroup1', 1);
  102. ?></td>
  103. </tr>
  104. <tr><td>&nbsp;</td></tr>
  105. <tr>
  106. <td><?php
  107. print $langs->trans('group2ToCompare').'</td><td>';
  108. print img_picto('', 'group', 'class="pictofixedwidth"');
  109. print $form->select_dolgroups($fk_usergroup2, 'fk_usergroup2', 1);
  110. ?></td>
  111. </tr>
  112. <tr>
  113. <td><STRONG><?php print $langs->trans('or'); ?></STRONG></td>
  114. </tr>
  115. <tr>
  116. <td><?php
  117. echo $langs->trans('OrJobToCompare') . '</td><td>';
  118. $j = new Job($db);
  119. $jobs = $j->fetchAll();
  120. $TJobs = array();
  121. foreach ($jobs as &$j) {
  122. $TJobs[$j->id] = $j->label;
  123. }
  124. print img_picto('', 'jobprofile', 'class="pictofixedwidth"').$form->selectarray('fk_job', $TJobs, $fk_job, 1);
  125. ?></td>
  126. </tr>
  127. </table>
  128. </div>
  129. <div style="background:#eee;border-radius:5px 0;margin:0px 0 10px;font-style:italic;padding:5px;" class="fichehalfright">
  130. <!--<h4><?php echo $langs->trans('legend'); ?></h4>-->
  131. <table class="border" width="100%">
  132. <tr>
  133. <td><span style="vertical-align:middle" class="toohappy diffnote little"></span>
  134. <?php echo $langs->trans('CompetenceAcquiredByOneOrMore'); ?></td>
  135. </tr>
  136. <tr>
  137. <td><span style="vertical-align:middle" class="veryhappy diffnote little"></span>
  138. <?php echo $langs->trans('MaxlevelGreaterThan'); ?></td>
  139. </tr>
  140. <tr>
  141. <td><span style="vertical-align:middle" class="happy diffnote little"></span>
  142. <?php echo $langs->trans('MaxLevelEqualTo'); ?></td>
  143. </tr>
  144. <tr>
  145. <td><span style="vertical-align:middle" class="sad diffnote little"></span>
  146. <?php echo $langs->trans('MaxLevelLowerThan'); ?></td>
  147. </tr>
  148. <tr>
  149. <td><span style="vertical-align:middle" class="toosad diffnote little"></span>
  150. <?php echo $langs->trans('SkillNotAcquired'); ?></td>
  151. </tr>
  152. </table>
  153. </div>
  154. <div class="clearboth"></div>
  155. </div>
  156. <br><br>
  157. <div class="center">
  158. <input class="button" type="SUBMIT" name="bt1" VALUE="<?php print $langs->trans('Refresh'); ?>">
  159. </div>
  160. <br><br>
  161. <div id="compare" width="100%" style="position:relative;">
  162. <?php if ($fk_usergroup1 > 0 || $fk_usergroup2 > 0 || $fk_job > 0) { ?>
  163. <table width="100%">
  164. <tr>
  165. <th></th>
  166. <th><?php print $langs->trans('skill'); ?></th>
  167. <th><?php print $langs->trans('rank'); ?></th>
  168. <th><?php print $langs->trans('difference'); ?></th>
  169. <th><?php print $langs->trans('rank'); ?></th>
  170. <th></th>
  171. </tr>
  172. <?php
  173. echo '<tr><td id="list-user-left" style="width:30%" valign="top">';
  174. $TUser1 = $TUser2 = array();
  175. $userlist1 = displayUsersListWithPicto($TUser1, $fk_usergroup1, 'list1');
  176. $skill = new Skill($db);
  177. $TSkill1 = getSkillForUsers($TUser1);
  178. if ($fk_job > 0) {
  179. $TSkill2 = getSkillForJob($fk_job);
  180. $job = new Job($db);
  181. $job->fetch($fk_job);
  182. $userlist2 = '<ul>
  183. <li>
  184. <h3>' . $job->label . '</h3>
  185. <p>' . $job->description . '</p>
  186. </li>
  187. </ul>';
  188. } else {
  189. $userlist2 = displayUsersListWithPicto($TUser2, $fk_usergroup2, 'list2');
  190. $TSkill2 = getSkillForUsers($TUser2);
  191. }
  192. $TMergedSkills = mergeSkills($TSkill1, $TSkill2);
  193. echo $userlist1;
  194. echo '</td>';
  195. echo '<td id="" style="width:20%" valign="top">' . skillList($TMergedSkills) . '</td>';
  196. echo '<td id="" style="width:5%" valign="top">' . rate($TMergedSkills, 'rate1') . '</td>';
  197. echo '<td id="" style="width:10%" valign="top">' . diff($TMergedSkills) . '</td>';
  198. echo '<td id="" style="width:5%" valign="top">' . rate($TMergedSkills, 'rate2') . '</td>';
  199. echo '<td id="list-user-right" style="width:30%" valign="top">';
  200. echo $userlist2;
  201. echo '</td></tr>';
  202. ?>
  203. </table>
  204. <?php } ?>
  205. </div>
  206. </form>
  207. </div>
  208. <?php
  209. print dol_get_fiche_end();
  210. llxFooter();
  211. /**
  212. *
  213. * Return a html list element with diff between required rank and user rank
  214. *
  215. * @param array $TMergedSkills skill list with all rate to add good picto
  216. * @return string
  217. */
  218. function diff(&$TMergedSkills)
  219. {
  220. $out = '<ul class="diff">';
  221. foreach ($TMergedSkills as $id => &$sk) {
  222. $class = 'diffnote';
  223. if (empty($sk->rate2)) {
  224. $class .= ' toohappy';
  225. } elseif (empty($sk->rate1)) {
  226. $class .= ' toosad';
  227. } elseif ($sk->rate1 == $sk->rate2) {
  228. $class .= ' happy';
  229. } elseif ($sk->rate2 < $sk->rate1) {
  230. $class .= ' veryhappy';
  231. } elseif ($sk->rate2 > $sk->rate1) {
  232. $class .= ' sad';
  233. }
  234. $out .= '<li fk_skill="' . $id . '" class="' . $class . '" style="text-align:center;">
  235. <span class="' . $class . '">&nbsp;</span>
  236. </li>';
  237. }
  238. $out .= '</ul>';
  239. return $out;
  240. }
  241. /**
  242. * Return a html list with rank informations
  243. * @param array $TMergedSkills skill list for display
  244. * @param string $field which column of comparison we are working with
  245. * @return string
  246. */
  247. function rate(&$TMergedSkills, $field)
  248. {
  249. global $langs, $fk_job;
  250. $out = '<ul class="competence">';
  251. foreach ($TMergedSkills as $id => &$sk) {
  252. $class = "note";
  253. $how_many = 0;
  254. if (empty($sk->$field)) {
  255. $note = 'x';
  256. $class .= ' none';
  257. } else {
  258. $note = $sk->$field;
  259. $how_many = ($field === 'rate1') ? $sk->how_many_max1 : $sk->how_many_max2;
  260. }
  261. if ($field === 'rate2' && $fk_job > 0) {
  262. $trad = $langs->trans('RequiredRank');
  263. } else {
  264. $trad = $langs->trans('HighestRank');
  265. }
  266. $out .= '<li fk_skill="' . $id . '" style="text-align:center;">
  267. <p><span class="' . $class . ' classfortooltip" title="' . $trad . '">' . $note . '</span>' . ($how_many > 0 ? '<span class="bubble classfortooltip" title="' . $langs->trans('HowManyUserWithThisMaxNote') . '">' . $how_many . '</span>' : '') . '</p>
  268. </li>';
  269. }
  270. $out .= '</ul>';
  271. return $out;
  272. }
  273. /**
  274. * return a html ul list of skills
  275. *
  276. * @param array $TMergedSkills skill list for display
  277. * @return string (ul list in html )
  278. */
  279. function skillList(&$TMergedSkills)
  280. {
  281. $out = '<ul class="competence">';
  282. foreach ($TMergedSkills as $id => &$sk) {
  283. $out .= '<li fk_skill="' . $id . '">
  284. <h3>' . $sk->label . '</h3>
  285. <p>' . $sk->description . '</p>
  286. </li>';
  287. }
  288. $out .= '</ul>';
  289. return $out;
  290. }
  291. /**
  292. * create an array of lines [ skillLabel,dscription, maxrank on group1 , minrank needed for this skill ]
  293. *
  294. * @param array $TSkill1 skill list of first column
  295. * @param array $TSkill2 skill list of second column
  296. * @return array
  297. */
  298. function mergeSkills($TSkill1, $TSkill2)
  299. {
  300. $Tab = array();
  301. foreach ($TSkill1 as &$sk) {
  302. if (empty($Tab[$sk->fk_skill])) {
  303. $Tab[$sk->fk_skill] = new stdClass();
  304. }
  305. $Tab[$sk->fk_skill]->rate1 = $sk->rankorder;
  306. $Tab[$sk->fk_skill]->how_many_max1 = $sk->how_many_max;
  307. $Tab[$sk->fk_skill]->label = $sk->label;
  308. $Tab[$sk->fk_skill]->description = $sk->description;
  309. }
  310. foreach ($TSkill2 as &$sk) {
  311. if (empty($Tab[$sk->fk_skill])) {
  312. $Tab[$sk->fk_skill] = new stdClass();
  313. }
  314. $Tab[$sk->fk_skill]->rate2 = $sk->rankorder;
  315. $Tab[$sk->fk_skill]->label = $sk->label;
  316. $Tab[$sk->fk_skill]->description = $sk->description;
  317. $Tab[$sk->fk_skill]->how_many_max2 = $sk->how_many_max;
  318. }
  319. return $Tab;
  320. }
  321. /**
  322. * Display a list of User with picto
  323. *
  324. * @param array $TUser list of users (employees) in selected usergroup of a column
  325. * @param int $fk_usergroup selected usergroup id
  326. * @param string $namelist html name
  327. * @return string
  328. */
  329. function displayUsersListWithPicto(&$TUser, $fk_usergroup = 0, $namelist = 'list-user')
  330. {
  331. global $db, $langs, $conf, $form;
  332. $out = '';
  333. if ($fk_usergroup > 0) {
  334. $list = $namelist . '_excluded_id';
  335. $excludedIdsList = GETPOST($list);
  336. $sql = "SELECT u.rowid FROM " . MAIN_DB_PREFIX . "user u
  337. LEFT JOIN " . MAIN_DB_PREFIX . "usergroup_user as ugu ON (u.rowid = ugu.fk_user)
  338. WHERE u.statut > 0 AND ugu.entity = ".((int) $conf->entity);
  339. $sql .= " AND ugu.fk_usergroup=" . ((int) $fk_usergroup);
  340. $res = $db->query($sql);
  341. $out .= '<ul name="' . $namelist . '">';
  342. $TExcludedId = explode(',', $excludedIdsList);
  343. $out .= '<input id="'.$list.'" type="hidden" name="'.$list.'" value="'.$excludedIdsList.'"> ';
  344. $job = new Job($db);
  345. while ($obj = $db->fetch_object($res)) {
  346. $class = '';
  347. $user = new User($db);
  348. $user->fetch($obj->rowid);
  349. $name = $user->getFullName($langs);
  350. if (empty($name)) {
  351. $name = $user->login;
  352. }
  353. if (in_array($user->id, $TExcludedId)) {
  354. $class .= ' disabled';
  355. } else {
  356. if (!in_array($user->id, $TUser)) {
  357. $TUser[] = $user->id;
  358. }
  359. }
  360. $desc = '';
  361. $jobstring = $job->getLastJobForUser($user->id);
  362. $desc .= $jobstring;
  363. $static_eval = new Evaluation($db);
  364. $evaluation = $static_eval->getLastEvaluationForUser($user->id);
  365. if (!empty($evaluation) && !empty($evaluation->date_eval)) {
  366. $desc .= $langs->trans('DateLastEval') . ' : ' . dol_print_date($evaluation->date_eval);
  367. } else {
  368. $desc .= $langs->trans('NoEval');
  369. }
  370. if (!empty($user->array_options['options_DDA'])) {
  371. $desc .= '<br>' . $langs->trans('Anciennete') . ' : ' . dol_print_date(strtotime($user->array_options['options_DDA']));
  372. }
  373. $out .= '<li fk_user="' . $user->id . '" class="' . $class . '">
  374. ' . $form->showphoto('userphoto', $user, 0, 0, 0, 'photoref', 'small', 1, 0, 1) . '
  375. <h3>' . $name . '</h3>
  376. <p>' . $desc . '</p>
  377. </li>';
  378. }
  379. $out .= '</ul>';
  380. }
  381. return $out;
  382. }
  383. /**
  384. *
  385. * Allow to get skill(s) of a user
  386. *
  387. * @param array $TUser array of employees we need to get skills
  388. * @return array|int
  389. */
  390. function getSkillForUsers($TUser)
  391. {
  392. global $db;
  393. //I go back to the user with the highest score in a given group for all the skills assessed in that group
  394. if (empty($TUser)) {
  395. return array();
  396. }
  397. $sql = 'SELECT sk.rowid, sk.label, sk.description, sk.skill_type, sr.fk_object, sr.objecttype, sr.fk_skill, ';
  398. $sql.= ' MAX(sr.rankorder) as rankorder';
  399. $sql.= ' FROM '.MAIN_DB_PREFIX.'hrm_skill sk';
  400. $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'hrm_skillrank sr ON (sk.rowid = sr.fk_skill)';
  401. $sql.= " WHERE sr.objecttype = '".$db->escape(SkillRank::SKILLRANK_TYPE_USER)."'";
  402. $sql.= ' AND sr.fk_object IN ('.$db->sanitize(implode(',', $TUser)).')';
  403. $sql.= " GROUP BY sk.rowid, sk.label, sk.description, sk.skill_type, sr.fk_object, sr.objecttype, sr.fk_skill "; // group par competence
  404. $resql = $db->query($sql);
  405. $Tab = array();
  406. if ($resql) {
  407. //For each skill, we count the number of times that the max score has been reached within a given group
  408. $num = 0;
  409. while ($obj = $db->fetch_object($resql)) {
  410. $sql1 = "SELECT COUNT(rowid) as how_many_max FROM ".MAIN_DB_PREFIX."hrm_skillrank as sr";
  411. $sql1.=" WHERE sr.rankorder = ".((int) $obj->rankorder);
  412. $sql1.=" AND sr.objecttype = '".$db->escape(SkillRank::SKILLRANK_TYPE_USER)."'";
  413. $sql1.=" AND sr.fk_skill = ".((int) $obj->fk_skill);
  414. $sql1.=" AND sr.fk_object IN (".$db->sanitize(implode(',', $TUser)).")";
  415. $resql1 = $db->query($sql1);
  416. $objMax = $db->fetch_object($resql1);
  417. $Tab[$num] = new stdClass();
  418. $Tab[$num]->fk_skill = $obj->fk_skill;
  419. $Tab[$num]->label = $obj->label;
  420. $Tab[$num]->description = $obj->description;
  421. $Tab[$num]->skill_type = $obj->skill_type;
  422. $Tab[$num]->fk_object = $obj->fk_object;
  423. $Tab[$num]->objectType = SkillRank::SKILLRANK_TYPE_USER;
  424. $Tab[$num]->rankorder = $obj->rankorder;
  425. $Tab[$num]->how_many_max = $objMax->how_many_max;
  426. $num++;
  427. }
  428. } else {
  429. dol_print_error($db);
  430. }
  431. return $Tab;
  432. }
  433. /**
  434. * Allow to get skill(s) of a job
  435. *
  436. * @param int $fk_job job we need to get required skills
  437. * @return array|int
  438. */
  439. function getSkillForJob($fk_job)
  440. {
  441. global $db;
  442. if (empty($fk_job)) {
  443. return array();
  444. }
  445. $sql = 'SELECT sk.rowid, sk.label, sk.description, sk.skill_type, sr.fk_object, sr.objecttype, sr.fk_skill,';
  446. $sql.= " MAX(sr.rankorder) as rankorder";
  447. $sql.=' FROM '.MAIN_DB_PREFIX.'hrm_skill as sk';
  448. $sql.=' LEFT JOIN '.MAIN_DB_PREFIX.'hrm_skillrank as sr ON (sk.rowid = sr.fk_skill)';
  449. $sql.=" WHERE sr.objecttype = '".SkillRank::SKILLRANK_TYPE_JOB."'";
  450. $sql.=' AND sr.fk_object = '.((int) $fk_job);
  451. $sql.=' GROUP BY sk.rowid, sk.label, sk.description, sk.skill_type, sr.fk_object, sr.objecttype, sr.fk_skill'; // group par competence*/
  452. $resql = $db->query($sql);
  453. $Tab = array();
  454. if ($resql) {
  455. $num = 0;
  456. while ($obj = $db->fetch_object($resql)) {
  457. $Tab[$num] = new stdClass();
  458. $Tab[$num]->fk_skill = $obj->fk_skill;
  459. $Tab[$num]->label = $obj->label;
  460. $Tab[$num]->description = $obj->description;
  461. $Tab[$num]->skill_type = $obj->skill_type;
  462. //$Tab[$num]->date_start = '';// du poste
  463. //$Tab[$num]->date_end = ''; // du poste
  464. $Tab[$num]->fk_object = $obj->fk_object;
  465. $Tab[$num]->objectType = SkillRank::SKILLRANK_TYPE_JOB;
  466. $Tab[$num]->rankorder = $obj->rankorder;
  467. $Tab[$num]->how_many_max = $obj->how_many_max;
  468. $num++;
  469. }
  470. } else {
  471. dol_print_error($db);
  472. }
  473. return $Tab;
  474. }