dolibarr 25.0.0-alpha
compare.php
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 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
8 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23
24 * \file htdocs/hrm/compare.php
25 * \ingroup hrm
26 * \brief This file compares skills of user groups
27 *
28 * Displays a table in three parts.
29 * 1- the left part displays the list of users for the selected group 1.
30 *
31 * 2- the central part displays the skills. Display of the maximum score for this group and the number of occurrences.
32 *
33 * 3- the right part displays the members of group 2 or the job to be compared
34 */
35
36
37// Load Dolibarr environment
38require_once '../main.inc.php';
47require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
48require_once DOL_DOCUMENT_ROOT . '/hrm/class/skill.class.php';
49require_once DOL_DOCUMENT_ROOT . '/hrm/class/job.class.php';
50require_once DOL_DOCUMENT_ROOT . '/hrm/class/evaluation.class.php';
51require_once DOL_DOCUMENT_ROOT . '/hrm/class/position.class.php';
52require_once DOL_DOCUMENT_ROOT . '/hrm/lib/hrm.lib.php';
53
54$action = GETPOST('action');
55
56// Load translation files required by the page
57$langs->load('hrm');
58
59$job = new Job($db);
60
61// Permissions
62$permissiontoread = $user->hasRight('hrm', 'evaluation', 'read') || $user->hasRight('hrm', 'compare_advance', 'read');
63$permissiontoadd = 0;
64
65if (!isModEnabled('hrm')) {
67}
68if (!$permissiontoread || ($action === 'create' && !$permissiontoadd)) {
70}
71
72
73/*
74 * View
75 */
76
77$css = array('/hrm/css/style.css');
78
79llxHeader('', $langs->trans('SkillComparison'), '', '', 0, 0, '', $css);
80
81$head = array();
82
83$h = 0;
84$head[$h][0] = DOL_URL_ROOT.'/hrm/compare.php';
85$head[$h][1] = $langs->trans("SkillComparison");
86$head[$h][2] = 'compare';
87
88print dol_get_fiche_head($head, 'compare', '', 1);
89
90?>
91 <script type="text/javascript">
92
93 $(document).ready(function () {
94
95 $("li[fk_user]").click(function () {
96
97 if ($(this).hasClass('disabled')) {
98 $(this).removeClass('disabled');
99 } else {
100 $(this).addClass('disabled');
101 }
102
103
104 var $userl = $(this).closest('ul');
105 var listname = $userl.attr('name');
106
107 var TId = [];
108
109 $userl.find('li').each(function (i, item) {
110
111 if ($(item).hasClass('disabled')) {
112 TId.push($(item).attr('fk_user'));
113 }
114
115 });
116
117 $('#' + listname + '_excluded_id').val(TId.join(','));
118
119 });
120
121 });
122
123
124 </script>
125
126
127<?php
128
129$fk_usergroup2 = 0;
130$fk_job = (int) GETPOST('fk_job');
131if ($fk_job <= 0) {
132 $fk_usergroup2 = GETPOSTINT('fk_usergroup2');
133}
134
135$fk_usergroup1 = GETPOSTINT('fk_usergroup1');
136
137?>
138
139
140 <div class="fichecenter">
141 <form action="<?php echo $_SERVER['PHP_SELF'] ?>">
142
143 <div class="tabBar tabBarWithBottom">
144 <div class="fichehalfleft">
145 <table class="border tableforfield" width="100%">
146 <tr>
147 <td><?php
148 print $langs->trans('group1ToCompare').'</td><td>';
149 print img_picto('', 'group', 'class="pictofixedwidth"');
150 print $form->select_dolgroups($fk_usergroup1, 'fk_usergroup1', 1);
151 ?></td>
152 </tr>
153 <tr><td></td><td></td></tr>
154 <tr>
155 <td><?php
156 print $langs->trans('group2ToCompare').'</td><td>';
157 print img_picto('', 'group', 'class="pictofixedwidth"');
158 print $form->select_dolgroups($fk_usergroup2, 'fk_usergroup2', 1);
159 ?></td>
160 </tr>
161 <tr>
162 <td><b><?php print $langs->trans('or'); ?></b></td><td></td>
163 </tr>
164 <tr>
165 <td><?php
166 echo $langs->trans('OrJobToCompare') . '</td><td>';
167 $j = new Job($db);
168 $jobs = $j->fetchAll();
169 $TJobs = array();
170
171 foreach ($jobs as &$j) {
172 $TJobs[$j->id] = $j->label;
173 }
174
175 print img_picto('', 'jobprofile', 'class="pictofixedwidth"').$form->selectarray('fk_job', $TJobs, $fk_job, 1);
176 ?></td>
177 </tr>
178 </table>
179 </div>
180
181 <div style="background:#eee;border-radius:5px 0;margin:0px 0 10px;font-style:italic;padding:5px;" class="fichehalfright">
182 <!--<h4><?php echo $langs->trans('legend'); ?></h4>-->
183 <table class="border" width="100%">
184 <tr>
185 <td><span style="vertical-align:middle" class="toohappy diffnote little"></span>
186 <?php echo $langs->trans('CompetenceAcquiredByOneOrMore'); ?></td>
187 </tr>
188 <tr>
189 <td><span style="vertical-align:middle" class="veryhappy diffnote little"></span>
190 <?php echo $langs->trans('MaxlevelGreaterThan'); ?></td>
191 </tr>
192 <tr>
193 <td><span style="vertical-align:middle" class="happy diffnote little"></span>
194 <?php echo $langs->trans('MaxLevelEqualTo'); ?></td>
195 </tr>
196 <tr>
197 <td><span style="vertical-align:middle" class="sad diffnote little"></span>
198 <?php echo $langs->trans('MaxLevelLowerThan'); ?></td>
199 </tr>
200 <tr>
201 <td><span style="vertical-align:middle" class="toosad diffnote little"></span>
202 <?php echo $langs->trans('SkillNotAcquired'); ?></td>
203 </tr>
204 </table>
205
206 </div>
207
208 <div class="clearboth"></div>
209
210 </div>
211
212 <br><br>
213 <div class="center">
214 <input class="button" type="SUBMIT" name="bt1" VALUE="<?php print $langs->trans('Refresh'); ?>">
215 </div>
216 <br><br>
217
218 <div id="compare" class="centpercent" style="position:relative;">
219
220 <?php if ($fk_usergroup1 > 0 || $fk_usergroup2 > 0 || $fk_job > 0) {
221 $TUser1 = $TUser2 = array();
222
223 $userlist1 = displayUsersListWithPicto($TUser1, $fk_usergroup1, 'list1'); // This fill also the $TUser1
224
225 $TSkill1 = getSkillForUsers($TUser1);
226
227 if ($fk_job > 0) {
228 $TSkill2 = getSkillForJob($fk_job);
229
230 $job = new Job($db);
231 $job->fetch($fk_job);
232 $userlist2 = '<ul>
233 <li>
234 <h3>' . $job->label . '</h3>
235 <p>' . $job->description . '</p>
236 </li>
237 </ul>';
238 } else {
239 $userlist2 = displayUsersListWithPicto($TUser2, $fk_usergroup2, 'list2');
240 $TSkill2 = getSkillForUsers($TUser2);
241 }
242
243 $TMergedSkills = mergeSkills($TSkill1, $TSkill2);
244 ?>
245 <table class="centpercent">
246 <tr>
247 <th class="left"><?php print $langs->trans('Employees'); ?></th>
248 <th class="left" style="padding-left: 10px;"><?php print $langs->trans('Skill'); ?></th>
249 <th><?php print $langs->trans('HighestRank'); ?></th>
250 <th><?php print $langs->trans('difference'); ?></th>
251 <th><?php print $langs->trans($fk_job > 0 ? 'ExpectedRank' : 'HighestRank'); ?></th>
252 <th></th>
253 </tr>
254
255 <?php
256 echo '<tr>';
257
258 echo '<td id="list-user-left" style="width:25%; padding-right: 10px; border-right: 1px solid #ccc" class="valigntop">';
259 echo $userlist1;
260 echo '</td>';
261
262 echo '<td id="" style="width:20%; padding-left: 10px;" valign="top">' . skillList($TMergedSkills) . '</td>';
263
264 echo '<td id="" style="width:10%" valign="top">' . rate($TMergedSkills, 'rate1') . '</td>';
265
266 echo '<td id="" style="width:10%" valign="top">' . diff($TMergedSkills) . '</td>';
267
268 echo '<td id="" style="width:10%; padding-right: 10px;" valign="top">' . rate($TMergedSkills, 'rate2') . '</td>';
269
270 echo '<td id="list-user-right" style="width:25%; padding-left: 10px; border-left: 1px solid #ccc;" class="valigntop">';
271 echo $userlist2;
272 echo '</td>';
273
274 echo '</tr>';
275
276 ?>
277
278 </table>
279
280 <?php } ?>
281
282 </div>
283
284 </form>
285
286 </div>
287
288<?php
289
290print dol_get_fiche_end();
291
292llxFooter();
293$db->close();
294
295
296
303function diff(&$TMergedSkills)
304{
305 $out = '<ul class="diff">';
306
307 foreach ($TMergedSkills as $id => &$sk) {
308 $class = 'diffnote';
309
310 if (empty($sk->rate2)) {
311 $class .= ' toohappy';
312 } elseif (empty($sk->rate1)) {
313 $class .= ' toosad';
314 } elseif ($sk->rate1 == $sk->rate2) {
315 $class .= ' happy';
316 } elseif ($sk->rate2 < $sk->rate1) {
317 $class .= ' veryhappy';
318 } elseif ($sk->rate2 > $sk->rate1) {
319 $class .= ' sad';
320 }
321
322 $out .= '<li fk_skill="' . $id . '" class="' . $class . '" style="text-align:center;">
323 <span class="' . $class . '">&nbsp;</span>
324 </li>';
325 }
326
327 $out .= '</ul>';
328
329 return $out;
330}
331
339function rate(&$TMergedSkills, $field)
340{
341 global $langs, $fk_job;
342
343 $out = '<ul class="competence">';
344
345 foreach ($TMergedSkills as $id => &$sk) {
346 $class = "note";
347 $how_many = 0;
348 if (empty($sk->$field)) {
349 $note = 'x';
350 $class .= ' none';
351 } else {
352 $note = $sk->$field < 0 ? $langs->trans("NA") : $sk->$field;
353 $how_many = ($field === 'rate1') ? $sk->how_many_max1 : $sk->how_many_max2;
354 }
355
356 if ($field === 'rate2' && $fk_job > 0) {
357 $trad = $langs->trans('RequiredRank');
358 } else {
359 $trad = $langs->trans('HighestRank');
360 }
361
362 $out .= '<li fk_skill="' . $id . '" class="center">
363 <p class="nowraponall"><span class="' . $class . ' classfortooltip" title="' . $trad . '">' . $note . '</span>' . ($how_many > 0 ? '<span class="bubble classfortooltip" title="' . $langs->trans('HowManyUserWithThisMaxNote') . '">' . $how_many . '</span>' : '') . '</p>
364 </li>';
365 }
366
367 $out .= '</ul>';
368
369 return $out;
370}
371
378function skillList(&$TMergedSkills)
379{
380 $out = '<ul class="competence">';
381
382 foreach ($TMergedSkills as $id => &$sk) {
383 $out .= '<li fk_skill="' . $id . '">
384 <h3>' . $sk->label . '</h3>
385 <p>' . $sk->description . '</p>
386 </li>';
387 }
388
389 $out .= '</ul>';
390
391 return $out;
392}
393
401function mergeSkills($TSkill1, $TSkill2)
402{
403 $Tab = array();
404
405 foreach ($TSkill1 as &$sk) {
406 if (empty($Tab[$sk->fk_skill])) {
407 $Tab[$sk->fk_skill] = new stdClass();
408 }
409
410 $Tab[$sk->fk_skill]->rate1 = $sk->rankorder;
411 $Tab[$sk->fk_skill]->how_many_max1 = $sk->how_many_max;
412
413 $Tab[$sk->fk_skill]->label = $sk->label;
414 $Tab[$sk->fk_skill]->description = $sk->description;
415 }
416
417 foreach ($TSkill2 as &$sk) {
418 if (empty($Tab[$sk->fk_skill])) {
419 $Tab[$sk->fk_skill] = new stdClass();
420 }
421 $Tab[$sk->fk_skill]->rate2 = $sk->rankorder;
422 $Tab[$sk->fk_skill]->how_many_max2 = $sk->how_many_max;
423
424 $Tab[$sk->fk_skill]->label = $sk->label;
425 $Tab[$sk->fk_skill]->description = $sk->description;
426 }
427
428 return $Tab;
429}
430
439function displayUsersListWithPicto(&$TUser, $fk_usergroup = 0, $namelist = 'list-user')
440{
441 global $db, $langs, $conf, $form;
442
443 $out = '';
444 if ($fk_usergroup > 0) {
445 $list = $namelist . '_excluded_id';
446
447 $excludedIdsList = GETPOST($list);
448
449 $sql = "SELECT u.rowid FROM " . MAIN_DB_PREFIX . "user u
450 LEFT JOIN " . MAIN_DB_PREFIX . "usergroup_user as ugu ON (u.rowid = ugu.fk_user)
451 WHERE u.statut > 0 AND ugu.entity = ".((int) $conf->entity);
452 $sql .= " AND ugu.fk_usergroup=" . ((int) $fk_usergroup);
453
454 $res = $db->query($sql);
455 $out .= '<ul name="' . $namelist . '">';
456
457 $TExcludedId = explode(',', $excludedIdsList);
458
459 $out .= '<input id="'.$list.'" type="hidden" name="'.$list.'" value="'.$excludedIdsList.'"> ';
460
461 $job = new Job($db);
462
463 while ($obj = $db->fetch_object($res)) {
464 $class = '';
465
466 $user = new User($db);
467 $user->fetch($obj->rowid);
468
469 $name = $user->getFullName($langs);
470 if (empty($name)) {
471 $name = $user->login;
472 }
473
474 if (in_array($user->id, $TExcludedId)) {
475 $class .= ' disabled';
476 } else {
477 if (!in_array($user->id, $TUser)) {
478 $TUser[] = $user->id;
479 }
480 }
481
482 $desc = '';
483
484 $jobstring = $job->getLastJobForUser($user->id);
485 $desc .= $jobstring;
486
487 $static_eval = new Evaluation($db);
488 $evaluation = $static_eval->getLastEvaluationForUser($user->id);
489
490 if (!empty($evaluation) && !empty($evaluation->date_eval)) {
491 $desc .= $langs->trans('DateLastEval') . ' : ' . dol_print_date($evaluation->date_eval);
492 } else {
493 $desc .= $langs->trans('NoEval');
494 }
495
496 if (!empty($user->array_options['options_DDA'])) {
497 $desc .= '<br>' . $langs->trans('Seniority') . ' : ' . dol_print_date(strtotime($user->array_options['options_DDA']));
498 }
499
500 $out .= '<li fk_user="' . $user->id . '" class="' . $class . '">
501 ' . $form->showphoto('userphoto', $user, 0, 0, 0, 'photoref', 'small', 1, 0, '', 1) . '
502 <h3>' . $name . '</h3>
503 <p>' . $desc . '</p>
504 </li>';
505 }
506
507 $out .= '</ul>';
508 }
509
510 return $out;
511}
512
513
520function getSkillForUsers($TUser)
521{
522 global $db;
523
524 // I go back to the user with the highest score in a given group for all the skills assessed in that group
525 if (empty($TUser)) {
526 return array();
527 }
528
529 $sql = 'SELECT sk.rowid, sk.label, sk.description, sk.skill_type,';
530 $sql .= ' MAX(sr.rankorder) as rankorder';
531 $sql .= ' FROM '.MAIN_DB_PREFIX.'hrm_skill sk';
532 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'hrm_skillrank sr';
533 $sql .= " WHERE sk.rowid = sr.fk_skill AND sr.objecttype = '".$db->escape(SkillRank::SKILLRANK_TYPE_USER)."'";
534 $sql .= ' AND sr.fk_object IN ('.$db->sanitize(implode(',', $TUser)).')';
535 $sql .= ' AND rankorder >= 0';
536 $sql .= " GROUP BY sk.rowid, sk.label, sk.description, sk.skill_type"; // group by skill
537 $sql .= " ORDER BY sk.rowid ASC";
538
539 $resql = $db->query($sql);
540 $Tab = array();
541
542 if ($resql) {
543 // For each skill, we count the number of times that the max score has been reached within a given group
544 $num = 0;
545 while ($obj = $db->fetch_object($resql)) {
546 $Tab[$num] = new stdClass();
547
548 $Tab[$num]->fk_skill = $obj->rowid;
549 $Tab[$num]->label = $obj->label;
550 $Tab[$num]->description = $obj->description;
551 $Tab[$num]->skill_type = $obj->skill_type;
552 //$Tab[$num]->fk_object = $obj->fk_object;
553 $Tab[$num]->objectType = SkillRank::SKILLRANK_TYPE_USER;
554
555 $Tab[$num]->rankorder = $obj->rankorder;
556
557 // Get how_many_max
558 $sql1 = "SELECT COUNT(rowid) as how_many_max FROM ".MAIN_DB_PREFIX."hrm_skillrank as sr";
559 $sql1 .= " WHERE sr.rankorder = ".((int) $obj->rankorder);
560 $sql1 .= " AND sr.objecttype = '".$db->escape(SkillRank::SKILLRANK_TYPE_USER)."'";
561 $sql1 .= " AND sr.fk_skill = ".((int) $obj->rowid);
562 $sql1 .= " AND sr.fk_object IN (".$db->sanitize(implode(',', $TUser)).")";
563 $resql1 = $db->query($sql1);
564
565 $objMax = $db->fetch_object($resql1);
566
567 $Tab[$num]->how_many_max = $objMax->how_many_max;
568
569 $db->free($resql1);
570
571 $num++;
572 }
573 } else {
575 }
576
577 return $Tab;
578}
579
586function getSkillForJob($fk_job)
587{
588 global $db;
589
590 if (empty($fk_job)) {
591 return array();
592 }
593
594 $sql = 'SELECT sk.rowid, sk.label, sk.description, sk.skill_type, sr.fk_object, sr.objecttype, sr.fk_skill,';
595 $sql .= " MAX(sr.rankorder) as rankorder";
596 $sql .= ' FROM '.MAIN_DB_PREFIX.'hrm_skill as sk';
597 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'hrm_skillrank as sr ON (sk.rowid = sr.fk_skill)';
598 $sql .= " WHERE sr.objecttype = '".SkillRank::SKILLRANK_TYPE_JOB."'";
599 $sql .= ' AND sr.fk_object = '.((int) $fk_job);
600 $sql .= ' GROUP BY sk.rowid, sk.label, sk.description, sk.skill_type, sr.fk_object, sr.objecttype, sr.fk_skill'; // group par competence*/
601
602 $resql = $db->query($sql);
603 $Tab = array();
604
605 if ($resql) {
606 $num = 0;
607 while ($obj = $db->fetch_object($resql)) {
608 $Tab[$num] = new stdClass();
609 $Tab[$num]->fk_skill = $obj->fk_skill;
610 $Tab[$num]->label = $obj->label;
611 $Tab[$num]->description = $obj->description;
612 $Tab[$num]->skill_type = $obj->skill_type;
613 //$Tab[$num]->date_start = '';// du poste
614 //$Tab[$num]->date_end = ''; // du poste
615 $Tab[$num]->fk_object = $obj->fk_object;
616 $Tab[$num]->objectType = SkillRank::SKILLRANK_TYPE_JOB;
617 $Tab[$num]->rankorder = $obj->rankorder;
618 $Tab[$num]->how_many_max = $obj->how_many_max;
619
620 $num++;
621 }
622 } else {
624 }
625
626 return $Tab;
627}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class for Evaluation.
Class for Job.
Definition job.class.php:38
Class to manage Dolibarr users.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
isModEnabled($module)
Is Dolibarr module enabled.
treeview li table
No Email.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:519
dol_get_fiche_end($notab=0)
Return tab footer of a card.
Definition html.lib.php:717
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:130
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:133
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.