diff --git a/ChangeLog b/ChangeLog index bcded58d383ae..fb7bef6680240 100644 --- a/ChangeLog +++ b/ChangeLog @@ -452,6 +452,9 @@ NEW: Accountancy - Option to select the label of operation (#31200) NEW: Accountancy - Allow grouping taxes with primary line price (#26732) NEW: Activate PHPUnit with tests on permission on $action ==... NEW: Add advice for max size on list for better performance +NEW: Holiday module - Option "Les demandes de congés requièrent une double validation" in module settings +NEW: Approbation de niveau 2 des demandes de congés +NEW: Holiday module - Add "Responsible de seconde approbation" responsible field on holiday card NEW: Add an advanced permission to validate knowledge (#30855) NEW: Add a test mode into the setup of AI module to test the AI prompts. NEW: Add a tool to decrypt data encrypted in database. diff --git a/htdocs/admin/holiday.php b/htdocs/admin/holiday.php index ca8d0732c7e64..ba6312c8f83b8 100644 --- a/htdocs/admin/holiday.php +++ b/htdocs/admin/holiday.php @@ -529,6 +529,8 @@ print ""; print ""; +// EN: Display toggle to consume holiday balance at end of month. +// FR: Affiche le bascule pour consommer le solde de congés en fin de mois. // Set holiday decrease at the end of month print ''; print "".$langs->trans("ConsumeHolidaysAtTheEndOfTheMonthTheyAreTakenAt").' ('.$langs->trans("ConsumeHolidaysAtTheEndOfTheMonthTheyAreTakenAtBis").')'; @@ -545,6 +547,23 @@ print ""; print ""; +// EN: Display toggle to require double approval for leave requests. +// FR: Affiche le bascule pour exiger une double validation des demandes de congés. +print ''; +print "".$langs->trans("HolidayRequireDoubleApproval").""; +print ''; +if ($conf->use_javascript_ajax) { + print ajax_constantonoff('HOLIDAY_REQUIRE_DOUBLE_APPROVAL', array(), null, 0, 0, 0, 2, 0, 1); +} else { + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + print ''.img_picto($langs->trans("Enabled"), 'on').''; + } else { + print ''.img_picto($langs->trans("Disabled"), 'off').''; + } +} +print ""; +print ""; + if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { $substitutionarray = pdf_getSubstitutionArray($langs, array('objectamount'), null, 2); $substitutionarray['__(AnyTranslationKey)__'] = $langs->trans("Translation"); diff --git a/htdocs/core/modules/modHoliday.class.php b/htdocs/core/modules/modHoliday.class.php index fb76dfcd3ad0e..b9af68d29c332 100644 --- a/htdocs/core/modules/modHoliday.class.php +++ b/htdocs/core/modules/modHoliday.class.php @@ -186,6 +186,15 @@ public function __construct($db) $this->rights[$r][4] = 'approve'; $r++; + // EN: Add level 2 approval permission for double validation + // FR: Ajouter la permission d'approbation de niveau 2 pour la double validation + $this->rights[$r][0] = 20008; + $this->rights[$r][1] = 'PermissionHolidayDoubleApproval'; + $this->rights[$r][2] = 'w'; + $this->rights[$r][3] = 0; + $this->rights[$r][4] = 'approvelevel2'; + $r++; + $this->rights[$r][0] = 20004; // Permission id (must not be already used) $this->rights[$r][1] = 'Read leave requests for everybody'; // Permission label $this->rights[$r][3] = 0; // Permission by default for new user (0/1) diff --git a/htdocs/holiday/card.php b/htdocs/holiday/card.php index 386277ba8f1db..b35e1bf64f06b 100644 --- a/htdocs/holiday/card.php +++ b/htdocs/holiday/card.php @@ -200,6 +200,8 @@ } $approverid = GETPOSTINT('valideur'); + // Capture the second approver from the request + $approverid2 = GETPOSTINT('valideur2'); $description = trim(GETPOST('description', 'restricthtml')); // Check that leave is for a user inside the hierarchy or advanced permission for all is set @@ -278,6 +280,18 @@ setEventMessages($langs->transnoentitiesnoconv('InvalidValidator'), null, 'errors'); $error++; } + // Allow selection of the second approver + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + // Validate the chosen second approver + if ($approverid2 < 1) { + setEventMessages($langs->trans('SecondApproverRequired'), null, 'errors'); + $error++; + } + if (!empty($approverslist) && !in_array($approverid2, $approverslist)) { + setEventMessages($langs->trans('InvalidSecondValidatorCP'), null, 'errors'); + $error++; + } + } // Fill array 'array_options' with data from add form $ret = $extrafields->setOptionalsFromPost(null, $object); @@ -291,6 +305,13 @@ $object->fk_user = $fuserid; $object->description = $description; $object->fk_validator = $approverid; + // Offer edition of the second approver + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + // Keep the expected second approver + $object->fk_user_approve2 = ($approverid2 > 0 ? $approverid2 : 0); + } else { + $object->fk_user_approve2 = 0; + } $object->fk_type = $type; $object->date_debut = $date_debut; $object->date_fin = $date_fin; @@ -322,8 +343,26 @@ $object->oldcopy = dol_clone($object, 2); // @phan-suppress-current-line PhanTypeMismatchProperty $object->fk_validator = GETPOSTINT('valideur'); + // Capture the second approver during quick edit + $approverid2 = GETPOSTINT('valideur2'); + $localerror = 0; + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + // Validate and store the second approver during quick edit + if ($approverid2 < 1) { + setEventMessages($langs->trans('SecondApproverRequired'), null, 'warnings'); + $localerror++; + } + $approverslist = $object->fetch_users_approver_holiday(); + if (!empty($approverslist) && !in_array($approverid2, $approverslist)) { + setEventMessages($langs->trans('InvalidSecondValidatorCP'), null, 'warnings'); + $localerror++; + } + $object->fk_user_approve2 = ($approverid2 > 0 ? $approverid2 : 0); + } else { + $object->fk_user_approve2 = 0; + } - if ($object->fk_validator != $object->oldcopy->fk_validator) { + if (!$localerror && ($object->fk_validator != $object->oldcopy->fk_validator || $object->fk_user_approve2 != $object->oldcopy->fk_user_approve2)) { $verif = $object->update($user); if ($verif <= 0) { @@ -333,6 +372,8 @@ header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); exit; } + } elseif ($localerror) { + $action = 'editvalidator'; } $action = ''; @@ -376,6 +417,8 @@ // If this is the requester or has read/write rights if ($permissiontoadd) { $approverid = GETPOSTINT('valideur'); + // Capture the second approver during edition + $approverid2 = GETPOSTINT('valideur2'); // TODO Check this approver user id has the permission for approval $description = trim(GETPOST('description', 'restricthtml')); @@ -400,6 +443,20 @@ $error++; $action = 'edit'; } + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + // Validate the second approver while editing + if ($approverid2 < 1) { + setEventMessages($langs->trans('SecondApproverRequired'), null, 'warnings'); + $error++; + $action = 'edit'; + } + $approverslist = $object->fetch_users_approver_holiday(); + if (!empty($approverslist) && !in_array($approverid2, $approverslist)) { + setEventMessages($langs->trans('InvalidSecondValidatorCP'), null, 'warnings'); + $error++; + $action = 'edit'; + } + } // If there is no Business Days within request $nbopenedday = num_open_day($date_debut_gmt, $date_fin_gmt, 0, 1, $halfday); @@ -416,6 +473,12 @@ $object->date_debut = $date_debut; $object->date_fin = $date_fin; $object->fk_validator = $approverid; + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + // Keep the updated second approver + $object->fk_user_approve2 = ($approverid2 > 0 ? $approverid2 : 0); + } else { + $object->fk_user_approve2 = 0; + } $object->halfday = $halfday; // Update @@ -1207,6 +1270,10 @@ $object = new Holiday($db); $include_users = $object->fetch_users_approver_holiday(); + // Normalize the approvers list + if (!is_array($include_users)) { + $include_users = array(); + } if (empty($include_users)) { print img_warning().' '.$langs->trans("NobodyHasPermissionToValidateHolidays"); } else { @@ -1226,7 +1293,24 @@ //print $form->select_dolusers((GETPOST('valideur','int')>0?GETPOST('valideur','int'):$user->fk_user), "valideur", 1, ($user->admin ? '' : array($user->id)), 0, '', 0, 0, 0, 0, '', 0, '', '', 1); // By default, hierarchical parent print ''; print ''; - + // Allow selection of the second approver + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + print ''; + print ''.$langs->trans("SecondApprovalResponsible").''; + print ''; + if (empty($include_users)) { + print img_warning().' '.$langs->trans("NobodyHasPermissionToValidateHolidays"); + } else { + $defaultselectuser2 = GETPOSTINT('valideur2'); + if (empty($defaultselectuser2) && !empty($defaultselectuser)) { + $defaultselectuser2 = $defaultselectuser; + } + $s2 = $form->select_dolusers($defaultselectuser2, "valideur2", 1, '', 0, $include_users, '', '0,'.$conf->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); + print img_picto('', 'user', 'class="pictofixedwidth"').$form->textwithpicto($s2, $langs->trans("AnyOtherInThisListCanValidate")); + } + print ''; + print ''; + } // Description print ''; print ''.$langs->trans("DescCP").''; @@ -1259,6 +1343,11 @@ $approverexpected = new User($db); $approverexpected->fetch($object->fk_validator); // Use that should be the approver + $secondapproverexpected = new User($db); + if ($object->fk_user_approve2 > 0) { + // Preload the second approver + $secondapproverexpected->fetch($object->fk_user_approve2); + } $userRequest = new User($db); $userRequest->fetch($object->fk_user); @@ -1491,11 +1580,29 @@ } print ''; print ''; + + // Display the second approver + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + print ''; + print ''.$langs->trans("SecondApprovalResponsible").''; + print ''; + if ($object->fk_user_approve2 > 0) { + print $secondapproverexpected->getNomUrl(-1); + } else { + print $langs->trans('None'); + } + print ''; + print ''; + } } else { print ''; print ''.$langs->trans('ReviewedByCP').''; // Will be approved by print ''; $include_users = $object->fetch_users_approver_holiday(); + // Normalize the approvers list + if (!is_array($include_users)) { + $include_users = array(); + } if (!in_array($object->fk_validator, $include_users)) { // Add the current validator to the list to not lose it when editing. $include_users[] = $object->fk_validator; } @@ -1505,6 +1612,22 @@ $arrayofvalidatorstoexclude = (($user->admin || ($user->id != $userRequest->id)) ? '' : array($user->id)); // We exclude ourself from validator list. Not if we are admin or if we are on the leave of someone else $s = $form->select_dolusers($object->fk_validator, "valideur", (($action == 'editvalidator') ? 0 : 1), $arrayofvalidatorstoexclude, 0, $include_users); print $form->textwithpicto($s, $langs->trans("AnyOtherInThisListCanValidate")); + // Offer edition of the second approver + if (getDolGlobalString('HOLIDAY_REQUIRE_DOUBLE_APPROVAL')) { + if (!in_array($object->fk_user_approve2, $include_users)) { + $include_users[] = $object->fk_user_approve2; + } + // Fetch the proposed second approver + $defaultselectuser2 = $object->fk_user_approve2; + // Preserve the posted value for the second approver + if ($action == 'editvalidator' && GETPOSTINT('valideur2') > 0) { + // Refresh the second approver with the posted value + $defaultselectuser2 = GETPOSTINT('valideur2'); + } + $s2 = $form->select_dolusers($defaultselectuser2, "valideur2", (($action == 'editvalidator') ? 0 : 1), $arrayofvalidatorstoexclude, 0, $include_users); + print '
'.$form->textwithpicto($s2, $langs->trans("AnyOtherInThisListCanValidate")).'
'; + } + } if ($action == 'editvalidator') { print ''; diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index a8ba624ba2dad..ceb706546cb58 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -131,6 +131,16 @@ class Holiday extends CommonObject */ public $fk_user_approve; + /** + * @var int Date of second approval / Date de la seconde approbation + */ + public $date_approval2; + + /** + * @var int ID for second approval / ID de la seconde approbation + */ + public $fk_user_approve2; + /** * @var int Date for refuse */ @@ -328,6 +338,7 @@ public function create($user, $notrigger = 0) $sql .= "fk_validator,"; $sql .= "fk_type,"; $sql .= "fk_user_create,"; + $sql .= "fk_user_approve2,"; $sql .= "entity"; $sql .= ") VALUES ("; $sql .= "'(PROV)',"; @@ -341,6 +352,8 @@ public function create($user, $notrigger = 0) $sql .= " ".((int) $this->fk_validator).","; $sql .= " ".((int) $this->fk_type).","; $sql .= " ".((int) $user->id).","; + // Persist the chosen second approver on creation + $sql .= ($this->fk_user_approve2 > 0 ? " ".((int) $this->fk_user_approve2)."," : " null,"); $sql .= " ".((int) $conf->entity); $sql .= ")"; @@ -425,6 +438,8 @@ public function fetch($id, $ref = '') $sql .= " cp.fk_user_valid,"; $sql .= " cp.date_approval,"; $sql .= " cp.fk_user_approve,"; + $sql .= " cp.date_approval2,"; + $sql .= " cp.fk_user_approve2,"; $sql .= " cp.date_refuse,"; $sql .= " cp.fk_user_refuse,"; $sql .= " cp.date_cancel,"; @@ -466,6 +481,9 @@ public function fetch($id, $ref = '') $this->user_validation_id = $obj->fk_user_valid; $this->date_approval = $this->db->jdate($obj->date_approval); $this->fk_user_approve = $obj->fk_user_approve; + // Store the second approval metadata + $this->date_approval2 = $this->db->jdate($obj->date_approval2); + $this->fk_user_approve2 = $obj->fk_user_approve2; $this->date_refuse = $this->db->jdate($obj->date_refuse); $this->fk_user_refuse = $obj->fk_user_refuse; $this->date_cancel = $this->db->jdate($obj->date_cancel); @@ -520,6 +538,8 @@ public function fetchByUser($user_id, $order = '', $filter = '') $sql .= " cp.fk_user_valid,"; $sql .= " cp.date_approval,"; $sql .= " cp.fk_user_approve,"; + $sql .= " cp.date_approval2,"; + $sql .= " cp.fk_user_approve2,"; $sql .= " cp.date_refuse,"; $sql .= " cp.fk_user_refuse,"; $sql .= " cp.date_cancel,"; @@ -591,6 +611,9 @@ public function fetchByUser($user_id, $order = '', $filter = '') $tab_result[$i]['fk_user_valid'] = $obj->fk_user_valid; $tab_result[$i]['date_approval'] = $this->db->jdate($obj->date_approval); $tab_result[$i]['fk_user_approve'] = $obj->fk_user_approve; + // Keep the second approval data + $tab_result[$i]['date_approval2'] = $this->db->jdate($obj->date_approval2); + $tab_result[$i]['fk_user_approve2'] = $obj->fk_user_approve2; $tab_result[$i]['date_refuse'] = $this->db->jdate($obj->date_refuse); $tab_result[$i]['fk_user_refuse'] = $obj->fk_user_refuse; $tab_result[$i]['date_cancel'] = $this->db->jdate($obj->date_cancel); @@ -650,6 +673,8 @@ public function fetchAll($order, $filter) $sql .= " cp.fk_user_valid,"; $sql .= " cp.date_approval,"; $sql .= " cp.fk_user_approve,"; + $sql .= " cp.date_approval2,"; + $sql .= " cp.fk_user_approve2,"; $sql .= " cp.date_refuse,"; $sql .= " cp.fk_user_refuse,"; $sql .= " cp.date_cancel,"; @@ -721,6 +746,9 @@ public function fetchAll($order, $filter) $tab_result[$i]['fk_user_valid'] = $obj->fk_user_valid; $tab_result[$i]['date_approval'] = $this->db->jdate($obj->date_approval); $tab_result[$i]['fk_user_approve'] = $obj->fk_user_approve; + // Keep the second approval data + $tab_result[$i]['date_approval2'] = $this->db->jdate($obj->date_approval2); + $tab_result[$i]['fk_user_approve2'] = $obj->fk_user_approve2; $tab_result[$i]['date_refuse'] = $obj->date_refuse; $tab_result[$i]['fk_user_refuse'] = $obj->fk_user_refuse; $tab_result[$i]['date_cancel'] = $obj->date_cancel; @@ -945,6 +973,17 @@ public function approve($user = null, $notrigger = 0) } else { $sql .= " fk_user_approve = NULL,"; } + // Persist the second approval info + if (!empty($this->date_approval2)) { + $sql .= " date_approval2 = '".$this->db->idate($this->date_approval2)."',"; + } else { + $sql .= " date_approval2 = NULL,"; + } + if (!empty($this->fk_user_approve2)) { + $sql .= " fk_user_approve2 = ".((int) $this->fk_user_approve2).","; + } else { + $sql .= " fk_user_approve2 = NULL,"; + } if (!empty($this->date_refuse)) { $sql .= " date_refuse = '".$this->db->idate($this->date_refuse)."',"; } else { @@ -1075,6 +1114,17 @@ public function update($user = null, $notrigger = 0) } else { $sql .= " fk_user_approve = NULL,"; } + // Persist the second approval info + if (!empty($this->date_approval2)) { + $sql .= " date_approval2 = '".$this->db->idate($this->date_approval2)."',"; + } else { + $sql .= " date_approval2 = NULL,"; + } + if (!empty($this->fk_user_approve2)) { + $sql .= " fk_user_approve2 = ".((int) $this->fk_user_approve2).","; + } else { + $sql .= " fk_user_approve2 = NULL,"; + } if (!empty($this->date_refuse)) { $sql .= " date_refuse = '".$this->db->idate($this->date_refuse)."',"; } else { diff --git a/htdocs/install/mysql/migration/20.0.0-21.0.0.sql b/htdocs/install/mysql/migration/20.0.0-21.0.0.sql index c5dc2a64a90ab..9fc748bdf74f0 100644 --- a/htdocs/install/mysql/migration/20.0.0-21.0.0.sql +++ b/htdocs/install/mysql/migration/20.0.0-21.0.0.sql @@ -38,6 +38,9 @@ UPDATE llx_paiement SET ref = rowid WHERE ref IS NULL OR ref = ''; ALTER TABLE llx_c_holiday_types ADD COLUMN block_if_negative integer NOT NULL DEFAULT 0 AFTER fk_country; ALTER TABLE llx_c_holiday_types ADD COLUMN sortorder smallint; +-- EN: Support second approval columns for holidays / FR: Ajouter les colonnes de seconde approbation pour les congés +ALTER TABLE llx_holiday ADD COLUMN date_approval2 datetime DEFAULT NULL; +ALTER TABLE llx_holiday ADD COLUMN fk_user_approve2 integer DEFAULT NULL; -- Clean very old temporary tables (created during v9 migration or repair) diff --git a/htdocs/install/mysql/tables/llx_holiday.sql b/htdocs/install/mysql/tables/llx_holiday.sql index 8a2af4515a8a8..cc464f01d7272 100644 --- a/htdocs/install/mysql/tables/llx_holiday.sql +++ b/htdocs/install/mysql/tables/llx_holiday.sql @@ -38,6 +38,8 @@ date_valid DATETIME DEFAULT NULL, -- date validation fk_user_valid integer DEFAULT NULL, -- user validation date_approval DATETIME DEFAULT NULL, -- date approval fk_user_approve integer DEFAULT NULL, -- user approval + date_approval2 DATETIME DEFAULT NULL, -- EN: date of second approval / FR: date de la seconde approbation + fk_user_approve2 integer DEFAULT NULL, -- EN: user for second approval / FR: utilisateur pour la seconde approbation date_refuse DATETIME DEFAULT NULL, fk_user_refuse integer DEFAULT NULL, date_cancel DATETIME DEFAULT NULL, diff --git a/htdocs/langs/en_US/holiday.lang b/htdocs/langs/en_US/holiday.lang index 829f76c123692..2b4f943ca416d 100644 --- a/htdocs/langs/en_US/holiday.lang +++ b/htdocs/langs/en_US/holiday.lang @@ -60,6 +60,9 @@ ErrorCantDeleteCP=Error you don't have the right to delete this leave request. CantCreateCP=You don't have the right to make leave requests. InvalidValidatorCP=You must choose the approver for your leave request. InvalidValidator=The user chosen isn't an approver. +SecondApproverRequired=You must choose the second approver for your leave request. +InvalidSecondValidatorCP=The user chosen isn't authorized as a second approver. +SecondApprovalResponsible=Second approval responsible NoDateDebut=You must select a start date. NoDateFin=You must select an end date. ErrorDureeCP=Your leave request does not contain working day. @@ -146,6 +149,7 @@ FreeLegalTextOnHolidays=Free text on PDF WatermarkOnDraftHolidayCards=Watermarks on draft leave requests HolidaysToApprove=Holidays to approve NobodyHasPermissionToValidateHolidays=Nobody has permission to validate leave requests +PermissionHolidayDoubleApproval=Confirm leave requests (Level 2 approval) HolidayBalanceMonthlyUpdate=Monthly update of leave balance XIsAUsualNonWorkingDay=Exclude %s from the leave calculation, as they are typically designated as non-working days. BlockHolidayIfNegative=Block if balance negative @@ -159,6 +163,7 @@ NumberDayAddMass=Number of day to add to the selection ConfirmMassIncreaseHolidayQuestion=Are you sure you want to increase holiday of the %s selected record(s)? HolidayQtyNotModified=Balance of remaining days for %s has not been changed ConsumeHolidaysAtTheEndOfTheMonthTheyAreTakenAt=Consume holidays at the end of the month they are taken at +HolidayRequireDoubleApproval=Leave requests require double validation ConsumeHolidaysAtTheEndOfTheMonthTheyAreTakenAtBis=By default, they are consumed/deducted as soon as they are approved PleaseDefineSomeTypeOfHolidayFirst=Please define first some types of holiday into the dictionary (Menu Home - Setup - dictionaries) HolidayMailSenderAdress=Email address for holiday requests diff --git a/htdocs/langs/fr_FR/holiday.lang b/htdocs/langs/fr_FR/holiday.lang index 366db788d531c..885c9604fe5c9 100644 --- a/htdocs/langs/fr_FR/holiday.lang +++ b/htdocs/langs/fr_FR/holiday.lang @@ -59,6 +59,9 @@ ErrorCantDeleteCP=Erreur, vous n'avez pas le droit de supprimer cette demande de CantCreateCP=Erreur, vous n'avez pas le droit de créer une demande de congés. InvalidValidatorCP=Vous devez choisir un approbateur pour votre demande de congés. InvalidValidator=L'utilisateur choisi n'est pas un approbateur. +SecondApproverRequired=Vous devez choisir le second valideur pour votre demande de congé. +InvalidSecondValidatorCP=L'utilisateur choisi n'est pas autorisé comme second valideur. +SecondApprovalResponsible=Utilisateur responsable de la seconde approbation NoDateDebut=Vous devez choisir une date de début. NoDateFin=Vous devez choisir une date de fin. ErrorDureeCP=Votre demande de congés payés ne contient aucun jour ouvré. @@ -145,6 +148,7 @@ FreeLegalTextOnHolidays=Texte libre sur PDF WatermarkOnDraftHolidayCards=Filigranes sur les demandes de congés brouillons HolidaysToApprove=Vacances à approuver NobodyHasPermissionToValidateHolidays=Aucun utilisateur ne dispose des permissions pour valider les demandes de congés +PermissionHolidayDoubleApproval=Confirmer les demandes de congés (Approbation de niveau 2) HolidayBalanceMonthlyUpdate=Mise à jour mensuelle du solde des congés XIsAUsualNonWorkingDay=Exclure %s du calcul des congés, car ils sont généralement désignés comme des jours non ouvrables. BlockHolidayIfNegative=Bloqué lorsque le solde est négatif @@ -157,6 +161,8 @@ ConfirmMassIncreaseHoliday=Augmentation massive du solde des congés NumberDayAddMass=Nombre de jours à ajouter à la sélection ConfirmMassIncreaseHolidayQuestion=Êtes-vous sûr de vouloir augmenter les vacances du ou des %s enregistrement(s) sélectionnés  ? HolidayQtyNotModified=Le solde des jours restants pour %s n'a pas été modifié +ConsumeHolidaysAtTheEndOfTheMonthTheyAreTakenAt=Consommer les jours de congés à la fin du mois où ils sont pris +HolidayRequireDoubleApproval=Les demandes de congés requièrent une double validation ConsumeHolidaysAtTheEndOfTheMonthTheyAreTakenAt=Décompte les congés à la fin du mois où ils sont pris PleaseDefineSomeTypeOfHolidayFirst=Veuillez d'abord définir certains types de vacances dans le dictionnaire (Menu Accueil - Configuration - dictionnaires) HolidayMailSenderAdress=Adresse e-mail pour les demandes de vacances